diff --git a/README.md b/README.md index f84dc143..02660d83 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,8 @@ [RON]: https://github.com/ron-rs/ron [JSON5]: https://github.com/callum-oakley/json5-rs -Please note this library - - - can not be used to write changed configuration values back to the configuration file(s)! - - Is case insensitive and all the keys are converted to lowercase internally +Please note that this library can not be used to write changed configuration +values back to the configuration file(s)! ## Usage diff --git a/src/config.rs b/src/config.rs index ac72e4dd..e9818d7a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -110,8 +110,7 @@ impl Config { where T: Into, { - self.defaults - .insert(key.to_lowercase().as_str().parse()?, value.into()); + self.defaults.insert(key.parse()?, value.into()); #[allow(deprecated)] self.refresh() @@ -130,8 +129,7 @@ impl Config { where T: Into, { - self.overrides - .insert(key.to_lowercase().as_str().parse()?, value.into()); + self.overrides.insert(key.parse()?, value.into()); #[allow(deprecated)] self.refresh() @@ -139,7 +137,7 @@ impl Config { #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")] pub fn set_once(&mut self, key: &str, value: Value) -> Result<()> { - let expr: path::Expression = key.to_lowercase().as_str().parse()?; + let expr: path::Expression = key.parse()?; // Traverse the cache using the path to (possibly) retrieve a value if let Some(ref mut val) = expr.get_mut(&mut self.cache) { @@ -151,8 +149,6 @@ impl Config { } fn get_value(&self, key: &str) -> Result { - let k = key.to_lowercase(); - let key = k.as_str(); // Parse the key into a path expression let expr: path::Expression = key.parse()?; diff --git a/src/de.rs b/src/de.rs index 26a4259e..9700d5ce 100644 --- a/src/de.rs +++ b/src/de.rs @@ -272,7 +272,7 @@ impl EnumAccess { fn variant_deserializer(&self, name: &str) -> Result> { self.variants .iter() - .find(|&&s| s.to_lowercase() == name.to_lowercase()) // changing to lowercase will enable deserialization of lowercase values to enums + .find(|&&s| s == name) .map(|&s| StrDeserializer(s)) .ok_or_else(|| self.no_constructor_error(name)) } diff --git a/src/env.rs b/src/env.rs index 890f0997..2dad9235 100644 --- a/src/env.rs +++ b/src/env.rs @@ -155,10 +155,10 @@ impl Environment { /// To switch the default type back to type Strings you need to provide the keys which should be [`Vec`] using this function. pub fn with_list_parse_key(mut self, key: &str) -> Self { if self.list_parse_keys.is_none() { - self.list_parse_keys = Some(vec![key.to_lowercase()]); + self.list_parse_keys = Some(vec![key.into()]); } else { self.list_parse_keys = self.list_parse_keys.map(|mut keys| { - keys.push(key.to_lowercase()); + keys.push(key.into()); keys }); } @@ -289,9 +289,6 @@ impl Source for Environment { ValueKind::Float(parsed) } else if let Some(separator) = &self.list_separator { if let Some(keys) = &self.list_parse_keys { - #[cfg(feature = "convert-case")] - let key = key.to_lowercase(); - if keys.contains(&key) { let v: Vec = value .split(separator) diff --git a/src/path/mod.rs b/src/path/mod.rs index 730d0edf..3908d933 100644 --- a/src/path/mod.rs +++ b/src/path/mod.rs @@ -120,7 +120,7 @@ impl Expression { match *self { Self::Identifier(ref id) => match root.kind { ValueKind::Table(ref mut map) => Some( - map.entry(id.to_lowercase()) + map.entry(id.clone()) .or_insert_with(|| Value::new(None, ValueKind::Nil)), ), @@ -131,7 +131,7 @@ impl Expression { Some(value) => { if let ValueKind::Table(ref mut map) = value.kind { Some( - map.entry(key.to_lowercase()) + map.entry(key.clone()) .or_insert_with(|| Value::new(None, ValueKind::Nil)), ) } else { @@ -139,7 +139,7 @@ impl Expression { if let ValueKind::Table(ref mut map) = value.kind { Some( - map.entry(key.to_lowercase()) + map.entry(key.clone()) .or_insert_with(|| Value::new(None, ValueKind::Nil)), ) } else { @@ -193,7 +193,7 @@ impl Expression { ValueKind::Table(ref incoming_map) => { // Pull out another table let target = if let ValueKind::Table(ref mut map) = root.kind { - map.entry(id.to_lowercase()) + map.entry(id.clone()) .or_insert_with(|| Map::::new().into()) } else { unreachable!(); @@ -201,17 +201,17 @@ impl Expression { // Continue the deep merge for (key, val) in incoming_map { - Self::Identifier(key.to_lowercase()).set(target, val.clone()); + Self::Identifier(key.clone()).set(target, val.clone()); } } _ => { if let ValueKind::Table(ref mut map) = root.kind { // Just do a simple set - if let Some(existing) = map.get_mut(&id.to_lowercase()) { + if let Some(existing) = map.get_mut(id) { *existing = value; } else { - map.insert(id.to_lowercase(), value); + map.insert(id.clone(), value); } } } @@ -224,7 +224,7 @@ impl Expression { // Didn't find a table. Oh well. Make a table and do this anyway *parent = Map::::new().into(); } - Self::Identifier(key.to_lowercase()).set(parent, value); + Self::Identifier(key.clone()).set(parent, value); } } diff --git a/tests/testsuite/case.rs b/tests/testsuite/case.rs new file mode 100644 index 00000000..eb1a756e --- /dev/null +++ b/tests/testsuite/case.rs @@ -0,0 +1,78 @@ +use serde_derive::Deserialize; + +use config::{Config, File, FileFormat}; + +#[test] +#[cfg(feature = "json")] +fn respect_field_case() { + #[derive(Deserialize, Debug)] + #[allow(non_snake_case)] + #[allow(dead_code)] + struct Kafka { + broker: String, + topic: String, + pollSleep: u64, //<--- + } + + let c = Config::builder() + .add_source(File::from_str( + r#" +{ + "broker": "localhost:29092", + "topic": "rust", + "pollSleep": 1000 +} +"#, + FileFormat::Json, + )) + .build() + .unwrap(); + + c.try_deserialize::().unwrap(); +} + +#[test] +#[cfg(feature = "json")] +fn respect_renamed_field() { + #[derive(Deserialize, Debug)] + #[allow(dead_code)] + struct MyConfig { + #[serde(rename = "FooBar")] + foo_bar: String, + } + + let c = Config::builder() + .add_source(File::from_str( + r#" +{ + "FooBar": "Hello, world!" +} +"#, + FileFormat::Json, + )) + .build() + .unwrap(); + + c.try_deserialize::().unwrap(); +} + +#[test] +#[cfg(feature = "json")] +fn respect_path_case() { + let c = Config::builder() + .add_source(File::from_str( + r#" +{ + "Student": [ + { "Name": "1" }, + { "Name": "2" } + ] +} +"#, + FileFormat::Json, + )) + .build() + .unwrap(); + + c.get_string("Student[0].Name").unwrap(); +} diff --git a/tests/testsuite/env.rs b/tests/testsuite/env.rs index 4befa5bb..00a59e8e 100644 --- a/tests/testsuite/env.rs +++ b/tests/testsuite/env.rs @@ -1,5 +1,7 @@ -use config::{Config, Environment, Source}; use serde_derive::Deserialize; +use snapbox::{assert_data_eq, str}; + +use config::{Config, Environment, Source}; /// Reminder that tests using env variables need to use different env variable names, since /// tests can be run in parallel @@ -474,11 +476,13 @@ fn test_parse_string_and_list_ignore_list_parse_key_case() { // using a struct in an enum here to make serde use `deserialize_any` #[derive(Deserialize, Debug)] #[serde(tag = "tag")] + #[allow(dead_code)] enum TestStringEnum { String(TestString), } #[derive(Deserialize, Debug)] + #[allow(dead_code)] struct TestString { string_val: String, string_list: Vec, @@ -503,20 +507,13 @@ fn test_parse_string_and_list_ignore_list_parse_key_case() { .build() .unwrap(); - let config: TestStringEnum = config.try_deserialize().unwrap(); + let res = config.try_deserialize::(); - match config { - TestStringEnum::String(TestString { - string_val, - string_list, - }) => { - assert_eq!(String::from("test,string"), string_val); - assert_eq!( - vec![String::from("test"), String::from("string")], - string_list - ); - } - } + assert!(res.is_err()); + assert_data_eq!( + res.unwrap_err().to_string(), + str![[r#"invalid type: string "test,string", expected a sequence"#]] + ); }, ); } diff --git a/tests/testsuite/file_ini.rs b/tests/testsuite/file_ini.rs index c4c2eba4..3dc696a0 100644 --- a/tests/testsuite/file_ini.rs +++ b/tests/testsuite/file_ini.rs @@ -121,7 +121,7 @@ rating = 4.5 match cap_settings { Ok(v) => { // this assertion will ensure that the map has only lowercase keys - assert_ne!(v.FOO, "FOO should be overridden"); + assert_eq!(v.FOO, "FOO should be overridden"); assert_eq!( lower_settings.foo, "I HAVE BEEN OVERRIDDEN_WITH_UPPER_CASE".to_owned() @@ -198,11 +198,12 @@ bar = "bar is a lowercase param" .add_source(config::Environment::with_prefix("APPS").separator("_")) .build() .unwrap(); - let val: EnumSettings = cfg.try_deserialize().unwrap(); - assert_eq!( - val, - EnumSettings::Bar("I HAVE BEEN OVERRIDDEN_WITH_UPPER_CASE".to_owned()) + let param = cfg.try_deserialize::(); + assert!(param.is_err()); + assert_data_eq!( + param.unwrap_err().to_string(), + str!["enum EnumSettings does not have variant constructor bar"] ); } @@ -226,11 +227,11 @@ bar = "bar is a lowercase param" .build() .unwrap(); - let param: EnumSettings = cfg.try_deserialize().unwrap(); - - assert_eq!( - param, - EnumSettings::Bar("I have been overridden_with_lower_case".to_owned()) + let param = cfg.try_deserialize::(); + assert!(param.is_err()); + assert_data_eq!( + param.unwrap_err().to_string(), + str!["enum EnumSettings does not have variant constructor bar"] ); } diff --git a/tests/testsuite/file_json.rs b/tests/testsuite/file_json.rs index 029d687d..929abb8b 100644 --- a/tests/testsuite/file_json.rs +++ b/tests/testsuite/file_json.rs @@ -115,27 +115,6 @@ fn test_error_parse() { ); } -#[test] -fn test_json_vec() { - let c = Config::builder() - .add_source(File::from_str( - r#" - { - "WASTE": ["example_dir1", "example_dir2"] - } -"#, - FileFormat::Json, - )) - .build() - .unwrap(); - - let v = c.get_array("WASTE").unwrap(); - let mut vi = v.into_iter(); - assert_eq!(vi.next().unwrap().into_string().unwrap(), "example_dir1"); - assert_eq!(vi.next().unwrap().into_string().unwrap(), "example_dir2"); - assert!(vi.next().is_none()); -} - #[test] fn test_override_uppercase_value_for_struct() { #[derive(Debug, Deserialize, PartialEq)] @@ -189,7 +168,7 @@ fn test_override_uppercase_value_for_struct() { match cap_settings { Ok(v) => { // this assertion will ensure that the map has only lowercase keys - assert_ne!(v.FOO, "FOO should be overridden"); + assert_eq!(v.FOO, "FOO should be overridden"); assert_eq!( lower_settings.foo, "I HAVE BEEN OVERRIDDEN_WITH_UPPER_CASE".to_owned() @@ -279,11 +258,12 @@ fn test_override_uppercase_value_for_enums() { .add_source(config::Environment::with_prefix("APPS").separator("_")) .build() .unwrap(); - let val: EnumSettings = cfg.try_deserialize().unwrap(); - assert_eq!( - val, - EnumSettings::Bar("I HAVE BEEN OVERRIDDEN_WITH_UPPER_CASE".to_owned()) + let param = cfg.try_deserialize::(); + assert!(param.is_err()); + assert_data_eq!( + param.unwrap_err().to_string(), + str!["enum EnumSettings does not have variant constructor bar"] ); } @@ -309,11 +289,11 @@ fn test_override_lowercase_value_for_enums() { .build() .unwrap(); - let param: EnumSettings = cfg.try_deserialize().unwrap(); - - assert_eq!( - param, - EnumSettings::Bar("I have been overridden_with_lower_case".to_owned()) + let param = cfg.try_deserialize::(); + assert!(param.is_err()); + assert_data_eq!( + param.unwrap_err().to_string(), + str!["enum EnumSettings does not have variant constructor bar"] ); } diff --git a/tests/testsuite/file_json5.rs b/tests/testsuite/file_json5.rs index 0e64de29..16cb5b54 100644 --- a/tests/testsuite/file_json5.rs +++ b/tests/testsuite/file_json5.rs @@ -177,7 +177,7 @@ fn test_override_uppercase_value_for_struct() { match cap_settings { Ok(v) => { // this assertion will ensure that the map has only lowercase keys - assert_ne!(v.FOO, "FOO should be overridden"); + assert_eq!(v.FOO, "FOO should be overridden"); assert_eq!( lower_settings.foo, "I HAVE BEEN OVERRIDDEN_WITH_UPPER_CASE".to_owned() @@ -267,11 +267,12 @@ fn test_override_uppercase_value_for_enums() { .add_source(config::Environment::with_prefix("APPS").separator("_")) .build() .unwrap(); - let val: EnumSettings = cfg.try_deserialize().unwrap(); - assert_eq!( - val, - EnumSettings::Bar("I HAVE BEEN OVERRIDDEN_WITH_UPPER_CASE".to_owned()) + let param = cfg.try_deserialize::(); + assert!(param.is_err()); + assert_data_eq!( + param.unwrap_err().to_string(), + str!["enum EnumSettings does not have variant constructor bar"] ); } @@ -297,11 +298,11 @@ fn test_override_lowercase_value_for_enums() { .build() .unwrap(); - let param: EnumSettings = cfg.try_deserialize().unwrap(); - - assert_eq!( - param, - EnumSettings::Bar("I have been overridden_with_lower_case".to_owned()) + let param = cfg.try_deserialize::(); + assert!(param.is_err()); + assert_data_eq!( + param.unwrap_err().to_string(), + str!["enum EnumSettings does not have variant constructor bar"] ); } diff --git a/tests/testsuite/file_ron.rs b/tests/testsuite/file_ron.rs index 154b806b..2c6f3e23 100644 --- a/tests/testsuite/file_ron.rs +++ b/tests/testsuite/file_ron.rs @@ -169,7 +169,7 @@ fn test_override_uppercase_value_for_struct() { match cap_settings { Ok(v) => { // this assertion will ensure that the map has only lowercase keys - assert_ne!(v.FOO, "FOO should be overridden"); + assert_eq!(v.FOO, "FOO should be overridden"); assert_eq!( lower_settings.foo, "I HAVE BEEN OVERRIDDEN_WITH_UPPER_CASE".to_owned() @@ -259,11 +259,12 @@ fn test_override_uppercase_value_for_enums() { .add_source(config::Environment::with_prefix("APPS").separator("_")) .build() .unwrap(); - let val: EnumSettings = cfg.try_deserialize().unwrap(); - assert_eq!( - val, - EnumSettings::Bar("I HAVE BEEN OVERRIDDEN_WITH_UPPER_CASE".to_owned()) + let param = cfg.try_deserialize::(); + assert!(param.is_err()); + assert_data_eq!( + param.unwrap_err().to_string(), + str!["enum EnumSettings does not have variant constructor bar"] ); } @@ -289,11 +290,11 @@ fn test_override_lowercase_value_for_enums() { .build() .unwrap(); - let param: EnumSettings = cfg.try_deserialize().unwrap(); - - assert_eq!( - param, - EnumSettings::Bar("I have been overridden_with_lower_case".to_owned()) + let param = cfg.try_deserialize::(); + assert!(param.is_err()); + assert_data_eq!( + param.unwrap_err().to_string(), + str!["enum EnumSettings does not have variant constructor bar"] ); } diff --git a/tests/testsuite/file_toml.rs b/tests/testsuite/file_toml.rs index 43fa391e..6c3980c5 100644 --- a/tests/testsuite/file_toml.rs +++ b/tests/testsuite/file_toml.rs @@ -260,7 +260,7 @@ down = 1 match cap_settings { Ok(v) => { // this assertion will ensure that the map has only lowercase keys - assert_ne!(v.FOO, "FOO should be overridden"); + assert_eq!(v.FOO, "FOO should be overridden"); assert_eq!( lower_settings.foo, "I HAVE BEEN OVERRIDDEN_WITH_UPPER_CASE".to_owned() @@ -386,11 +386,11 @@ bar = "bar is a lowercase param" .build() .unwrap(); - let values: EnumSettings = cfg.try_deserialize().unwrap(); - - assert_eq!( - values, - EnumSettings::Bar("I HAVE BEEN OVERRIDDEN_WITH_UPPER_CASE".to_owned()) + let param = cfg.try_deserialize::(); + assert!(param.is_err()); + assert_data_eq!( + param.unwrap_err().to_string(), + str!["enum EnumSettings does not have variant constructor bar"] ); } @@ -414,11 +414,11 @@ bar = "bar is a lowercase param" .build() .unwrap(); - let values: EnumSettings = cfg.try_deserialize().unwrap(); - - assert_eq!( - values, - EnumSettings::Bar("I have been overridden_with_lower_case".to_owned()) + let param = cfg.try_deserialize::(); + assert!(param.is_err()); + assert_data_eq!( + param.unwrap_err().to_string(), + str!["enum EnumSettings does not have variant constructor bar"] ); } diff --git a/tests/testsuite/file_yaml.rs b/tests/testsuite/file_yaml.rs index 505245dd..bb9ad323 100644 --- a/tests/testsuite/file_yaml.rs +++ b/tests/testsuite/file_yaml.rs @@ -200,7 +200,7 @@ bar: I am bar match cap_settings { Ok(v) => { // this assertion will ensure that the map has only lowercase keys - assert_ne!(v.FOO, "FOO should be overridden"); + assert_eq!(v.FOO, "FOO should be overridden"); assert_eq!( lower_settings.foo, "I HAVE BEEN OVERRIDDEN_WITH_UPPER_CASE".to_owned() @@ -284,11 +284,12 @@ bar: bar is a lowercase param .add_source(config::Environment::with_prefix("APPS").separator("_")) .build() .unwrap(); - let values: EnumSettings = cfg.try_deserialize().unwrap(); - assert_eq!( - values, - EnumSettings::Bar("I HAVE BEEN OVERRIDDEN_WITH_UPPER_CASE".to_owned()) + let param = cfg.try_deserialize::(); + assert!(param.is_err()); + assert_data_eq!( + param.unwrap_err().to_string(), + str!["enum EnumSettings does not have variant constructor bar"] ); } @@ -312,11 +313,11 @@ bar: bar is a lowercase param .build() .unwrap(); - let values: EnumSettings = cfg.try_deserialize().unwrap(); - - assert_eq!( - values, - EnumSettings::Bar("I have been overridden_with_lower_case".to_owned()) + let param = cfg.try_deserialize::(); + assert!(param.is_err()); + assert_data_eq!( + param.unwrap_err().to_string(), + str!["enum EnumSettings does not have variant constructor bar"] ); } diff --git a/tests/testsuite/log.rs b/tests/testsuite/log.rs index 7ae50845..29bf7eca 100644 --- a/tests/testsuite/log.rs +++ b/tests/testsuite/log.rs @@ -1,3 +1,5 @@ +use snapbox::{assert_data_eq, str}; + use config::*; #[derive(Debug, Deserialize)] @@ -38,14 +40,19 @@ fn test_case_sensitivity_json_from_str() { #[test] #[cfg(feature = "json")] -fn test_load_level_lowercase_succeeding() { +fn test_load_level_lowercase() { let s = r#"{ "log": "error" }"#; let c = Config::builder() .add_source(File::from_str(s, FileFormat::Json)) .build() .unwrap(); + assert_eq!(c.get_string("log").unwrap(), "error"); + let s = c.try_deserialize::(); - assert!(s.is_ok(), "Expected Ok(_) for {:?}", s); - assert_eq!(s.unwrap().log, log::Level::Error); + assert!(s.is_err()); + assert_data_eq!( + s.unwrap_err().to_string(), + str!["enum Level does not have variant constructor error"] + ); } diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs index ed2d239f..1b1bcc20 100644 --- a/tests/testsuite/main.rs +++ b/tests/testsuite/main.rs @@ -2,6 +2,7 @@ extern crate serde_derive; pub mod async_builder; +pub mod case; pub mod defaults; pub mod empty; pub mod env; diff --git a/tests/testsuite/set.rs b/tests/testsuite/set.rs index 9e1b3a8d..4a02b5c0 100644 --- a/tests/testsuite/set.rs +++ b/tests/testsuite/set.rs @@ -115,7 +115,7 @@ fn test_set_capital() { .build() .unwrap(); - assert_eq!(config.get("this").ok(), Some(false)); - assert_eq!(config.get("ThAt").ok(), Some(true)); - assert_eq!(config.get("logLevel").ok(), Some(5)); + assert_eq!(config.get::("this").unwrap(), false); + assert_eq!(config.get::("ThAt").unwrap(), true); + assert_eq!(config.get::("logLevel").unwrap(), 5); }