2023-07-02 00:07:15 +01:00
|
|
|
//! Integration with the `axum` web framework.
|
|
|
|
|
//!
|
2023-07-02 14:02:34 +01:00
|
|
|
//! Provides the `Dep` and `Obtain` axum extractors for easily accessing
|
2023-07-02 00:07:15 +01:00
|
|
|
//! resources from within route handlers.
|
|
|
|
|
//!
|
|
|
|
|
//! To make use of these extractors, your application state must either be
|
2023-08-04 00:56:01 +01:00
|
|
|
//! an `Aero`, or you must implement `FromRef<YourState>` for `Aero`.
|
2023-07-02 00:07:15 +01:00
|
|
|
|
|
|
|
|
use std::any::type_name;
|
|
|
|
|
|
|
|
|
|
use async_trait::async_trait;
|
|
|
|
|
use axum::{
|
|
|
|
|
extract::{FromRef, FromRequestParts},
|
|
|
|
|
http::{request::Parts, StatusCode},
|
|
|
|
|
response::{IntoResponse, Response},
|
|
|
|
|
};
|
2023-08-14 10:58:22 +01:00
|
|
|
use frunk::HCons;
|
2023-07-02 00:07:15 +01:00
|
|
|
|
2023-08-14 12:31:02 +01:00
|
|
|
use crate::{Aero, AsyncConstructibleResource, Resource, ResourceList};
|
2023-07-02 00:07:15 +01:00
|
|
|
|
|
|
|
|
/// 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(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-04 00:56:01 +01:00
|
|
|
/// Get an already-existing resource from the state. Equivalent to calling `Aero::try_get_async`.
|
2023-07-02 00:07:15 +01:00
|
|
|
pub struct Dep<T: Resource>(pub T);
|
|
|
|
|
|
|
|
|
|
#[async_trait]
|
2023-08-14 12:31:02 +01:00
|
|
|
impl<T: Resource, S: Send + Sync> FromRequestParts<S> for Dep<T>
|
2023-07-02 00:07:15 +01:00
|
|
|
where
|
2023-08-04 00:56:01 +01:00
|
|
|
Aero: FromRef<S>,
|
2023-07-02 00:07:15 +01:00
|
|
|
{
|
|
|
|
|
type Rejection = DependencyError;
|
|
|
|
|
|
|
|
|
|
async fn from_request_parts(_parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
2023-08-04 00:56:01 +01:00
|
|
|
Aero::from_ref(state)
|
2023-07-02 00:07:15 +01:00
|
|
|
.try_get_async()
|
|
|
|
|
.await
|
|
|
|
|
.map(Self)
|
|
|
|
|
.ok_or_else(DependencyError::does_not_exist::<T>)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-04 00:56:01 +01:00
|
|
|
/// Get a resource from the state, or construct it if it doesn't exist. Equivalent to calling `Aero::try_obtain_async`.
|
2023-07-02 00:07:15 +01:00
|
|
|
pub struct Obtain<T: AsyncConstructibleResource>(pub T);
|
|
|
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
|
impl<T: AsyncConstructibleResource, S: Send + Sync> FromRequestParts<S> for Obtain<T>
|
|
|
|
|
where
|
2023-08-04 00:56:01 +01:00
|
|
|
Aero: FromRef<S>,
|
2023-07-02 00:07:15 +01:00
|
|
|
{
|
|
|
|
|
type Rejection = DependencyError;
|
|
|
|
|
|
|
|
|
|
async fn from_request_parts(_parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
2023-08-04 00:56:01 +01:00
|
|
|
Aero::from_ref(state)
|
2023-07-02 00:07:15 +01:00
|
|
|
.try_obtain_async()
|
|
|
|
|
.await
|
|
|
|
|
.map(Self)
|
|
|
|
|
.map_err(DependencyError::failed_to_construct::<T>)
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-14 10:58:22 +01:00
|
|
|
|
|
|
|
|
impl<H: Resource, T: ResourceList> FromRef<Aero<HCons<H, T>>> for Aero {
|
|
|
|
|
fn from_ref(input: &Aero<HCons<H, T>>) -> Self {
|
|
|
|
|
input.clone().into()
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-14 12:31:02 +01:00
|
|
|
#[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")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|