1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
use crate::error::Error as GoogleMapsError;
use crate::geocoding::{
error::Error as GeocodingError, response::status::Status as GeocodingStatus,
response::Response as GeocodingResponse, reverse::ReverseRequest as ReverseGeocodingRequest,
OUTPUT_FORMAT, SERVICE_URL,
};
use crate::request_rate::api::Api;
use backoff::future::retry;
use backoff::Error::{Permanent, Transient};
use backoff::ExponentialBackoff;
// -----------------------------------------------------------------------------
impl<'a> ReverseGeocodingRequest<'a> {
/// Performs the HTTP get request and returns the response to the caller.
///
/// ## Arguments
///
/// This method accepts no arguments.
#[tracing::instrument(level = "info", skip(self))]
pub async fn get(&mut self) -> Result<GeocodingResponse, GoogleMapsError> {
// Build the URL stem for the HTTP get request:
let mut url = format!("{SERVICE_URL}/{OUTPUT_FORMAT}?");
match &self.query {
// If query string built, append it to the URL stem.
Some(query) => url.push_str(query.as_ref()),
// If query string not built, return an error.
None => return Err(GeocodingError::QueryNotBuilt)?,
} // match
// Observe any rate limiting before executing request:
tracing::info!("making HTTP GET request to Google Maps Geocoding API");
self.client
.rate_limit
.limit_apis(vec![&Api::All, &Api::Geocoding])
.await;
tracing::debug!("{url}");
// Retries the get request until successful, an error ineligible for
// retries is returned, or we have reached the maximum retries. Note:
// errors wrapped in `Transient()` will retried by the `backoff` crate
// while errors wrapped in `Permanent()` will exit the retry loop.
let response = retry(ExponentialBackoff::default(), || async {
// Query the Google Cloud Maps Platform using using an HTTP get
// request, and return result to caller:
let response = self.client.get_request(&url).await;
// Check response from the HTTP client:
match response {
Ok(response) => {
// HTTP client was successful getting a response from the
// server. Check the HTTP status code:
if response.status().is_success() {
// If the HTTP GET request was successful, get the
// response text:
let text = &response.text().await;
match text {
Ok(text) => {
match serde_json::from_str::<GeocodingResponse>(text) {
Ok(deserialized) => {
// If the response JSON was successfully
// parsed, check the Google API status
// before returning it to the caller:
if deserialized.status == GeocodingStatus::Ok {
// If Google's response was "Ok"
// return the struct deserialized
// from JSON:
Ok(deserialized)
// Google API returned an error. This
// indicates an issue with the request.
// In most cases, retrying will not
// help:
} else {
let error = GeocodingError::GoogleMapsService(
deserialized.status.clone(),
deserialized.error_message,
);
// Check Google API response status
// for error type:
if deserialized.status == GeocodingStatus::UnknownError
{
// Only Google's "Unknown Error"
// is eligible for retries:
tracing::warn!("{}", error);
Err(Transient {
err: error,
retry_after: None,
})
} else {
// Not an "Unknown Error." The
// error is permanent, do not
// retry:
tracing::error!("{}", error);
Err(Permanent(error))
} // if
} // if
} // Ok(deserialized)
Err(error) => {
tracing::error!("JSON parsing error: {}", error);
Err(Permanent(GeocodingError::SerdeJson(error)))
} // Err
} // match
} // Ok(text)
Err(error) => {
tracing::error!("HTTP client returned: {}", error);
Err(Permanent(GeocodingError::ReqwestMessage(error.to_string())))
} // Err
} // match
// We got a response from the server but it was not OK.
// Only HTTP "500 Server Errors", and HTTP "429 Too Many
// Requests" are eligible for retries.
} else if response.status().is_server_error() || response.status() == 429 {
tracing::warn!("HTTP client returned: {}", response.status());
Err(Transient {
err: GeocodingError::HttpUnsuccessful(response.status().to_string()),
retry_after: None,
})
// Not a 500 Server Error or "429 Too Many Requests" error.
// The error is permanent, do not retry:
} else {
tracing::error!("HTTP client returned: {}", response.status());
Err(Permanent(GeocodingError::HttpUnsuccessful(
response.status().to_string(),
)))
} // if
} // case
// HTTP client did not get a response from the server. Retry:
Err(error) => {
tracing::warn!("HTTP client returned: {}", error);
Err(Transient {
err: GeocodingError::Reqwest(error),
retry_after: None,
})
} // case
} // match
})
.await?;
// Return response to caller:
Ok(response)
} // fn
} // impl