use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
use std::path::PathBuf;
use crate::scope::ConfigScope;
pub use crate::scope::ConfigScope as SourceScope;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum SettingSource {
Default,
File {
path: PathBuf,
#[serde(skip_serializing_if = "Option::is_none")]
scope: Option<ConfigScope>,
},
EnvVar {
name: String,
},
EnvVars {
prefix: String,
},
Secrets {
path: PathBuf,
},
Override {
name: String,
},
}
impl fmt::Display for SettingSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Default => write!(f, "default"),
Self::File { path, scope } => {
let scope_str = scope.map(|s| format!(" ({:?})", s)).unwrap_or_default();
write!(f, "file:{}{}", path.display(), scope_str)
},
Self::EnvVar { name } => write!(f, "env:{}", name),
Self::EnvVars { prefix } => write!(f, "env_vars:{}*", prefix),
Self::Secrets { path } => write!(f, "secrets:{}", path.display()),
Self::Override { name } => write!(f, "override:{}", name),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SourceMetadata {
pub source: SettingSource,
pub layer_index: usize,
}
impl SourceMetadata {
pub fn file(path: PathBuf, scope: Option<ConfigScope>, layer_index: usize) -> Self {
Self {
source: SettingSource::File { path, scope },
layer_index,
}
}
pub fn env(name: String, layer_index: usize) -> Self {
Self {
source: SettingSource::EnvVar { name },
layer_index,
}
}
pub fn default(layer_index: usize) -> Self {
Self { source: SettingSource::Default, layer_index }
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SourceMap {
entries: HashMap<String, SourceMetadata>,
}
impl SourceMap {
pub fn new() -> Self {
Self { entries: HashMap::new() }
}
pub fn insert(&mut self, key: String, metadata: SourceMetadata) {
if let Some(existing) = self.entries.get(&key) {
if metadata.layer_index >= existing.layer_index {
self.entries.insert(key, metadata);
}
} else {
self.entries.insert(key, metadata);
}
}
pub fn source_of(&self, key: &str) -> Option<&SourceMetadata> {
self.entries.get(key)
}
pub fn entries(&self) -> &HashMap<String, SourceMetadata> {
&self.entries
}
pub fn insert_layer(&mut self, metadata: SourceMetadata, props: HashMap<String, config::Value>) {
for key in props.keys() {
let should_insert = match self.entries.get(key) {
Some(existing) => metadata.layer_index >= existing.layer_index,
None => true,
};
if should_insert {
self.entries.insert(key.clone(), metadata.clone());
}
}
}
pub fn audit_report(&self) -> String {
let mut report = String::from("Configuration Audit Report\n");
report.push_str("==========================\n\n");
let mut sorted_keys: Vec<_> = self.entries.keys().collect();
sorted_keys.sort();
for key in sorted_keys {
let meta = &self.entries[key];
report.push_str(&format!("{:<30} -> Layer {}: {}\n", key, meta.layer_index, meta.source));
}
report
}
}