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 BoundTransport, EndpointIdentity, EndpointIdentityError, EndpointScheme,
13 MAX_ENDPOINT_BASE_PATH_BYTES, MAX_ENDPOINT_HOST_BYTES,
14};
15
16use core::fmt;
17
18use crate::Method;
19use crate::rate_limit::RateLimit;
20
21pub const MAX_REQUEST_TARGET_BYTES: usize = 8192;
23
24pub trait ResponseStorageSanitizer {
33 fn sanitize_response_storage(&self, response_storage: &mut [u8]);
35}
36
37#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39pub enum RequestTargetError {
40 Empty,
42 NotOriginForm,
44 TooLong,
46 InvalidByte,
49}
50
51impl_static_error!(RequestTargetError,
52 Self::Empty => "request target is empty",
53 Self::NotOriginForm => "request target is not in origin form",
54 Self::TooLong => "request target exceeds the length limit",
55 Self::InvalidByte => "request target contains a forbidden byte",
56);
57
58#[derive(Clone, Copy, Eq, PartialEq)]
60pub struct RequestTarget<'a> {
61 value: &'a str,
62}
63
64impl<'a> RequestTarget<'a> {
65 pub fn new(value: &'a str) -> Result<Self, RequestTargetError> {
67 if value.is_empty() {
68 return Err(RequestTargetError::Empty);
69 }
70 if value.len() > MAX_REQUEST_TARGET_BYTES {
71 return Err(RequestTargetError::TooLong);
72 }
73 if !value.starts_with('/') || value.starts_with("//") {
74 return Err(RequestTargetError::NotOriginForm);
75 }
76 if !value
77 .bytes()
78 .all(|byte| byte.is_ascii_graphic() && byte != b'#' && byte != b'\\')
79 {
80 return Err(RequestTargetError::InvalidByte);
81 }
82 Ok(Self { value })
83 }
84
85 #[must_use]
87 pub const fn as_str(self) -> &'a str {
88 self.value
89 }
90}
91
92impl fmt::Debug for RequestTarget<'_> {
93 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
94 formatter.write_str("RequestTarget([redacted])")
95 }
96}
97
98#[derive(Clone, Copy)]
100pub struct TransportRequest<'a> {
101 method: Method,
102 target: RequestTarget<'a>,
103 body: &'a [u8],
104 content_type: Option<ContentType<'a>>,
105}
106
107impl<'a> TransportRequest<'a> {
108 #[must_use]
110 pub const fn new(method: Method, target: RequestTarget<'a>) -> Self {
111 Self {
112 method,
113 target,
114 body: &[],
115 content_type: None,
116 }
117 }
118
119 #[must_use]
121 pub const fn with_body(mut self, body: &'a [u8]) -> Self {
122 self.body = body;
123 self
124 }
125
126 #[must_use]
128 pub const fn with_content_type(mut self, content_type: ContentType<'a>) -> Self {
129 self.content_type = Some(content_type);
130 self
131 }
132
133 #[must_use]
135 pub const fn method(self) -> Method {
136 self.method
137 }
138
139 #[must_use]
141 pub const fn target(self) -> RequestTarget<'a> {
142 self.target
143 }
144
145 #[must_use]
147 pub const fn body(self) -> &'a [u8] {
148 self.body
149 }
150
151 #[must_use]
153 pub const fn content_type(self) -> Option<ContentType<'a>> {
154 self.content_type
155 }
156}
157
158impl fmt::Debug for TransportRequest<'_> {
159 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
160 formatter
161 .debug_struct("TransportRequest")
162 .field("method", &self.method)
163 .field("target", &self.target)
164 .field("body", &"[redacted]")
165 .field("content_type", &self.content_type)
166 .finish()
167 }
168}
169
170#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
172pub struct StatusCode(u16);
173
174impl StatusCode {
175 pub const OK: Self = Self(200);
177 pub const CREATED: Self = Self(201);
179 pub const ACCEPTED: Self = Self(202);
181 pub const NO_CONTENT: Self = Self(204);
183 pub const TOO_MANY_REQUESTS: Self = Self(429);
185
186 #[must_use]
188 pub const fn new(value: u16) -> Option<Self> {
189 if value < 100 || value > 599 {
190 return None;
191 }
192 Some(Self(value))
193 }
194
195 #[must_use]
197 pub const fn get(self) -> u16 {
198 self.0
199 }
200
201 #[must_use]
203 pub const fn is_success(self) -> bool {
204 self.0 >= 200 && self.0 <= 299
205 }
206
207 #[must_use]
209 pub const fn is_error(self) -> bool {
210 self.0 >= 400
211 }
212}
213
214#[derive(Clone, Copy, Eq, PartialEq)]
226pub struct TransportResponse<'buffer> {
227 status: StatusCode,
228 body: &'buffer [u8],
229 content_type: Option<ResponseContentType>,
230 rate_limit: Option<RateLimit>,
231}
232
233impl<'buffer> TransportResponse<'buffer> {
234 #[must_use]
236 pub const fn new(status: StatusCode, body: &'buffer [u8]) -> Self {
237 Self {
238 status,
239 body,
240 content_type: None,
241 rate_limit: None,
242 }
243 }
244
245 #[must_use]
247 pub const fn with_content_type(mut self, content_type: ResponseContentType) -> Self {
248 self.content_type = Some(content_type);
249 self
250 }
251
252 #[must_use]
254 pub const fn with_rate_limit(mut self, rate_limit: RateLimit) -> Self {
255 self.rate_limit = Some(rate_limit);
256 self
257 }
258
259 #[must_use]
261 pub const fn status(&self) -> StatusCode {
262 self.status
263 }
264
265 #[must_use]
267 pub const fn body(&self) -> &'buffer [u8] {
268 self.body
269 }
270
271 #[must_use]
273 pub const fn content_type(&self) -> Option<ResponseContentType> {
274 self.content_type
275 }
276
277 #[must_use]
279 pub const fn rate_limit(&self) -> Option<RateLimit> {
280 self.rate_limit
281 }
282}
283
284impl fmt::Debug for TransportResponse<'_> {
285 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
286 formatter
287 .debug_struct("TransportResponse")
288 .field("status", &self.status)
289 .field("body_len", &self.body.len())
290 .field("body", &"[redacted]")
291 .field("content_type", &self.content_type)
292 .field("rate_limit", &self.rate_limit)
293 .finish()
294 }
295}
296
297pub trait BlockingTransport {
306 type Error;
308
309 fn send<'buffer>(
311 &self,
312 request: TransportRequest<'_>,
313 response_body: &'buffer mut [u8],
314 ) -> Result<TransportResponse<'buffer>, Self::Error>;
315}
316
317#[cfg(test)]
318mod tests;