dynamic_config_tower/lib.rs
1//! One reading of configuration per request, as a plain `tower` layer.
2//!
3//! This is the layer [`dynamic-config-axum`](https://docs.rs/dynamic-config-axum)
4//! is built on, published on its own because nothing in it is axum's:
5//! it wraps any `tower::Service` over an `http::Request`, takes one
6//! [`Snapshot`] when the request begins, and puts it in the request's
7//! extensions. tonic, plain hyper, or any tower stack can use it
8//! directly; axum adds only its extractor on top.
9//!
10//! ```no_run
11//! use dynamic_config_tower::SnapshotLayer;
12//! use dynamic_config_web_core::sections;
13//! # use dynamic_config::dynamic_config;
14//! # use serde::Deserialize;
15//! # #[dynamic_config] #[derive(Deserialize)] struct Server { port: u16 }
16//!
17//! # fn wire<S>(service: S) -> impl tower::Layer<S> {
18//! SnapshotLayer::new(sections![Server])
19//! # }
20//! ```
21//!
22//! Reading it back out is `request.extensions().get::<Snapshot>()`, and
23//! [`Snapshot::require`] is the form whose error says which mistake was
24//! made. The crate owns no lifecycle: loading, watching and the
25//! `WatchHandle` stay in `main`, exactly as the web-core README says.
26//!
27//! # Long-lived connections
28//!
29//! A WebSocket upgrade, an SSE route and a streaming body all *begin* as
30//! an HTTP request, so the layer gives each one a snapshot — and that is
31//! correct for the handshake: whether to accept, from which
32//! configuration, is a request-scoped question. What the snapshot must
33//! not become is the connection's configuration for life. Inside the
34//! connection loop, read fresh state per iteration or per message batch
35//! — `T::current()` is that read — exactly as the Python package's
36//! Limitations chapter puts it for ASGI: a connection that lives an hour
37//! pinned to the configuration it opened with is the opposite of what
38//! any of this is for.
39
40#![forbid(unsafe_code)]
41#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)]
42#![cfg_attr(docsrs, feature(doc_cfg))]
43
44use std::sync::Arc;
45use std::task::{Context, Poll};
46
47use http::Request;
48use tower::{Layer, Service};
49
50pub use dynamic_config_web_core::{sections, NotInScope, Sections, Snapshot};
51
52/// Takes one snapshot per request and puts it in the request's extensions.
53///
54/// Attach it *after* the routes it should cover, exactly as with any
55/// tower layer: a layer wraps only what was there when it was added.
56#[derive(Clone)]
57pub struct SnapshotLayer {
58 sections: Arc<Sections>,
59}
60
61impl SnapshotLayer {
62 /// Builds the layer over the sections a request should read.
63 #[must_use]
64 pub fn new(sections: Sections) -> Self {
65 Self {
66 sections: Arc::new(sections),
67 }
68 }
69
70 /// The type names it will take, in order.
71 #[must_use]
72 pub fn names(&self) -> Vec<&'static str> {
73 self.sections.names()
74 }
75}
76
77impl std::fmt::Debug for SnapshotLayer {
78 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 formatter
80 .debug_struct("SnapshotLayer")
81 .field("sections", &self.sections.names())
82 .finish()
83 }
84}
85
86impl<S> Layer<S> for SnapshotLayer {
87 type Service = SnapshotService<S>;
88
89 fn layer(&self, inner: S) -> Self::Service {
90 SnapshotService {
91 inner,
92 sections: Arc::clone(&self.sections),
93 }
94 }
95}
96
97/// The service [`SnapshotLayer`] wraps a stack in.
98#[derive(Clone)]
99pub struct SnapshotService<S> {
100 inner: S,
101 sections: Arc<Sections>,
102}
103
104impl<S> std::fmt::Debug for SnapshotService<S> {
105 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 formatter
107 .debug_struct("SnapshotService")
108 .field("sections", &self.sections.names())
109 .finish_non_exhaustive()
110 }
111}
112
113impl<S, B> Service<Request<B>> for SnapshotService<S>
114where
115 S: Service<Request<B>>,
116{
117 type Response = S::Response;
118 type Error = S::Error;
119 type Future = S::Future;
120
121 fn poll_ready(&mut self, context: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
122 self.inner.poll_ready(context)
123 }
124
125 fn call(&mut self, mut request: Request<B>) -> Self::Future {
126 // Once, here, before anything downstream runs. Every read in the
127 // handler comes out of this one value.
128 let taken = self.sections.take();
129
130 // Merged rather than inserted, because layers nest: an outer
131 // stack and an inner one may each carry a layer, the outer runs
132 // first, and a bare `insert` here would erase what it took.
133 let merged = match request.extensions_mut().remove::<Snapshot>() {
134 Some(outer) => outer.merged_with(taken),
135 None => taken,
136 };
137
138 request.extensions_mut().insert(merged);
139
140 self.inner.call(request)
141 }
142}