1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
|
package maxminddb
import (
"math"
"net/netip"
)
const notFound uint = math.MaxUint
// Result holds the result of the database lookup.
type Result struct {
ip netip.Addr
err error
reader *Reader
offset uint
prefixLen uint8
}
// Decode unmarshals the data from the data section into the value pointed to
// by v. If v is nil or not a pointer, an error is returned. If the data in
// the database record cannot be stored in v because of type differences, an
// UnmarshalTypeError is returned. If the database is invalid or otherwise
// cannot be read, an InvalidDatabaseError is returned.
//
// An error will also be returned if there was an error during the
// Reader.Lookup call.
//
// If the Reader.Lookup call did not find a value for the IP address, no error
// will be returned and v will be unchanged.
func (r Result) Decode(v any) error {
if r.err != nil {
return r.err
}
if r.offset == notFound {
return nil
}
return r.reader.decoder.Decode(r.offset, v)
}
// DecodePath unmarshals a value from data section into v, following the
// specified path.
//
// The v parameter should be a pointer to the value where the decoded data
// will be stored. If v is nil or not a pointer, an error is returned. If the
// data in the database record cannot be stored in v because of type
// differences, an UnmarshalTypeError is returned.
//
// The path is a variadic list of keys (strings) and/or indices (ints) that
// describe the nested structure to traverse in the data to reach the desired
// value.
//
// For maps, string path elements are used as keys.
// For arrays, int path elements are used as indices. A negative offset will
// return values from the end of the array, e.g., -1 will return the last
// element.
//
// If the path is empty, the entire data structure is decoded into v.
//
// To check if a path exists (rather than relying on zero values), decode
// into a pointer and check if it remains nil:
//
// var city *string
// err := result.DecodePath(&city, "city", "names", "en")
// if err != nil {
// // Handle error
// }
// if city == nil {
// // Path not found
// } else {
// // Path exists, city contains the value
// }
//
// Returns an error if:
// - the path is invalid
// - the data cannot be decoded into the type of v
// - v is not a pointer or the database record cannot be stored in v due to
// type mismatch
// - the Result does not contain valid data
//
// Example usage:
//
// var city string
// err := result.DecodePath(&city, "city", "names", "en")
//
// var geonameID int
// err := result.DecodePath(&geonameID, "subdivisions", 0, "geoname_id")
func (r Result) DecodePath(v any, path ...any) error {
if r.err != nil {
return r.err
}
if r.offset == notFound {
return nil
}
return r.reader.decoder.DecodePath(r.offset, path, v)
}
// Err provides a way to check whether there was an error during the lookup
// without calling Result.Decode. If there was an error, it will also be
// returned from Result.Decode.
func (r Result) Err() error {
return r.err
}
// Found will return true if the IP was found in the search tree. It will
// return false if the IP was not found or if there was an error.
func (r Result) Found() bool {
return r.err == nil && r.offset != notFound
}
// Offset returns the offset of the record in the database. This can be
// passed to (*Reader).LookupOffset. It can also be used as a unique
// identifier for the data record in the particular database to cache the data
// record across lookups. Note that while the offset uniquely identifies the
// data record, other data in Result may differ between lookups. The offset
// is only valid for the current database version. If you update the database
// file, you must invalidate any cache associated with the previous version.
func (r Result) Offset() uintptr {
return uintptr(r.offset)
}
// Prefix returns the netip.Prefix representing the network associated with
// the data record in the database.
func (r Result) Prefix() netip.Prefix {
ip := r.ip
prefixLen := int(r.prefixLen)
if ip.Is4() {
// This is necessary as the node that the IPv4 start is at may
// be at a bit depth that is less that 96, i.e., ipv4Start points
// to a leaf node. For instance, if a record was inserted at ::/8,
// the ipv4Start would point directly at the leaf node for the
// record and would have a bit depth of 8. This would not happen
// with databases currently distributed by MaxMind as all of them
// have an IPv4 subtree that is greater than a single node.
if prefixLen < 96 {
return netip.PrefixFrom(zeroIP, prefixLen)
}
prefixLen -= 96
}
prefix, _ := ip.Prefix(prefixLen)
return prefix
}
|