1mod asynchronous;
4
5pub use asynchronous::AsyncTransport;
6
7use core::fmt;
8
9use crate::Method;
10use crate::rate_limit::RateLimit;
11
12pub const MAX_REQUEST_TARGET_BYTES: usize = 8192;
14
15pub const MAX_CONTENT_TYPE_BYTES: usize = 128;
17
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20pub enum ContentTypeError {
21 Empty,
23 TooLong,
25 Invalid,
28}
29
30impl_static_error!(ContentTypeError,
31 Self::Empty => "content type is empty",
32 Self::TooLong => "content type exceeds the length limit",
33 Self::Invalid => "content type is invalid",
34);
35
36#[derive(Clone, Copy, Eq, PartialEq)]
38pub struct ContentType<'a> {
39 value: &'a str,
40}
41
42impl fmt::Debug for ContentType<'_> {
43 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
44 formatter.write_str("ContentType([redacted])")
45 }
46}
47
48impl<'a> ContentType<'a> {
49 pub const JSON: Self = Self {
51 value: "application/json",
52 };
53
54 pub fn new(value: &'a str) -> Result<Self, ContentTypeError> {
56 if value.is_empty() {
57 return Err(ContentTypeError::Empty);
58 }
59 if value.len() > MAX_CONTENT_TYPE_BYTES {
60 return Err(ContentTypeError::TooLong);
61 }
62 if !value.bytes().all(|byte| (b' '..=b'~').contains(&byte)) {
63 return Err(ContentTypeError::Invalid);
64 }
65 let essence = value.split(';').next().unwrap_or_default();
66 let Some((media_type, subtype)) = essence.split_once('/') else {
67 return Err(ContentTypeError::Invalid);
68 };
69 if media_type.is_empty()
70 || subtype.is_empty()
71 || !media_type.bytes().all(is_http_token_byte)
72 || !subtype.bytes().all(is_http_token_byte)
73 {
74 return Err(ContentTypeError::Invalid);
75 }
76 Ok(Self { value })
77 }
78
79 #[must_use]
81 pub const fn as_str(self) -> &'a str {
82 self.value
83 }
84}
85
86const fn is_http_token_byte(byte: u8) -> bool {
87 byte.is_ascii_alphanumeric()
88 || matches!(
89 byte,
90 b'!' | b'#'
91 | b'$'
92 | b'%'
93 | b'&'
94 | b'\''
95 | b'*'
96 | b'+'
97 | b'-'
98 | b'.'
99 | b'^'
100 | b'_'
101 | b'`'
102 | b'|'
103 | b'~'
104 )
105}
106
107#[derive(Clone, Copy, Debug, Eq, PartialEq)]
109pub enum RequestTargetError {
110 Empty,
112 NotOriginForm,
114 TooLong,
116 InvalidByte,
119}
120
121impl_static_error!(RequestTargetError,
122 Self::Empty => "request target is empty",
123 Self::NotOriginForm => "request target is not in origin form",
124 Self::TooLong => "request target exceeds the length limit",
125 Self::InvalidByte => "request target contains a forbidden byte",
126);
127
128#[derive(Clone, Copy, Eq, PartialEq)]
130pub struct RequestTarget<'a> {
131 value: &'a str,
132}
133
134impl<'a> RequestTarget<'a> {
135 pub fn new(value: &'a str) -> Result<Self, RequestTargetError> {
137 if value.is_empty() {
138 return Err(RequestTargetError::Empty);
139 }
140 if value.len() > MAX_REQUEST_TARGET_BYTES {
141 return Err(RequestTargetError::TooLong);
142 }
143 if !value.starts_with('/') || value.starts_with("//") {
144 return Err(RequestTargetError::NotOriginForm);
145 }
146 if !value
147 .bytes()
148 .all(|byte| byte.is_ascii_graphic() && byte != b'#' && byte != b'\\')
149 {
150 return Err(RequestTargetError::InvalidByte);
151 }
152 Ok(Self { value })
153 }
154
155 #[must_use]
157 pub const fn as_str(self) -> &'a str {
158 self.value
159 }
160}
161
162impl fmt::Debug for RequestTarget<'_> {
163 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
164 formatter.write_str("RequestTarget([redacted])")
165 }
166}
167
168#[derive(Clone, Copy)]
170pub struct TransportRequest<'a> {
171 method: Method,
172 target: RequestTarget<'a>,
173 body: &'a [u8],
174 content_type: Option<ContentType<'a>>,
175}
176
177impl<'a> TransportRequest<'a> {
178 #[must_use]
180 pub const fn new(method: Method, target: RequestTarget<'a>) -> Self {
181 Self {
182 method,
183 target,
184 body: &[],
185 content_type: None,
186 }
187 }
188
189 #[must_use]
191 pub const fn with_body(mut self, body: &'a [u8]) -> Self {
192 self.body = body;
193 self
194 }
195
196 #[must_use]
198 pub const fn with_content_type(mut self, content_type: ContentType<'a>) -> Self {
199 self.content_type = Some(content_type);
200 self
201 }
202
203 #[must_use]
205 pub const fn method(self) -> Method {
206 self.method
207 }
208
209 #[must_use]
211 pub const fn target(self) -> RequestTarget<'a> {
212 self.target
213 }
214
215 #[must_use]
217 pub const fn body(self) -> &'a [u8] {
218 self.body
219 }
220
221 #[must_use]
223 pub const fn content_type(self) -> Option<ContentType<'a>> {
224 self.content_type
225 }
226}
227
228impl fmt::Debug for TransportRequest<'_> {
229 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
230 formatter
231 .debug_struct("TransportRequest")
232 .field("method", &self.method)
233 .field("target", &self.target)
234 .field("body", &"[redacted]")
235 .field("content_type", &self.content_type)
236 .finish()
237 }
238}
239
240#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
242pub struct StatusCode(u16);
243
244impl StatusCode {
245 pub const OK: Self = Self(200);
247 pub const TOO_MANY_REQUESTS: Self = Self(429);
249
250 #[must_use]
252 pub const fn new(value: u16) -> Option<Self> {
253 if value < 100 || value > 599 {
254 return None;
255 }
256 Some(Self(value))
257 }
258
259 #[must_use]
261 pub const fn get(self) -> u16 {
262 self.0
263 }
264
265 #[must_use]
267 pub const fn is_success(self) -> bool {
268 self.0 >= 200 && self.0 <= 299
269 }
270
271 #[must_use]
273 pub const fn is_error(self) -> bool {
274 self.0 >= 400
275 }
276}
277
278#[derive(Clone, Copy)]
290pub struct TransportResponse<'buffer> {
291 status: StatusCode,
292 body: &'buffer [u8],
293 rate_limit: Option<RateLimit>,
294}
295
296impl<'buffer> TransportResponse<'buffer> {
297 #[must_use]
299 pub const fn new(status: StatusCode, body: &'buffer [u8]) -> Self {
300 Self {
301 status,
302 body,
303 rate_limit: None,
304 }
305 }
306
307 #[must_use]
309 pub const fn with_rate_limit(mut self, rate_limit: RateLimit) -> Self {
310 self.rate_limit = Some(rate_limit);
311 self
312 }
313
314 #[must_use]
316 pub const fn status(&self) -> StatusCode {
317 self.status
318 }
319
320 #[must_use]
322 pub const fn body(&self) -> &'buffer [u8] {
323 self.body
324 }
325
326 #[must_use]
328 pub const fn rate_limit(&self) -> Option<RateLimit> {
329 self.rate_limit
330 }
331}
332
333impl fmt::Debug for TransportResponse<'_> {
334 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
335 formatter
336 .debug_struct("TransportResponse")
337 .field("status", &self.status)
338 .field("body_len", &self.body.len())
339 .field("body", &"[redacted]")
340 .field("rate_limit", &self.rate_limit)
341 .finish()
342 }
343}
344
345pub trait BlockingTransport {
350 type Error;
352
353 fn send<'buffer>(
355 &mut self,
356 request: TransportRequest<'_>,
357 response_body: &'buffer mut [u8],
358 ) -> Result<TransportResponse<'buffer>, Self::Error>;
359}
360
361#[cfg(test)]
362mod tests;