Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
# 2.0.0-beta.2

* **BREAKING CHANGE**: Replaced `IsZero()` methods with `HasData()` methods on all
result structs (including Names). The new methods provide clearer semantics:
`HasData()` returns `true` when GeoIP data is found and `false` when no data is
available. Unlike `IsZero()`, `HasData()` excludes Network and IPAddress fields
from validation, allowing users to access network topology information even when
no GeoIP data is found. The Network and IPAddress fields are now always
populated for all lookups, regardless of whether GeoIP data is available.
* **BREAKING CHANGE**: Replaced all anonymous nested structs with named types to
improve struct initialization ergonomics. All result structs (Enterprise, City,
Country) now use named types like `EnterpriseCityRecord`, `CityTraits`,
`CountryRecord`, etc. This makes it much easier to initialize structs in user
code while maintaining the same JSON serialization behavior.
* **BREAKING CHANGE**: Changed `Location.Latitude` and `Location.Longitude` from
`float64` to `*float64` to properly distinguish between missing coordinates and
the valid location (0, 0). Missing coordinates are now represented as `nil`
and are omitted from JSON output, while valid zero coordinates are preserved.
This fixes the ambiguity where (0, 0) was incorrectly treated as "no data".
Added `Location.HasCoordinates()` method for safe coordinate access. Reported
by Nick Bruun. GitHub #5.

# 2.0.0-beta.1 - 2025-06-22

