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
147
148
149
150
use backon::Retryable;
use crate::{Error, traits::ClassifiableError};
// -----------------------------------------------------------------------------
impl crate::Client {
/// Performs the HTTP get request and returns the response to the caller.
///
/// ## Arguments
///
/// * `request` ยท This request will be converted into a URL query string and
/// forwarded to Google Maps.
///
/// # Errors
///
/// This method can fail if:
///
/// * This can fail if the request `struct` fails validation. For example,
/// parameters in the request conflict with one another, or the request
/// parameters are set in a way that's incompatible.
///
/// For example, Google Maps Directions API cannot calculate alternative
/// routes if waypoints have been set. This will cause a validation
/// failure.
///
/// * The HTTP client cannot make a connection to the Google Maps API
/// server, or successfully send the request or receive the resposne over
/// the network.
///
/// * The Google Maps API server returns an unexpected response, or data in
/// a format that's not expected.
#[cfg_attr(feature = "tracing-instrumentation", tracing::instrument(
level = "debug",
skip(self, request),
fields(
endpoint = %REQ::title(),
),
err
))]
pub(crate) async fn get_request<REQ, RSP, ERR>(
&self,
request: REQ
) -> Result<RSP, Error>
where
REQ: std::fmt::Debug
+ crate::traits::EndPoint
+ crate::traits::QueryUrl
+ crate::traits::RequestHeaders
+ Send,
RSP: serde::de::DeserializeOwned + Into<Result<RSP, ERR>>,
ERR: std::fmt::Display + From::<ERR> + Into<Error>
{
// Build the URL and query string
// Attempt to build the URL and query string for the HTTP `GET` request:
let url: String = request
.query_url()
.inspect_err(|error| tracing::error!(error = %error, "failed to build request URL"))?
.trim_matches('?')
.to_string();
tracing::info!(url = %url, "GET request");
// Get and set custom headers
let mut headers = request.request_headers(); // Request-specific headers
if REQ::send_x_goog_api_key() { // For requests that require API key in headers
let mut api_key = reqwest::header::HeaderValue::from_str(&self.key)
.map_err(|_error| Error::InvalidHeaderValue {
header_name: "X-Goog-Api-Key".to_string()
})?;
api_key.set_sensitive(true);
headers.insert("X-Goog-Api-Key", api_key);
}
// Observe any rate limiting before executing request:
self
.rate_limit
.limit_apis(REQ::apis())
.await;
// Build an async function that will be used to perform the HTTP
// request, deserialize the response, and anaylze the results. This
// function will be called by the `backon` crate, which will handle
// exponential back-offs:
let http_requestor = || async {
match self.reqwest_client.get(url.clone()).headers(headers.clone()).build() {
// Attempt to build a `GET` request for the `reqwest` client
// using the URL and query string:
Ok(request) => match self.reqwest_client.execute(request).await {
Ok(response) => if response.status().is_success() {
match response.text().await.map(String::into_bytes) {
Ok(bytes) => match serde_json::from_slice::<RSP>(&bytes) {
Ok(deserialized) => {
let result: Result<RSP, ERR> = deserialized.into();
if let Err(error) = &result {
tracing::error!(error = %error, "API error");
} // if
result.map_err(Into::into)
},
Err(error) => {
tracing::error!(error = %error, "JSON deserialization error");
if let Ok(text) = String::from_utf8(bytes) {
tracing::trace!("{text}");
}
Err(Error::from(error))
},
}, // Ok
Err(error) => {
tracing::error!(error = %error, "HTTP request error");
Err(Error::from(error))
},
} // match
} else {
let status = response.status();
tracing::error!(
http.status_code = %status.as_u16(),
http.status_text = %status.canonical_reason().unwrap_or("unknown"),
"request returned non-success status"
);
Err(Error::from(response.status()))
}, // Ok
Err(error) => {
tracing::error!(error = %error, "HTTP request error");
Err(Error::from(error))
},
}, // match
Err(error) => {
tracing::error!(error = %error, "HTTP request error");
Err(Error::from(crate::ReqError::from(error)))
},
} // match
}; // async function closure
// Perform the HTTP request. Retry on when error is possibly transient,
// according to the default `backon` policy:
let response = http_requestor
.retry(backon::ExponentialBuilder::default())
.when(|e: &Error| e.classify().is_transient())
.notify(|err, dur: std::time::Duration| {
tracing::warn!("error {} retrying after {:?}", err, dur);
}).await?;
// Return the response to the caller:
Ok(response)
} // fn
} // impl