Skip to main content

cloud_sdk/client/
response.rs

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