use praxis_core::config::Config;
use crate::dump;
pub(crate) fn load_and_validate_for_cli(
explicit: Option<&str>,
) -> Result<Config, Box<dyn std::error::Error + Send + Sync>> {
let config = praxis::load_config(explicit)?;
validate_config_for_startup(&config)?;
Ok(config)
}
pub(crate) fn validate_config_for_startup(config: &Config) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
praxis_core::logging::validate_log_overrides(config)?;
let registry = praxis::build_full_registry();
let health_registry = praxis_core::health::build_health_registry(&config.clusters);
let kv_stores = praxis_core::kv::KvStoreRegistry::new();
praxis::resolve_pipelines(config, ®istry, &health_registry, &kv_stores)?;
Ok(())
}
pub(crate) fn run_dump(explicit: Option<&str>) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let config = load_and_validate_for_cli(explicit)?;
let source = match explicit {
Some(path) => path.to_owned(),
None => default_config_source(),
};
let dump_model = dump::build_dump(&config, &source)?;
dump::write_dump(&dump_model)
}
fn default_config_source() -> String {
if std::path::Path::new("praxis.yaml").exists() {
return "praxis.yaml".to_owned();
}
"<built-in default>".to_owned()
}
#[cfg(test)]
#[expect(clippy::allow_attributes, reason = "blanket test suppressions")]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing, reason = "tests")]
mod tests {
use super::*;
#[test]
fn validate_catches_invalid_log_overrides() {
let config = Config::from_yaml(
r#"
runtime:
log_overrides:
"invalid module": "info"
"praxis_core": "invalid_level"
listeners:
- name: web
address: "127.0.0.1:8080"
filter_chains: [main]
filter_chains:
- name: main
filters: []
"#,
)
.unwrap();
let result = validate_config_for_startup(&config);
assert!(result.is_err(), "invalid log overrides should fail validation");
let err = result.err().unwrap().to_string();
assert!(
err.contains("invalid module path 'invalid module'"),
"error should mention invalid module path: {err}"
);
assert!(
err.contains("invalid level 'invalid_level'"),
"error should mention invalid level: {err}"
);
}
#[test]
fn validate_rejects_unknown_filter_type() {
let config = Config::from_yaml(
r#"
listeners:
- name: web
address: "127.0.0.1:8080"
filter_chains: [main]
filter_chains:
- name: main
filters:
- filter: nonexistent_filter_type
"#,
)
.unwrap();
let result = validate_config_for_startup(&config);
assert!(result.is_err(), "unknown filter type should fail validation");
}
#[test]
fn invalid_yaml_syntax_returns_error() {
let result = Config::from_yaml("{{{{ not: valid: yaml: [");
assert!(result.is_err(), "malformed YAML syntax should fail parsing");
}
#[test]
fn empty_config_string_returns_error() {
let result = Config::from_yaml("");
assert!(result.is_err(), "empty config string should fail parsing");
}
#[test]
fn config_with_invalid_chain_reference_returns_error() {
let result = Config::from_yaml(
r#"
listeners:
- name: web
address: "127.0.0.1:8080"
filter_chains: [nonexistent_chain]
filter_chains:
- name: main
filters: []
"#,
);
assert!(
result.is_err(),
"listener referencing undefined chain should fail validation"
);
let err = result.unwrap_err().to_string();
assert!(
err.contains("nonexistent_chain"),
"error should mention the missing chain name: {err}"
);
}
}