Google Places API (New) adapter — text search with a pre-call dry-run cost estimator, API-key auth
| Tier | infrastructure |
| Role | unclassified (baselined) |
| Path | crates/infrastructure/adapters-google-places |
| Edition | 2021 |
| Targets | infrastructure_adapters_google_places |
| Public items | 0 across 0 modules |
| Tests | 29 |
What it is for
Google Places (New) API client adapter.
A pure API-client adapter over Google's Places (New) searchText endpoint (https://places.googleapis.com/v1), plus a pre-call dry-run cost estimator — Places billing is per-call, so knowing the cost of a planned batch of searches before spending money is a real safety feature, not source-specific dead weight to drop on harvest.
Lifted from two plumber source files (Gate 4.5 provenance, see CHANGELOG.md):
plumber/src/collectors/google_places.rs—GooglePlacesCollector,
Places (New), impl Collector for GooglePlacesCollector. This is the crate's client (GooglePlacesClient::search_text) and its SourceIngest implementor (GooglePlacesIngest) — see ingest's module doc for the full determination and evidence.
plumber/src/discovery/google_places.rs—GooglePlacesDiscovery,
the legacy Places API. Not its client (that API is deprecated for new callers, and collectors/google_places.rs already targets the current one) — only its dry-run cost estimator feature, adapted (not verbatim-ported) onto the New API's single-call-per-query shape. See types::DryRunEstimate's doc comment for why the math differs from the source's per-zip "N detail calls" multiplier.
# Example
# async fn example() -> Result<(), infrastructure_adapters_google_places::GooglePlacesError> {
let client = infrastructure_adapters_google_places::GooglePlacesClient::from_api_key("YOUR_API_KEY")?;
// Check the cost before spending anything.
let estimate = client.dry_run_estimate(3);
println!("~${:.2} for {} calls", estimate.estimated_cost_usd, estimate.estimated_calls);
let request = infrastructure_adapters_google_places::TextSearchRequest::new("coffee shops in Austin, TX");
let places = client.search_text(&request).await?;
println!("{} places found", places.len());
# Ok(())
# }
Capabilities
No public items.
How to use it
From this crate's own rustdoc:
# async fn example() -> Result<(), infrastructure_adapters_google_places::GooglePlacesError> {
let client = infrastructure_adapters_google_places::GooglePlacesClient::from_api_key("YOUR_API_KEY")?;
// Check the cost before spending anything.
let estimate = client.dry_run_estimate(3);
println!("~${:.2} for {} calls", estimate.estimated_cost_usd, estimate.estimated_calls);
let request = infrastructure_adapters_google_places::TextSearchRequest::new("coffee shops in Austin, TX");
let places = client.search_text(&request).await?;
println!("{} places found", places.len());
# Ok(())
# }
Module structure
No public modules: the crate root is its whole surface.
Public surface
No public items.
Re-exports. Exported here, defined elsewhere.
| Export | Defined in |
|---|---|
GooglePlacesConfig | auth::GooglePlacesConfig |
GooglePlacesError | error::GooglePlacesError |
{AuthorAttribution,Circle,DisplayName,DryRunEstimate,LatLng,LocationBias,OpeningHours,Place,PlaceReview,PlacesApiError,PlacesApiErrorEnvelope,ReviewText,TextSearchRequest,TextSearchResponse,} | types::{AuthorAttribution,Circle,DisplayName,DryRunEstimate,LatLng,LocationBias,OpeningHours,Place,PlaceReview,PlacesApiError,PlacesApiErrorEnvelope,ReviewText,TextSearchRequest,TextSearchResponse,} |
{GooglePlacesClient,DEFAULT_FIELD_MASK} | client::{GooglePlacesClient,DEFAULT_FIELD_MASK} |
{GooglePlacesIngest,GooglePlacesIngestConfig} | ingest::{GooglePlacesIngest,GooglePlacesIngestConfig} |
Boundary
Reaches into foundation.
Shares tier infrastructure with 82 other crates: infrastructure-acquire, infrastructure-adapters-google-calendar, infrastructure-adapters-google-gmail, infrastructure-adapters-google-trends, infrastructure-adapters-shodan, infrastructure-adapters-yelp, infrastructure-agent, infrastructure-ai, … (82 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | infrastructure |
| Architectural role (taxonomy) | unclassified (baselined) |
| Location | crates/infrastructure/adapters-google-places |
| Vocabulary in force (lexicon) | current |
Tier flow. Which tiers this crate's own edges cross.
flowchart LR n_infrastructure["infrastructure"] --> n_foundation["foundation"]
Dependencies
Runtime, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `foundation-source-ingest` | foundation | no | always |
| `infrastructure-fetcher` | infrastructure | yes | always |
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
async-trait | ^0.1 | — | no | always |
reqwest | ^0.12 | json | no | always |
serde | ^1 | derive | no | always |
serde_json | ^1 | — | no | always |
thiserror | ^2 | — | no | always |
tokio | ^1 | sync, time | no | always |
tracing | ^0.1 | — | no | always |
Development, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
tokio | ^1 | macros, rt-multi-thread, time | no | always |
wiremock | ^0.6 | — | no | always |
Build. None.
Depended on by. 1 workspace crate.
Signal flow — what reaches this crate, and what it reaches.
flowchart LR n_application_party_discovery["application-party-discovery"] -->|uses| SELF SELF["infrastructure-adapters-google-places"] SELF -->|runtime| n_foundation_source_ingest["foundation-source-ingest"] SELF -->|runtime| n_infrastructure_fetcher["infrastructure-fetcher"] classDef self fill:#1f883d,stroke:#1f883d,color:#fff; class SELF self;
Feature flags
| Feature | Enables | On by default |
|---|---|---|
default | secure-egress | yes |
secure-egress | dep:infrastructure-fetcher | yes |
flowchart LR n_default["default"] --> n_secure_egress["secure-egress"] n_secure_egress["secure-egress"] --> n_dep_infrastructure_fetcher["dep:infrastructure-fetcher"]
Targets
| Kind | Name | Source |
|---|---|---|
| lib | infrastructure_adapters_google_places | `src/lib.rs` |
Error model
No public error type was detected: no public item declares a type named *Error, and no public signature returns one.
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | none detected |
| async runtime | yes |
| database access | none detected |
| network I/O | yes |
| unsafe code | none detected |
| environment variables | none detected |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
No environment variable is read with a literal name anywhere in this crate. A variable whose key is computed at run time cannot be listed here, and is not claimed to be absent.
Related capabilities
1 workspace crate depends on this one: application-party-discovery.
Verification
| Kind | Count |
|---|---|
| Unit tests | 29 |
| Integration tests | 0 |
| Examples | 0 |
| Doctests | 1 |
What the tests establish, by name:
debug_does_not_leak_api_key—src/auth.rsdefault_config_is_invalid_without_a_key—src/auth.rsdefault_cost_per_call_matches_ported_source_constant—src/auth.rsdisabled_config_with_a_key_is_still_invalid—src/auth.rsempty_key_is_invalid_even_if_enabled—src/auth.rsnew_with_a_key_is_valid_and_enabled—src/auth.rscreating_a_client_with_a_valid_config_succeeds—src/client.rscreating_a_client_with_an_invalid_config_fails—src/client.rsdry_run_estimate_uses_configured_cost_per_call—src/client.rsfield_mask_header_carries_every_field—src/client.rsfrom_api_key_succeeds—src/client.rsnon_success_status_becomes_typed_api_error—src/client.rsnon_success_status_without_json_body_falls_back_to_http_status—src/client.rsrate_limit_enforces_minimum_interval_between_requests—src/client.rssearch_text_sends_api_key_header_and_field_mask_never_in_url—src/client.rssearch_text_with_location_bias_round_trips—src/client.rsstrict_egress_client_is_constructible—src/client.rsbuild_request_applies_location_bias_and_result_cap—src/ingest.rsingest_aggregates_items_across_multiple_queries—src/ingest.rsingest_records_error_count_and_continues_on_a_failed_query—src/ingest.rsingest_with_no_queries_fails_fast—src/ingest.rsname_matches_source_collectors_name—src/ingest.rsdry_run_estimate_computes_one_call_per_query—src/types.rsdry_run_estimate_zero_queries_is_zero_cost—src/types.rserror_envelope_deserializes_google_standard_shape—src/types.rstext_search_request_serializes_camel_case_without_location_bias—src/types.rstext_search_request_with_location_bias_serializes_circle—src/types.rstext_search_response_deserializes_a_place_with_reviews—src/types.rstext_search_response_missing_optional_fields_defaults_cleanly—src/types.rs
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 0 | 0 |
Public modules with a //! block | 0 | 0 |
Metrics
| Metric | Value |
|---|---|
| Rust source files | 6 |
| Source lines | 1174 |
| Code lines | 822 |
| Public API items | 0 |
| Public modules | 0 |
| Tests | 29 |
| Examples | 0 |
| Cargo features | 2 |
| Direct runtime dependencies | 9 |
| Workspace reverse dependencies | 1 |
pie showData
title Rust source composition
"Code" : 822
"Blank or comment" : 352
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.