Skip to main content

cloud_sdk/operation/
prepared.rs

1//! Prepared operation storage, endpoint binding, and execution.
2
3use core::fmt;
4
5use crate::operation::{CheckedResponse, OperationMetadata, ResponsePolicy, ResponsePolicyError};
6use crate::transport::{
7    AsyncTransport, BlockingTransport, BoundTransport, EndpointIdentity, EndpointIdentityError,
8    ResponseStorageSanitizer, TransportRequest,
9};
10use crate::{ApiFamily, Provider};
11
12/// Caller-owned target and request-body storage supplied to preparation.
13pub struct PreparationStorage<'storage> {
14    target: &'storage mut [u8],
15    body: &'storage mut [u8],
16}
17
18impl<'storage> PreparationStorage<'storage> {
19    /// Creates complete caller-owned storage for one preparation attempt.
20    ///
21    /// # Security
22    ///
23    /// Preparation may write credentials or other secrets into `body`. A
24    /// successful [`PreparedRequest`] must retain those bytes until transport
25    /// use, so this wrapper cannot clear them on success. For secret-bearing
26    /// operations, guard `body` with a volatile-clearing type such as
27    /// `cloud_sdk_sanitization::SecretBuffer` and drop the guard immediately
28    /// after transport use. A plain mutable slice is not cleared when the
29    /// prepared request is dropped.
30    #[must_use]
31    pub const fn new(target: &'storage mut [u8], body: &'storage mut [u8]) -> Self {
32        Self { target, body }
33    }
34
35    /// Consumes the storage wrapper and returns both independent buffers.
36    #[must_use]
37    pub fn into_parts(self) -> (&'storage mut [u8], &'storage mut [u8]) {
38        (self.target, self.body)
39    }
40}
41
42impl fmt::Debug for PreparationStorage<'_> {
43    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
44        formatter
45            .debug_struct("PreparationStorage")
46            .field("target_capacity", &self.target.len())
47            .field("body_capacity", &self.body.len())
48            .finish()
49    }
50}
51
52/// Typed provider operation that can prepare one complete request.
53///
54/// ```compile_fail
55/// use cloud_sdk::operation::PrepareOperation;
56///
57/// fn prepare_without_storage<O: PrepareOperation>(operation: &O) {
58///     let _ = operation.prepare();
59/// }
60/// ```
61pub trait PrepareOperation {
62    /// Preparation-specific failure.
63    type Error;
64
65    /// Writes into caller storage and returns an executable prepared request.
66    fn prepare<'storage>(
67        &self,
68        storage: PreparationStorage<'storage>,
69    ) -> Result<PreparedRequest<'storage>, Self::Error>;
70}
71
72/// Provider service and immutable expected endpoint identity.
73#[derive(Clone, Copy, Debug, Eq, PartialEq)]
74pub struct ProviderService {
75    provider: Provider,
76    family: ApiFamily,
77    endpoint: EndpointIdentity<'static>,
78}
79
80impl ProviderService {
81    /// Binds a provider API family to one normalized expected endpoint.
82    #[must_use]
83    pub const fn new(
84        provider: Provider,
85        family: ApiFamily,
86        endpoint: EndpointIdentity<'static>,
87    ) -> Self {
88        Self {
89            provider,
90            family,
91            endpoint,
92        }
93    }
94
95    /// Returns the provider namespace.
96    #[must_use]
97    pub const fn provider(self) -> Provider {
98        self.provider
99    }
100
101    /// Returns the provider API family.
102    #[must_use]
103    pub const fn family(self) -> ApiFamily {
104        self.family
105    }
106
107    /// Returns the immutable expected endpoint identity.
108    #[must_use]
109    pub const fn endpoint(self) -> EndpointIdentity<'static> {
110        self.endpoint
111    }
112}
113
114/// Complete request, endpoint, operation metadata, and response policy.
115#[derive(Clone, Copy)]
116pub struct PreparedRequest<'request> {
117    request: TransportRequest<'request>,
118    service: ProviderService,
119    metadata: OperationMetadata,
120    response_policy: ResponsePolicy,
121}
122
123impl<'request> PreparedRequest<'request> {
124    /// Creates a complete prepared request without policy defaults.
125    #[must_use]
126    pub const fn new(
127        request: TransportRequest<'request>,
128        service: ProviderService,
129        metadata: OperationMetadata,
130        response_policy: ResponsePolicy,
131    ) -> Self {
132        Self {
133            request,
134            service,
135            metadata,
136            response_policy,
137        }
138    }
139
140    /// Returns the validated transport request.
141    #[must_use]
142    pub const fn transport_request(self) -> TransportRequest<'request> {
143        self.request
144    }
145
146    /// Returns the bound provider service.
147    #[must_use]
148    pub const fn service(self) -> ProviderService {
149        self.service
150    }
151
152    /// Returns complete safety and retry metadata.
153    #[must_use]
154    pub const fn metadata(self) -> OperationMetadata {
155        self.metadata
156    }
157
158    /// Returns complete checked-response policy.
159    #[must_use]
160    pub const fn response_policy(self) -> ResponsePolicy {
161        self.response_policy
162    }
163
164    /// Verifies endpoint identity, executes once, and validates the response.
165    pub fn execute_blocking<'buffer, T>(
166        self,
167        transport: &T,
168        response_storage: &'buffer mut [u8],
169    ) -> Result<CheckedResponse<'buffer>, PreparedExecutionError<T::Error>>
170    where
171        T: BlockingTransport + BoundTransport + ResponseStorageSanitizer,
172    {
173        transport.sanitize_response_storage(response_storage);
174        self.verify_endpoint(transport)
175            .map_err(map_endpoint_error)?;
176        let admitted = self.admit_response_storage(response_storage)?;
177        let response = transport
178            .send(self.request, admitted)
179            .map_err(PreparedExecutionError::Transport)?;
180        self.response_policy
181            .validate(response)
182            .map_err(PreparedExecutionError::ResponsePolicy)
183    }
184
185    /// Async equivalent of [`Self::execute_blocking`] without owning an executor.
186    pub async fn execute_async<'transport, 'buffer, T>(
187        &'transport self,
188        transport: &'transport T,
189        response_storage: &'buffer mut [u8],
190    ) -> Result<CheckedResponse<'buffer>, PreparedExecutionError<T::Error>>
191    where
192        T: AsyncTransport + BoundTransport + ResponseStorageSanitizer,
193        'request: 'transport,
194        'buffer: 'transport,
195    {
196        transport.sanitize_response_storage(response_storage);
197        self.verify_endpoint(transport)
198            .map_err(map_endpoint_error)?;
199        let admitted = self.admit_response_storage(response_storage)?;
200        let response = transport
201            .send(self.request, admitted)
202            .await
203            .map_err(PreparedExecutionError::Transport)?;
204        self.response_policy
205            .validate(response)
206            .map_err(PreparedExecutionError::ResponsePolicy)
207    }
208
209    fn verify_endpoint<T>(self, transport: &T) -> Result<(), EndpointCheckError>
210    where
211        T: BoundTransport,
212    {
213        let actual = transport
214            .endpoint_identity()
215            .map_err(EndpointCheckError::Invalid)?;
216        if actual != self.service.endpoint {
217            return Err(EndpointCheckError::Mismatch);
218        }
219        Ok(())
220    }
221
222    fn admit_response_storage<E>(
223        self,
224        storage: &mut [u8],
225    ) -> Result<&mut [u8], PreparedExecutionError<E>> {
226        let admitted_len = core::cmp::min(storage.len(), self.response_policy.max_body_bytes());
227        storage
228            .get_mut(..admitted_len)
229            .ok_or(PreparedExecutionError::ResponseStorageUnavailable)
230    }
231}
232
233impl fmt::Debug for PreparedRequest<'_> {
234    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
235        formatter
236            .debug_struct("PreparedRequest")
237            .field("request", &self.request)
238            .field("service", &self.service)
239            .field("metadata", &self.metadata)
240            .field("response_policy", &self.response_policy)
241            .finish()
242    }
243}
244
245/// Prepared execution failure with transport details redacted from diagnostics.
246#[derive(Clone, Copy, Eq, PartialEq)]
247pub enum PreparedExecutionError<E> {
248    /// The bound transport returned invalid endpoint identity.
249    EndpointIdentity(EndpointIdentityError),
250    /// The bound endpoint differs from the prepared provider service.
251    EndpointMismatch,
252    /// Response storage could not be structurally limited.
253    ResponseStorageUnavailable,
254    /// The concrete transport failed.
255    Transport(E),
256    /// The response failed provider-neutral policy.
257    ResponsePolicy(ResponsePolicyError),
258}
259
260impl<E> fmt::Debug for PreparedExecutionError<E> {
261    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
262        match self {
263            Self::EndpointIdentity(error) => formatter
264                .debug_tuple("EndpointIdentity")
265                .field(error)
266                .finish(),
267            Self::EndpointMismatch => formatter.write_str("EndpointMismatch"),
268            Self::ResponseStorageUnavailable => formatter.write_str("ResponseStorageUnavailable"),
269            Self::Transport(_) => formatter.write_str("Transport([redacted])"),
270            Self::ResponsePolicy(error) => formatter
271                .debug_tuple("ResponsePolicy")
272                .field(error)
273                .finish(),
274        }
275    }
276}
277
278impl<E> fmt::Display for PreparedExecutionError<E> {
279    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
280        formatter.write_str(match self {
281            Self::EndpointIdentity(_) => "transport endpoint identity is invalid",
282            Self::EndpointMismatch => "transport endpoint differs from prepared service",
283            Self::ResponseStorageUnavailable => "response storage is unavailable",
284            Self::Transport(_) => "prepared request transport failed",
285            Self::ResponsePolicy(_) => "prepared response policy failed",
286        })
287    }
288}
289
290impl<E: fmt::Debug> core::error::Error for PreparedExecutionError<E> {}
291
292enum EndpointCheckError {
293    Invalid(EndpointIdentityError),
294    Mismatch,
295}
296
297fn map_endpoint_error<E>(error: EndpointCheckError) -> PreparedExecutionError<E> {
298    match error {
299        EndpointCheckError::Invalid(error) => PreparedExecutionError::EndpointIdentity(error),
300        EndpointCheckError::Mismatch => PreparedExecutionError::EndpointMismatch,
301    }
302}