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}