mirror of
https://github.com/TECHNOFAB11/aerosol.git
synced 2025-12-11 23:50:07 +01:00
99 lines
3 KiB
Rust
99 lines
3 KiB
Rust
|
|
//! Integration with the `axum` web framework.
|
||
|
|
//!
|
||
|
|
//! Provies the `Dep` and `Obtain` axum extractors for easily accessing
|
||
|
|
//! resources from within route handlers.
|
||
|
|
//!
|
||
|
|
//! To make use of these extractors, your application state must either be
|
||
|
|
//! an `Aerosol`, or you must implement `FromRef<YourState>` for `Aerosol`.
|
||
|
|
|
||
|
|
use std::any::type_name;
|
||
|
|
|
||
|
|
use async_trait::async_trait;
|
||
|
|
use axum::{
|
||
|
|
extract::{FromRef, FromRequestParts},
|
||
|
|
http::{request::Parts, StatusCode},
|
||
|
|
response::{IntoResponse, Response},
|
||
|
|
};
|
||
|
|
|
||
|
|
use crate::{Aerosol, AsyncConstructibleResource, ConstructibleResource, Resource};
|
||
|
|
|
||
|
|
/// Type of axum Rejection returned when a resource cannot be acquired
|
||
|
|
#[derive(Debug, thiserror::Error)]
|
||
|
|
pub enum DependencyError {
|
||
|
|
/// Tried to get a resource which did not exist. Use `Obtain(..)` if you want aerosol to
|
||
|
|
/// try to construct the resource on demand.
|
||
|
|
#[error("Resource `{name}` does not exist")]
|
||
|
|
DoesNotExist {
|
||
|
|
/// Name of the resource type
|
||
|
|
name: &'static str,
|
||
|
|
},
|
||
|
|
/// Tried and failed to construct a resource.
|
||
|
|
#[error("Failed to construct `{name}`: {source}")]
|
||
|
|
FailedToConstruct {
|
||
|
|
/// Name of the resource type
|
||
|
|
name: &'static str,
|
||
|
|
/// Error returned by the resource constructor
|
||
|
|
#[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(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get an already-existing resource from the state. Equivalent to calling `Aerosol::try_get_async`.
|
||
|
|
pub struct Dep<T: Resource>(pub T);
|
||
|
|
|
||
|
|
#[async_trait]
|
||
|
|
impl<T: ConstructibleResource, S: Send + Sync> FromRequestParts<S> for Dep<T>
|
||
|
|
where
|
||
|
|
Aerosol: FromRef<S>,
|
||
|
|
{
|
||
|
|
type Rejection = DependencyError;
|
||
|
|
|
||
|
|
async fn from_request_parts(_parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||
|
|
Aerosol::from_ref(state)
|
||
|
|
.try_get_async()
|
||
|
|
.await
|
||
|
|
.map(Self)
|
||
|
|
.ok_or_else(DependencyError::does_not_exist::<T>)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get a resource from the state, or construct it if it doesn't exist. Equivalent to calling `Aerosol::try_obtain_async`.
|
||
|
|
pub struct Obtain<T: AsyncConstructibleResource>(pub T);
|
||
|
|
|
||
|
|
#[async_trait]
|
||
|
|
impl<T: AsyncConstructibleResource, S: Send + Sync> FromRequestParts<S> for Obtain<T>
|
||
|
|
where
|
||
|
|
Aerosol: FromRef<S>,
|
||
|
|
{
|
||
|
|
type Rejection = DependencyError;
|
||
|
|
|
||
|
|
async fn from_request_parts(_parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||
|
|
Aerosol::from_ref(state)
|
||
|
|
.try_obtain_async()
|
||
|
|
.await
|
||
|
|
.map(Self)
|
||
|
|
.map_err(DependencyError::failed_to_construct::<T>)
|
||
|
|
}
|
||
|
|
}
|