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 rule as the generated method, and the cache's: a path
32        // that is, sits under, or contains a secret is redacted.
33        if let Some(secrets) = &self.secrets {
34            if crate::touches_secret(path, secrets) {
35                return Ok(explanation.redacted());
36            }
37        }
38
39        Ok(explanation)
40    }
41
42    /// Where the value at `path` would come from, if anything supplies it.
43    ///
44    /// # Errors
45    ///
46    /// The same failures as [`load`](Self::load).
47    pub fn source_of(&self, path: &str) -> Result<Option<crate::Origin>, Error> {
48        self.with_spec(|spec| crate::loader::source_of(spec, path))
49    }
50
51    /// Whether anything supplies `path`.
52    ///
53    /// # Errors
54    ///
55    /// The same failures as [`load`](Self::load).
56    pub fn is_set(&self, path: &str) -> Result<bool, Error> {
57        self.with_spec(|spec| crate::loader::is_set(spec, path))
58    }
59
60    /// Resolves the section without deserializing it.
61    ///
62    /// # Errors
63    ///
64    /// The same failures as [`load`](Self::load).
65    pub fn snapshot(&self) -> Result<crate::Snapshot, Error> {
66        self.with_spec(crate::loader::snapshot)
67    }
68
69    /// What this configuration resolves to, and whether it would load —
70    /// see [`check`](crate::check). Unknown-key detection uses the field
71    /// names only the generated `builder()` carries; a bare builder reports
72    /// none.
73    ///
74    /// # Errors
75    ///
76    /// Only if the sources cannot be read at all.
77    pub fn check(&self) -> Result<crate::Report, Error> {
78        self.with_spec(|spec| crate::check::<T>(spec, self.fields))
79    }
80}
81
82#[cfg(feature = "schema")]
83#[cfg_attr(docsrs, doc(cfg(feature = "schema")))]
84impl<T: DeserializeOwned> Builder<T> {
85    /// A JSON Schema for the *file* this section lives in.
86    ///
87    /// The struct's schema wrapped under this builder's key, with
88    /// `#[config(secret)]` fields carrying `writeOnly` — which the generated
89    /// `builder()` knows and a bare one does not. Combine several with
90    /// [`schema::merge`](crate::schema::merge) when more than one config
91    /// type shares a file.
92    #[must_use]
93    pub fn schema(&self) -> serde_json::Value
94    where
95        T: schemars::JsonSchema,
96    {
97        let secrets = self.secrets.clone().unwrap_or_default();
98        let secret_refs: Vec<&str> = secrets.iter().map(String::as_str).collect();
99
100        crate::schema::section(&self.key, schemars::schema_for!(T).into(), &secret_refs)
101    }
102}