dynamic_config_axum/lib.rs
1//! A request-scoped configuration snapshot for axum.
2//!
3//! ```no_run
4//! use axum::{routing::get, Router};
5//! use dynamic_config_axum::{Config, SnapshotLayer};
6//! use dynamic_config_web_core::sections;
7//! # use dynamic_config::dynamic_config;
8//! # use serde::Deserialize;
9//! # #[dynamic_config] #[derive(Deserialize)] struct Server { port: u16 }
10//! # #[dynamic_config] #[derive(Deserialize)] struct Features { cache: bool }
11//!
12//! async fn index(
13//! Config(server): Config<Server>,
14//! Config(features): Config<Features>,
15//! ) -> String {
16//! // Both came out of one snapshot, taken when the request began.
17//! // `Sections::take` retries if a reload lands mid-read, so these
18//! // two cannot be different generations.
19//! format!("{} {}", server.port, features.cache)
20//! }
21//!
22//! let app: Router = Router::new()
23//! .route("/", get(index))
24//! .layer(SnapshotLayer::new(sections![Server, Features]));
25//! ```
26//!
27//! # What this is for
28//!
29//! `Server::current()` is an atomic load, and calling it in a handler is
30//! correct. Calling it *twice*, or calling it for two sections, is where a
31//! reload landing mid-request lets one response mix generations.
32//!
33//! [`SnapshotLayer`] reads every listed section once, before the handler
34//! runs, and stores the result in the request's extensions. [`Config<T>`]
35//! reads it back out. Request extensions are axum's request scope, so
36//! nothing here is thread-local and nothing has to be undone afterwards.
37//!
38//! # What this is not
39//!
40//! It does not load configuration, watch files, or own a [`WatchHandle`].
41//! That stays where it already is — in the startup code that calls
42//! `init()` and holds the handles for the life of the process.
43//!
44//! [`WatchHandle`]: https://docs.rs/dynamic-config/latest/dynamic_config/watch/struct.WatchHandle.html
45
46#![forbid(unsafe_code)]
47#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)]
48#![cfg_attr(docsrs, feature(doc_cfg))]
49
50use std::any::Any;
51use std::sync::Arc;
52use std::task::{Context, Poll};
53
54use axum::extract::FromRequestParts;
55use axum::http::request::Parts;
56use axum::http::{Request, StatusCode};
57use axum::response::{IntoResponse, Response};
58use dynamic_config_web_core::{NotInScope, Sections, Snapshot};
59use tower::{Layer, Service};
60
61pub use dynamic_config_web_core::{sections, NotInScope as OutOfScope, Sections as ConfigSections};
62
63/// One section of this request's configuration.
64///
65/// ```no_run
66/// # use dynamic_config_axum::Config;
67/// # struct Database { host: String }
68/// async fn handler(Config(db): Config<Database>) -> String {
69/// db.host.clone()
70/// }
71/// ```
72///
73/// Extracting the same type twice in one handler answers the same `Arc`.
74/// Extracting one the layer was not given is a wiring mistake and answers
75/// `500` — see [`SnapshotMissing`].
76pub struct Config<T>(pub Arc<T>);
77
78impl<T> Clone for Config<T> {
79 /// Hand-written: cloning an `Arc` never needs `T: Clone`, and a
80 /// derive would demand it of every section.
81 fn clone(&self) -> Self {
82 Self(Arc::clone(&self.0))
83 }
84}
85
86impl<T> std::fmt::Debug for Config<T> {
87 /// The type's name, never the section's contents.
88 ///
89 /// A configuration section holds credentials, and `?config` in a
90 /// `tracing` call is exactly how one reaches a log line. `Snapshot`
91 /// holds the same line; this is the extractor keeping it.
92 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 formatter
94 .debug_tuple("Config")
95 .field(&std::any::type_name::<T>())
96 .finish()
97 }
98}
99
100impl<T> std::ops::Deref for Config<T> {
101 type Target = T;
102
103 fn deref(&self) -> &Self::Target {
104 &self.0
105 }
106}
107
108impl<S, T> FromRequestParts<S> for Config<T>
109where
110 S: Send + Sync,
111 T: Any + Send + Sync,
112{
113 type Rejection = SnapshotMissing;
114
115 async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
116 let snapshot = parts
117 .extensions
118 .get::<Snapshot>()
119 .ok_or(SnapshotMissing::NoLayer)?;
120
121 snapshot
122 .require::<T>()
123 .map(Config)
124 .map_err(SnapshotMissing::Section)
125 }
126}
127
128/// Why a [`Config`] extractor could not answer.
129///
130/// Every variant is a wiring mistake rather than anything a client did,
131/// which is why they are all `500`: a request that asks for a section the
132/// application never registered would be wrong however it was sent.
133#[derive(Debug, Clone, Copy)]
134pub enum SnapshotMissing {
135 /// No [`SnapshotLayer`] ran for this request.
136 NoLayer,
137 /// The layer ran, and this section was not in what it took.
138 Section(NotInScope),
139}
140
141impl std::fmt::Display for SnapshotMissing {
142 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 match self {
144 Self::NoLayer => formatter.write_str(
145 "no configuration snapshot on this request: add \
146 `.layer(SnapshotLayer::new(sections![..]))` to the router",
147 ),
148 Self::Section(why) => write!(formatter, "{why}"),
149 }
150 }
151}
152
153impl std::error::Error for SnapshotMissing {}
154
155impl IntoResponse for SnapshotMissing {
156 fn into_response(self) -> Response {
157 // The detail names an internal type path, which is for whoever
158 // reads the logs rather than for whoever sent the request. The
159 // body says only that the server is misconfigured; `Display`
160 // carries the rest.
161 (
162 StatusCode::INTERNAL_SERVER_ERROR,
163 "configuration is not wired for this handler",
164 )
165 .into_response()
166 }
167}
168
169/// Takes one snapshot per request and puts it in the request's extensions.
170///
171/// ```no_run
172/// # use axum::Router;
173/// # use dynamic_config_axum::SnapshotLayer;
174/// # use dynamic_config_web_core::Sections;
175/// # let sections = Sections::new();
176/// let app: Router = Router::new().layer(SnapshotLayer::new(sections));
177/// ```
178///
179/// `.layer()` wraps the routes a `Router` holds **when it is called**, so
180/// it goes last:
181///
182/// ```ignore
183/// Router::new().route("/", get(handler)).layer(layer) // handler is wrapped
184/// Router::new().layer(layer).route("/", get(handler)) // it is NOT
185/// ```
186///
187/// The second form compiles and answers `500` on every request.
188#[derive(Clone)]
189pub struct SnapshotLayer {
190 sections: Arc<Sections>,
191}
192
193impl SnapshotLayer {
194 /// Builds the layer over the sections a request should read.
195 #[must_use]
196 pub fn new(sections: Sections) -> Self {
197 Self {
198 sections: Arc::new(sections),
199 }
200 }
201
202 /// The type names it will take, in order.
203 #[must_use]
204 pub fn names(&self) -> Vec<&'static str> {
205 self.sections.names()
206 }
207}
208
209impl std::fmt::Debug for SnapshotLayer {
210 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 formatter
212 .debug_struct("SnapshotLayer")
213 .field("sections", &self.sections.names())
214 .finish()
215 }
216}
217
218impl<S> Layer<S> for SnapshotLayer {
219 type Service = SnapshotService<S>;
220
221 fn layer(&self, inner: S) -> Self::Service {
222 SnapshotService {
223 inner,
224 sections: Arc::clone(&self.sections),
225 }
226 }
227}
228
229/// The service [`SnapshotLayer`] wraps a router in.
230#[derive(Clone)]
231pub struct SnapshotService<S> {
232 inner: S,
233 sections: Arc<Sections>,
234}
235
236impl<S> std::fmt::Debug for SnapshotService<S> {
237 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238 formatter
239 .debug_struct("SnapshotService")
240 .field("sections", &self.sections.names())
241 .finish_non_exhaustive()
242 }
243}
244
245impl<S, B> Service<Request<B>> for SnapshotService<S>
246where
247 S: Service<Request<B>>,
248{
249 type Response = S::Response;
250 type Error = S::Error;
251 type Future = S::Future;
252
253 fn poll_ready(&mut self, context: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
254 self.inner.poll_ready(context)
255 }
256
257 fn call(&mut self, mut request: Request<B>) -> Self::Future {
258 // Once, here, before anything downstream runs. Every read in the
259 // handler comes out of this one value.
260 let taken = self.sections.take();
261
262 // Merged rather than inserted, because layers nest: an outer
263 // `Router` and a `nest`ed one may each carry a layer, the outer
264 // runs first, and a bare `insert` here would erase what it took.
265 // A handler under both then sees only the inner list, and asks for
266 // a section whose 500 says to add what is already there.
267 let merged = match request.extensions_mut().remove::<Snapshot>() {
268 Some(outer) => outer.merged_with(taken),
269 None => taken,
270 };
271
272 request.extensions_mut().insert(merged);
273
274 self.inner.call(request)
275 }
276}
277
278/// The snapshot this request began with, for code that has the parts in
279/// hand rather than an extractor — another middleware, or a handler that
280/// takes `Request` whole.
281///
282/// # Errors
283///
284/// [`SnapshotMissing::NoLayer`] when no [`SnapshotLayer`] ran.
285pub fn snapshot(parts: &Parts) -> Result<&Snapshot, SnapshotMissing> {
286 parts
287 .extensions
288 .get::<Snapshot>()
289 .ok_or(SnapshotMissing::NoLayer)
290}