use super::bound::{self};
use crate::{FromRequest, IntoResponse, Request, Response, util::ErrorMessage};
use std::borrow::Cow;
#[cfg(feature = "openapi")]
use crate::openapi;
pub struct Query<T: bound::Schema>(pub T);
impl<'req, T: bound::Incoming<'req>> FromRequest<'req> for Query<T> {
type Error = Response;
fn from_request(req: &'req crate::Request) -> Option<Result<Self, Self::Error>> {
req.query.parse().map_err(super::reject).map(Query).into()
}
#[cfg(feature = "openapi")]
fn openapi_inbound() -> openapi::Inbound {
let Some(schema) = T::schema().into().into_inline() else {
return openapi::Inbound::None;
};
openapi::Inbound::Params(
schema
.into_properties()
.into_iter()
.map(|(name, schema, required)| {
if required {
openapi::Parameter::in_query(name, schema)
} else {
openapi::Parameter::in_query_optional(name, schema)
}
})
.collect(),
)
}
}
pub struct Path<T>(pub T);
pub trait FromParam<'p>: bound::Schema + Sized {
type Error: IntoResponse;
fn from_param(param: Cow<'p, str>) -> Result<Self, Self::Error>;
#[inline(always)]
fn from_raw_param(raw_param: &'p [u8]) -> Result<Self, Response> {
Self::from_param(ohkami_lib::percent_decode_utf8(raw_param).map_err(|_e| {
#[cfg(debug_assertions)]
crate::WARNING!(
"Failed to decode percent encoded param `{}`: {_e}",
raw_param.escape_ascii()
);
Response::InternalServerError()
})?)
.map_err(IntoResponse::into_response)
}
#[cfg(feature = "openapi")]
fn openapi_param() -> openapi::Parameter {
openapi::Parameter::in_path(Self::schema())
}
}
const _: () = {
impl<'p> FromParam<'p> for String {
type Error = std::convert::Infallible;
#[inline(always)]
fn from_param(param: Cow<'p, str>) -> Result<Self, Self::Error> {
Ok(match param {
Cow::Owned(s) => s,
Cow::Borrowed(s) => s.into(),
})
}
}
impl<'p> FromParam<'p> for Cow<'p, str> {
type Error = std::convert::Infallible;
#[inline(always)]
fn from_param(param: Cow<'p, str>) -> Result<Self, Self::Error> {
Ok(param)
}
}
impl<'p> FromParam<'p> for &'p str {
type Error = ErrorMessage;
#[inline(always)]
fn from_param(param: Cow<'p, str>) -> Result<Self, Self::Error> {
match param {
Cow::Borrowed(s) => Ok(s),
Cow::Owned(_) => Err({
#[cold]
#[inline(never)]
fn unexpected(param: &str) -> ErrorMessage {
crate::WARNING!(
"\
`&str` can't handle percent encoded parameters. \
Use `Cow<'_, str>` or `String` to handle them. \
"
);
ErrorMessage(format!("Unexpected path params `{param}`: percent encoded"))
}
unexpected(¶m)
}),
}
}
}
macro_rules! unsigned_integers {
($( $unsigned_int:ty ),*) => {
$(
impl<'p> FromParam<'p> for $unsigned_int {
type Error = Response;
fn from_param(param: Cow<'p, str>) -> Result<Self, Self::Error> {
::byte_reader::Reader::new(param.as_bytes())
.read_uint()
.map(|i| Self::try_from(i).ok())
.flatten()
.ok_or_else(|| {
#[cfg(debug_assertions)] {
crate::WARNING!(
"Failed to parse `{}` from path param `{}`",
stringify!($unsigned_int), param
);
}
Response::BadRequest().with_text("invalid path")
})
}
}
)*
};
}
unsigned_integers! { u8, u16, u32, u64, usize }
macro_rules! signed_integers {
($( $signed_int:ty ),*) => {
$(
impl<'p> FromParam<'p> for $signed_int {
type Error = Response;
fn from_param(param: Cow<'p, str>) -> Result<Self, Self::Error> {
::byte_reader::Reader::new(param.as_bytes())
.read_int()
.map(|i| Self::try_from(i).ok())
.flatten()
.ok_or_else(|| {
#[cfg(debug_assertions)] {
crate::WARNING!(
"Failed to parse `{}` from path param `{}`",
stringify!($signed_int), param
);
}
Response::BadRequest().with_text("invalid path")
})
}
}
)*
};
}
signed_integers! { i8, i16, i32, i64, isize }
impl<'p> FromParam<'p> for uuid::Uuid {
type Error = Response;
fn from_param(param: Cow<'p, str>) -> Result<Self, Self::Error> {
uuid::Uuid::try_parse(¶m).map_err(|_| {
#[cfg(debug_assertions)]
{
crate::WARNING!("Failed to parse UUID from path param `{param}`",);
}
Response::BadRequest().with_text("invalid path")
})
}
}
};
impl<'req, P: FromParam<'req>> FromRequest<'req> for Path<P> {
type Error = Response;
fn from_request(req: &'req Request) -> Option<Result<Self, Self::Error>> {
let p = unsafe { req.path.assume_one_param() };
Some(P::from_raw_param(p).map(Path))
}
#[cfg(feature = "openapi")]
fn openapi_inbound() -> openapi::Inbound {
openapi::Inbound::Params(vec![P::openapi_param()])
}
fn n_pathparams() -> usize {
1
}
}
impl<'req, P1: FromParam<'req>> FromRequest<'req> for Path<(P1,)> {
type Error = Response;
fn from_request(req: &'req Request) -> Option<Result<Self, Self::Error>> {
let p = unsafe { req.path.assume_one_param() };
Some(P1::from_raw_param(p).map(|p1| Path((p1,))))
}
#[cfg(feature = "openapi")]
fn openapi_inbound() -> openapi::Inbound {
openapi::Inbound::Params(vec![P1::openapi_param()])
}
fn n_pathparams() -> usize {
1
}
}
impl<'req, P1: FromParam<'req>, P2: FromParam<'req>> FromRequest<'req> for Path<(P1, P2)> {
type Error = Response;
fn from_request(req: &'req Request) -> Option<Result<Self, Self::Error>> {
let (p1, p2) = unsafe { req.path.assume_two_params() };
Some(match (P1::from_raw_param(p1), P2::from_raw_param(p2)) {
(Ok(p1), Ok(p2)) => Ok(Path((p1, p2))),
(Err(e), _) | (_, Err(e)) => Err(e),
})
}
#[cfg(feature = "openapi")]
fn openapi_inbound() -> openapi::Inbound {
openapi::Inbound::Params(vec![P1::openapi_param(), P2::openapi_param()])
}
fn n_pathparams() -> usize {
2
}
}