dynamic_config/builder/diagnostics.rs
1//! Questions a builder answers without installing anything.
2//!
3//! Every method here is a read: `explain`, `source_of`, `is_set`,
4//! `snapshot`, `check` — and, with the `schema` feature, the JSON Schema
5//! for the file this section lives in. All of them run through the same
6//! `with_spec` funnel the loads use, so an answer cannot drift from what a
7//! load would do.
8
9use serde::de::DeserializeOwned;
10
11use crate::error::Error;
12
13use super::Builder;
14
15impl<T: DeserializeOwned> Builder<T> {
16 /// Explains `path` against this builder's sources; see [`crate::explain`].
17 ///
18 /// A builder that knows which fields are secret — every generated
19 /// `builder()` does — hands back a path under one of them already
20 /// redacted, the same as the type-level `explain`. A bare
21 /// [`Builder::new`] knows no secrets and redacts nothing; pass the
22 /// result through [`Explanation::redacted`](crate::Explanation::redacted)
23 /// for a path you know to be sensitive.
24 ///
25 /// # Errors
26 ///
27 /// The same failures as [`load`](Self::load).
28 pub fn explain(&self, path: &str) -> Result<crate::Explanation, Error> {
29 let explanation = self.with_spec(|spec| crate::explain::explain(spec, path))?;
30
31 // The same head-of-path check as the generated method: secrets are
32 // field names, and every path under one is the secret's.
33 if let Some(secrets) = &self.secrets {
34 let head = path.split('.').next().unwrap_or(path);
35
36 if secrets.iter().any(|secret| secret == head) {
37 return Ok(explanation.redacted());
38 }
39 }
40
41 Ok(explanation)
42 }
43
44 /// Where the value at `path` would come from, if anything supplies it.
45 ///
46 /// # Errors
47 ///
48 /// The same failures as [`load`](Self::load).
49 pub fn source_of(&self, path: &str) -> Result<Option<crate::Origin>, Error> {
50 self.with_spec(|spec| crate::loader::source_of(spec, path))
51 }
52
53 /// Whether anything supplies `path`.
54 ///
55 /// # Errors
56 ///
57 /// The same failures as [`load`](Self::load).
58 pub fn is_set(&self, path: &str) -> Result<bool, Error> {
59 self.with_spec(|spec| crate::loader::is_set(spec, path))
60 }
61
62 /// Resolves the section without deserializing it.
63 ///
64 /// # Errors
65 ///
66 /// The same failures as [`load`](Self::load).
67 pub fn snapshot(&self) -> Result<crate::Snapshot, Error> {
68 self.with_spec(crate::loader::snapshot)
69 }
70
71 /// What this configuration resolves to, and whether it would load —
72 /// see [`check`](crate::check). Unknown-key detection uses the field
73 /// names only the generated `builder()` carries; a bare builder reports
74 /// none.
75 ///
76 /// # Errors
77 ///
78 /// Only if the sources cannot be read at all.
79 pub fn check(&self) -> Result<crate::Report, Error> {
80 self.with_spec(|spec| crate::check::<T>(spec, self.fields))
81 }
82}
83
84#[cfg(feature = "schema")]
85#[cfg_attr(docsrs, doc(cfg(feature = "schema")))]
86impl<T: DeserializeOwned> Builder<T> {
87 /// A JSON Schema for the *file* this section lives in.
88 ///
89 /// The struct's schema wrapped under this builder's key, with
90 /// `#[config(secret)]` fields carrying `writeOnly` — which the generated
91 /// `builder()` knows and a bare one does not. Combine several with
92 /// [`schema::merge`](crate::schema::merge) when more than one config
93 /// type shares a file.
94 #[must_use]
95 pub fn schema(&self) -> serde_json::Value
96 where
97 T: schemars::JsonSchema,
98 {
99 let secrets = self.secrets.clone().unwrap_or_default();
100 let secret_refs: Vec<&str> = secrets.iter().map(String::as_str).collect();
101
102 crate::schema::section(&self.key, schemars::schema_for!(T).into(), &secret_refs)
103 }
104}