Skip to content

Commit

Permalink
Merge pull request #354 from YounessBird/fix-uppercase-lowercase-isses
Browse files Browse the repository at this point in the history
Fix uppercase lowercase isses
  • Loading branch information
matthiasbeyer committed Sep 17, 2022
2 parents bfe819a + b2dfb93 commit 0d58da2
Show file tree
Hide file tree
Showing 23 changed files with 682 additions and 18 deletions.
8 changes: 5 additions & 3 deletions README.md
Expand Up @@ -23,8 +23,10 @@
[RON]: https://github.com/ron-rs/ron
[JSON5]: https://github.com/callum-oakley/json5-rs

Please note that this library can not be used to write changed configuration
values back to the configuration file(s)!
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

## Usage

Expand Down Expand Up @@ -63,4 +65,4 @@ We currently support Rust 1.56.1 and newer.

config-rs is primarily distributed under the terms of both the MIT license and the Apache License (Version 2.0).

See LICENSE-APACHE and LICENSE-MIT for details.
See LICENSE-APACHE and LICENSE-MIT for details.
10 changes: 7 additions & 3 deletions src/config.rs
Expand Up @@ -110,7 +110,8 @@ impl Config {
where
T: Into<Value>,
{
self.defaults.insert(key.parse()?, value.into());
self.defaults
.insert(key.to_lowercase().as_str().parse()?, value.into());

#[allow(deprecated)]
self.refresh()
Expand All @@ -129,15 +130,16 @@ impl Config {
where
T: Into<Value>,
{
self.overrides.insert(key.parse()?, value.into());
self.overrides
.insert(key.to_lowercase().as_str().parse()?, value.into());

#[allow(deprecated)]
self.refresh()
}

#[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.parse()?;
let expr: path::Expression = key.to_lowercase().as_str().parse()?;

// Traverse the cache using the path to (possibly) retrieve a value
if let Some(ref mut val) = expr.get_mut(&mut self.cache) {
Expand All @@ -149,6 +151,8 @@ impl Config {
}

pub fn get<'de, T: Deserialize<'de>>(&self, key: &str) -> Result<T> {
let k = key.to_lowercase();
let key = k.as_str();
// Parse the key into a path expression
let expr: path::Expression = key.parse()?;

Expand Down
2 changes: 1 addition & 1 deletion src/de.rs
Expand Up @@ -272,7 +272,7 @@ impl EnumAccess {
fn variant_deserializer(&self, name: &str) -> Result<StrDeserializer> {
self.variants
.iter()
.find(|&&s| s == name)
.find(|&&s| s.to_lowercase() == name.to_lowercase()) // changing to lowercase will enable deserialization of lowercase values to enums
.map(|&s| StrDeserializer(s))
.ok_or_else(|| self.no_constructor_error(name))
}
Expand Down
16 changes: 8 additions & 8 deletions src/path/mod.rs
Expand Up @@ -120,7 +120,7 @@ impl Expression {
match *self {
Self::Identifier(ref id) => match root.kind {
ValueKind::Table(ref mut map) => Some(
map.entry(id.clone())
map.entry(id.to_lowercase())
.or_insert_with(|| Value::new(None, ValueKind::Nil)),
),

Expand All @@ -131,15 +131,15 @@ impl Expression {
Some(value) => {
if let ValueKind::Table(ref mut map) = value.kind {
Some(
map.entry(key.clone())
map.entry(key.to_lowercase())
.or_insert_with(|| Value::new(None, ValueKind::Nil)),
)
} else {
*value = Map::<String, Value>::new().into();

if let ValueKind::Table(ref mut map) = value.kind {
Some(
map.entry(key.clone())
map.entry(key.to_lowercase())
.or_insert_with(|| Value::new(None, ValueKind::Nil)),
)
} else {
Expand Down Expand Up @@ -194,25 +194,25 @@ 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.clone())
map.entry(id.to_lowercase())
.or_insert_with(|| Map::<String, Value>::new().into())
} else {
unreachable!();
};

// Continue the deep merge
for (key, val) in incoming_map {
Self::Identifier(key.clone()).set(target, val.clone());
Self::Identifier(key.to_lowercase()).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) {
if let Some(existing) = map.get_mut(&id.to_lowercase()) {
*existing = value;
} else {
map.insert(id.clone(), value);
map.insert(id.to_lowercase(), value);
}
}
}
Expand All @@ -225,7 +225,7 @@ impl Expression {
// Didn't find a table. Oh well. Make a table and do this anyway
*parent = Map::<String, Value>::new().into();
}
Self::Identifier(key.clone()).set(parent, value);
Self::Identifier(key.to_lowercase()).set(parent, value);
}
}

Expand Down
2 changes: 2 additions & 0 deletions tests/Settings-enum-test.ini
@@ -0,0 +1,2 @@
# Test for Enum deserialization. See file_tomls.rs override tests
bar = "bar is a lowercase param"
3 changes: 3 additions & 0 deletions tests/Settings-enum-test.json
@@ -0,0 +1,3 @@
{
"bar": "bar is a lowercase param"
}
3 changes: 3 additions & 0 deletions tests/Settings-enum-test.json5
@@ -0,0 +1,3 @@
{
bar: "bar is a lowercase param",
}
3 changes: 3 additions & 0 deletions tests/Settings-enum-test.ron
@@ -0,0 +1,3 @@
(
bar: "bar is a lowercase param"
)
2 changes: 2 additions & 0 deletions tests/Settings-enum-test.toml
@@ -0,0 +1,2 @@
# Test for Enum deserialization. See file_tomls.rs override tests
bar = "bar is a lowercase param"
2 changes: 2 additions & 0 deletions tests/Settings-enum-test.yaml
@@ -0,0 +1,2 @@
# Test for Enum deserialization. See file_yaml.rs override tests
bar: bar is a lowercase param
3 changes: 3 additions & 0 deletions tests/Settings-upper-struct.toml
@@ -0,0 +1,3 @@
# For override tests
FOO = "FOO should be overridden"
bar = "I am bar"
2 changes: 2 additions & 0 deletions tests/Settings.ini
@@ -1,5 +1,7 @@
debug = true
production = false
FOO = FOO should be overridden
bar = I am bar
[place]
name = Torre di Pisa
longitude = 43.7224985
Expand Down
4 changes: 3 additions & 1 deletion tests/Settings.json
Expand Up @@ -15,5 +15,7 @@
"username": "jsmith",
"email": "jsmith@localhost"
}
}
},
"FOO": "FOO should be overridden",
"bar": "I am bar"
}
4 changes: 3 additions & 1 deletion tests/Settings.json5
Expand Up @@ -16,5 +16,7 @@
"username": "jsmith",
"email": "jsmith@localhost",
}
}
},
FOO: "FOO should be overridden",
bar: "I am bar",
}
4 changes: 3 additions & 1 deletion tests/Settings.ron
Expand Up @@ -16,5 +16,7 @@
"username": "jsmith",
"email": "jsmith@localhost"
}
)
),
FOO: "FOO should be overridden",
bar: "I am bar"
)
4 changes: 4 additions & 0 deletions tests/Settings.toml
Expand Up @@ -8,6 +8,10 @@ code = 53
# errors
boolean_s_parse = "fals"

