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}