dynamic_config_server/routes/mod.rs
1//! The HTTP surface.
2//!
3//! Two path segments carry the whole addressing scheme: the first is the
4//! application, the second is the profile, and what hangs off them is this
5//! crate's own vocabulary.
6//!
7//! # One endpoint returns values
8//!
9//! `GET /{application}/{profile}` is the handover — the resolved document,
10//! secrets included, which is what a config server is *for*. Every other
11//! endpoint returns shape, provenance or counts: paths without what is at
12//! them, an explanation with every value replaced by `***`, a check report
13//! that names keys and origins, a status that is timestamps and numbers,
14//! and a metrics scrape that is the same numbers with a label naming the
15//! section they belong to.
16//!
17//! That line is drawn once, here, and it is drawn wider than the library
18//! draws it. `explain` in the library deliberately *does* carry values —
19//! you asked, at a terminal, for one path. Over a socket the same answer is
20//! a value that has left the process for a reason nobody weighed, so the
21//! server pushes every explanation through
22//! [`Explanation::redacted`](dynamic_config::Explanation::redacted) rather
23//! than only the paths it believes are secret. Reusing the library's
24//! redaction rather than writing a second one is the point; applying it
25//! unconditionally is the server's own decision.
26//!
27//! # It will not be an oracle
28//!
29//! A caller that may not read `billing` and a caller asking for an
30//! application nobody serves get the same 404, with the same body, having
31//! done the same work: authorisation is decided from the caller's grants
32//! alone, and the section map is never consulted for an application the
33//! caller was not granted. There is nothing to time and nothing to read.
34//!
35//! Seven files, one concern each: the router here, then the endpoints
36//! grouped by what they answer — liveness, metrics, documents,
37//! diagnostics, the change stream — with admission and the refusal
38//! responses in [`admit`](admit) because every handler goes through them,
39//! and the two path predicates in [`names`](names) because
40//! [`ServerConfig::validate`](crate::ServerConfig) uses one of them too.
41
42mod admit;
43mod diagnostics;
44mod documents;
45mod health;
46mod metrics;
47mod names;
48mod stream;
49
50pub(crate) use names::is_name;
51
52use std::sync::Arc;
53
54use axum::routing::get;
55use axum::Router;
56
57use crate::server::Server;
58
59use admit::not_found;
60use diagnostics::{check, explain};
61use documents::{document, paths, status};
62use health::{healthz, readyz};
63use metrics::metrics;
64use stream::stream;
65
66/// The router, over a started [`Server`].
67///
68/// Everything is a `GET`: this server serves configuration and changes
69/// nothing, so there is no verb here that could.
70pub fn router(server: Arc<Server>) -> Router {
71 Router::new()
72 .route("/healthz", get(healthz))
73 .route("/readyz", get(readyz))
74 .route("/metrics", get(metrics))
75 .route("/{application}/{profile}", get(document))
76 .route("/{application}/{profile}/paths", get(paths))
77 .route("/{application}/{profile}/check", get(check))
78 .route("/{application}/{profile}/status", get(status))
79 .route("/{application}/{profile}/stream", get(stream))
80 .route("/{application}/{profile}/explain/{path}", get(explain))
81 // So that a path this server does not route answers exactly like a
82 // section it will not serve: same status, same body.
83 .fallback(|| async { not_found() })
84 .with_state(server)
85}