1mod asynchronous;
4mod content_type;
5mod endpoint;
6
7pub use asynchronous::AsyncTransport;
8pub use content_type::{
9 ContentType, ContentTypeError, MAX_CONTENT_TYPE_BYTES, MediaType, ResponseContentType,
10};
11pub use endpoint::{
12 AcknowledgedCustomEndpoint, BoundTransport, CustomEndpointAcknowledgement, EndpointIdentity,
13 EndpointIdentityError, EndpointPolicy, EndpointPolicyError, EndpointPolicyKind, EndpointScheme,
14 MAX_ENDPOINT_BASE_PATH_BYTES, MAX_ENDPOINT_HOST_BYTES, MAX_ENDPOINT_REGION_BYTES,
15 MAX_OFFICIAL_ENDPOINTS, RegionEndpoint,
16};
17
18use core::fmt;
19
20use crate::Method;
21use crate::rate_limit::RateLimit;
22
23pub const MAX_REQUEST_TARGET_BYTES: usize = 8192;
25
26pub trait ResponseStorageSanitizer {
35 fn sanitize_response_storage(&self, response_storage: &mut [u8]);
37}
38
39#[derive(Clone, Copy, Debug, Eq, PartialEq)]
41pub enum RequestTargetError {
42 Empty,
44 NotOriginForm,
46 TooLong,
48 InvalidByte,
51}
52
53impl_static_error!(RequestTargetError,
54 Self::Empty => "request target is empty",
55 Self::NotOriginForm => "request target is not in origin form",
56 Self::TooLong => "request target exceeds the length limit",
57 Self::InvalidByte => "request target contains a forbidden byte",
58);
59
60#[derive(Clone, Copy, Eq, PartialEq)]
62pub struct RequestTarget<'a> {
63 value: &'a str,
64}
65
66impl<'a> RequestTarget<'a> {
67 pub fn new(value: &'a str) -> Result<Self, RequestTargetError> {
69 if value.is_empty() {
70 return Err(RequestTargetError::Empty);
71 }
72 if value.len() > MAX_REQUEST_TARGET_BYTES {
73 return Err(RequestTargetError::TooLong);
74 }
75 if !value.starts_with('/') || value.starts_with("//") {
76 return Err(RequestTargetError::NotOriginForm);
77 }
78 if !value
79 .bytes()
80 .all(|byte| byte.is_ascii_graphic() && byte != b'#' && byte != b'\\')
81 {
82 return Err(RequestTargetError::InvalidByte);
83 }
84 Ok(Self { value })
85 }
86
87 #[must_use]
89 pub const fn as_str(self) -> &'a str {
90 self.value
91 }
92}
93
94impl fmt::Debug for RequestTarget<'_> {
95 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
96 formatter.write_str("RequestTarget([redacted])")
97 }
98}
99
100#[derive(Clone, Copy)]
102pub struct TransportRequest<'a> {
103 method: Method,
104 target: RequestTarget<'a>,
105 body: &'a [u8],
106 content_type: Option<ContentType<'a>>,
107}
108
109impl<'a> TransportRequest<'a> {
110 #[must_use]
112 pub const fn new(method: Method, target: RequestTarget<'a>) -> Self {
113 Self {
114 method,
115 target,
116 body: &[],
117 content_type: None,
118 }
119 }
120
121 #[must_use]
123 pub const fn with_body(mut self, body: &'a [u8]) -> Self {
124 self.body = body;
125 self
126 }
127
128 #[must_use]
130 pub const fn with_content_type(mut self, content_type: ContentType<'a>) -> Self {
131 self.content_type = Some(content_type);
132 self
133 }
134
135 #[must_use]
137 pub const fn method(self) -> Method {
138 self.method
139 }
140
141 #[must_use]
143 pub const fn target(self) -> RequestTarget<'a> {
144 self.target
145 }
146
147 #[must_use]
149 pub const fn body(self) -> &'a [u8] {
150 self.body
151 }
152
153 #[must_use]
155 pub const fn content_type(self) -> Option<ContentType<'a>> {
156 self.content_type
157 }
158}
159
160impl fmt::Debug for TransportRequest<'_> {
161 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
162 formatter
163 .debug_struct("TransportRequest")
164 .field("method", &self.method)
165 .field("target", &self.target)
166 .field("body", &"[redacted]")
167 .field("content_type", &self.content_type)
168 .finish()
169 }
170}
171
172#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
174pub struct StatusCode(u16);
175
176impl StatusCode {
177 pub const OK: Self = Self(200);
179 pub const CREATED: Self = Self(201);
181 pub const ACCEPTED: Self = Self(202);
183 pub const NO_CONTENT: Self = Self(204);
185 pub const TOO_MANY_REQUESTS: Self = Self(429);
187
188 #[must_use]
190 pub const fn new(value: u16) -> Option<Self> {
191 if value < 100 || value > 599 {
192 return None;
193 }
194 Some(Self(value))
195 }
196
197 #[must_use]
199 pub const fn get(self) -> u16 {
200 self.0
201 }
202
203 #[must_use]
205 pub const fn is_success(self) -> bool {
206 self.0 >= 200 && self.0 <= 299
207 }
208
209 #[must_use]
211 pub const fn is_error(self) -> bool {
212 self.0 >= 400
213 }
214}
215
216#[derive(Clone, Copy, Eq, PartialEq)]
228pub struct TransportResponse<'buffer> {
229 status: StatusCode,
230 body: &'buffer [u8],
231 content_type: Option<ResponseContentType>,
232 rate_limit: Option<RateLimit>,
233}
234
235impl<'buffer> TransportResponse<'buffer> {
236 #[must_use]
238 pub const fn new(status: StatusCode, body: &'buffer [u8]) -> Self {
239 Self {
240 status,
241 body,
242 content_type: None,
243 rate_limit: None,
244 }
245 }
246
247 #[must_use]
249 pub const fn with_content_type(mut self, content_type: ResponseContentType) -> Self {
250 self.content_type = Some(content_type);
251 self
252 }
253
254 #[must_use]
256 pub const fn with_rate_limit(mut self, rate_limit: RateLimit) -> Self {
257 self.rate_limit = Some(rate_limit);
258 self
259 }
260
261 #[must_use]
263 pub const fn status(&self) -> StatusCode {
264 self.status
265 }
266
267 #[must_use]
269 pub const fn body(&self) -> &'buffer [u8] {
270 self.body
271 }
272
273 #[must_use]
275 pub const fn content_type(&self) -> Option<ResponseContentType> {
276 self.content_type
277 }
278
279 #[must_use]
281 pub const fn rate_limit(&self) -> Option<RateLimit> {
282 self.rate_limit
283 }
284}
285
286impl fmt::Debug for TransportResponse<'_> {
287 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
288 formatter
289 .debug_struct("TransportResponse")
290 .field("status", &self.status)
291 .field("body_len", &self.body.len())
292 .field("body", &"[redacted]")
293 .field("content_type", &self.content_type)
294 .field("rate_limit", &self.rate_limit)
295 .finish()
296 }
297}
298
299pub trait BlockingTransport {
308 type Error;
310
311 fn send<'buffer>(
313 &self,
314 request: TransportRequest<'_>,
315 response_body: &'buffer mut [u8],
316 ) -> Result<TransportResponse<'buffer>, Self::Error>;
317}
318
319#[cfg(test)]
320mod tests;