use serde_json::Value;
use super::config::NO_INPUT_VALIDATION_REASON;
const JOBS_VALIDATION_REASON: &str = "job count is outside the supported range";
pub(crate) fn is_empty_configuration_value(value: &Value) -> bool {
matches!(value, Value::Object(map) if map.is_empty())
}
pub(crate) fn validation_rejection_reason(key: &str) -> Option<&'static str> {
match key {
"no_input" => Some(NO_INPUT_VALIDATION_REASON),
"jobs" => Some(JOBS_VALIDATION_REASON),
_ => None,
}
}
#[derive(Debug)]
pub enum MergeEvent {
DefaultsApplied,
DefaultsFailed,
FileLayersCollected {
layer_count: usize,
},
FileLayerCollectionFailed {
error_count: usize,
},
FileLayerApplied {
path_hash: Option<String>,
},
EnvironmentApplied {
is_empty: bool,
},
EnvironmentFailed,
CliOverridesApplied {
override_keys: Vec<String>,
},
CliOverridesAbsent,
CliOverridesFailed,
ValidationRejected {
key: String,
reason: &'static str,
},
}
pub trait MergeObserver {
fn observe(&mut self, event: MergeEvent);
}
#[derive(Debug, Default)]
pub struct TracingMergeObserver;
impl MergeObserver for TracingMergeObserver {
fn observe(&mut self, event: MergeEvent) {
record_default_event(&event);
record_file_event(&event);
record_environment_event(&event);
record_cli_event(&event);
record_validation_event(&event);
}
}
fn record_default_event(event: &MergeEvent) {
match event {
MergeEvent::DefaultsApplied => {
tracing::debug!(layer = "defaults", "applied default configuration layer");
}
MergeEvent::DefaultsFailed => {
tracing::debug!(layer = "defaults", "default configuration layer failed");
}
_ => {}
}
}
fn record_file_event(event: &MergeEvent) {
match event {
MergeEvent::FileLayersCollected { layer_count } => {
tracing::debug!(
layer = "file",
layer_count,
"collected configuration file layers"
);
}
MergeEvent::FileLayerCollectionFailed { error_count } => {
tracing::debug!(
layer = "file",
error_count,
"configuration file layer collection failed"
);
}
MergeEvent::FileLayerApplied { path_hash } => {
tracing::debug!(layer = "file", path_hash = ?path_hash, "applied configuration file layer");
}
_ => {}
}
}
fn record_environment_event(event: &MergeEvent) {
match event {
MergeEvent::EnvironmentApplied { is_empty } => {
tracing::debug!(
layer = "environment",
is_empty,
"merged environment configuration layer"
);
}
MergeEvent::EnvironmentFailed => {
tracing::debug!(
layer = "environment",
"environment configuration layer failed"
);
}
_ => {}
}
}
fn record_cli_event(event: &MergeEvent) {
match event {
MergeEvent::CliOverridesApplied { override_keys } => {
tracing::debug!(layer = "cli", override_keys = ?override_keys, "applied CLI override layer");
}
MergeEvent::CliOverridesAbsent => {
tracing::debug!(layer = "cli", "no explicit CLI overrides supplied");
}
MergeEvent::CliOverridesFailed => {
tracing::debug!(layer = "cli", "CLI override layer failed");
}
_ => {}
}
}
fn record_validation_event(event: &MergeEvent) {
if let MergeEvent::ValidationRejected { key, reason } = event {
tracing::debug!(key, reason, "validation rejected merged configuration");
}
}
pub(crate) fn collect_override_leaf_paths(value: &Value) -> Vec<String> {
let mut paths = Vec::new();
collect_leaf_paths(value, "", &mut paths);
paths
}
fn collect_leaf_paths(value: &Value, prefix: &str, paths: &mut Vec<String>) {
if let Value::Object(map) = value {
for (key, nested_value) in map {
let path = if prefix.is_empty() {
key.clone()
} else {
format!("{prefix}.{key}")
};
collect_leaf_paths(nested_value, &path, paths);
}
} else if !prefix.is_empty() {
paths.push(prefix.to_owned());
}
}