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