From 7280ee594f4f0405d48d6402e4747c28dd8483d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20B=C3=A4ckevik?= Date: Thu, 2 Jul 2026 11:40:54 +0200 Subject: [PATCH] fix(geocoding): add PointGeometry for Feature types GeoJSONGeometry.Coordinates was [][]float64 (LineString), but Geocoding v6 returns Point geometry with a flat [lon, lat] coordinate. JSON decode failed with 'cannot unmarshal number into Go struct field GeoJSONGeometry'. Add PointGeometry with []float64 coordinates and use it for Feature and RetrieveFeature. GeoJSONGeometry (LineString) remains unchanged for map matching. --- AGENTS.md | 2 ++ geocoding.go | 8 +++++++- geocoding_reverse_test.go | 43 +++++++++++++++++++++++++++++++++++++++ searchbox.go | 2 +- 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 5e9634c..355fd84 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -93,6 +93,8 @@ cmd/mapbox/ - Testing: standard `testing` + `github.com/google/go-cmp/cmp` only. No Testify or other frameworks. - Linting: GolangCI-Lint v2, configured in `.golangci.yml`. +- Commit subject: max 60 characters (enforced by commitlint in CI). Conventional commit prefix counts toward the limit. +- Commit subject must not be sentence-case: first word after the prefix must be lowercase (`fix(x): add foo`, not `fix(x): Add foo`). ## Retry diff --git a/geocoding.go b/geocoding.go index 552c991..f0fe4d8 100644 --- a/geocoding.go +++ b/geocoding.go @@ -6,11 +6,17 @@ type FeatureCollection struct { Features []Feature `json:"features"` } +// PointGeometry is a GeoJSON Point geometry. Coordinates holds [longitude, latitude]. +type PointGeometry struct { + Type string `json:"type"` + Coordinates []float64 `json:"coordinates"` +} + // Feature is a GeoJSON Feature with Mapbox v6 geocoding properties. type Feature struct { Type string `json:"type"` ID string `json:"id"` - Geometry GeoJSONGeometry `json:"geometry"` + Geometry PointGeometry `json:"geometry"` Properties FeatureProperties `json:"properties"` } diff --git a/geocoding_reverse_test.go b/geocoding_reverse_test.go index 405ec34..98d4e9a 100644 --- a/geocoding_reverse_test.go +++ b/geocoding_reverse_test.go @@ -121,6 +121,49 @@ func TestReverseGeocode_ContextParsing(t *testing.T) { } } +func TestReverseGeocode_PointGeometry(t *testing.T) { + // Mapbox returns Point geometry with flat [lon, lat] coordinates. + // Regression: GeoJSONGeometry used [][]float64 (LineString), causing decode to fail. + const body = `{ + "type": "FeatureCollection", + "features": [{ + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [24.9384, 60.1699]}, + "properties": { + "mapbox_id": "addr.1", + "feature_type": "address", + "full_address": "Mannerheimintie 1, 00100 Helsinki, Finland" + } + }] + }` + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + if _, err := w.Write([]byte(body)); err != nil { + t.Errorf("write response: %v", err) + } + })) + defer srv.Close() + + client := mapbox.NewClient(mapbox.WithAccessToken("t"), mapbox.WithBaseURL(srv.URL)) + result, err := client.ReverseGeocode(context.Background(), &mapbox.ReverseGeocodeRequest{ + Longitude: 24.9384, + Latitude: 60.1699, + }) + if err != nil { + t.Fatalf("ReverseGeocode error: %v", err) + } + if len(result.Features) != 1 { + t.Fatalf("len(Features) = %d, want 1", len(result.Features)) + } + f := result.Features[0] + if f.Geometry.Type != "Point" { + t.Errorf("Geometry.Type = %q, want Point", f.Geometry.Type) + } + if len(f.Geometry.Coordinates) != 2 { + t.Errorf("Geometry.Coordinates len = %d, want 2", len(f.Geometry.Coordinates)) + } +} + func TestReverseGeocode_HTTPError(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, `{"message":"Not Authorized - Invalid Token"}`, http.StatusUnauthorized) diff --git a/searchbox.go b/searchbox.go index b52509f..1019b87 100644 --- a/searchbox.go +++ b/searchbox.go @@ -28,7 +28,7 @@ type Suggestion struct { // RetrieveFeature is a GeoJSON Feature returned by /retrieve. type RetrieveFeature struct { Type string `json:"type"` - Geometry GeoJSONGeometry `json:"geometry"` + Geometry PointGeometry `json:"geometry"` Properties RetrieveFeatureProperties `json:"properties"` }