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