google_maps/places_new/autocomplete/
request_with_client.rs

1#![allow(clippy::ref_option, reason = "this is how getset works")]
2
3use crate::places_new::autocomplete::ResponseWithContext;
4use crate::places_new::LatLng;
5use crate::places_new::types::request::{LocationBias, LocationRestriction, PlaceTypeSet};
6use icu_locale::Locale;
7use reqwest::header::HeaderMap;
8use rust_iso3166::CountryCode;
9
10// -------------------------------------------------------------------------------------------------
11//
12/// Request for the Google Maps Places API (New) Autocomplete service.
13///
14/// Autocomplete requests return predictions based on user input text and optional parameters
15/// like location bias, restrictions, and language preferences. This is used to power
16/// search-as-you-type functionality in applications.
17///
18/// Session tokens should be used to group queries into sessions for billing purposes.
19///
20/// # `Request` vs. `RequestWithClient`
21///
22/// * `Request` - Serializable, no client reference. For caching, storage, transmission.
23/// * `RequestWithClient` - Contains client reference, executable. For immediate use.
24///
25/// You can convert between these types using `with_client()` or `into()`.
26#[derive(
27    //std
28    Clone,
29    Debug,
30    // serde
31    serde::Serialize,
32    // getset
33    getset::Getters,
34    getset::CopyGetters,
35    getset::MutGetters,
36    getset::Setters,
37    // other
38    bon::Builder
39)]
40#[serde(rename_all = "camelCase")]
41pub struct RequestWithClient<'c> {
42    /// The Google Maps API client.
43    ///
44    /// The `Client` structure contains the application's API key and other user-definable settings
45    /// such as "maximum retries," and most importantly the
46    /// [reqwest](https://crates.io/crates/reqwest) client itself.
47    #[serde(skip_deserializing, skip_serializing)]
48    pub(crate) client: &'c crate::Client,
49
50    /// The text string on which to search.
51    ///
52    /// Required. The user's input text that will be matched against places and queries to generate
53    /// predictions.
54    #[getset(get = "pub", set = "pub", get_mut = "pub")]
55    pub input: String,
56
57    /// Bias results to a specified location.
58    ///
59    /// Biases results toward the specified location without restricting them.
60    ///
61    /// At most one of `location_bias` or `location_restriction` should be set. If neither are set,
62    /// the results will be biased by IP address, meaning the IP address will be mapped to an
63    /// imprecise location and used as a biasing signal.
64    #[serde(default, skip_serializing_if = "Option::is_none")]
65    #[builder(into)]
66    #[getset(get = "pub", set = "pub", get_mut = "pub")]
67    pub location_bias: Option<LocationBias>,
68
69    /// Restrict results to a specified location.
70    ///
71    /// Restricts results to the specified location, excluding results outside.
72    ///
73    /// At most one of `location_bias` or `location_restriction` should be set. If neither are set,
74    /// the results will be biased by IP address, meaning the IP address will be mapped to an
75    /// imprecise location and used as a biasing signal.
76    #[serde(default, skip_serializing_if = "Option::is_none")]
77    #[builder(into)]
78    #[getset(get = "pub", set = "pub", get_mut = "pub")]
79    pub location_restriction: Option<LocationRestriction>,
80
81    /// Included primary Place types.
82    ///
83    /// Included primary Place type (for example, `restaurant` or `gas_station`) in [Place
84    /// Types](https://developers.google.com/maps/documentation/places/web-service/place-types), or
85    /// only (regions), or only (cities). A Place is only returned if its primary type is included
86    /// in this list. Up to 5 values can be specified. If no types are specified, all Place types
87    /// are returned.
88    #[serde(default, skip_serializing_if = "PlaceTypeSet::is_empty")]
89    #[builder(default, into)]
90    #[getset(get = "pub", set = "pub", get_mut = "pub")]
91    pub included_primary_types: PlaceTypeSet,
92
93    /// Only include results in the specified regions.
94    ///
95    /// Specified as up to 15 CLDR two-character region codes. An empty set will not restrict
96    /// results. If both `location_restriction` and `included_regions` are set, results will be in
97    /// the intersection of the two.
98    #[serde(
99        rename = "includedRegionCodes",
100        default,
101        skip_serializing_if = "Vec::is_empty",
102        serialize_with = "crate::places_new::serde::serialize_vec_country_code",
103        deserialize_with = "crate::places_new::serde::deserialize_vec_country_code"
104    )]
105    #[builder(default)]
106    #[getset(get = "pub", set = "pub", get_mut = "pub")]
107    pub included_regions: Vec<CountryCode>,
108
109    /// Language for results.
110    ///
111    /// Uses a BCP-47 language code. Defaults to `en` if not specified. Street addresses appear in
112    /// the local language (transliterated if needed), while other text uses this preferred language
113    /// when available.
114    ///
115    /// # Notes
116    ///
117    /// * See the [list of supported languages](https://developers.google.com/maps/faq#languagesupport).
118    ///   Google often updates the supported languages, so this list may not be exhaustive.
119    ///
120    /// * If `language` is not supplied, the API defaults to `en`. If you specify an invalid
121    ///   language code, the API returns an `INVALID_ARGUMENT` error.
122    ///
123    /// * The API does its best to provide a street address that is readable for both the user and
124    ///   locals. To achieve that goal, it returns street addresses in the local language,
125    ///   transliterated to a script readable by the user if necessary, observing the preferred
126    ///   language. All other addresses are returned in the preferred language. Address components
127    ///   are all returned in the same language, which is chosen from the first component.
128    ///
129    /// * If a name is not available in the preferred language, the API uses the closest match.
130    ///
131    /// * The preferred language has a small influence on the set of results that the API chooses to
132    ///   return, and the order in which they are returned. The geocoder interprets abbreviations
133    ///   differently depending on language, such as the abbreviations for street types, or synonyms
134    ///   that may be valid in one language but not in another.
135    #[serde(
136        rename = "languageCode",
137        default,
138        skip_serializing_if = "Option::is_none",
139        serialize_with = "crate::places_new::serde::serialize_optional_locale",
140        deserialize_with = "crate::places_new::serde::deserialize_optional_locale"
141    )]
142    #[builder(into)]
143    #[getset(get = "pub", set = "pub", get_mut = "pub")]
144    pub language: Option<Locale>,
145
146    /// Region for formatting the response.
147    ///
148    /// Affects how addresses are formatted (e.g., country code omitted if it matches) and can bias
149    /// results based on applicable law.
150    ///
151    /// The region code used to format the response, specified as a [two-character CLDR
152    /// code](https://www.unicode.org/cldr/charts/latest/supplemental/territory_language_information.html)
153    /// value. This parameter can also have a bias effect on the search results. There is no default
154    /// value.
155    ///
156    /// If the country name of the `formatted_address` field in the response matches the `region`,
157    /// the country code is omitted from `formatted_address`. This parameter has no effect on
158    /// `adr_format_address`, which always includes the country name when available, or on
159    /// `short_formatted_address`, which never includes it.
160    ///
161    /// Most CLDR codes are identical to ISO 3166-1 codes, with some notable exceptions. For
162    /// example, the United Kingdom's ccTLD is "uk" (.co.uk) while its ISO 3166-1 code is "gb"
163    /// (technically for the entity of "The United Kingdom of Great Britain and Northern Ireland").
164    /// The parameter can affect results based on applicable law.
165    #[serde(
166        rename = "regionCode",
167        default,
168        skip_serializing_if = "Option::is_none",
169        serialize_with = "crate::places_new::serde::serialize_optional_country_code",
170        deserialize_with = "crate::places_new::serde::deserialize_optional_country_code"
171    )]
172    pub region: Option<CountryCode>,
173
174    /// The origin point for calculating geodesic distance.
175    ///
176    /// The origin point from which to calculate geodesic distance to the destination (returned as
177    /// `distance_meters`). If this value is omitted, geodesic distance will not be returned.
178    #[serde(default, skip_serializing_if = "Option::is_none")]
179    #[builder(into)]
180    #[getset(get = "pub", set = "pub", get_mut = "pub")]
181    pub origin: Option<LatLng>,
182
183    /// Unicode character offset of input where user cursor is.
184    ///
185    /// A zero-based Unicode character offset of input indicating the cursor position in input. The
186    /// cursor position may influence what predictions are returned.
187    ///
188    /// If empty, defaults to the length of input.
189    #[serde(default, skip_serializing_if = "Option::is_none")]
190    #[builder(into)]
191    #[getset(get = "pub", set = "pub", get_mut = "pub")]
192    pub input_offset: Option<i32>,
193
194    /// Whether to include query predictions in the response.
195    ///
196    /// If `true`, response includes both Place and query predictions. Otherwise only `Place`
197    /// predictions are returned.
198    #[serde(default, skip_serializing_if = "Option::is_none")]
199    #[getset(get = "pub", set = "pub", get_mut = "pub")]
200    pub include_query_predictions: Option<bool>,
201
202    /// Session token for billing purposes.
203    ///
204    /// Session tokens are user-generated strings that track Autocomplete (New) calls as "sessions."
205    /// Autocomplete (New) uses session tokens to group the query and place selection phases of a
206    /// user autocomplete search into a discrete session for billing purposes. Session tokens are
207    /// passed into Place Details (New) calls that follow Autocomplete (New) calls. For more
208    /// information, see [Session
209    /// tokens](https://developers.google.com/maps/documentation/places/web-service/place-session-tokens).
210    ///
211    /// A string which identifies an Autocomplete session for billing purposes. Must be a URL and
212    /// filename safe base64 string with at most 36 ASCII characters in length. Otherwise an
213    /// `INVALID_ARGUMENT` error is returned.
214    ///
215    /// The session begins when the user starts typing a query, and concludes when they select a
216    /// place and a call to Place Details or Address Validation is made. Each session can have
217    /// multiple queries, followed by one Place Details or Address Validation request. The
218    /// credentials used for each request within a session must belong to the same Google Cloud
219    /// Console project. Once a session has concluded, the token is no longer valid; your app must
220    /// generate a fresh token for each session. If the `sessionToken` parameter is omitted, or if
221    /// you reuse a session token, the session is charged as if no session token was provided (each
222    /// request is billed separately).
223    ///
224    /// We recommend the following guidelines:
225    ///
226    /// * Use session tokens for all Place Autocomplete calls.
227    ///
228    /// * Generate a fresh token for each session. Using a version 4 UUID is recommended.
229    ///
230    /// * Ensure that the credentials used for all Place Autocomplete, Place Details, and Address
231    ///   Validation requests within a session belong to the same Cloud Console project.
232    ///
233    /// * Be sure to pass a unique session token for each new session. Using the same token for more
234    ///   than one session will result in each request being billed individually.
235    #[serde(default, skip_serializing_if = "Option::is_none")]
236    #[getset(get = "pub", set = "pub", get_mut = "pub")]
237    pub session_token: Option<uuid::Uuid>,
238
239    /// Include pure service area businesses.
240    ///
241    /// Include pure service area businesses if the field is set to true. Pure service area business
242    /// is a business that visits or delivers to customers directly but does not serve customers at
243    /// their business address.
244    ///
245    /// For example, businesses like cleaning services or plumbers. Those businesses do not have a
246    /// physical address or location on Google Maps. Places will not return fields including
247    /// `location`, `plusCode`, and other location related fields for these businesses.
248    #[serde(default, skip_serializing_if = "Option::is_none")]
249    #[getset(get = "pub", set = "pub", get_mut = "pub")]
250    pub include_pure_service_area_businesses: Option<bool>,
251}
252
253// -------------------------------------------------------------------------------------------------
254//
255// Method Implementations
256
257impl RequestWithClient<'_> {
258    /// Converts to a serializable request by stripping the client reference.
259    ///
260    /// Creates a `Request` containing all query parameters but without the client reference, making
261    /// it possible to serialize, store, or transmit. Use this when you need to persist request
262    /// state for later execution.
263    ///
264    /// Note: Typically you don't need to call this directly, as `execute()` automatically returns a
265    /// `ResponseWithContext` containing the serializable request.
266    #[must_use]
267    pub fn into_request(self) -> crate::places_new::autocomplete::Request {
268        self.into()
269    }
270
271    /// Executes the autocomplete request.
272    ///
273    /// Sends the configured request to the Google Maps API and returns both the response and a
274    /// serializable copy of the request parameters in a `ResponseWithContext`.
275    ///
276    /// The returned context preserves all request state including the session token, enabling
277    /// session continuation with `Client::next_autocomplete()`.
278    ///
279    /// This method is available on both the builder (via `.build().execute()` shorthand) and on
280    /// `RequestWithClient` directly when constructing requests manually.
281    ///
282    /// # Examples
283    ///
284    /// Using the builder pattern (most common):
285    /// ```rust,ignore
286    /// let ctx = client
287    ///     .autocomplete("pizza")
288    ///     .location_bias(LocationBias::circle(circle))
289    ///     .execute()
290    ///     .await?;
291    /// ```
292    ///
293    /// Using `RequestWithClient` directly:
294    /// ```rust,ignore
295    /// let request = Request::default().with_client(&client);
296    /// let ctx = request.execute().await?;
297    /// ```
298    ///
299    /// Continue the session
300    /// ```rust,ignore
301    /// let ctx = client.next_autocomplete("pizza margherita", ctx).await?;
302    /// ```
303    pub async fn execute(self) -> Result<ResponseWithContext, crate::Error> {
304        let response = self.client.post_request(&self).await?;
305        Ok(ResponseWithContext { response, request: self.into() })
306    }
307}
308
309impl<S: request_with_client_builder::State> RequestWithClientBuilder<'_, S> {
310    /// Executes the autocomplete request.
311    ///
312    /// Builds the request and sends it to the Google Maps API, returning the parsed autocomplete
313    /// response. This method both completes the builder and executes the HTTP request in one step.
314    ///
315    /// # Examples
316    ///
317    /// ```rust,ignore
318    /// let response = client
319    ///     .autocomplete("pizza")
320    ///     .location_bias(LocationBias::circle(circle))
321    ///     .execute()
322    ///     .await?;
323    /// ```
324    pub async fn execute(self) -> Result<ResponseWithContext, crate::Error>
325    where
326        S: request_with_client_builder::IsComplete,
327    {
328        let request = self.build();  // Build request
329        let response = request.client.post_request(&request).await?;
330        Ok(ResponseWithContext { response, request: request.into() })
331    }
332}
333
334impl crate::client::Client {
335    /// The Places API **Place Autocomplete** service returns place predictions.
336    ///
337    /// Autocomplete (New) is a web service that returns place predictions and query predictions in
338    /// response to an HTTP request. In the request, specify a text search string and geographic
339    /// bounds that controls the search area.
340    ///
341    /// Autocomplete (New) can match on full words and substrings of the input, resolving place
342    /// names, addresses, and plus codes. Applications can therefore send queries as the user types,
343    /// to provide on-the-fly place and query predictions.
344    ///
345    /// The response from Autocomplete (New) can contain two types of predictions:
346    ///
347    /// - **Place predictions**: Places, such as businesses, addresses and points of interest, based
348    ///   on the specified input text string and search area. Place predictions are returned by
349    ///   default.
350    ///
351    /// - **Query predictions**: Query strings matching the input text string and search area. Query
352    ///   predictions are not returned by default. Use the `.include_query_predictions(true)`
353    ///   request parameter to add query predictions to the response.
354    ///
355    /// For example, you call Autocomplete (New) using as input a string that contains a partial
356    /// user input, "Sicilian piz", with the search area limited to San Francisco, CA. The response
357    /// then contains a list of place predictions that match the search string and search area, such
358    /// as the restaurant named "Sicilian Pizza Kitchen", along with details about the place.
359    ///
360    /// The returned place predictions are designed to be presented to the user to aid them in
361    /// selecting the intended place. You can make a Place Details (New) request to get more
362    /// information about any of the returned place predictions.
363    ///
364    /// The response can also contain a list of query predictions that match the search string and
365    /// search area, such as "Sicilian Pizza & Pasta". Each query prediction in the response
366    /// includes the text field containing a recommended text search string. Use that string as an
367    /// input to Text Search (New) to perform a more detailed search.
368    ///
369    /// The APIs Explorer lets you make live requests so that you can get familiar with the API and
370    /// the API options: <https://developers.google.com/maps/documentation/places/web-service/place-autocomplete#try_it>
371    ///
372    /// ## Arguments
373    ///
374    /// * `input` · The text string on which to search.
375    ///
376    /// ## Examples
377    ///
378    /// ```rust,no_run
379    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
380    /// use google_maps::places_new::PlaceType;
381    ///
382    /// let google_maps_client = google_maps::Client::try_new("YOUR_API_KEY_HERE")?;
383    ///
384    /// // Start a new autocomplete session
385    /// let response = google_maps_client
386    ///     .autocomplete("pizza")
387    ///     .included_primary_types(vec![PlaceType::Restaurant])
388    ///     .execute()
389    ///     .await?;
390    ///
391    /// // Display suggestions with HTML highlighting
392    /// for suggestion in &response.suggestions {
393    ///     println!("{}", suggestion.to_html("mark"));
394    /// }
395    ///
396    /// // Output:
397    /// // <mark>Pizza</mark> By The Bay, Marine Drive, Churchgate, Mumbai, Maharashtra, India
398    /// // <mark>Pizza</mark> 4P's Indiranagar, 12th Main Road, HAL 2nd Stage, Bengaluru, Karnataka, India
399    /// // <mark>Pizza</mark> Culture Napoletana, Edmonton Trail, Calgary, AB, Canada
400    ///
401    /// # Ok(())
402    /// # }
403    /// ```
404    #[cfg(feature = "places-new-autocomplete")]
405    pub fn autocomplete(
406        &self,
407        input: impl Into<String>
408    ) -> RequestWithClientBuilder<
409        '_,
410        crate::places_new::autocomplete::request_with_client::request_with_client_builder::SetInput<
411            crate::places_new::autocomplete::request_with_client::request_with_client_builder::SetClient
412        >
413    > {
414        RequestWithClient::builder().client(self).input(input.into())
415    }
416
417    /// Continue a Place Autocomplete session with new input.
418    ///
419    /// Reuses the session token and all other parameters from the previous response, only updating
420    /// the input text. This maintains session continuity for Google's billing model and ensures
421    /// consistent relevance scoring across the autocomplete interaction.
422    ///
423    /// This method immediately executes the request with the preserved parameters. If you need to
424    /// modify parameters before the next request, update the `ResponseWithContext` first:
425    ///
426    /// ## Arguments
427    ///
428    /// * `input` · The new text string on which to search.
429    /// * `previous` · The previous response context containing the session state to continue.
430    ///
431    /// ## Examples
432    ///
433    /// ```rust,no_run
434    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
435    /// use google_maps::places_new::PlaceType;
436    ///
437    /// let google_maps_client = google_maps::Client::try_new("YOUR_API_KEY_HERE")?;
438    ///
439    /// // Initial autocomplete request
440    /// let response = google_maps_client
441    ///     .autocomplete("pizza")
442    ///     .included_primary_types(vec![PlaceType::Restaurant])
443    ///     .execute()
444    ///     .await?;
445    ///
446    /// for suggestion in &response.suggestions {
447    ///     println!("{}", suggestion.to_html("mark"));
448    /// }
449    ///
450    /// // Output:
451    /// // <mark>Pizza</mark> By The Bay, Marine Drive, Churchgate, Mumbai, Maharashtra, India
452    /// // <mark>Pizza</mark> 4P's Indiranagar, 12th Main Road, HAL 2nd Stage, Bengaluru, Karnataka, India
453    ///
454    /// // Continue the session as the user types more
455    /// let response = google_maps_client
456    ///     .next_autocomplete("pizza sicilian", response)
457    ///     .await?;
458    ///
459    /// for suggestion in &response.suggestions {
460    ///     println!("{}", suggestion.to_html("b"));
461    /// }
462    ///
463    /// // Output:
464    /// // <b>Pizza Sicilian</b>a, Rue Sully Prudhomme, Châtillon, France
465    /// // <b>Pizza Sicilian</b>a, 6a Avenida, Puerto Barrios, Guatemala
466    /// // <b>Pizza Sicilian</b>a 762, Chiquimula, Guatemala
467    ///
468    /// # Ok(())
469    /// # }
470    /// ```
471    pub async fn next_autocomplete(
472        &self,
473        input: impl Into<String>,
474        previous: ResponseWithContext,
475    ) -> Result<ResponseWithContext, crate::Error> {
476        let mut request_with_client = previous.request.with_client(self);
477        request_with_client.set_input(input.into());
478        request_with_client.execute().await
479    }
480}
481
482// -------------------------------------------------------------------------------------------------
483//
484// Trait Implementations
485
486#[cfg(feature = "reqwest")]
487use crate::request_rate::api::Api;
488
489/// Defines the Google Maps Places API HTTP endpoint for requests.
490///
491/// This trait returns information needed to make HTTP `POST` requests to the Places API endpoint.
492/// It includes service URL, debugging info, and rate-limiting configuration.
493impl crate::traits::EndPoint for &RequestWithClient<'_> {
494    fn service_url() -> &'static str {
495        "https://places.googleapis.com/v1/places:autocomplete"
496    }
497
498    fn output_format() -> std::option::Option<&'static str> {
499        None // No need to specify the output format, this end-point always returns JSON.
500    }
501
502    #[cfg(feature = "reqwest")]
503    fn title() -> &'static str {
504        "Places API (New) Autocomplete"
505    }
506
507    #[cfg(feature = "reqwest")]
508    fn apis() -> &'static [Api] {
509        &[Api::All, Api::PlacesNew, Api::Autocomplete]
510    }
511}
512
513#[cfg(feature = "reqwest")]
514impl crate::traits::RequestBody for &RequestWithClient<'_> {
515    /// Converts the `RequestWithClient` struct into JSON for submission to Google Maps.
516    ///
517    /// Serializes the request body fields into a JSON object for the HTTP POST request body.
518    ///
519    /// # Errors
520    ///
521    /// Returns an error if JSON serialization fails.
522    fn request_body(&self) -> Result<String, crate::Error> {
523        Ok(serde_json::to_string(self)?)
524    }
525}
526
527#[cfg(feature = "reqwest")]
528impl crate::traits::QueryString for &RequestWithClient<'_> {
529    /// Builds the URL query string for the HTTP request.
530    ///
531    /// The Places (New) API uses the HTTP body for most request data, so the query string only
532    /// contains the API key for authentication.
533    ///
534    /// ## Arguments
535    ///
536    /// This method accepts no arguments.
537    fn query_string(&self) -> String {
538        String::new()
539    }
540}
541
542#[cfg(feature = "reqwest")]
543impl crate::traits::RequestHeaders for &RequestWithClient<'_> {
544    /// Returns a map of HTTP header names to values.
545    ///
546    /// These headers will be added to the HTTP request alongside the standard headers like
547    /// `X-Goog-Api-Key`.
548    fn request_headers(&self) -> HeaderMap {
549        HeaderMap::default()
550    }
551
552    /// Returns whether the `X-Goog-Api-Key` header should be set for this request.
553    fn send_x_goog_api_key() -> bool {
554        true
555    }
556}
557
558#[cfg(feature = "reqwest")]
559impl crate::traits::Validatable for &RequestWithClient<'_> {
560    /// Validates the autocomplete request parameters.
561    ///
562    /// Checks that the combination of parameters makes sense and will be accepted by the Google
563    /// Maps Places API. This does not validate individual parameter values (like coordinate
564    /// ranges), only the logical consistency of the request.
565    ///
566    /// ## Validation Rules
567    ///
568    /// - `location_bias` and `location_restriction` cannot both be set
569    ///
570    /// # Errors
571    ///
572    /// Returns an error if:
573    /// - Both `location_bias` and `location_restriction` are set
574    fn validate(&self) -> Result<(), crate::Error> {
575        // Check that location_bias and location_restriction are mutually exclusive
576        if self.location_bias.is_some() && self.location_restriction.is_some() {
577            let debug = "location_bias: Some(...), location_restriction: Some(...)".to_string();
578            let span = (0, debug.len());
579
580            return Err(crate::places_new::autocomplete::Error::MutuallyExclusiveLocationFields {
581                debug,
582                span,
583	        }.into());
584        }
585
586        Ok(())
587    }
588}