#![forbid(unsafe_code)]
#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)]
#![cfg_attr(docsrs, feature(doc_cfg))]
use std::any::Any;
use std::future::{ready, Ready};
use std::sync::Arc;
use actix_web::dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform};
use actix_web::http::StatusCode;
use actix_web::{Error, FromRequest, HttpMessage, HttpRequest, ResponseError};
use dynamic_config_web_core::{NotInScope, Sections, Snapshot};
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<T> FromRequest for Config<T>
where
T: Any + Send + Sync,
{
type Error = Error;
type Future = Ready<Result<Self, Error>>;
fn from_request(request: &HttpRequest, _payload: &mut actix_web::dev::Payload) -> Self::Future {
ready(from_parts(request).map_err(Into::into))
}
}
fn from_parts<T: Any + Send + Sync>(request: &HttpRequest) -> Result<Config<T>, SnapshotMissing> {
let extensions = request.extensions();
let snapshot = extensions
.get::<Snapshot>()
.ok_or(SnapshotMissing::NoMiddleware)?;
snapshot
.require::<T>()
.map(Config)
.map_err(SnapshotMissing::Section)
}
#[derive(Debug, Clone, Copy)]
pub enum SnapshotMissing {
NoMiddleware,
Section(NotInScope),
}
impl std::fmt::Display for SnapshotMissing {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NoMiddleware => formatter.write_str(
"no configuration snapshot on this request: add \
`.wrap(DynamicConfig::new(sections![..]))` to the app",
),
Self::Section(why) => write!(formatter, "{why}"),
}
}
}
impl std::error::Error for SnapshotMissing {}
impl ResponseError for SnapshotMissing {
fn status_code(&self) -> StatusCode {
StatusCode::INTERNAL_SERVER_ERROR
}
fn error_response(&self) -> actix_web::HttpResponse {
actix_web::HttpResponse::build(self.status_code())
.content_type("text/plain; charset=utf-8")
.body("configuration is not wired for this handler")
}
}
pub fn snapshot(request: &HttpRequest) -> Result<Snapshot, SnapshotMissing> {
request
.extensions()
.get::<Snapshot>()
.cloned()
.ok_or(SnapshotMissing::NoMiddleware)
}
pub struct DynamicConfig {
sections: Arc<Sections>,
}
impl DynamicConfig {
#[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 DynamicConfig {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("DynamicConfig")
.field("sections", &self.sections.names())
.finish()
}
}
impl Clone for DynamicConfig {
fn clone(&self) -> Self {
Self {
sections: Arc::clone(&self.sections),
}
}
}
impl<S, B> Transform<S, ServiceRequest> for DynamicConfig
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = SnapshotMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(SnapshotMiddleware {
service,
sections: Arc::clone(&self.sections),
}))
}
}
pub struct SnapshotMiddleware<S> {
service: S,
sections: Arc<Sections>,
}
impl<S> std::fmt::Debug for SnapshotMiddleware<S> {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("SnapshotMiddleware")
.field("sections", &self.sections.names())
.finish_non_exhaustive()
}
}
impl<S, B> Service<ServiceRequest> for SnapshotMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = S::Future;
forward_ready!(service);
fn call(&self, request: ServiceRequest) -> 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.service.call(request)
}
}