use std::{any::Any, convert::Infallible, error::Error, future};
use axum::{
RequestExt,
extract::{FromRequest, FromRequestParts, Request},
handler::Handler,
http::{HeaderValue, Method, StatusCode, header, request::Parts},
middleware::{self, Next},
response::{IntoResponse, Response},
routing::{MethodRouter, any},
};
use futures_util::StreamExt;
use serde::de::DeserializeOwned;
use super::{
HttpError, MAX_REQUEST_BODY, Reading,
body::{body_of, no_body, require_json, require_same_origin, too_large},
};
#[cfg(test)]
mod tests;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Strict<T>(pub T);
impl<S, T> FromRequest<S> for Strict<T>
where
S: Send + Sync,
T: DeserializeOwned,
{
type Rejection = HttpError;
async fn from_request(request: Request, _: &S) -> Result<Self, HttpError> {
require_json(request.headers())?;
let (parts, body) = request.with_limited_body().into_parts();
let body = read(body).await?;
body_of(&parts, &body).map(Strict)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct NoBody;
impl<S: Send + Sync> FromRequest<S> for NoBody {
type Rejection = HttpError;
async fn from_request(request: Request, _: &S) -> Result<Self, HttpError> {
let (parts, body) = request.with_limited_body().into_parts();
require_same_origin(&parts)?;
let body = read(body).await?;
no_body(&parts, &body).map(|()| NoBody)
}
}
async fn read(body: axum::body::Body) -> Result<Vec<u8>, HttpError> {
let mut chunks = body.into_data_stream();
let mut read = Vec::new();
while let Some(chunk) = chunks.next().await {
let chunk = chunk.map_err(|failure| {
if exceeds_a_limit(&failure) {
HttpError::too_large("the body exceeds the limit of the server")
} else {
HttpError::malformed("the body could not be read")
}
})?;
if read.len() + chunk.len() > MAX_REQUEST_BODY {
return Err(too_large());
}
read.extend_from_slice(&chunk);
}
Ok(read)
}
fn exceeds_a_limit(failure: &axum::Error) -> bool {
let mut cause: Option<&(dyn Error + 'static)> = Some(failure);
while let Some(error) = cause {
if error.is::<http_body_util::LengthLimitError>() {
return true;
}
cause = error.source();
}
false
}
impl<S: Send + Sync> FromRequestParts<S> for Reading {
type Rejection = Infallible;
fn from_request_parts(
parts: &mut Parts,
_: &S,
) -> impl Future<Output = Result<Self, Infallible>> + Send {
future::ready(Ok(Self::of(parts)))
}
}
pub fn query<H, T, S>(handler: H) -> MethodRouter<S>
where
H: Handler<T, S>,
T: 'static,
S: Clone + Send + Sync + 'static,
{
any(handler).layer(middleware::from_fn(only_query))
}
async fn only_query(request: Request, next: Next) -> Response {
if request.method() == Method::QUERY {
return next.run(request).await;
}
let mut response = StatusCode::METHOD_NOT_ALLOWED.into_response();
let _ = response
.headers_mut()
.insert(header::ALLOW, HeaderValue::from_static("QUERY"));
response
}
#[must_use]
pub fn answer_panic(payload: Box<dyn Any + Send + 'static>) -> Response {
let message = payload
.downcast::<String>()
.map(|message| *message)
.or_else(|payload| {
payload
.downcast::<&str>()
.map(|message| (*message).to_owned())
})
.unwrap_or_else(|_| "a value that is no text".to_owned());
HttpError::internal(format!("panic while handling the request: {message}")).into_response()
}