google_cloud_gax/response.rs
1// Copyright 2025 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Response types.
16//!
17//! This module contains types related to Google Cloud service responses.
18//! Notably it contains the `Response` type itself. Typically you'll import
19//! this type.
20//!
21//! # Examples
22//!
23//! Inspecting the result of a request
24//!
25//! ```no_run
26//! # use google_cloud_gax::Result;
27//! # use google_cloud_gax::response::Response;
28//! // A type representing a Google Cloud service resource, for example, a
29//! // secret manager secret.
30//! struct Resource {
31//! // ...
32//! }
33//!
34//! async fn make_google_service_rpc(project_id: &str) -> Result<Response<Resource>> {
35//! // ...
36//! # panic!()
37//! }
38//!
39//! # tokio_test::block_on(async {
40//! let response = make_google_service_rpc("my-project").await?;
41//! if let Some(date) = response.headers().get("Date") {
42//! // do something with the date
43//! }
44//! let resource = response.body();
45//! // do something with
46//! # Result::<()>::Ok(()) });
47//! ```
48//!
49//! Creating a response for mocks
50//!
51//! ```
52//! # use google_cloud_gax::Result;
53//! # use google_cloud_gax::response::Response;
54//! // A type representing a Google Cloud service resource, for example, a
55//! // secret manager secret.
56//! struct Resource {
57//! // ...
58//! }
59//!
60//! fn make_mock_response(body: Resource) -> Result<Response<Resource>> {
61//! Ok(Response::from(body))
62//! }
63//! ```
64
65/// Represents a Google Cloud service response.
66///
67/// A response from a Google Cloud service consists of a body (potentially the
68/// unit type), and some metadata, currently just headers.
69///
70/// Typically you get a response as the result of making a request via some
71/// client in the Google Cloud client libraries for Rust. You may also create
72/// responses directly when mocking clients for your own tests.
73///
74/// # Examples
75/// Inspecting the result of a request
76///
77/// ```no_run
78/// # use google_cloud_gax::Result;
79/// # use google_cloud_gax::response::Response;
80/// // A type representing a Google Cloud service resource, for example, a
81/// // secret manager secret.
82/// struct Resource {
83/// // ...
84/// }
85///
86/// async fn make_google_service_rpc(project_id: &str) -> Result<Response<Resource>> {
87/// // ...
88/// # panic!()
89/// }
90///
91/// # tokio_test::block_on(async {
92/// let response = make_google_service_rpc("my-project").await?;
93/// if let Some(date) = response.headers().get("Date") {
94/// // do something with the date
95/// }
96/// let resource = response.body();
97/// // do something with
98/// # Result::<()>::Ok(()) });
99/// ```
100///
101/// Creating a response for mocks
102///
103/// ```
104/// # use google_cloud_gax::Result;
105/// # use google_cloud_gax::response::Response;
106/// // A type representing a Google Cloud service resource, for example, a
107/// // secret manager secret.
108/// struct Resource {
109/// // ...
110/// }
111///
112/// fn make_mock_response(body: Resource) -> Result<Response<Resource>> {
113/// Ok(Response::from(body))
114/// }
115/// ```
116///
117#[derive(Clone, Debug)]
118pub struct Response<T> {
119 parts: Parts,
120 body: T,
121}
122
123impl<T> Response<T> {
124 /// Creates a response from the body.
125 ///
126 /// # Example
127 /// ```
128 /// # use google_cloud_gax::response::Response;
129 /// #[derive(Clone, Default)]
130 /// pub struct Resource {
131 /// // ...
132 /// }
133 ///
134 /// let body = Resource::default();
135 /// let response = Response::from(body);
136 /// ```
137 pub fn from(body: T) -> Self {
138 Self {
139 body,
140 parts: Parts::default(),
141 }
142 }
143
144 /// Creates a response from the given parts.
145 ///
146 /// # Example
147 /// ```
148 /// # use google_cloud_gax::response::Response;
149 /// # use google_cloud_gax::response::Parts;
150 /// #[derive(Clone, Default)]
151 /// pub struct Resource {
152 /// // ...
153 /// }
154 ///
155 /// let mut headers = http::HeaderMap::new();
156 /// headers.insert(http::header::CONTENT_TYPE, http::HeaderValue::from_static("application/json"));
157 /// let body = Resource::default();
158 /// let response : Response<Resource> = Response::from_parts(
159 /// Parts::new().set_headers(headers), body);
160 /// assert!(response.headers().get(http::header::CONTENT_TYPE).is_some());
161 /// ```
162 pub fn from_parts(parts: Parts, body: T) -> Self {
163 Self { parts, body }
164 }
165
166 /// Returns the headers associated with this response.
167 ///
168 /// # Example
169 /// ```
170 /// # use google_cloud_gax::response::Response;
171 /// let response = Response::from(());
172 /// assert!(response.headers().is_empty());
173 /// ```
174 pub fn headers(&self) -> &http::HeaderMap<http::HeaderValue> {
175 &self.parts.headers
176 }
177
178 /// Returns the body associated with this response.
179 ///
180 /// # Example
181 /// ```
182 /// # use google_cloud_gax::response::Response;
183 /// let response = Response::from("test".to_string());
184 /// assert_eq!(response.body().as_str(), "test");
185 /// ```
186 pub fn body(&self) -> &T {
187 &self.body
188 }
189
190 /// Consumes the response returning the metadata, and body.
191 ///
192 /// # Example
193 /// ```
194 /// # use google_cloud_gax::response::Response;
195 /// let response = Response::from("test".to_string());
196 /// let (parts, body) = response.into_parts();
197 /// assert_eq!(body.as_str(), "test");
198 /// assert!(parts.headers.is_empty());
199 /// ```
200 pub fn into_parts(self) -> (Parts, T) {
201 (self.parts, self.body)
202 }
203
204 /// Consumes the response returning only its body.
205 ///
206 /// # Example
207 /// ```
208 /// # use google_cloud_gax::response::Response;
209 /// let response = Response::from("test".to_string());
210 /// let body = response.into_body();
211 /// assert_eq!(body.as_str(), "test");
212 /// ```
213 pub fn into_body(self) -> T {
214 self.body
215 }
216}
217
218/// Component parts of a response.
219///
220/// The response parts, other than the body, consist of just headers. We
221/// anticipate the addition of new fields over time.
222///
223/// The headers are used to return gRPC metadata, as well as (unsurprisingly)
224/// HTTP headers.
225///
226/// # Example
227/// ```
228/// # use google_cloud_gax::response::Parts;
229/// let mut headers = http::HeaderMap::new();
230/// headers.insert(http::header::CONTENT_TYPE, http::HeaderValue::from_static("application/json"));
231/// let parts = Parts::new().set_headers(headers);
232///
233/// assert_eq!(
234/// parts.headers.get(http::header::CONTENT_TYPE),
235/// Some(&http::HeaderValue::from_static("application/json"))
236/// );
237/// ```
238///
239/// [tower]: https://github.com/tower-rs/tower
240#[derive(Clone, Debug, Default)]
241#[non_exhaustive]
242pub struct Parts {
243 /// The HTTP headers or the gRPC metadata converted to HTTP headers.
244 pub headers: http::HeaderMap<http::HeaderValue>,
245 // Internal field for transport-specific observability data.
246 #[cfg(google_cloud_unstable_tracing)]
247 pub(crate) transport_span_info: Option<internal::TransportSpanInfo>,
248}
249
250impl Parts {
251 /// Create a new instance.
252 ///
253 /// # Example
254 /// ```
255 /// # use google_cloud_gax::response::Parts;
256 /// let parts = Parts::new();
257 /// assert!(parts.headers.is_empty());
258 /// ```
259 pub fn new() -> Self {
260 Parts::default()
261 }
262
263 /// Set the headers.
264 ///
265 /// # Example
266 /// ```
267 /// # use google_cloud_gax::response::Parts;
268 /// let mut headers = http::HeaderMap::new();
269 /// headers.insert(
270 /// http::header::CONTENT_TYPE,
271 /// http::HeaderValue::from_static("application/json"),
272 /// );
273 /// let parts = Parts::new().set_headers(headers.clone());
274 /// assert_eq!(parts.headers, headers);
275 /// ```
276 pub fn set_headers<V>(mut self, v: V) -> Self
277 where
278 V: Into<http::HeaderMap>,
279 {
280 self.headers = v.into();
281 self
282 }
283}
284
285#[cfg(google_cloud_unstable_tracing)]
286#[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
287pub mod internal {
288 //! This module contains implementation details. It is not part of the
289 //! public API. Types and functions in this module may be changed or removed
290 //! without warnings. Applications should not use any types contained
291 //! within.
292
293 /// Holds information about the final outcome of a transport-level request.
294 ///
295 /// This struct is populated by the transport layer (e.g., HTTP or gRPC)
296 /// after a logical client request operation (Client Request Span) is complete.
297 /// It contains details derived from the *final* network interaction,
298 /// including status, any errors, the endpoint contacted, and retry/redirect
299 /// counts. This information is used to enrich the Client Request Span with
300 /// attributes that are only known after the response is received.
301 #[derive(Debug, Clone, Default)]
302 pub struct TransportSpanInfo {
303 // Response status
304 /// The numeric HTTP response status code.
305 ///
306 /// Examples: 200, 404, 500
307 pub http_status_code: Option<u16>,
308 /// The gRPC status code.
309 ///
310 /// See <https://github.com/grpc/grpc/blob/master/doc/statuscodes.md>
311 /// Examples: 0, 4, 14
312 pub rpc_grpc_status_code: Option<i32>,
313 /// A low-cardinality classification of the error.
314 ///
315 /// Examples: "404", "RATE_LIMIT_EXCEEDED", "CLIENT_TIMEOUT"
316 pub error_type: Option<String>, // Derived from status or transport error
317
318 /// The destination host name or IP address.
319 ///
320 /// Examples: myservice.googleapis.com, 10.0.0.1
321 pub server_address: Option<String>,
322 /// The destination port number.
323 ///
324 /// Examples: 443, 8080
325 pub server_port: Option<i32>,
326 /// The full URL of the *final* request.
327 ///
328 /// Example: https://myservice.googleapis.com/v1/projects/my-project/data
329 pub url_full: Option<String>, // The URL of the *final* request
330
331 /// The total number of underlying requests sent.
332 pub request_resend_count: Option<i64>, // Total underlying requests
333 }
334
335 /// Gets a reference to the TransportSpanInfo from the response.
336 pub fn transport_span_info<T>(response: &super::Response<T>) -> Option<&TransportSpanInfo> {
337 response.parts.transport_span_info.as_ref()
338 }
339
340 /// Sets the TransportSpanInfo on the response.
341 pub fn set_transport_span_info<T>(
342 response: &mut super::Response<T>,
343 info: Option<TransportSpanInfo>,
344 ) {
345 response.parts.transport_span_info = info;
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[test]
354 fn response_from() {
355 let response = Response::from("abc123".to_string());
356 assert!(response.headers().is_empty());
357 assert_eq!(response.body().as_str(), "abc123");
358
359 let body = response.into_body();
360 assert_eq!(body.as_str(), "abc123");
361 }
362
363 #[test]
364 fn response_from_parts() {
365 let mut headers = http::HeaderMap::new();
366 headers.insert(
367 http::header::CONTENT_TYPE,
368 http::HeaderValue::from_static("application/json"),
369 );
370 let parts = Parts::new().set_headers(headers.clone());
371
372 let response = Response::from_parts(parts, "abc123".to_string());
373 assert_eq!(response.body().as_str(), "abc123");
374 assert_eq!(response.headers(), &headers);
375
376 let (parts, body) = response.into_parts();
377 assert_eq!(body.as_str(), "abc123");
378 assert_eq!(parts.headers, headers);
379 }
380
381 #[test]
382 fn parts() {
383 let parts = Parts::new();
384 assert!(parts.headers.is_empty());
385
386 let mut headers = http::HeaderMap::new();
387 headers.insert(
388 http::header::CONTENT_TYPE,
389 http::HeaderValue::from_static("application/json"),
390 );
391 let parts = Parts::new().set_headers(headers.clone());
392
393 assert_eq!(parts.headers, headers);
394 assert_eq!(
395 parts.headers.get(http::header::CONTENT_TYPE),
396 Some(&http::HeaderValue::from_static("application/json"))
397 );
398 }
399
400 #[test]
401 #[cfg(google_cloud_unstable_tracing)]
402 fn transport_span_info_accessors() {
403 let mut response = Response::from("test".to_string());
404
405 assert!(internal::transport_span_info(&response).is_none());
406
407 // Set a value
408 let info = internal::TransportSpanInfo {
409 http_status_code: Some(200),
410 ..Default::default()
411 };
412 internal::set_transport_span_info(&mut response, Some(info.clone()));
413
414 // Get the value
415 let retrieved = internal::transport_span_info(&response);
416 assert!(retrieved.is_some());
417 assert_eq!(retrieved.unwrap().http_status_code, Some(200));
418
419 // Set to None
420 internal::set_transport_span_info(&mut response, None);
421 assert!(internal::transport_span_info(&response).is_none());
422 }
423}