use std::borrow::Cow;
#[cfg(feature = "body-util")]
use std::error::Error as StdError;
use std::str::FromStr;
use std::sync::Arc;
use async_trait::async_trait;
#[cfg(feature = "body-util")]
use bytes::Bytes;
use http::header::LOCATION;
use http::request::Parts;
use http::Request;
use http::{Response, StatusCode};
use http_body::Body as HttpBody;
use percent_encoding::percent_decode_str;
use thiserror::Error;
pub use mendes_macros::{handler, route, scope};
#[async_trait]
pub trait Application: Send + Sized {
type RequestBody: Send;
type ResponseBody: HttpBody;
type Error: IntoResponse<Self> + WithStatus + From<Error> + Send;
async fn handle(cx: Context<Self>) -> Response<Self::ResponseBody>;
fn from_query<'a, T: serde::Deserialize<'a>>(req: &'a Parts) -> Result<T, Self::Error> {
let query = req.uri.query().ok_or(Error::QueryMissing)?;
let data =
serde_urlencoded::from_bytes::<T>(query.as_bytes()).map_err(Error::QueryDecode)?;
Ok(data)
}
fn from_body_bytes<'de, T: serde::de::Deserialize<'de>>(
req: &Parts,
bytes: &'de [u8],
) -> Result<T, Error> {
from_bytes::<T>(req, bytes)
}
#[cfg(feature = "body-util")]
#[cfg_attr(docsrs, doc(cfg(feature = "body-util")))]
async fn from_body<T: serde::de::DeserializeOwned>(
req: &Parts,
body: Self::RequestBody,
max_len: usize,
) -> Result<T, Error>
where
Self::RequestBody: HttpBody + Send,
<Self::RequestBody as HttpBody>::Data: Send,
<Self::RequestBody as HttpBody>::Error: Into<Box<dyn StdError + Sync + Send>>,
{
let expected_len = match body.size_hint().upper() {
Some(length) => length,
None => body.size_hint().lower(),
};
if expected_len > max_len as u64 {
return Err(Error::BodyTooLarge);
}
from_body::<Self::RequestBody, T>(req, body, max_len).await
}
#[cfg(feature = "body-util")]
#[cfg_attr(docsrs, doc(cfg(feature = "body-util")))]
async fn body_bytes<B: HttpBody + Send>(body: B, max_len: usize) -> Result<Bytes, Error>
where
B::Data: Send,
B::Error: Into<Box<dyn StdError + Sync + Send + 'static>>,
{
let expected_len = match body.size_hint().upper() {
Some(length) => length,
None => body.size_hint().lower(),
};
if expected_len > max_len as u64 {
return Err(Error::BodyTooLarge);
}
Ok(to_bytes(body, max_len).await?)
}
fn redirect(status: StatusCode, path: impl AsRef<str>) -> Response<Self::ResponseBody>
where
Self::ResponseBody: Default,
{
http::Response::builder()
.status(status)
.header(LOCATION, path.as_ref())
.body(Self::ResponseBody::default())
.unwrap()
}
}
pub trait WithStatus {}
impl<T> WithStatus for T where StatusCode: for<'a> From<&'a T> {}
pub trait IntoResponse<A: Application> {
fn into_response(self, app: &A, req: &Parts) -> Response<A::ResponseBody>;
}
impl<A: Application> IntoResponse<A> for Response<A::ResponseBody> {
fn into_response(self, _: &A, _: &Parts) -> Response<A::ResponseBody> {
self
}
}
impl<A: Application, T: IntoResponse<A>, E: IntoResponse<A>> IntoResponse<A> for Result<T, E> {
fn into_response(self, app: &A, req: &Parts) -> Response<A::ResponseBody> {
match self {
Ok(rsp) => rsp.into_response(app, req),
Err(e) => e.into_response(app, req),
}
}
}
impl<A: Application> IntoResponse<A> for Error {
fn into_response(self, app: &A, req: &Parts) -> Response<A::ResponseBody> {
A::Error::from(self).into_response(app, req)
}
}
pub struct Context<A>
where
A: Application,
{
pub app: Arc<A>,
pub req: http::request::Parts,
#[doc(hidden)]
pub body: Option<A::RequestBody>,
#[doc(hidden)]
pub path: PathState,
}
impl<A> Context<A>
where
A: Application,
{
#[doc(hidden)]
pub fn new(app: Arc<A>, req: Request<A::RequestBody>) -> Context<A> {
let path = PathState::new(req.uri().path());
let (req, body) = req.into_parts();
Context {
app,
req,
body: Some(body),
path,
}
}
#[doc(hidden)]
pub fn path(&mut self) -> Option<Cow<'_, str>> {
path_str(&self.req, &mut self.path).ok().flatten()
}
#[doc(hidden)]
pub fn rewind(&mut self) {
self.path.rewind();
}
#[doc(hidden)]
pub fn take_body(&mut self) -> Option<A::RequestBody> {
self.body.take()
}
#[doc(hidden)]
pub fn app(&self) -> &Arc<A> {
&self.app
}
#[doc(hidden)]
pub fn method(&self) -> &http::Method {
&self.req.method
}
#[doc(hidden)]
pub fn uri(&self) -> &http::uri::Uri {
&self.req.uri
}
#[doc(hidden)]
pub fn headers(&self) -> &http::HeaderMap {
&self.req.headers
}
}
impl<A: Application> AsMut<Context<A>> for Context<A> {
fn as_mut(&mut self) -> &mut Context<A> {
self
}
}
pub trait FromContext<'a, A>: Sized
where
A: Application,
{
fn from_context(
app: &'a Arc<A>,
req: &'a Parts,
state: &mut PathState,
body: &mut Option<A::RequestBody>,
) -> Result<Self, A::Error>;
}
macro_rules! from_context_from_str {
($self:ty) => {
impl<'a, A: Application> FromContext<'a, A> for $self {
fn from_context(
_: &'a Arc<A>,
req: &'a Parts,
state: &mut PathState,
_: &mut Option<A::RequestBody>,
) -> Result<Self, A::Error> {
let s = state
.next(req.uri.path())
.ok_or(Error::PathComponentMissing.into())?;
<$self>::from_str(s).map_err(|_| Error::PathParse.into())
}
}
impl<'a, A: Application> FromContext<'a, A> for Option<$self> {
fn from_context(
_: &'a Arc<A>,
req: &'a Parts,
state: &mut PathState,
_: &mut Option<A::RequestBody>,
) -> Result<Self, A::Error> {
match state.next(req.uri.path()) {
Some(s) => match <$self>::from_str(s) {
Ok(v) => Ok(Some(v)),
Err(_) => Err(Error::PathParse.into()),
},
None => Ok(None),
}
}
}
};
}
impl<'a, A: Application> FromContext<'a, A> for &'a A {
fn from_context(
app: &'a Arc<A>,
_: &'a Parts,
_: &mut PathState,
_: &mut Option<A::RequestBody>,
) -> Result<Self, A::Error> {
Ok(app)
}
}
impl<'a, A: Application> FromContext<'a, A> for &'a Arc<A> {
fn from_context(
app: &'a Arc<A>,
_: &'a Parts,
_: &mut PathState,
_: &mut Option<A::RequestBody>,
) -> Result<Self, A::Error> {
Ok(app)
}
}
impl<'a, A: Application> FromContext<'a, A> for &'a http::request::Parts {
fn from_context(
_: &'a Arc<A>,
req: &'a Parts,
_: &mut PathState,
_: &mut Option<A::RequestBody>,
) -> Result<Self, A::Error> {
Ok(req)
}
}
impl<'a, A: Application> FromContext<'a, A> for Option<&'a [u8]> {
fn from_context(
_: &'a Arc<A>,
req: &'a Parts,
state: &mut PathState,
_: &mut Option<A::RequestBody>,
) -> Result<Self, A::Error> {
Ok(state.next(req.uri.path()).map(|s| s.as_bytes()))
}
}
impl<'a, A: Application> FromContext<'a, A> for &'a [u8] {
fn from_context(
_: &'a Arc<A>,
req: &'a Parts,
state: &mut PathState,
_: &mut Option<A::RequestBody>,
) -> Result<Self, A::Error> {
state
.next(req.uri.path())
.ok_or_else(|| Error::PathComponentMissing.into())
.map(|s| s.as_bytes())
}
}
impl<'a, A: Application> FromContext<'a, A> for Option<Cow<'a, str>> {
fn from_context(
_: &'a Arc<A>,
req: &'a Parts,
state: &mut PathState,
_: &mut Option<A::RequestBody>,
) -> Result<Self, A::Error> {
Ok(path_str(req, state)?)
}
}
impl<'a, A: Application> FromContext<'a, A> for Cow<'a, str> {
fn from_context(
_: &'a Arc<A>,
req: &'a Parts,
state: &mut PathState,
_: &mut Option<A::RequestBody>,
) -> Result<Self, A::Error> {
match path_str(req, state)? {
Some(s) => Ok(s),
None => Err(Error::PathComponentMissing.into()),
}
}
}
impl<'a, A: Application> FromContext<'a, A> for Option<String> {
fn from_context(
_: &'a Arc<A>,
req: &'a Parts,
state: &mut PathState,
_: &mut Option<A::RequestBody>,
) -> Result<Self, A::Error> {
Ok(path_str(req, state)?.map(|s| s.into_owned()))
}
}
impl<'a, A: Application> FromContext<'a, A> for String {
fn from_context(
_: &'a Arc<A>,
req: &'a Parts,
state: &mut PathState,
_: &mut Option<A::RequestBody>,
) -> Result<Self, A::Error> {
match path_str(req, state)? {
Some(s) => Ok(s.into_owned()),
None => Err(Error::PathComponentMissing.into()),
}
}
}
fn path_str<'a>(req: &'a Parts, state: &mut PathState) -> Result<Option<Cow<'a, str>>, Error> {
let s = match state.next(req.uri.path()) {
Some(s) => s,
None => return Ok(None),
};
percent_decode_str(s)
.decode_utf8()
.map(Some)
.map_err(|_| Error::PathDecode)
}
from_context_from_str!(bool);
from_context_from_str!(char);
from_context_from_str!(f32);
from_context_from_str!(f64);
from_context_from_str!(i8);
from_context_from_str!(i16);
from_context_from_str!(i32);
from_context_from_str!(i64);
from_context_from_str!(i128);
from_context_from_str!(isize);
from_context_from_str!(u8);
from_context_from_str!(u16);
from_context_from_str!(u32);
from_context_from_str!(u64);
from_context_from_str!(u128);
from_context_from_str!(usize);
macro_rules! deserialize_body {
($req:ident, $bytes:ident) => {{
let content_type = $req.headers.get("content-type").ok_or(Error::BodyNoType)?;
let ct_str = content_type.to_str().map_err(|_| {
Error::BodyUnknownType(String::from_utf8_lossy(content_type.as_bytes()).into_owned())
})?;
let mut parts = ct_str.splitn(2, ';');
match parts.next().map(|s| s.trim()) {
Some("application/x-www-form-urlencoded") => {
serde_urlencoded::from_bytes::<T>(&$bytes).map_err(Error::BodyDecodeForm)
}
#[cfg(feature = "json")]
Some("application/json") => {
serde_json::from_slice::<T>(&$bytes).map_err(Error::BodyDecodeJson)
}
#[cfg(feature = "uploads")]
Some("multipart/form-data") => {
crate::forms::from_form_data::<T>(&$req.headers, &$bytes)
.map_err(Error::BodyDecodeMultipart)
}
Some(_) | None => Err(Error::BodyUnknownType(ct_str.to_owned())),
}
}};
}
#[doc(hidden)]
pub struct Rest<T>(pub T);
impl<'a, A: Application> FromContext<'a, A> for Rest<&'a [u8]> {
fn from_context(
_: &'a Arc<A>,
req: &'a Parts,
state: &mut PathState,
_: &mut Option<A::RequestBody>,
) -> Result<Self, A::Error> {
Ok(Rest(state.rest(req.uri.path()).as_bytes()))
}
}
impl<'a, A: Application> FromContext<'a, A> for Rest<Cow<'a, str>> {
fn from_context(
_: &'a Arc<A>,
req: &'a Parts,
state: &mut PathState,
_: &mut Option<A::RequestBody>,
) -> Result<Self, A::Error> {
Ok(Rest(
percent_decode_str(state.rest(req.uri.path()))
.decode_utf8()
.map_err(|_| Error::PathDecode)?,
))
}
}
#[doc(hidden)]
pub struct Query<T>(pub T);
impl<'de, 'a: 'de, A: Application, T> FromContext<'a, A> for Query<T>
where
T: serde::Deserialize<'de>,
{
fn from_context(
_: &'a Arc<A>,
req: &'a Parts,
_: &mut PathState,
_: &mut Option<A::RequestBody>,
) -> Result<Self, A::Error> {
A::from_query(req).map(Query)
}
}
#[cfg(feature = "body-util")]
#[cfg_attr(docsrs, doc(cfg(feature = "body-util")))]
async fn from_body<B, T: serde::de::DeserializeOwned>(
req: &Parts,
body: B,
max_len: usize,
) -> Result<T, Error>
where
B: HttpBody,
B::Error: Into<Box<dyn StdError + Send + Sync + 'static>>,
{
let bytes = to_bytes(body, max_len).await?;
deserialize_body!(req, bytes)
}
fn from_bytes<'de, T: serde::de::Deserialize<'de>>(
req: &Parts,
bytes: &'de [u8],
) -> Result<T, Error> {
deserialize_body!(req, bytes)
}
#[cfg(feature = "body-util")]
#[cfg_attr(docsrs, doc(cfg(feature = "body-util")))]
#[cfg_attr(feature = "tracing", tracing::instrument(skip(body)))]
async fn to_bytes<B: HttpBody>(body: B, max_len: usize) -> Result<Bytes, Error>
where
B::Error: Into<Box<dyn StdError + Send + Sync + 'static>>,
{
#[cfg(feature = "body-util")]
use http_body_util::BodyExt;
let limited = http_body_util::Limited::new(body, max_len);
match limited.collect().await {
Ok(collected) => Ok(collected.to_bytes()),
Err(err) => Err(Error::BodyReceive(err)),
}
}
#[doc(hidden)]
pub struct PathState {
prev: Option<usize>,
next: Option<usize>,
}
impl PathState {
fn new(path: &str) -> Self {
let next = if path.is_empty() || path == "/" {
None
} else if path.find('/') == Some(0) {
Some(1)
} else {
Some(0)
};
Self { prev: None, next }
}
#[doc(hidden)]
pub fn next<'r>(&mut self, path: &'r str) -> Option<&'r str> {
let start = match self.next.as_ref() {
Some(v) => *v,
None => return None,
};
let path = &path[start..];
if path.is_empty() {
self.prev = self.next.take();
return None;
}
match path.find('/') {
Some(end) => {
self.prev = self.next.replace(start + end + 1);
Some(&path[..end])
}
None => {
self.prev = self.next.take();
Some(path)
}
}
}
#[doc(hidden)]
pub fn rest<'r>(&mut self, path: &'r str) -> &'r str {
let start = match self.next.take() {
Some(v) => v,
None => return "",
};
self.prev = Some(start);
&path[start..]
}
#[doc(hidden)]
pub fn rewind(&mut self) {
self.next = self.prev.take();
}
}
#[derive(Debug, Error)]
pub enum Error {
#[error("method not allowed")]
MethodNotAllowed,
#[error("no matching routes")]
PathNotFound,
#[error("missing path component")]
PathComponentMissing,
#[error("unable to parse path component")]
PathParse,
#[error("unable to decode UTF-8 from path component")]
PathDecode,
#[error("no query in request URL")]
QueryMissing,
#[error("unable to decode request URI query: {0}")]
QueryDecode(serde_urlencoded::de::Error),
#[cfg(feature = "body-util")]
#[error("unable to receive request body: {0}")]
BodyReceive(Box<dyn StdError + Send + Sync + 'static>),
#[cfg(feature = "body-util")]
#[error("request body too large")]
BodyTooLarge,
#[cfg(feature = "json")]
#[error("unable to decode body as JSON: {0}")]
BodyDecodeJson(#[from] serde_json::Error),
#[error("unable to decode body as form data: {0}")]
BodyDecodeForm(serde_urlencoded::de::Error),
#[cfg(feature = "uploads")]
#[error("unable to decode body as multipart form data: {0}")]
BodyDecodeMultipart(#[from] crate::multipart::Error),
#[error("content type on request body unknown: {0}")]
BodyUnknownType(String),
#[error("no content type on request body")]
BodyNoType,
#[cfg(feature = "static")]
#[error("file not found")]
FileNotFound,
}
impl From<&Error> for StatusCode {
fn from(e: &Error) -> StatusCode {
use Error::*;
match e {
MethodNotAllowed => StatusCode::METHOD_NOT_ALLOWED,
QueryMissing | QueryDecode(_) | BodyNoType => StatusCode::BAD_REQUEST,
BodyUnknownType(_) => StatusCode::UNSUPPORTED_MEDIA_TYPE,
PathNotFound | PathComponentMissing | PathParse | PathDecode => StatusCode::NOT_FOUND,
#[cfg(feature = "body-util")]
BodyReceive(_) => StatusCode::INTERNAL_SERVER_ERROR,
#[cfg(feature = "body-util")]
BodyTooLarge => StatusCode::BAD_REQUEST,
BodyDecodeForm(_) => StatusCode::UNPROCESSABLE_ENTITY,
#[cfg(feature = "json")]
BodyDecodeJson(_) => StatusCode::UNPROCESSABLE_ENTITY,
#[cfg(feature = "uploads")]
BodyDecodeMultipart(_) => StatusCode::UNPROCESSABLE_ENTITY,
#[cfg(feature = "static")]
FileNotFound => StatusCode::NOT_FOUND,
}
}
}