use axum_core::{
extract::{FromRequest, RequestParts},
response::{IntoResponse, Response},
};
use http::{header, status::StatusCode, HeaderValue};
#[derive(Debug, Clone, Copy, Hash, PartialEq, PartialOrd, Eq, Ord)]
pub enum AxumEither<L, R> {
Left(L),
Right(R),
}
impl<L, R> AxumEither<L, R> {
pub fn map_left<U>(self, f: impl FnOnce(L) -> U) -> AxumEither<U, R> {
match self {
Self::Left(l) => AxumEither::Left(f(l)),
Self::Right(r) => AxumEither::Right(r),
}
}
pub fn map_right<U>(self, f: impl FnOnce(R) -> U) -> AxumEither<L, U> {
match self {
Self::Left(l) => AxumEither::Left(l),
Self::Right(r) => AxumEither::Right(f(r)),
}
}
pub fn map_lr<L2, R2>(
self,
lf: impl FnOnce(L) -> L2,
rf: impl FnOnce(R) -> R2,
) -> AxumEither<L2, R2> {
self.map_left(lf).map_right(rf)
}
pub fn left(self) -> Option<L> {
match self {
Self::Left(l) => Some(l),
Self::Right(_r) => None,
}
}
pub fn right(self) -> Option<R> {
match self {
Self::Left(_l) => None,
Self::Right(r) => Some(r),
}
}
#[cfg(feature = "either")]
pub fn into_either(self) -> either::Either<L, R> {
match self {
Self::Left(l) => either::Either::Left(l),
Self::Right(r) => either::Either::Right(r),
}
}
}
impl<T> AxumEither<T, T> {
pub fn into_inner(self) -> T {
match self {
Self::Left(l) => l,
Self::Right(r) => r,
}
}
}
#[async_trait::async_trait]
impl<L, R, B> FromRequest<B> for AxumEither<L, R>
where
L: FromRequest<B>,
L::Rejection: Send,
R: FromRequest<B>,
B: Send,
{
type Rejection = AxumEitherRejection<L::Rejection, R::Rejection>;
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
let left_error = match L::from_request(req).await {
Ok(l) => return Ok(Self::Left(l)),
Err(e) => e,
};
let right_error = match R::from_request(req).await {
Ok(r) => return Ok(Self::Right(r)),
Err(e) => e,
};
Err(AxumEitherRejection {
left_error,
right_error,
})
}
}
impl<L, R> IntoResponse for AxumEither<L, R>
where
L: IntoResponse,
R: IntoResponse,
{
fn into_response(self) -> Response {
match self {
Self::Left(l) => l.into_response(),
Self::Right(r) => r.into_response(),
}
}
}
#[derive(Debug, Clone, Copy, Hash, Default, PartialEq, PartialOrd, Eq, Ord)]
pub struct AxumEitherRejection<LE, RE>
where
LE: IntoResponse,
RE: IntoResponse,
{
pub left_error: LE,
pub right_error: RE,
}
impl<LE, RE> IntoResponse for AxumEitherRejection<LE, RE>
where
LE: IntoResponse,
RE: IntoResponse,
{
fn into_response(self) -> Response {
let left_response = self.left_error.into_response();
let right_response = self.right_error.into_response();
let status = if left_response.status().is_server_error()
|| right_response.status().is_server_error()
{
StatusCode::INTERNAL_SERVER_ERROR
} else {
StatusCode::BAD_REQUEST
};
(
status,
[(header::CONTENT_TYPE, HeaderValue::from_static("text/plain"))],
format!(
"Could not parse request\n\tleft error: {:?}\n\tright error: {:?}",
left_response, right_response
),
)
.into_response()
}
}
#[macro_export]
macro_rules! one_of {
($t0:ty, $t1:ty) => {
AxumEither<$t0, $t1>
};
($t0:ty, $($tleft:ty),+) => {
AxumEither<$t0, $crate::one_of!($($tleft),+)>
};
}
#[macro_export]
macro_rules! match_one_of {
($either:expr, $id0:pat => $expr0:expr, $id1:pat => $expr1:expr,) => {
match $either {
$crate::AxumEither::Left($id0) => $expr0,
$crate::AxumEither::Right($id1) => $expr1,
}
};
($either:expr, $id0:pat => $expr0:expr, $($idleft:pat => $exprleft:expr,)+) => {
match $either {
$crate::AxumEither::Left($id0) => $expr0,
$crate::AxumEither::Right(eithers_left) => {
$crate::match_one_of!{eithers_left, $($idleft => $exprleft,)+}
}
}
};
}
#[macro_export]
macro_rules! map_one_of {
($either:expr, $id0:pat => $expr0:expr, $id1:pat => $expr1:expr,) => {
match $either {
$crate::AxumEither::Left($id0) => $crate::AxumEither::Left($expr0),
$crate::AxumEither::Right($id1) => $crate::AxumEither::Right($expr1),
}
};
($either:expr, $id0:pat => $expr0:expr, $($idleft:pat => $exprleft:expr,)+) => {
match $either {
$crate::AxumEither::Left($id0) => $crate::AxumEither::Left($expr0),
$crate::AxumEither::Right(eithers_left) => {
$crate::AxumEither::Right(
$crate::map_one_of!{eithers_left, $(($idleft) => $exprleft,)+}
)
}
}
};
}