use std::any::type_name;
use std::convert::Infallible;
use axum::{
extract::{FromRef, FromRequestParts, OptionalFromRequestParts},
http::{request::Parts, StatusCode},
response::{IntoResponse, Response},
};
use frunk::HCons;
use crate::{Aero, AsyncConstructibleResource, Resource, ResourceList};
#[cfg(feature = "aide")]
mod aide;
#[derive(Debug, thiserror::Error)]
pub enum DependencyError {
#[error("Resource `{name}` does not exist")]
DoesNotExist {
name: &'static str,
},
#[error("Failed to construct `{name}`: {source}")]
FailedToConstruct {
name: &'static str,
#[source]
source: anyhow::Error,
},
}
impl IntoResponse for DependencyError {
fn into_response(self) -> Response {
tracing::error!("{}", self);
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
}
impl DependencyError {
pub(crate) fn does_not_exist<T>() -> Self {
Self::DoesNotExist {
name: type_name::<T>(),
}
}
pub(crate) fn failed_to_construct<T>(error: impl Into<anyhow::Error>) -> Self {
Self::FailedToConstruct {
name: type_name::<T>(),
source: error.into(),
}
}
}
pub struct Dep<T: Resource>(pub T);
impl<T: Resource, S: Send + Sync> FromRequestParts<S> for Dep<T>
where
Aero: FromRef<S>,
{
type Rejection = DependencyError;
async fn from_request_parts(_parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
Aero::from_ref(state)
.try_get_async()
.await
.map(Self)
.ok_or_else(DependencyError::does_not_exist::<T>)
}
}
impl<T: Resource, S: Send + Sync> OptionalFromRequestParts<S> for Dep<T>
where
Aero: FromRef<S>,
{
type Rejection = Infallible;
async fn from_request_parts(
parts: &mut Parts,
state: &S,
) -> Result<Option<Self>, Self::Rejection> {
Ok(
<Self as FromRequestParts<S>>::from_request_parts(parts, state)
.await
.ok(),
)
}
}
pub struct Obtain<T: AsyncConstructibleResource>(pub T);
impl<T: AsyncConstructibleResource, S: Send + Sync> FromRequestParts<S> for Obtain<T>
where
Aero: FromRef<S>,
{
type Rejection = DependencyError;
async fn from_request_parts(_parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
Aero::from_ref(state)
.try_obtain_async()
.await
.map(Self)
.map_err(DependencyError::failed_to_construct::<T>)
}
}
impl<T: AsyncConstructibleResource, S: Send + Sync> OptionalFromRequestParts<S> for Obtain<T>
where
Aero: FromRef<S>,
{
type Rejection = Infallible;
async fn from_request_parts(
parts: &mut Parts,
state: &S,
) -> Result<Option<Self>, Self::Rejection> {
Ok(
<Self as FromRequestParts<S>>::from_request_parts(parts, state)
.await
.ok(),
)
}
}
impl<H: Resource, T: ResourceList> FromRef<Aero<HCons<H, T>>> for Aero {
fn from_ref(input: &Aero<HCons<H, T>>) -> Self {
input.clone().into()
}
}
#[cfg(feature = "axum-extra")]
mod extra_impls {
use axum::extract::FromRef;
use crate::{Aero, ResourceList};
impl<R: ResourceList> FromRef<Aero<R>> for axum_extra::extract::cookie::Key {
fn from_ref(input: &Aero<R>) -> Self {
input.try_get().expect("Missing cookie key")
}
}
}