mod asynchronous;
mod content_type;
mod endpoint;
pub use asynchronous::AsyncTransport;
pub use content_type::{
ContentType, ContentTypeError, MAX_CONTENT_TYPE_BYTES, MediaType, ResponseContentType,
};
pub use endpoint::{
BoundTransport, EndpointIdentity, EndpointIdentityError, EndpointScheme,
MAX_ENDPOINT_BASE_PATH_BYTES, MAX_ENDPOINT_HOST_BYTES,
};
use core::fmt;
use crate::Method;
use crate::rate_limit::RateLimit;
pub const MAX_REQUEST_TARGET_BYTES: usize = 8192;
pub trait ResponseStorageSanitizer {
fn sanitize_response_storage(&self, response_storage: &mut [u8]);
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RequestTargetError {
Empty,
NotOriginForm,
TooLong,
InvalidByte,
}
impl_static_error!(RequestTargetError,
Self::Empty => "request target is empty",
Self::NotOriginForm => "request target is not in origin form",
Self::TooLong => "request target exceeds the length limit",
Self::InvalidByte => "request target contains a forbidden byte",
);
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct RequestTarget<'a> {
value: &'a str,
}
impl<'a> RequestTarget<'a> {
pub fn new(value: &'a str) -> Result<Self, RequestTargetError> {
if value.is_empty() {
return Err(RequestTargetError::Empty);
}
if value.len() > MAX_REQUEST_TARGET_BYTES {
return Err(RequestTargetError::TooLong);
}
if !value.starts_with('/') || value.starts_with("//") {
return Err(RequestTargetError::NotOriginForm);
}
if !value
.bytes()
.all(|byte| byte.is_ascii_graphic() && byte != b'#' && byte != b'\\')
{
return Err(RequestTargetError::InvalidByte);
}
Ok(Self { value })
}
#[must_use]
pub const fn as_str(self) -> &'a str {
self.value
}
}
impl fmt::Debug for RequestTarget<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("RequestTarget([redacted])")
}
}
#[derive(Clone, Copy)]
pub struct TransportRequest<'a> {
method: Method,
target: RequestTarget<'a>,
body: &'a [u8],
content_type: Option<ContentType<'a>>,
}
impl<'a> TransportRequest<'a> {
#[must_use]
pub const fn new(method: Method, target: RequestTarget<'a>) -> Self {
Self {
method,
target,
body: &[],
content_type: None,
}
}
#[must_use]
pub const fn with_body(mut self, body: &'a [u8]) -> Self {
self.body = body;
self
}
#[must_use]
pub const fn with_content_type(mut self, content_type: ContentType<'a>) -> Self {
self.content_type = Some(content_type);
self
}
#[must_use]
pub const fn method(self) -> Method {
self.method
}
#[must_use]
pub const fn target(self) -> RequestTarget<'a> {
self.target
}
#[must_use]
pub const fn body(self) -> &'a [u8] {
self.body
}
#[must_use]
pub const fn content_type(self) -> Option<ContentType<'a>> {
self.content_type
}
}
impl fmt::Debug for TransportRequest<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("TransportRequest")
.field("method", &self.method)
.field("target", &self.target)
.field("body", &"[redacted]")
.field("content_type", &self.content_type)
.finish()
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct StatusCode(u16);
impl StatusCode {
pub const OK: Self = Self(200);
pub const CREATED: Self = Self(201);
pub const ACCEPTED: Self = Self(202);
pub const NO_CONTENT: Self = Self(204);
pub const TOO_MANY_REQUESTS: Self = Self(429);
#[must_use]
pub const fn new(value: u16) -> Option<Self> {
if value < 100 || value > 599 {
return None;
}
Some(Self(value))
}
#[must_use]
pub const fn get(self) -> u16 {
self.0
}
#[must_use]
pub const fn is_success(self) -> bool {
self.0 >= 200 && self.0 <= 299
}
#[must_use]
pub const fn is_error(self) -> bool {
self.0 >= 400
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct TransportResponse<'buffer> {
status: StatusCode,
body: &'buffer [u8],
content_type: Option<ResponseContentType>,
rate_limit: Option<RateLimit>,
}
impl<'buffer> TransportResponse<'buffer> {
#[must_use]
pub const fn new(status: StatusCode, body: &'buffer [u8]) -> Self {
Self {
status,
body,
content_type: None,
rate_limit: None,
}
}
#[must_use]
pub const fn with_content_type(mut self, content_type: ResponseContentType) -> Self {
self.content_type = Some(content_type);
self
}
#[must_use]
pub const fn with_rate_limit(mut self, rate_limit: RateLimit) -> Self {
self.rate_limit = Some(rate_limit);
self
}
#[must_use]
pub const fn status(&self) -> StatusCode {
self.status
}
#[must_use]
pub const fn body(&self) -> &'buffer [u8] {
self.body
}
#[must_use]
pub const fn content_type(&self) -> Option<ResponseContentType> {
self.content_type
}
#[must_use]
pub const fn rate_limit(&self) -> Option<RateLimit> {
self.rate_limit
}
}
impl fmt::Debug for TransportResponse<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("TransportResponse")
.field("status", &self.status)
.field("body_len", &self.body.len())
.field("body", &"[redacted]")
.field("content_type", &self.content_type)
.field("rate_limit", &self.rate_limit)
.finish()
}
}
pub trait BlockingTransport {
type Error;
fn send<'buffer>(
&self,
request: TransportRequest<'_>,
response_body: &'buffer mut [u8],
) -> Result<TransportResponse<'buffer>, Self::Error>;
}
#[cfg(test)]
mod tests;