Skip to main content

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}