Repository navigation
Resolve place names with Places API (New) instead of the legacy Places API - #2124
carlosinfantes wants to merge 3 commits into
Conversation
…s API The place-name resolver calls FindPlaceFromText from googlemaps.github.io/maps, which is backed by the legacy Places API. Google has designated that API as Legacy: it receives no new feature development, and the documentation states that the legacy services are expected to be turned down in the coming years. Operationally this also bites at deployment time. A project that has not enabled places-backend.googleapis.com gets REQUEST_DENIED from Maps, which surfaces to the caller as a 500 (codes.Internal) reading "failed to resolve place names: maps: REQUEST_DENIED - You're calling a legacy API, which is not enabled for your project ... switch to the Places API (New) or Routes API". Any request naming a place absent from the KG fails that way. The resolver only ever needed one thing: given a text query, return place ids. That maps one-to-one onto SearchText in Places API (New), so this swaps the client and narrows the interface to what the caller actually uses: FindPlaceIDsFromText(ctx context.Context, query string) ([]string, error) The field mask is pinned to "places.id". Text Search bills at the highest tier of any field requested, so requesting only the id keeps the call in the cheapest SKU -- which also carries a larger monthly free allowance than the legacy Find Place SKU it replaces. Narrowing the interface removes the last references to the legacy module, so googlemaps.github.io/maps is dropped from go.mod entirely. cloud.google.com/go/maps is pinned to v1.28.0: v1.29.0 and later require Go >= 1.25, and this module is on go 1.24.0. Verified with go build ./..., go vet and go test on the affected packages. The FakeMapsClient contract is unchanged -- "Hawaii" still resolves to hawaii_place_id and anything else returns empty.
There was a problem hiding this comment.
Code Review
This pull request migrates the Google Maps integration from the legacy googlemaps.github.io/maps library to the official cloud.google.com/go/maps Places API client. It replaces the FindPlaceFromText method with FindPlaceIDsFromText using the new SearchText endpoint, which includes setting a narrow field mask (places.id) to optimize billing. Feedback suggests wrapping the generic error returned by SearchText with a more descriptive message and ensuring only non-empty place IDs are appended to the returned slice.
| resp, err := c.client.SearchText(ctx, &placespb.SearchTextRequest{TextQuery: query}) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| placeIDs := make([]string, 0, len(resp.GetPlaces())) | ||
| for _, place := range resp.GetPlaces() { | ||
| placeIDs = append(placeIDs, place.GetId()) | ||
| } |
There was a problem hiding this comment.
To adhere to defensive programming practices and the repository's general rules, we should:
- Wrap the generic error returned by
SearchTextwith a more descriptive, user-friendly error message to aid in debugging. - Ensure we only append non-empty place IDs to the returned slice, filtering out any potential nil or empty values.
Note: You may need to import the "fmt" package if it is not already imported.
| resp, err := c.client.SearchText(ctx, &placespb.SearchTextRequest{TextQuery: query}) | |
| if err != nil { | |
| return nil, err | |
| } | |
| placeIDs := make([]string, 0, len(resp.GetPlaces())) | |
| for _, place := range resp.GetPlaces() { | |
| placeIDs = append(placeIDs, place.GetId()) | |
| } | |
| resp, err := c.client.SearchText(ctx, &placespb.SearchTextRequest{TextQuery: query}) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to search text via Places API: %w", err) | |
| } | |
| placeIDs := make([]string, 0, len(resp.GetPlaces())) | |
| for _, place := range resp.GetPlaces() { | |
| if id := place.GetId(); id != "" { | |
| placeIDs = append(placeIDs, id) | |
| } | |
| } |
References
- Wrap generic errors from underlying functions with more descriptive, user-friendly error messages.
There was a problem hiding this comment.
Thanks, both points applied in 711d5c5: the SearchText error is now wrapped with %w, and candidates without an id are skipped.
I applied them by hand rather than through the suggestion block, because the block was written against the first commit and would have dropped MaxResultCount: 1. That cap was added deliberately: findPlaceIDsForEntity only keeps placeIDs[0], and without it Text Search returns up to 20 results that are billed and then discarded.
…viour findPlaceIDsForEntity keeps only placeIDs[0] and discards the rest. Text Search returns up to 20 candidates by default, so without a cap the extra results are billed, serialised and thrown away. Legacy Find Place returned a single candidate, so MaxResultCount=1 also preserves the previous behaviour.
Adds context to errors returned by SearchText and ignores any candidate that comes back without an id, so an empty string can never reach the resolver as placeIDs[0].
The place-name resolver calls
FindPlaceFromTextfromgooglemaps.github.io/maps, which is backed by the legacy Places API. Google has designated that API as Legacy — no new feature development, and the pricing FAQ states the legacy services are expected to be turned down in the coming years.Why this matters in practice
A deployment whose project has not enabled
places-backend.googleapis.comgetsREQUEST_DENIEDfrom Maps, and the resolver turns it into a 500 (codes.Internal):Every request naming a place absent from the KG fails that way — including through the MCP server, where the raw upstream error reaches the caller. Google’s own error text points at the fix this PR implements.
The change
The resolver only ever needed one thing: given a text query, return place ids. That maps one-to-one onto
SearchTextin Places API (New), so this swaps the client and narrows the interface to what the caller actually uses:The existing comment already described
MapsClientas "a thin facade ... for ease of testing", so this keeps that shape while dropping the leaked SDK types from the signature.findPlaceIDsForEntitycollapses to a single delegating call.The field mask is pinned to
places.id. Text Search bills at the highest tier of any field requested, so requesting only the id keeps the call in the cheapest SKU — which also carries a larger monthly free allowance than the legacy Find Place SKU it replaces.Narrowing the interface removes the last references to the legacy module, so
googlemaps.github.io/mapsis dropped fromgo.modentirely.Version pin
cloud.google.com/go/mapsis pinned to v1.28.0: v1.29.0 and later requirego >= 1.25, and this module is ongo 1.24.0. Happy to bump it if you would rather move the toolchain.Verification
go build ./...— cleango vet ./internal/maps/... ./internal/server/recon/...— cleango test ./internal/server/recon/...— passes (recon/goldenneeds Bigtable credentials and was not run)FakeMapsClientcontract unchanged:"Hawaii"still resolves tohawaii_place_id, anything else returns emptyNote: the Terraform in
datacommonsorg/datacommonswould need a matching change — enablingplaces.googleapis.comand updating the API keyapiTargets, where the entry is currently the non-qualified stringplaces_backend. Happy to send that separately if this direction looks right.