use crate::{FromRequest, IntoResponse, Request, Response};
use std::borrow::Cow;
#[cfg(feature = "openapi")]
use crate::openapi;
pub trait FromContent<'req>: Sized {
const MIME_TYPE: &'static str;
fn from_content(body: &'req [u8]) -> Result<Self, impl std::fmt::Display>;
#[cfg(feature = "openapi")]
fn openapi_requestbody() -> impl Into<openapi::schema::SchemaRef>;
}
impl<'req, B: FromContent<'req>> FromRequest<'req> for B {
type Error = Response;
fn from_request(req: &'req Request) -> Option<Result<Self, Self::Error>> {
if req.headers.content_type()?.starts_with(B::MIME_TYPE) {
Some(B::from_content(req.payload()?).map_err(super::reject))
} else {
None
}
}
#[cfg(feature = "openapi")]
fn openapi_inbound() -> openapi::Inbound {
openapi::Inbound::Body(openapi::RequestBody::of(
B::MIME_TYPE,
B::openapi_requestbody(),
))
}
}
pub trait IntoContent {
const CONTENT_TYPE: &'static str;
fn into_content(self) -> Result<Cow<'static, [u8]>, impl std::fmt::Display>;
#[cfg(feature = "openapi")]
fn openapi_responsebody() -> impl Into<openapi::schema::SchemaRef>;
}
impl<B: IntoContent> IntoResponse for B {
#[inline]
fn into_response(self) -> Response {
if const { Self::CONTENT_TYPE.is_empty() } {
return Response::OK();
}
match self.into_content() {
Ok(body) => Response::OK().with_payload(Self::CONTENT_TYPE, body),
Err(_err) => {
#[cfg(debug_assertions)]
{
eprintln!(
"Failed to serialize `{}` as `{}` in `IntoContent`: {_err}",
std::any::type_name::<B>(),
Self::CONTENT_TYPE
)
}
Response::InternalServerError()
}
}
}
#[cfg(feature = "openapi")]
fn openapi_responses() -> openapi::Responses {
let mut res = openapi::Response::when("OK");
if !Self::CONTENT_TYPE.is_empty() {
let mime_type = match Self::CONTENT_TYPE.split_once(';') {
None => Self::CONTENT_TYPE,
Some((mime_type, _)) => mime_type,
};
res = res.content(mime_type, Self::openapi_responsebody());
}
openapi::Responses::new([(200, res)])
}
}
impl IntoContent for () {
const CONTENT_TYPE: &'static str = "";
#[cold]
#[inline(never)]
fn into_content(self) -> Result<Cow<'static, [u8]>, impl std::fmt::Display> {
#[allow(unreachable_code)]
{
unreachable!("`into_body` of `()`")
as Result<Cow<'static, [u8]>, std::convert::Infallible>
}
}
#[cfg(feature = "openapi")]
#[cold]
#[inline(never)]
fn openapi_responsebody() -> impl Into<openapi::schema::SchemaRef> {
#[allow(unreachable_code)]
{
unreachable!("`openapi_responsebody` of `()`") as openapi::schema::SchemaRef
}
}
}
macro_rules! text_response {
($( $t:ty: $this:ident => $conv:expr ),*) => {$(
impl IntoContent for $t {
const CONTENT_TYPE: &'static str = "text/plain; charset=UTF-8";
#[inline(always)]
fn into_content(self) -> Result<Cow<'static, [u8]>, impl std::fmt::Display> {
let $this = self;
Ok::<_, std::convert::Infallible>($conv)
}
#[cfg(feature="openapi")]
fn openapi_responsebody() -> impl Into<openapi::schema::SchemaRef> {
openapi::string()
}
}
)*};
}
text_response! {
&'static str: s => Cow::Borrowed(s.as_bytes()),
String: s => Cow::Owned(s.into_bytes()),
Cow<'static, str>: s => match s {
Cow::Owned(s) => Cow::Owned(s.into_bytes()),
Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
}
}
mod json;
pub use json::Json;
mod multipart;
pub use multipart::{File, Multipart};
mod urlencoded;
pub use urlencoded::UrlEncoded;
mod text;
pub use text::Text;
mod html;
pub use html::Html;