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