use hyper::{Request, body::Incoming, http::request::Parts};
use std::future::Future;
use crate::{
HttpBody, HttpRequest,
error::Error,
http::{
endpoints::route::{PathArg, PathArgs},
request_scope::HttpRequestScope,
},
};
#[cfg(feature = "di")]
use crate::di::{Container, FromContainer};
pub mod byte_stream;
pub mod cancellation_token;
pub mod client_ip;
pub mod file;
pub mod form;
#[cfg(feature = "static-files")]
pub mod host_env;
pub mod json;
#[cfg(feature = "multipart")]
pub mod multipart;
pub mod option;
pub mod path;
pub mod query;
pub mod request;
pub mod result;
pub mod sse;
pub mod vec;
pub(crate) enum Payload<'a> {
None,
Request(Box<HttpRequest>),
Full(&'a Parts, HttpBody),
Parts(&'a Parts),
Path(PathArg),
Body(HttpBody),
PathArgs(&'a PathArgs),
}
#[derive(Debug, PartialEq)]
pub(crate) enum Source {
None,
Request,
Full,
Parts,
Path,
Body,
PathArgs,
}
pub trait FromRequest: Sized {
fn from_request(req: HttpRequest) -> impl Future<Output = Result<Self, Error>> + Send;
#[cfg(feature = "openapi")]
#[doc(hidden)]
fn describe_openapi(
config: crate::openapi::OpenApiRouteConfig,
) -> crate::openapi::OpenApiRouteConfig {
config
}
}
pub trait FromRawRequest: Sized {
fn from_request(req: Request<Incoming>) -> impl Future<Output = Result<Self, Error>> + Send;
}
pub trait FromRequestRef: Sized {
fn from_request(req: &HttpRequest) -> Result<Self, Error>;
}
pub trait FromRequestParts: Sized {
fn from_parts(parts: &Parts) -> Result<Self, Error>;
}
pub trait FromPathArgs: Sized {
fn from_path_args(args: &PathArgs) -> Result<Self, Error>;
}
pub(crate) trait FromPathArg: Sized {
fn from_path_arg(arg: &PathArg) -> Result<Self, Error>;
}
pub(crate) trait FromPayload: Send + Sized {
type Future: Future<Output = Result<Self, Error>> + Send;
const SOURCE: Source = Source::None;
fn from_payload(payload: Payload<'_>) -> Self::Future;
#[cfg(feature = "openapi")]
fn describe_openapi(
config: crate::openapi::OpenApiRouteConfig,
) -> crate::openapi::OpenApiRouteConfig {
config
}
}
impl FromRequest for () {
#[inline]
async fn from_request(_: HttpRequest) -> Result<Self, Error> {
Ok(())
}
}
impl FromRequestRef for () {
#[inline]
fn from_request(_: &HttpRequest) -> Result<Self, Error> {
Ok(())
}
}
impl FromRequestParts for () {
#[inline]
fn from_parts(_: &Parts) -> Result<Self, Error> {
Ok(())
}
}
impl FromRawRequest for () {
#[inline]
async fn from_request(_: Request<Incoming>) -> Result<Self, Error> {
Ok(())
}
}
macro_rules! define_generic_from_request {
($($T: ident),*) => {
impl<$($T: FromRequestParts),+> FromRawRequest for ($($T,)+) {
#[inline]
async fn from_request(req: Request<Incoming>) -> Result<Self, Error> {
let (parts, _) = req.into_parts();
let tuple = (
$(
$T::from_parts(&parts)?,
)*
);
Ok(tuple)
}
}
impl<$($T: FromRequestRef),+> FromRequestRef for ($($T,)+) {
#[inline]
fn from_request(req: &HttpRequest) -> Result<Self, Error> {
let tuple = (
$(
$T::from_request(req)?,
)*
);
Ok(tuple)
}
}
impl<$($T: FromRequestParts),+> FromRequestParts for ($($T,)+) {
#[inline]
fn from_parts(parts: &Parts) -> Result<Self, Error> {
let tuple = (
$(
$T::from_parts(parts)?,
)*
);
Ok(tuple)
}
}
#[cfg(feature = "di")]
impl<$($T: FromContainer),+> FromContainer for ($($T,)+) {
#[inline]
fn from_container(container: &Container) -> Result<Self, Error> {
let tuple = (
$(
$T::from_container(container)?,
)*
);
Ok(tuple)
}
}
impl<$($T: FromPayload),+> FromRequest for ($($T,)+) {
#[inline]
async fn from_request(req: HttpRequest) -> Result<Self, Error> {
let uses_path = false $(|| matches!($T::SOURCE, Source::Path))*;
let uses_pathargs = false $(|| matches!($T::SOURCE, Source::PathArgs))*;
if uses_path && uses_pathargs {
return Err(invalid_extractor_combination());
}
let (mut parts, body) = req.into_parts();
let params = parts
.extensions
.get_mut::<HttpRequestScope>()
.map(|s| std::mem::take(&mut s.params))
.unwrap_or_default();
let (path_args, cached_query) = params.into_parts();
let mut parts = Some(parts);
let mut body = Some(body);
if uses_pathargs {
let params = PathArgs::from_parts(path_args, cached_query);
let tuple = (
$(
{
let payload = payload_for_path_args!($T::SOURCE, parts, body, ¶ms);
$T::from_payload(payload).await?
},
)*
);
return Ok(tuple);
}
let mut iter = path_args.into_iter();
let tuple = (
$(
{
let payload = payload_for_path!($T::SOURCE, parts, body, iter);
$T::from_payload(payload).await?
},
)*
);
Ok(tuple)
}
#[cfg(feature = "openapi")]
fn describe_openapi(config: crate::openapi::OpenApiRouteConfig) -> crate::openapi::OpenApiRouteConfig {
let mut config = config;
$(
config = $T::describe_openapi(config);
)*
config
}
}
}
}
#[cold]
#[inline(never)]
fn invalid_extractor_combination() -> Error {
Error::client_error(
"Invalid extractor combination: Cannot mix Path and PathArgs in the same handler",
)
}
macro_rules! payload_for_path {
($src:expr, $parts:expr, $body:expr, $iter:expr) => {{
match $src {
Source::Parts => match $parts.as_ref() {
Some(p) => Payload::Parts(p),
None => Payload::None,
},
Source::Path => match $iter.next() {
Some(arg) => Payload::Path(arg),
None => Payload::None,
},
Source::Body => match $body.take() {
Some(b) => Payload::Body(b),
None => Payload::None,
},
Source::Full => match ($parts.as_ref(), $body.take()) {
(Some(p), Some(b)) => Payload::Full(p, b),
_ => Payload::None,
},
Source::Request => match ($parts.take(), $body.take()) {
(Some(p), Some(b)) => Payload::Request(Box::new(HttpRequest::from_parts(p, b))),
_ => Payload::None,
},
Source::PathArgs => Payload::None,
Source::None => Payload::None,
}
}};
}
macro_rules! payload_for_path_args {
($src:expr, $parts:expr, $body:expr, $params:expr) => {{
match $src {
Source::Parts => match $parts.as_ref() {
Some(p) => Payload::Parts(p),
None => Payload::None,
},
Source::PathArgs => Payload::PathArgs($params),
Source::Body => match $body.take() {
Some(b) => Payload::Body(b),
None => Payload::None,
},
Source::Full => match ($parts.as_ref(), $body.take()) {
(Some(p), Some(b)) => Payload::Full(p, b),
_ => Payload::None,
},
Source::Request => match ($parts.take(), $body.take()) {
(Some(p), Some(b)) => Payload::Request(Box::new(HttpRequest::from_parts(p, b))),
_ => Payload::None,
},
Source::Path => Payload::None,
Source::None => Payload::None,
}
}};
}
define_generic_from_request! { T1 }
define_generic_from_request! { T1, T2 }
define_generic_from_request! { T1, T2, T3 }
define_generic_from_request! { T1, T2, T3, T4 }
define_generic_from_request! { T1, T2, T3, T4, T5 }
define_generic_from_request! { T1, T2, T3, T4, T5, T6 }
define_generic_from_request! { T1, T2, T3, T4, T5, T6, T7 }
define_generic_from_request! { T1, T2, T3, T4, T5, T6, T7, T8 }
define_generic_from_request! { T1, T2, T3, T4, T5, T6, T7, T8, T9 }
define_generic_from_request! { T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 }
#[cfg(test)]
mod tests {
use super::{FromPayload, Payload, Source};
use crate::error::Error;
use futures_util::future::{Ready, ok};
struct TestNone;
impl FromPayload for TestNone {
type Future = Ready<Result<TestNone, Error>>;
fn from_payload(_: Payload<'_>) -> Self::Future {
ok(TestNone)
}
}
#[tokio::test]
async fn it_reads_none_from_payload() {
let extraction = TestNone::from_payload(Payload::None).await;
assert!(extraction.is_ok());
assert_eq!(TestNone::SOURCE, Source::None);
}
}