Skip to main content

cloud_sdk/client/
response.rs

1use core::fmt;
2
3use crate::diagnostics::{DiagnosticRequestId, DiagnosticResponse};
4use crate::operation::CheckedResponseGuard;
5use crate::operation::{CheckedResponse, PreparedRequest, ResponsePolicyError};
6use crate::transport::{
7    ResponseBuffer, ResponseDecodeWorkspace, ResponseWriterError, StatusCode, TransportResponse,
8};
9
10/// HTTP response class observed after bounded authenticated transport.
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub enum ClientResponseKind {
13    /// A `2xx` response requiring operation success-policy validation.
14    Success,
15    /// A `4xx` or `5xx` response admitted by the raw error policy.
16    Error,
17    /// An informational or redirect status that is neither success nor error.
18    Other,
19}
20
21/// Failure while entering a checked success or provider-error decoder.
22pub enum CheckedDecodeError<E> {
23    /// The response writer did not contain one committed response.
24    ResponseWriter(ResponseWriterError),
25    /// Success status, body, media, or metadata policy rejected the response.
26    ResponsePolicy(ResponsePolicyError),
27    /// Provider-error decoding was requested for a non-error status.
28    ExpectedErrorStatus,
29    /// The provider-owned decoder rejected the bounded response.
30    Decoder(E),
31}
32
33impl<E> fmt::Debug for CheckedDecodeError<E> {
34    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
35        formatter.write_str(match self {
36            Self::ResponseWriter(_) => "CheckedDecodeError::ResponseWriter",
37            Self::ResponsePolicy(_) => "CheckedDecodeError::ResponsePolicy",
38            Self::ExpectedErrorStatus => "CheckedDecodeError::ExpectedErrorStatus",
39            Self::Decoder(_) => "CheckedDecodeError::Decoder([redacted])",
40        })
41    }
42}
43
44impl<E> fmt::Display for CheckedDecodeError<E> {
45    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
46        formatter.write_str(match self {
47            Self::ResponseWriter(_) => "client response is not committed",
48            Self::ResponsePolicy(_) => "client response policy rejected the response",
49            Self::ExpectedErrorStatus => "client error decoder requires an error status",
50            Self::Decoder(_) => "provider response decoder failed",
51        })
52    }
53}
54
55impl<E> core::error::Error for CheckedDecodeError<E> {}
56
57/// Bounded response coupled to the prepared policy that admitted it.
58///
59/// Raw body access is closure-scoped. Success decoding first applies the full
60/// operation success policy. Error decoding requires a `4xx`/`5xx` status and
61/// applies the operation request-ID policy before invoking provider code.
62pub struct ClientResponse<'request, 'buffer> {
63    prepared: PreparedRequest<'request>,
64    response: ResponseBuffer<'buffer>,
65}
66
67impl<'request, 'buffer> ClientResponse<'request, 'buffer> {
68    #[allow(clippy::large_types_passed_by_value)]
69    pub(crate) const fn new(
70        prepared: PreparedRequest<'request>,
71        response: ResponseBuffer<'buffer>,
72    ) -> Self {
73        Self { prepared, response }
74    }
75
76    /// Returns the committed status without exposing response bytes.
77    pub fn status(&self) -> Result<StatusCode, ResponseWriterError> {
78        self.response.with_response(|response| response.status())
79    }
80
81    /// Classifies the committed status without applying a decoder.
82    pub fn kind(&self) -> Result<ClientResponseKind, ResponseWriterError> {
83        self.status().map(|status| {
84            if status.is_success() {
85                ClientResponseKind::Success
86            } else if status.is_error() {
87                ClientResponseKind::Error
88            } else {
89                ClientResponseKind::Other
90            }
91        })
92    }
93
94    pub(crate) fn diagnostic_response(&self) -> Result<DiagnosticResponse, ResponseWriterError> {
95        let status = self.status()?;
96        let policy = self.prepared.metadata().request_id_policy();
97        let present = match policy {
98            crate::operation::RequestIdPolicy::Discard => false,
99            crate::operation::RequestIdPolicy::Protected
100            | crate::operation::RequestIdPolicy::Retain => self.response.has_request_id()?,
101        };
102        let request_id = DiagnosticRequestId::classify(policy, present);
103        Ok(DiagnosticResponse::new(status, request_id))
104    }
105
106    /// Applies success policy, decodes an owned value, and clears all storage.
107    pub fn decode_success_owned<R, E>(
108        self,
109        decode: impl for<'response> FnOnce(
110            CheckedResponse<'response>,
111            &mut ResponseDecodeWorkspace,
112        ) -> Result<R, E>,
113    ) -> Result<R, CheckedDecodeError<E>> {
114        let checked = self
115            .prepared
116            .validate_response(self.response)
117            .map_err(CheckedDecodeError::ResponsePolicy)?;
118        checked
119            .decode_owned_with_workspace(decode)
120            .map_err(CheckedDecodeError::Decoder)
121    }
122
123    /// Applies success policy and lends the cleanup-owning checked response.
124    ///
125    /// Provider SDKs use this form when an existing request-bound response
126    /// wrapper must retain its provenance while decoding. The guard still
127    /// clears all response and decoder storage when consumed or dropped.
128    pub fn decode_success_guarded<R, E>(
129        self,
130        decode: impl FnOnce(CheckedResponseGuard<'buffer>) -> Result<R, E>,
131    ) -> Result<R, CheckedDecodeError<E>> {
132        let checked = self
133            .prepared
134            .validate_response(self.response)
135            .map_err(CheckedDecodeError::ResponsePolicy)?;
136        decode(checked).map_err(CheckedDecodeError::Decoder)
137    }
138
139    /// Applies error metadata policy, decodes an owned value, and clears all storage.
140    pub fn decode_error_owned<R, E>(
141        mut self,
142        decode: impl for<'response> FnOnce(
143            TransportResponse<'response, 'buffer>,
144            &mut ResponseDecodeWorkspace,
145        ) -> Result<R, E>,
146    ) -> Result<R, CheckedDecodeError<E>> {
147        let status = self.status().map_err(CheckedDecodeError::ResponseWriter)?;
148        if !status.is_error() {
149            return Err(CheckedDecodeError::ExpectedErrorStatus);
150        }
151        self.prepared
152            .apply_response_metadata_policy(&mut self.response)
153            .map_err(CheckedDecodeError::ResponsePolicy)?;
154        let mut workspace = ResponseDecodeWorkspace::new_for_provider();
155        self.response
156            .with_response(|response| decode(response, &mut workspace))
157            .map_err(CheckedDecodeError::ResponseWriter)?
158            .map_err(CheckedDecodeError::Decoder)
159    }
160}
161
162impl fmt::Debug for ClientResponse<'_, '_> {
163    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
164        formatter
165            .debug_struct("ClientResponse")
166            .field("prepared", &"[bound]")
167            .field("response", &"[redacted]")
168            .finish()
169    }
170}