use std::{path::PathBuf, time::Duration}; use serde::{Deserialize, Serialize}; use layeredconf::{Builder, Format, LayeredConf, LayeredConfMerge, LayeredConfSolidify, Source}; #[derive(LayeredConf, Deserialize, Serialize, Clone, Debug)] struct Config { #[clap(long)] config: String, #[clap(long)] name: String, #[clap(long)] data_path: PathBuf, #[clap(long)] optional: Option, #[layered(subconfig)] subconfig: SubConfig, } #[derive(LayeredConf, Deserialize, Serialize, Clone, Debug)] #[layered(subconfig)] struct SubConfig { #[clap(long)] flibble: u64, #[clap(skip)] duration: Duration, } #[test] fn test_deser() { let input = r#" config = "Hello" name = "World" data_path = "/tmp/rar" subconfig = { flibble = 1997, duration = { secs = 10, nanos = 0 } } "#; let _: Config = toml::from_str(input).unwrap(); } #[test] fn test_generated_object_defaults() { let config_layer = ConfigLayer::default(); assert_eq!(serde_json::to_string(&config_layer).unwrap(), "{}"); } static INTERMED_JSON: &str = r#"{ "config": "string", "data_path": "/tmp/path", "subconfig": { "duration": { "secs": 50, "nanos": 99 } } }"#; static TOP_JSON: &str = r#"{ "name": "yes", "subconfig": { "flibble": 10 } }"#; static FULL_JSON: &str = r#"{"config":"string","name":"yes","data_path":"/tmp/path","subconfig":{"flibble":10,"duration":{"secs":50,"nanos":99}}}"#; static FULL_JSON_SOLID: &str = r#"{"config":"string","name":"yes","data_path":"/tmp/path","optional":null,"subconfig":{"flibble":10,"duration":{"secs":50,"nanos":99}}}"#; #[test] fn test_merge() { let mut base_layer = ConfigLayer::default(); let mut intermediate_layer: ConfigLayer = serde_json::from_str(INTERMED_JSON).unwrap(); let top_layer: ConfigLayer = serde_json::from_str(TOP_JSON).unwrap(); intermediate_layer.merge_from(&top_layer); base_layer.merge_from(&intermediate_layer); assert_eq!(serde_json::to_string(&base_layer).unwrap(), FULL_JSON); let solid = base_layer.solidify().unwrap(); assert_eq!(solid.config, "string"); assert_eq!(solid.name, "yes"); assert_eq!(solid.data_path, PathBuf::from("/tmp/path")); assert_eq!(solid.subconfig.flibble, 10); assert_eq!( solid.subconfig.duration, Duration::from_secs(50) + Duration::from_nanos(99) ); } #[test] fn test_layers() { let mut builder = Builder::::new(); builder.new_layer(Source::String("{}".to_string(), Format::Json)); builder.new_layer(Source::String(INTERMED_JSON.to_string(), Format::Json)); builder.new_layer(Source::String(TOP_JSON.to_string(), Format::Json)); let solid = builder.solidify().unwrap(); assert_eq!(serde_json::to_string(&solid).unwrap(), FULL_JSON_SOLID); } #[test] fn test_field_level_default() -> anyhow::Result<()> { use serde_json::json; fn default_config() -> String { "DEFAULT_CONFIG".to_string() } #[derive(LayeredConf, Deserialize, Serialize, Clone, Debug)] struct Config { #[layered(default = "default_config")] #[clap(long)] config: String, #[clap(long)] name: String, } let config: Config = Builder::new() .new_layer(Source::String( json!({"config": "layer_config", "name": "layer_name"}).to_string(), Format::Json, )) .solidify()?; assert_eq!(config.config, "layer_config"); assert_eq!(config.name, "layer_name"); let config: Config = Builder::new() .new_layer(Source::String( json!({"name": "layer_name"}).to_string(), Format::Json, )) .solidify()?; assert_eq!(config.config, "DEFAULT_CONFIG"); assert_eq!(config.name, "layer_name"); Ok(()) } #[test] fn test_field_level_default_path() -> anyhow::Result<()> { use serde_json::json; fn config_default() -> String { "CONFIG_DEFAULT_PATH".to_string() } #[derive(LayeredConf, Deserialize, Serialize, Clone, Debug)] struct Config { #[layered(default = "config_default")] #[clap(long)] config: String, #[clap(long)] name: String, } let config: Config = Builder::new() .new_layer(Source::String( json!({"config": "layer_config", "name": "layer_name"}).to_string(), Format::Json, )) .solidify()?; assert_eq!(config.config, "layer_config"); assert_eq!(config.name, "layer_name"); let config: Config = Builder::new() .new_layer(Source::String( json!({"name": "layer_name"}).to_string(), Format::Json, )) .solidify()?; assert_eq!(config.config, "CONFIG_DEFAULT_PATH"); assert_eq!(config.name, "layer_name"); Ok(()) } #[test] fn test_struct_level_default() -> anyhow::Result<()> { use serde_json::json; #[derive(LayeredConf, Deserialize, Serialize, Clone, Debug)] #[layered(default)] struct Config { #[clap(long)] config: String, #[clap(long)] name: String, } impl Default for Config { fn default() -> Self { Self { config: "DEFAULT_CONFIG".to_string(), name: "DEFAULT_NAME".to_string(), } } } let config: Config = Builder::new() .new_layer(Source::String( json!({"config": "layer_config", "name": "layer_name"}).to_string(), Format::Json, )) .solidify()?; assert_eq!(config.config, "layer_config"); assert_eq!(config.name, "layer_name"); let config: Config = Builder::new() .new_layer(Source::String( json!({"name": "layer_name"}).to_string(), Format::Json, )) .solidify()?; assert_eq!(config.config, "DEFAULT_CONFIG"); assert_eq!(config.name, "layer_name"); Ok(()) } #[test] fn test_clap() { let mut builder = Builder::::new(); builder.new_layer(Source::String("{}".to_string(), Format::Json)); builder.new_layer(Source::String(INTERMED_JSON.to_string(), Format::Json)); builder.new_layer(Source::String(TOP_JSON.to_string(), Format::Json)); let args = vec!["test_clap", "--name", "NAME_ARG"] .iter() .map(|v| v.to_string()) .collect(); builder.new_layer(Source::ArgumentsFrom(args)); let solid = builder.solidify().unwrap(); assert_eq!(solid.config, "string"); assert_eq!(solid.name, "NAME_ARG"); assert_eq!(solid.data_path, PathBuf::from("/tmp/path")); assert_eq!(solid.subconfig.flibble, 10); assert_eq!( solid.subconfig.duration, Duration::from_secs(50) + Duration::from_nanos(99) ); }