#![allow(clippy::result_large_err)]
use std::marker::PhantomData;
use axum::extract::FromRequestParts;
use axum::extract::Path;
use axum::extract::Request;
use axum::middleware::Next;
use axum::response::{IntoResponse, Response};
use http::StatusCode;
use http::request::Parts;
pub trait Guard: Send + Sync + 'static {
fn check(parts: &Parts) -> impl Future<Output = Result<(), Response>> + Send;
}
pub struct GuardCheck<G: Guard>(PhantomData<fn() -> G>);
impl<S, G> FromRequestParts<S> for GuardCheck<G>
where
G: Guard,
S: Send + Sync,
{
type Rejection = Response;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
G::check(parts).await?;
Ok(Self(PhantomData))
}
}
pub trait Pipe<I>: Send + Sync + 'static {
type Output;
fn transform(input: I) -> Result<Self::Output, Response>;
}
pub struct PipePath<P>(pub <P as Pipe<String>>::Output)
where
P: Pipe<String>;
impl<S, P> FromRequestParts<S> for PipePath<P>
where
P: Pipe<String>,
S: Send + Sync,
{
type Rejection = Response;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let Path(raw): Path<String> = Path::from_request_parts(parts, state)
.await
.map_err(IntoResponse::into_response)?;
let out = P::transform(raw)?;
Ok(PipePath(out))
}
}
pub struct ParseUuidPipe;
impl Pipe<String> for ParseUuidPipe {
type Output = uuid::Uuid;
fn transform(input: String) -> Result<Self::Output, Response> {
uuid::Uuid::parse_str(&input).map_err(|_| {
let body = serde_json::json!({
"error": "invalid_uuid",
"input": input,
});
(StatusCode::BAD_REQUEST, axum::Json(body)).into_response()
})
}
}
pub trait Interceptor: Send + Sync + 'static {
fn intercept(&self, req: Request, next: Next) -> impl Future<Output = Response> + Send;
}