Skip to main content

cloud_sdk/
transport.rs

1//! Provider-neutral blocking and asynchronous transport contracts.
2
3mod asynchronous;
4mod content_type;
5mod endpoint;
6mod header;
7mod request_target;
8
9pub use asynchronous::AsyncTransport;
10pub use content_type::{
11    ContentType, ContentTypeError, MAX_CONTENT_TYPE_BYTES, MediaType, ResponseContentType,
12};
13pub use endpoint::{
14    AcknowledgedCustomEndpoint, BoundTransport, CustomEndpointAcknowledgement, EndpointIdentity,
15    EndpointIdentityError, EndpointPolicy, EndpointPolicyError, EndpointPolicyKind, EndpointScheme,
16    MAX_ENDPOINT_BASE_PATH_BYTES, MAX_ENDPOINT_HOST_BYTES, MAX_ENDPOINT_REGION_BYTES,
17    MAX_OFFICIAL_ENDPOINTS, RegionEndpoint,
18};
19pub use header::{
20    HeaderError, HeaderName, HeaderSensitivity, HeaderValue, MAX_HEADER_NAME_BYTES,
21    MAX_HEADER_VALUE_BYTES, MAX_REQUEST_HEADER_BYTES, MAX_REQUEST_HEADERS,
22    MAX_RESPONSE_HEADER_BYTES, MAX_RESPONSE_HEADERS, RequestHeader, RequestHeaders, ResponseHeader,
23    ResponseHeaders,
24};
25pub use request_target::{
26    CanonicalQuery, FormQuery, MAX_REQUEST_TARGET_BYTES, QueryPair, QueryPairs, RequestPath,
27    RequestPathError, RequestQuery, RequestTarget, RequestTargetError, StructuredQueryError,
28};
29
30use core::fmt;
31
32use crate::Method;
33use crate::rate_limit::RateLimit;
34
35/// Explicit cleanup contract for caller-owned response storage.
36///
37/// Prepared execution invokes this for the complete supplied buffer before
38/// endpoint verification or response-capacity admission. Production
39/// implementations must use a cleanup primitive that cannot be removed as a
40/// dead store. This remains separate from [`BlockingTransport`] and
41/// [`AsyncTransport`] so direct transport implementations cannot silently
42/// acquire a weaker cleanup promise.
43pub trait ResponseStorageSanitizer {
44    /// Clears the complete caller-owned response buffer.
45    fn sanitize_response_storage(&self, response_storage: &mut [u8]);
46}
47
48/// Provider-neutral request passed to a blocking transport.
49#[derive(Clone, Copy)]
50pub struct TransportRequest<'a> {
51    method: Method,
52    target: RequestTarget<'a>,
53    body: &'a [u8],
54    headers: RequestHeaders<'a>,
55}
56
57impl<'a> TransportRequest<'a> {
58    /// Creates a bodyless request.
59    #[must_use]
60    pub const fn new(method: Method, target: RequestTarget<'a>) -> Self {
61        Self {
62            method,
63            target,
64            body: &[],
65            headers: RequestHeaders::EMPTY,
66        }
67    }
68
69    /// Adds a borrowed request body.
70    #[must_use]
71    pub const fn with_body(mut self, body: &'a [u8]) -> Self {
72        self.body = body;
73        self
74    }
75
76    /// Adds a complete validated request-header block.
77    #[must_use]
78    pub const fn with_headers(mut self, headers: RequestHeaders<'a>) -> Self {
79        self.headers = headers;
80        self
81    }
82
83    /// Returns the HTTP method.
84    #[must_use]
85    pub const fn method(self) -> Method {
86        self.method
87    }
88
89    /// Returns the validated origin-form target.
90    #[must_use]
91    pub const fn target(self) -> RequestTarget<'a> {
92        self.target
93    }
94
95    /// Returns the borrowed body bytes.
96    #[must_use]
97    pub const fn body(self) -> &'a [u8] {
98        self.body
99    }
100
101    /// Returns the complete ordered request-header block.
102    #[must_use]
103    pub const fn headers(self) -> RequestHeaders<'a> {
104        self.headers
105    }
106}
107
108impl fmt::Debug for TransportRequest<'_> {
109    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
110        formatter
111            .debug_struct("TransportRequest")
112            .field("method", &self.method)
113            .field("target", &self.target)
114            .field("body", &"[redacted]")
115            .field("headers", &self.headers)
116            .finish()
117    }
118}
119
120/// Valid HTTP response status code.
121#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
122pub struct StatusCode(u16);
123
124impl StatusCode {
125    /// `200 OK`.
126    pub const OK: Self = Self(200);
127    /// `201 Created`.
128    pub const CREATED: Self = Self(201);
129    /// `202 Accepted`.
130    pub const ACCEPTED: Self = Self(202);
131    /// `204 No Content`.
132    pub const NO_CONTENT: Self = Self(204);
133    /// `429 Too Many Requests`.
134    pub const TOO_MANY_REQUESTS: Self = Self(429);
135
136    /// Creates a status code in the HTTP `100..=599` range.
137    #[must_use]
138    pub const fn new(value: u16) -> Option<Self> {
139        if value < 100 || value > 599 {
140            return None;
141        }
142        Some(Self(value))
143    }
144
145    /// Returns the numeric status code.
146    #[must_use]
147    pub const fn get(self) -> u16 {
148        self.0
149    }
150
151    /// Reports whether this is a success status.
152    #[must_use]
153    pub const fn is_success(self) -> bool {
154        self.0 >= 200 && self.0 <= 299
155    }
156
157    /// Reports whether this is a client or server error status.
158    #[must_use]
159    pub const fn is_error(self) -> bool {
160        self.0 >= 400
161    }
162}
163
164/// Response returned after a transport initializes part of a caller buffer.
165///
166/// The body is structurally bounded by the borrowed initialized slice. A
167/// transport cannot report a numeric length independently of the caller's
168/// response buffer.
169///
170/// ```compile_fail
171/// use cloud_sdk::transport::{StatusCode, TransportResponse};
172///
173/// let _ = TransportResponse::new(StatusCode::OK, 1024_usize);
174/// ```
175#[derive(Clone, Copy)]
176pub struct TransportResponse<'buffer> {
177    status: StatusCode,
178    body: &'buffer [u8],
179    content_type: Option<ResponseContentType>,
180    rate_limit: Option<RateLimit>,
181    headers: ResponseHeaders,
182}
183
184impl<'buffer> TransportResponse<'buffer> {
185    /// Creates a response over the initialized body bytes.
186    #[must_use]
187    pub const fn new(status: StatusCode, body: &'buffer [u8]) -> Self {
188        Self {
189            status,
190            body,
191            content_type: None,
192            rate_limit: None,
193            headers: ResponseHeaders::new(),
194        }
195    }
196
197    /// Adds a validated response content type captured by the transport.
198    #[must_use]
199    pub const fn with_content_type(mut self, content_type: ResponseContentType) -> Self {
200        self.content_type = Some(content_type);
201        self
202    }
203
204    /// Adds validated rate-limit metadata captured by the transport.
205    #[must_use]
206    pub const fn with_rate_limit(mut self, rate_limit: RateLimit) -> Self {
207        self.rate_limit = Some(rate_limit);
208        self
209    }
210
211    /// Adds complete bounded response-header metadata.
212    #[must_use]
213    pub const fn with_headers(mut self, headers: ResponseHeaders) -> Self {
214        self.headers = headers;
215        self
216    }
217
218    /// Returns the status code.
219    #[must_use]
220    pub const fn status(&self) -> StatusCode {
221        self.status
222    }
223
224    /// Returns the initialized response body bytes.
225    #[must_use]
226    pub const fn body(&self) -> &'buffer [u8] {
227        self.body
228    }
229
230    /// Returns the validated response content type when supplied.
231    #[must_use]
232    pub const fn content_type(&self) -> Option<ResponseContentType> {
233        self.content_type
234    }
235
236    /// Returns validated rate-limit metadata when the response supplied it.
237    #[must_use]
238    pub const fn rate_limit(&self) -> Option<RateLimit> {
239        self.rate_limit
240    }
241
242    /// Returns complete bounded response-header metadata.
243    #[must_use]
244    pub const fn headers(&self) -> &ResponseHeaders {
245        &self.headers
246    }
247}
248
249impl fmt::Debug for TransportResponse<'_> {
250    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
251        formatter
252            .debug_struct("TransportResponse")
253            .field("status", &self.status)
254            .field("body_len", &self.body.len())
255            .field("body", &"[redacted]")
256            .field("content_type", &self.content_type)
257            .field("rate_limit", &self.rate_limit)
258            .field("headers", &self.headers)
259            .finish()
260    }
261}
262
263/// Synchronous transport over caller-owned request and response buffers.
264///
265/// Authentication, base URLs, headers, timeouts, TLS, and retry policy belong
266/// to adapters and are intentionally outside this minimal contract.
267/// The shared receiver does not itself promise concurrency: callers may issue
268/// overlapping requests only when the concrete implementation satisfies their
269/// required [`Sync`] and [`Send`] bounds. Sequential implementations may use
270/// safe interior mutability without becoming `Sync`.
271pub trait BlockingTransport {
272    /// Transport-specific failure.
273    type Error;
274
275    /// Sends one request and writes the response body into the caller buffer.
276    fn send<'buffer>(
277        &self,
278        request: TransportRequest<'_>,
279        response_body: &'buffer mut [u8],
280    ) -> Result<TransportResponse<'buffer>, Self::Error>;
281}
282
283#[cfg(test)]
284mod tests;