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}