siderite_config/
explain.rs1use crate::builder::ALIASES;
6use crate::error::is_secret_key;
7use crate::redact::{REDACTED, redact_text};
8use figment::Figment;
9use figment::value::{Dict, Value};
10use serde::Serialize;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
14#[serde(rename_all = "snake_case")]
15pub enum SourceKind {
16 Default,
18 File,
20 Environment,
22 Override,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
28pub struct ConfigSource {
29 pub kind: SourceKind,
31 pub detail: Option<String>,
33}
34
35#[derive(Debug, Clone, PartialEq, Serialize)]
37pub struct ConfigEntry {
38 pub key: String,
40 pub value: serde_json::Value,
43 pub source: ConfigSource,
45}
46
47type Entries = Vec<ConfigEntry>;
48
49pub(crate) fn entries(merged: &Figment, overrides: &Figment) -> Entries {
52 let Ok(root) = merged.extract::<Dict>() else {
53 return Vec::new();
54 };
55 let mut out = Vec::new();
56 walk(&root, "", &mut |key, value| {
57 out.push(ConfigEntry {
58 key: key.to_owned(),
59 value: shown(key, value),
60 source: source(merged, overrides, key),
61 });
62 });
63 out.sort_by(|a, b| a.key.cmp(&b.key));
64 out
65}
66
67fn walk(dict: &Dict, prefix: &str, leaf: &mut dyn FnMut(&str, &Value)) {
69 for (name, value) in dict {
70 let key = if prefix.is_empty() {
71 name.clone()
72 } else {
73 format!("{prefix}.{name}")
74 };
75 match value {
76 Value::Dict(_, inner) => walk(inner, &key, leaf),
77 other => leaf(&key, other),
78 }
79 }
80}
81
82fn shown(key: &str, value: &Value) -> serde_json::Value {
84 let secret = key.split('.').any(is_secret_key);
85 match value {
86 Value::String(_, text) if secret && text.contains("://") => {
87 serde_json::Value::String(redact_text(text))
88 }
89 _ if secret => serde_json::Value::String(REDACTED.to_owned()),
90 Value::String(_, text) => serde_json::Value::String(redact_text(text)),
91 other => {
92 let json = serde_json::to_value(other);
93 json.unwrap_or(serde_json::Value::Null)
94 }
95 }
96}
97
98fn variable(provider: &str, key: &str) -> Option<String> {
101 let prefix = provider.split('`').nth(1).filter(|p| !p.is_empty());
102 match prefix {
103 Some(prefix) => {
104 let nested = key.split('.').collect::<Vec<_>>().join("__");
105 Some(format!("{prefix}{}", nested.to_ascii_uppercase()))
106 }
107 None => ALIASES
108 .iter()
109 .find(|(_, path)| *path == key)
110 .map(|(var, _)| (*var).to_owned()),
111 }
112}
113
114fn source(merged: &Figment, overrides: &Figment, key: &str) -> ConfigSource {
115 if overrides.find_value(key).is_ok() {
116 return ConfigSource {
117 kind: SourceKind::Override,
118 detail: None,
119 };
120 }
121 let Some(meta) = merged.find_metadata(key) else {
122 return ConfigSource {
123 kind: SourceKind::Default,
124 detail: None,
125 };
126 };
127 if let Some(figment::Source::File(path)) = &meta.source {
128 return ConfigSource {
129 kind: SourceKind::File,
130 detail: Some(path.display().to_string()),
131 };
132 }
133 if meta.name.contains("environment") {
134 return ConfigSource {
135 kind: SourceKind::Environment,
136 detail: variable(&meta.name, key),
137 };
138 }
139 ConfigSource {
140 kind: SourceKind::Default,
141 detail: None,
142 }
143}