# For override tests
FOO="FOO should be overridden"
bar="I am bar"

arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
quarks = ["up", "down", "strange", "charm", "bottom", "top"]

Expand Down
3 changes: 3 additions & 0 deletions tests/Settings.yaml
Expand Up @@ -12,3 +12,6 @@ place:
name: John Smith
username: jsmith
email: jsmith@localhost
# For override tests
FOO: FOO should be overridden
bar: I am bar
103 changes: 103 additions & 0 deletions tests/file_ini.rs
Expand Up @@ -66,3 +66,106 @@ fn test_error_parse() {
)
);
}

#[derive(Debug, Deserialize, PartialEq)]
enum EnumSettings {
Bar(String),
}

#[derive(Debug, Deserialize, PartialEq)]
struct StructSettings {
foo: String,
bar: String,
}
#[derive(Debug, Deserialize, PartialEq)]
#[allow(non_snake_case)]
struct CapSettings {
FOO: String,
}

#[test]
fn test_override_uppercase_value_for_struct() {
std::env::set_var("APP_FOO", "I HAVE BEEN OVERRIDDEN_WITH_UPPER_CASE");

let cfg = Config::builder()
.add_source(File::new("tests/Settings", FileFormat::Ini))
.add_source(config::Environment::with_prefix("APP").separator("_"))
.build()
.unwrap();
let cap_settings = cfg.clone().try_deserialize::<CapSettings>();
let lower_settings = cfg.try_deserialize::<StructSettings>().unwrap();

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!(
lower_settings.foo,
"I HAVE BEEN OVERRIDDEN_WITH_UPPER_CASE".to_string()
);
}
Err(e) => {
if e.to_string().contains("missing field `FOO`") {
assert_eq!(
lower_settings.foo,
"I HAVE BEEN OVERRIDDEN_WITH_UPPER_CASE".to_string()
);
} else {
panic!("{}", e);
}
}
}
}

#[test]
fn test_override_lowercase_value_for_struct() {
std::env::set_var("config_foo", "I have been overridden_with_lower_case");

let cfg = Config::builder()
.add_source(File::new("tests/Settings", FileFormat::Ini))
.add_source(config::Environment::with_prefix("config").separator("_"))
.build()
.unwrap();

let values: StructSettings = cfg.try_deserialize().unwrap();
assert_eq!(
values.foo,
"I have been overridden_with_lower_case".to_string()
);
assert_ne!(values.foo, "I am bar".to_string());
}

#[test]
fn test_override_uppercase_value_for_enums() {
std::env::set_var("APPS_BAR", "I HAVE BEEN OVERRIDDEN_WITH_UPPER_CASE");

let cfg = Config::builder()
.add_source(File::new("tests/Settings-enum-test", FileFormat::Ini))
.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_string())
);
}

#[test]
fn test_override_lowercase_value_for_enums() {
std::env::set_var("test_bar", "I have been overridden_with_lower_case");

let cfg = Config::builder()
.add_source(File::new("tests/Settings-enum-test", FileFormat::Ini))
.add_source(config::Environment::with_prefix("test").separator("_"))
.build()
.unwrap();

let param: EnumSettings = cfg.try_deserialize().unwrap();

assert_eq!(
param,
EnumSettings::Bar("I have been overridden_with_lower_case".to_string())
);
}

0 comments on commit 0d58da2

Please sign in to comment.