* **BREAKING CHANGE**: Updated to use `maxminddb-golang/v2` which provides
Expand Down
42 changes: 24 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This library is built using
data for the database record is decoded using this library. Version 2.0
provides significant performance improvements with 56% fewer allocations and
34% less memory usage compared to v1. Version 2.0 also adds `Network` and
`IPAddress` fields to all result structs, and includes an `IsZero()` method to
`IPAddress` fields to all result structs, and includes a `HasData()` method to
easily check if data was found. If you only need several fields, you may get
superior performance by using maxminddb's `Lookup` directly with a result
struct that only contains the required fields. (See
Expand All @@ -32,7 +32,7 @@ Version 2.0 includes several major improvements:
- **Modern API**: Uses `netip.Addr` instead of `net.IP` for better performance
- **Network Information**: All result structs now include `Network` and
`IPAddress` fields
- **Data Validation**: New `IsZero()` method to easily check if data was found
- **Data Validation**: New `HasData()` method to easily check if data was found
- **Structured Names**: Replaced `map[string]string` with typed `Names` struct
for better performance
- **Go 1.24 Support**: Uses `omitzero` JSON tags to match MaxMind database
Expand Down Expand Up @@ -71,7 +71,7 @@ func main() {
if err != nil {
log.Fatal(err)
}
if record.IsZero() {
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
Expand All @@ -82,7 +82,9 @@ func main() {
fmt.Printf("Russian country name: %v\n", record.Country.Names.Russian)
fmt.Printf("ISO country code: %v\n", record.Country.ISOCode)
fmt.Printf("Time zone: %v\n", record.Location.TimeZone)
fmt.Printf("Coordinates: %v, %v\n", record.Location.Latitude, record.Location.Longitude)
if record.Location.HasCoordinates() {
fmt.Printf("Coordinates: %v, %v\n", *record.Location.Latitude, *record.Location.Longitude)
}
// Output:
// Portuguese (BR) city name: Londres
// English subdivision name: England
Expand Down Expand Up @@ -151,7 +153,7 @@ func main() {
log.Fatal(err)
}

if record.IsZero() {
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
Expand All @@ -161,7 +163,9 @@ func main() {
fmt.Printf("Country: %v (%v)\n", record.Country.Names.English, record.Country.ISOCode)
fmt.Printf("Continent: %v (%v)\n", record.Continent.Names.English, record.Continent.Code)
fmt.Printf("Postal Code: %v\n", record.Postal.Code)
fmt.Printf("Location: %v, %v\n", record.Location.Latitude, record.Location.Longitude)
if record.Location.HasCoordinates() {
fmt.Printf("Location: %v, %v\n", *record.Location.Latitude, *record.Location.Longitude)
}
fmt.Printf("Time Zone: %v\n", record.Location.TimeZone)
fmt.Printf("Network: %v\n", record.Traits.Network)
fmt.Printf("IP Address: %v\n", record.Traits.IPAddress)
Expand Down Expand Up @@ -200,7 +204,7 @@ func main() {
log.Fatal(err)
}

if record.IsZero() {
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
Expand Down Expand Up @@ -251,7 +255,7 @@ func main() {
log.Fatal(err)
}

if record.IsZero() {
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
Expand Down Expand Up @@ -296,7 +300,7 @@ func main() {
log.Fatal(err)
}

if record.IsZero() {
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
Expand Down Expand Up @@ -345,15 +349,17 @@ func main() {
log.Fatal(err)
}

if record.IsZero() {
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}

// Basic location information
fmt.Printf("City: %v\n", record.City.Names.English)
fmt.Printf("Country: %v (%v)\n", record.Country.Names.English, record.Country.ISOCode)
fmt.Printf("Location: %v, %v\n", record.Location.Latitude, record.Location.Longitude)
if record.Location.HasCoordinates() {
fmt.Printf("Location: %v, %v\n", *record.Location.Latitude, *record.Location.Longitude)
}

// Enterprise-specific fields
fmt.Printf("ISP: %v\n", record.Traits.ISP)
Expand Down Expand Up @@ -410,7 +416,7 @@ func main() {
log.Fatal(err)
}

if record.IsZero() {
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
Expand Down Expand Up @@ -464,7 +470,7 @@ func main() {
log.Fatal(err)
}

if record.IsZero() {
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
Expand Down Expand Up @@ -507,7 +513,7 @@ func main() {
log.Fatal(err)
}

if record.IsZero() {
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
Expand Down Expand Up @@ -551,7 +557,7 @@ func main() {
}

// Always check if data was found
if record.IsZero() {
if !record.HasData() {
fmt.Println("No data found for this IP address")
return
}
Expand Down Expand Up @@ -627,7 +633,7 @@ fmt.Println(string(jsonData))
- **IP Type**: Use `netip.Addr` instead of `net.IP`
- **Field Names**: `IsoCode` → `ISOCode`
- **Names Access**: Use struct fields instead of map access
- **Data Validation**: Use `IsZero()` method to check for data availability
- **Data Validation**: Use `HasData()` method to check for data availability

### Migration Example

Expand All @@ -647,7 +653,7 @@ if err != nil {
// handle error
}
record, err := db.City(ip)
if record.IsZero() {
if !record.HasData() {
// handle no data found
}
cityName := record.City.Names.English
Expand All @@ -659,7 +665,7 @@ cityName := record.City.Names.English

**Database not found**: Ensure the .mmdb file path is correct and readable.

**No data returned**: Check if `IsZero()` returns true - the IP may not be in
**No data returned**: Check if `HasData()` returns false - the IP may not be in
the database or may be a private/reserved IP.

**Performance issues**: Ensure you're reusing the database instance rather than
Expand Down
22 changes: 13 additions & 9 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ func Example() {
fmt.Printf("Russian country name: %v\n", record.Country.Names.Russian)
fmt.Printf("ISO country code: %v\n", record.Country.ISOCode)
fmt.Printf("Time zone: %v\n", record.Location.TimeZone)
fmt.Printf("Coordinates: %v, %v\n", record.Location.Latitude, record.Location.Longitude)
if record.Location.HasCoordinates() {
fmt.Printf("Coordinates: %v, %v\n", *record.Location.Latitude, *record.Location.Longitude)
} else {
fmt.Println("Coordinates: unavailable")
}
// Output:
// Portuguese (BR) city name: Londres
// English subdivision name: England
Expand All @@ -55,7 +59,7 @@ func ExampleReader_City() {
log.Panic(err)
}

if record.IsZero() {
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
Expand Down Expand Up @@ -86,7 +90,7 @@ func ExampleReader_Country() {
log.Panic(err)
}

if record.IsZero() {
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
Expand Down Expand Up @@ -115,7 +119,7 @@ func ExampleReader_ASN() {
log.Panic(err)
}

if record.IsZero() {
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
Expand Down Expand Up @@ -144,7 +148,7 @@ func ExampleReader_AnonymousIP() {
log.Panic(err)
}

if record.IsZero() {
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
Expand Down Expand Up @@ -175,7 +179,7 @@ func ExampleReader_Enterprise() {
log.Panic(err)
}

if record.IsZero() {
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
Expand Down Expand Up @@ -208,7 +212,7 @@ func ExampleReader_ISP() {
log.Panic(err)
}

if record.IsZero() {
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
Expand Down Expand Up @@ -239,7 +243,7 @@ func ExampleReader_Domain() {
log.Panic(err)
}

if record.IsZero() {
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
Expand All @@ -266,7 +270,7 @@ func ExampleReader_ConnectionType() {
log.Panic(err)
}

if record.IsZero() {
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
Expand Down
Loading
Loading