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
30#[derive(Clone, Copy, Eq, PartialEq)]
32pub struct ContentType<'a> {
33 value: &'a str,
34}
35
36impl fmt::Debug for ContentType<'_> {
37 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
38 formatter.write_str("ContentType([redacted])")
39 }
40}
41
42impl<'a> ContentType<'a> {
43 pub const JSON: Self = Self {
45 value: "application/json",
46 };
47
48 pub fn new(value: &'a str) -> Result<Self, ContentTypeError> {
50 if value.is_empty() {
51 return Err(ContentTypeError::Empty);
52 }
53 if value.len() > MAX_CONTENT_TYPE_BYTES {
54 return Err(ContentTypeError::TooLong);
55 }
56 if !value.bytes().all(|byte| (b' '..=b'~').contains(&byte)) {
57 return Err(ContentTypeError::Invalid);
58 }
59 let essence = value.split(';').next().unwrap_or_default();
60 let Some((media_type, subtype)) = essence.split_once('/') else {
61 return Err(ContentTypeError::Invalid);
62 };
63 if media_type.is_empty()
64 || subtype.is_empty()
65 || !media_type.bytes().all(is_http_token_byte)
66 || !subtype.bytes().all(is_http_token_byte)
67 {
68 return Err(ContentTypeError::Invalid);
69 }
70 Ok(Self { value })
71 }
72
73 #[must_use]
75 pub const fn as_str(self) -> &'a str {
76 self.value
77 }
78}
79
80const fn is_http_token_byte(byte: u8) -> bool {
81 byte.is_ascii_alphanumeric()
82 || matches!(
83 byte,
84 b'!' | b'#'
85 | b'$'
86 | b'%'
87 | b'&'
88 | b'\''
89 | b'*'
90 | b'+'
91 | b'-'
92 | b'.'
93 | b'^'
94 | b'_'
95 | b'`'
96 | b'|'
97 | b'~'
98 )
99}
100
101#[derive(Clone, Copy, Debug, Eq, PartialEq)]
103pub enum RequestTargetError {
104 Empty,
106 NotOriginForm,
108 TooLong,
110 InvalidByte,
113}
114
115#[derive(Clone, Copy, Eq, PartialEq)]
117pub struct RequestTarget<'a> {
118 value: &'a str,
119}
120
121impl<'a> RequestTarget<'a> {
122 pub fn new(value: &'a str) -> Result<Self, RequestTargetError> {
124 if value.is_empty() {
125 return Err(RequestTargetError::Empty);
126 }
127 if value.len() > MAX_REQUEST_TARGET_BYTES {
128 return Err(RequestTargetError::TooLong);
129 }
130 if !value.starts_with('/') || value.starts_with("//") {
131 return Err(RequestTargetError::NotOriginForm);
132 }
133 if !value
134 .bytes()
135 .all(|byte| byte.is_ascii_graphic() && byte != b'#' && byte != b'\\')
136 {
137 return Err(RequestTargetError::InvalidByte);
138 }
139 Ok(Self { value })
140 }
141
142 #[must_use]
144 pub const fn as_str(self) -> &'a str {
145 self.value
146 }
147}
148
149impl fmt::Debug for RequestTarget<'_> {
150 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
151 formatter.write_str("RequestTarget([redacted])")
152 }
153}
154
155#[derive(Clone, Copy)]
157pub struct TransportRequest<'a> {
158 method: Method,
159 target: RequestTarget<'a>,
160 body: &'a [u8],
161 content_type: Option<ContentType<'a>>,
162}
163
164impl<'a> TransportRequest<'a> {
165 #[must_use]
167 pub const fn new(method: Method, target: RequestTarget<'a>) -> Self {
168 Self {
169 method,
170 target,
171 body: &[],
172 content_type: None,
173 }
174 }
175
176 #[must_use]
178 pub const fn with_body(mut self, body: &'a [u8]) -> Self {
179 self.body = body;
180 self
181 }
182
183 #[must_use]
185 pub const fn with_content_type(mut self, content_type: ContentType<'a>) -> Self {
186 self.content_type = Some(content_type);
187 self
188 }
189
190 #[must_use]
192 pub const fn method(self) -> Method {
193 self.method
194 }
195
196 #[must_use]
198 pub const fn target(self) -> RequestTarget<'a> {
199 self.target
200 }
201
202 #[must_use]
204 pub const fn body(self) -> &'a [u8] {
205 self.body
206 }
207
208 #[must_use]
210 pub const fn content_type(self) -> Option<ContentType<'a>> {
211 self.content_type
212 }
213}
214
215impl fmt::Debug for TransportRequest<'_> {
216 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
217 formatter
218 .debug_struct("TransportRequest")
219 .field("method", &self.method)
220 .field("target", &self.target)
221 .field("body", &"[redacted]")
222 .field("content_type", &self.content_type)
223 .finish()
224 }
225}
226
227#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
229pub struct StatusCode(u16);
230
231impl StatusCode {
232 pub const OK: Self = Self(200);
234 pub const TOO_MANY_REQUESTS: Self = Self(429);
236
237 #[must_use]
239 pub const fn new(value: u16) -> Option<Self> {
240 if value < 100 || value > 599 {
241 return None;
242 }
243 Some(Self(value))
244 }
245
246 #[must_use]
248 pub const fn get(self) -> u16 {
249 self.0
250 }
251
252 #[must_use]
254 pub const fn is_success(self) -> bool {
255 self.0 >= 200 && self.0 <= 299
256 }
257
258 #[must_use]
260 pub const fn is_error(self) -> bool {
261 self.0 >= 400
262 }
263}
264
265#[derive(Clone, Copy)]
277pub struct TransportResponse<'buffer> {
278 status: StatusCode,
279 body: &'buffer [u8],
280 rate_limit: Option<RateLimit>,
281}
282
283impl<'buffer> TransportResponse<'buffer> {
284 #[must_use]
286 pub const fn new(status: StatusCode, body: &'buffer [u8]) -> Self {
287 Self {
288 status,
289 body,
290 rate_limit: None,
291 }
292 }
293
294 #[must_use]
296 pub const fn with_rate_limit(mut self, rate_limit: RateLimit) -> Self {
297 self.rate_limit = Some(rate_limit);
298 self
299 }
300
301 #[must_use]
303 pub const fn status(&self) -> StatusCode {
304 self.status
305 }
306
307 #[must_use]
309 pub const fn body(&self) -> &'buffer [u8] {
310 self.body
311 }
312
313 #[must_use]
315 pub const fn rate_limit(&self) -> Option<RateLimit> {
316 self.rate_limit
317 }
318}
319
320impl fmt::Debug for TransportResponse<'_> {
321 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
322 formatter
323 .debug_struct("TransportResponse")
324 .field("status", &self.status)
325 .field("body_len", &self.body.len())
326 .field("body", &"[redacted]")
327 .field("rate_limit", &self.rate_limit)
328 .finish()
329 }
330}
331
332pub trait BlockingTransport {
337 type Error;
339
340 fn send<'buffer>(
342 &mut self,
343 request: TransportRequest<'_>,
344 response_body: &'buffer mut [u8],
345 ) -> Result<TransportResponse<'buffer>, Self::Error>;
346}
347
348#[cfg(test)]
349mod tests;