package data import ( "errors" "fmt" "net" "net/netip" ) // ErrNotFound is returned by Query when the IP parses but is not present in the DB. var ErrNotFound = errors.New("IP not found") func (d *IpDb) Query(ipArg string) (map[string]any, *net.IPNet, error) { // maxminddb-golang v2 requires a netip.Addr. addr, err := netip.ParseAddr(ipArg) if err != nil { return nil, nil, fmt.Errorf("invalid IP: %s", ipArg) } // Hold the read lock: Reload() reassigns d.r and closes the old reader, // so reading d.r / calling Lookup without synchronization is a data race. d.mu.RLock() defer d.mu.RUnlock() if d.r == nil { return nil, nil, fmt.Errorf("db not loaded") } // Look up the record for the IP. result := d.r.Lookup(addr) if err := result.Err(); err != nil { return nil, nil, err } if !result.Found() { return nil, nil, fmt.Errorf("%w: %s", ErrNotFound, ipArg) } // Decode the record into a generic map. anyData := make(map[string]any) if err := result.Decode(&anyData); err != nil { return nil, nil, err } // Convert the matched prefix back to *net.IPNet for callers. var recordNet *net.IPNet if prefix := result.Prefix(); prefix.IsValid() { recordNet = prefixToIPNet(prefix) } return anyData, recordNet, nil } // prefixToIPNet converts a netip.Prefix to a *net.IPNet. func prefixToIPNet(prefix netip.Prefix) *net.IPNet { addr := prefix.Addr().AsSlice() mask := net.CIDRMask(prefix.Bits(), len(addr)*8) return &net.IPNet{IP: addr, Mask: mask} }