settings_loader/
provenance.rs1use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12use std::fmt;
13use std::path::PathBuf;
14
15use crate::scope::ConfigScope;
16pub use crate::scope::ConfigScope as SourceScope;
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(tag = "type", rename_all = "snake_case")]
21pub enum SettingSource {
22 Default,
24 File {
26 path: PathBuf,
28 #[serde(skip_serializing_if = "Option::is_none")]
30 scope: Option<ConfigScope>,
31 },
32 EnvVar {
34 name: String,
36 },
37 EnvVars {
39 prefix: String,
41 },
42 Secrets {
44 path: PathBuf,
46 },
47 Override {
49 name: String,
51 },
52}
53
54impl fmt::Display for SettingSource {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 match self {
57 Self::Default => write!(f, "default"),
58 Self::File { path, scope } => {
59 let scope_str = scope.map(|s| format!(" ({:?})", s)).unwrap_or_default();
60 write!(f, "file:{}{}", path.display(), scope_str)
61 },
62 Self::EnvVar { name } => write!(f, "env:{}", name),
63 Self::EnvVars { prefix } => write!(f, "env_vars:{}*", prefix),
64 Self::Secrets { path } => write!(f, "secrets:{}", path.display()),
65 Self::Override { name } => write!(f, "override:{}", name),
66 }
67 }
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
72pub struct SourceMetadata {
73 pub source: SettingSource,
75 pub layer_index: usize,
77}
78
79impl SourceMetadata {
80 pub fn file(path: PathBuf, scope: Option<ConfigScope>, layer_index: usize) -> Self {
82 Self {
83 source: SettingSource::File { path, scope },
84 layer_index,
85 }
86 }
87
88 pub fn env(name: String, layer_index: usize) -> Self {
90 Self {
91 source: SettingSource::EnvVar { name },
92 layer_index,
93 }
94 }
95
96 pub fn default(layer_index: usize) -> Self {
98 Self { source: SettingSource::Default, layer_index }
99 }
100}
101
102#[derive(Debug, Clone, Default, Serialize, Deserialize)]
107pub struct SourceMap {
108 entries: HashMap<String, SourceMetadata>,
110}
111
112impl SourceMap {
113 pub fn new() -> Self {
115 Self { entries: HashMap::new() }
116 }
117
118 pub fn insert(&mut self, key: String, metadata: SourceMetadata) {
123 if let Some(existing) = self.entries.get(&key) {
124 if metadata.layer_index >= existing.layer_index {
125 self.entries.insert(key, metadata);
126 }
127 } else {
128 self.entries.insert(key, metadata);
129 }
130 }
131
132 pub fn source_of(&self, key: &str) -> Option<&SourceMetadata> {
134 self.entries.get(key)
135 }
136
137 pub fn entries(&self) -> &HashMap<String, SourceMetadata> {
139 &self.entries
140 }
141
142 pub fn insert_layer(&mut self, metadata: SourceMetadata, props: HashMap<String, config::Value>) {
145 for key in props.keys() {
146 let should_insert = match self.entries.get(key) {
147 Some(existing) => metadata.layer_index >= existing.layer_index,
148 None => true,
149 };
150
151 if should_insert {
152 self.entries.insert(key.clone(), metadata.clone());
153 }
154 }
155 }
156
157 pub fn audit_report(&self) -> String {
158 let mut report = String::from("Configuration Audit Report\n");
159 report.push_str("==========================\n\n");
160
161 let mut sorted_keys: Vec<_> = self.entries.keys().collect();
162 sorted_keys.sort();
163
164 for key in sorted_keys {
165 let meta = &self.entries[key];
166 report.push_str(&format!("{:<30} -> Layer {}: {}\n", key, meta.layer_index, meta.source));
167 }
168
169 report
170 }
171}