Skip to content

Commit c674ee0

Browse files
authored
Merge pull request #2 from netloc8/feat/geo-accessors
feat: add RegionCode, Lat, Lng geo accessors
2 parents f6976cf + 0df5610 commit c674ee0

5 files changed

Lines changed: 103 additions & 14 deletions

File tree

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/),
66
and this project adheres to [Semantic Versioning](https://semver.org/).
77

8+
## [1.2.0] — 2026-06-13
9+
10+
### Added
11+
12+
- `RegionCode()` accessor — returns the ISO 3166-2 subdivision code (e.g. `"CA"`), nil-safe
13+
- `Lat()` accessor — returns latitude coordinate, nil-safe
14+
- `Lng()` accessor — returns longitude coordinate, nil-safe
15+
- `HasCoordinates()` predicate — distinguishes absent coordinates from a real `0,0` location
16+
17+
### Changed
18+
19+
- README "Response Shape" section now uses `RegionCode()`, `Lat()`, `Lng()` accessors instead of raw field access
20+
21+
### Fixed
22+
23+
- README nil-safety note now correctly states accessors return zero values (empty strings or `0`), not just "empty strings"
24+
825
## [1.1.0] — 2026-06-13
926

1027
### Added

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ timezone-accurate scheduling, and regional content delivery.
1616
- **Zero dependencies** — only the Go standard library. No transitive dependency tree to audit
1717
- **Proxy-aware** — plug in any `*http.Client` with a proxy transport to discover exit IPs through tunnels
1818
- **Context-native** — every API call takes `context.Context` for cancellation, timeouts, and tracing
19-
- **Nil-safe accessors**`geo.CountryCode()`, `geo.CityName()`, `geo.TZ()` never panic on partial responses
19+
- **Nil-safe accessors**`geo.CountryCode()`, `geo.CityName()`, `geo.Lat()`, `geo.TZ()` never panic on partial responses
2020
- **Typed errors**`*APIError` with machine-readable codes, `errors.As` support, and convenience predicates
2121
- **Privacy compliance ready**`IsEU()` helper for GDPR, country and region fields for CCPA and other regulations
2222

@@ -77,12 +77,12 @@ geo.CountryCode() // "US"
7777
geo.CountryName() // "United States"
7878
geo.Location.Country.Flag // "🇺🇸"
7979
geo.Location.Country.Unions // ["EU"] or []
80-
geo.Location.Region.Code // "CA"
80+
geo.RegionCode() // "CA"
8181
geo.RegionName() // "California"
8282
geo.CityName() // "Mountain View"
8383
geo.Location.PostalCode // "94043"
84-
geo.Location.Coordinates.Latitude // 37.386
85-
geo.Location.Coordinates.Longitude // -122.084
84+
geo.Lat() // 37.386
85+
geo.Lng() // -122.084
8686
geo.TZ() // "America/Los_Angeles"
8787
geo.Location.UTCOffset // "-07:00"
8888
geo.Location.GeoConfidence // 1.0
@@ -92,9 +92,10 @@ geo.Network.Domain // "google.com"
9292
geo.Meta.Precision // "city"
9393
```
9494

95-
All accessor methods (`CountryCode()`, `CityName()`, `TZ()`, etc.) are nil-safe
96-
— they return empty strings on nil or partial responses, so you never need to
97-
nil-check nested structs.
95+
All accessor methods (`CountryCode()`, `CityName()`, `Lat()`, `TZ()`, etc.) are nil-safe
96+
— they return zero values (empty strings or `0`) on nil or partial responses, so you
97+
never need to nil-check nested structs. Use `HasCoordinates()` to distinguish
98+
absent coordinates from a real `0,0` location.
9899

99100
## API Methods
100101

geo.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,15 @@ func ( g *Geo ) RegionName() string {
149149
return g.Location.Region.Name
150150
}
151151

152+
// RegionCode returns the ISO 3166-2 subdivision code (e.g. "CA"), or empty string.
153+
// Safe to call on nil Geo.
154+
func ( g *Geo ) RegionCode() string {
155+
if g == nil || g.Location == nil || g.Location.Region == nil {
156+
return ""
157+
}
158+
return g.Location.Region.Code
159+
}
160+
152161
// CityName returns the city name, or empty string.
153162
// Safe to call on nil Geo.
154163
func ( g *Geo ) CityName() string {
@@ -158,6 +167,32 @@ func ( g *Geo ) CityName() string {
158167
return g.Location.City
159168
}
160169

170+
// Lat returns the latitude coordinate, or 0.
171+
// Safe to call on nil Geo.
172+
func ( g *Geo ) Lat() float64 {
173+
if g == nil || g.Location == nil || g.Location.Coordinates == nil {
174+
return 0
175+
}
176+
return g.Location.Coordinates.Latitude
177+
}
178+
179+
// Lng returns the longitude coordinate, or 0.
180+
// Safe to call on nil Geo.
181+
func ( g *Geo ) Lng() float64 {
182+
if g == nil || g.Location == nil || g.Location.Coordinates == nil {
183+
return 0
184+
}
185+
return g.Location.Coordinates.Longitude
186+
}
187+
188+
// HasCoordinates reports whether the geolocation result includes
189+
// coordinate data. Use this to distinguish absent coordinates from
190+
// a real 0,0 location (equator/prime meridian).
191+
// Safe to call on nil Geo.
192+
func ( g *Geo ) HasCoordinates() bool {
193+
return g != nil && g.Location != nil && g.Location.Coordinates != nil
194+
}
195+
161196
// TZ returns the IANA timezone string, or empty string.
162197
// Safe to call on nil Geo.
163198
func ( g *Geo ) TZ() string {

geo_test.go

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,18 @@ func TestGeo_JSONRoundtrip( t *testing.T ) {
8383
if decoded.CountryName() != "United States" {
8484
t.Errorf( "CountryName() = %q, want United States", decoded.CountryName() )
8585
}
86+
if decoded.RegionCode() != "CA" {
87+
t.Errorf( "RegionCode() = %q, want CA", decoded.RegionCode() )
88+
}
89+
if decoded.Lat() != 37.386 {
90+
t.Errorf( "Lat() = %f, want 37.386", decoded.Lat() )
91+
}
92+
if decoded.Lng() != -122.084 {
93+
t.Errorf( "Lng() = %f, want -122.084", decoded.Lng() )
94+
}
95+
if !decoded.HasCoordinates() {
96+
t.Error( "HasCoordinates() should be true for full geo" )
97+
}
8698
}
8799

88100
func TestGeo_NilSafety( t *testing.T ) {
@@ -112,6 +124,18 @@ func TestGeo_NilSafety( t *testing.T ) {
112124
if g.Org() != "" {
113125
t.Error( "nil Geo.Org() should be empty" )
114126
}
127+
if g.RegionCode() != "" {
128+
t.Error( "nil Geo.RegionCode() should be empty" )
129+
}
130+
if g.Lat() != 0 {
131+
t.Error( "nil Geo.Lat() should be 0" )
132+
}
133+
if g.Lng() != 0 {
134+
t.Error( "nil Geo.Lng() should be 0" )
135+
}
136+
if g.HasCoordinates() {
137+
t.Error( "nil Geo.HasCoordinates() should be false" )
138+
}
115139

116140
// Partial Geo with nil sub-structs.
117141
partial := &Geo{}
@@ -121,6 +145,18 @@ func TestGeo_NilSafety( t *testing.T ) {
121145
if partial.CityName() != "" {
122146
t.Error( "empty Geo.CityName() should be empty" )
123147
}
148+
if partial.RegionCode() != "" {
149+
t.Error( "empty Geo.RegionCode() should be empty" )
150+
}
151+
if partial.Lat() != 0 {
152+
t.Error( "empty Geo.Lat() should be 0" )
153+
}
154+
if partial.Lng() != 0 {
155+
t.Error( "empty Geo.Lng() should be 0" )
156+
}
157+
if partial.HasCoordinates() {
158+
t.Error( "empty Geo.HasCoordinates() should be false" )
159+
}
124160
}
125161

126162
func TestGeo_ParseAPIResponse( t *testing.T ) {
@@ -276,20 +312,20 @@ func TestGeoFromPlatformHeaders( t *testing.T ) {
276312
if geo.CountryCode() != "US" {
277313
t.Errorf( "CountryCode() = %q, want US", geo.CountryCode() )
278314
}
279-
if geo.Location.Region.Code != "CA" {
280-
t.Errorf( "Region.Code = %q, want CA", geo.Location.Region.Code )
315+
if geo.RegionCode() != "CA" {
316+
t.Errorf( "RegionCode() = %q, want CA", geo.RegionCode() )
281317
}
282318
if geo.CityName() != "Mountain View" {
283319
t.Errorf( "CityName() = %q, want Mountain View", geo.CityName() )
284320
}
285321
if geo.TZ() != "America/Los_Angeles" {
286322
t.Errorf( "TZ() = %q, want America/Los_Angeles", geo.TZ() )
287323
}
288-
if geo.Location.Coordinates.Latitude != 37.386 {
289-
t.Errorf( "Latitude = %f, want 37.386", geo.Location.Coordinates.Latitude )
324+
if geo.Lat() != 37.386 {
325+
t.Errorf( "Lat() = %f, want 37.386", geo.Lat() )
290326
}
291-
if geo.Location.Coordinates.Longitude != -122.084 {
292-
t.Errorf( "Longitude = %f, want -122.084", geo.Location.Coordinates.Longitude )
327+
if geo.Lng() != -122.084 {
328+
t.Errorf( "Lng() = %f, want -122.084", geo.Lng() )
293329
}
294330
})
295331

version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ package netloc8
22

33
// Version is the semantic version of this SDK.
44
// Updated at release time.
5-
const Version = "1.1.0"
5+
const Version = "1.2.0"

0 commit comments

Comments
 (0)