#![forbid(unsafe_code)]
#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)]
#![cfg_attr(docsrs, feature(doc_cfg))]
use std::any::Any;
use std::sync::Arc;
use std::task::{Context, Poll};
use axum::extract::FromRequestParts;
use axum::http::request::Parts;
use axum::http::{Request, StatusCode};
use axum::response::{IntoResponse, Response};
use dynamic_config_web_core::{NotInScope, Sections, Snapshot};
use tower::{Layer, Service};
pub use dynamic_config_web_core::{sections, NotInScope as OutOfScope, Sections as ConfigSections};
pub struct Config<T>(pub Arc<T>);
impl<T> Clone for Config<T> {
fn clone(&self) -> Self {
Self(Arc::clone(&self.0))
}
}
impl<T> std::fmt::Debug for Config<T> {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_tuple("Config")
.field(&std::any::type_name::<T>())
.finish()
}
}
impl<T> std::ops::Deref for Config<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<S, T> FromRequestParts<S> for Config<T>
where
S: Send + Sync,
T: Any + Send + Sync,
{
type Rejection = SnapshotMissing;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
let snapshot = parts
.extensions
.get::<Snapshot>()
.ok_or(SnapshotMissing::NoLayer)?;
snapshot
.require::<T>()
.map(Config)
.map_err(SnapshotMissing::Section)
}
}
#[derive(Debug, Clone, Copy)]
pub enum SnapshotMissing {
NoLayer,
Section(NotInScope),
}
impl std::fmt::Display for SnapshotMissing {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NoLayer => formatter.write_str(
"no configuration snapshot on this request: add \
`.layer(SnapshotLayer::new(sections![..]))` to the router",
),
Self::Section(why) => write!(formatter, "{why}"),
}
}
}
impl std::error::Error for SnapshotMissing {}
impl IntoResponse for SnapshotMissing {
fn into_response(self) -> Response {
(
StatusCode::INTERNAL_SERVER_ERROR,
"configuration is not wired for this handler",
)
.into_response()
}
}
#[derive(Clone)]
pub struct SnapshotLayer {
sections: Arc<Sections>,
}
impl SnapshotLayer {
#[must_use]
pub fn new(sections: Sections) -> Self {
Self {
sections: Arc::new(sections),
}
}
#[must_use]
pub fn names(&self) -> Vec<&'static str> {
self.sections.names()
}
}
impl std::fmt::Debug for SnapshotLayer {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("SnapshotLayer")
.field("sections", &self.sections.names())
.finish()
}
}
impl<S> Layer<S> for SnapshotLayer {
type Service = SnapshotService<S>;
fn layer(&self, inner: S) -> Self::Service {
SnapshotService {
inner,
sections: Arc::clone(&self.sections),
}
}
}
#[derive(Clone)]
pub struct SnapshotService<S> {
inner: S,
sections: Arc<Sections>,
}
impl<S> std::fmt::Debug for SnapshotService<S> {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("SnapshotService")
.field("sections", &self.sections.names())
.finish_non_exhaustive()
}
}
impl<S, B> Service<Request<B>> for SnapshotService<S>
where
S: Service<Request<B>>,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&mut self, context: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(context)
}
fn call(&mut self, mut request: Request<B>) -> Self::Future {
let taken = self.sections.take();
let merged = match request.extensions_mut().remove::<Snapshot>() {
Some(outer) => outer.merged_with(taken),
None => taken,
};
request.extensions_mut().insert(merged);
self.inner.call(request)
}
}
pub fn snapshot(parts: &Parts) -> Result<&Snapshot, SnapshotMissing> {
parts
.extensions
.get::<Snapshot>()
.ok_or(SnapshotMissing::NoLayer)
}