Skip to content

Commit 13982e2

Browse files
mkvolkovpolarathene
authored andcommitted
feat: Added Gura support
Signed-off-by: Brennan Kinney <[email protected]>
1 parent d7c1656 commit 13982e2

File tree

6 files changed

+295
-1
lines changed

6 files changed

+295
-1
lines changed

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ edition = "2018"
1515
maintenance = { status = "actively-developed" }
1616

1717
[features]
18-
default = ["toml", "json", "yaml", "ini", "ron", "json5", "convert-case", "async"]
18+
default = ["toml", "json", "yaml", "ini", "ron", "json5", "ura", "convert-case", "async"]
1919
json = ["serde_json"]
2020
yaml = ["yaml-rust"]
2121
ini = ["rust-ini"]
2222
json5 = ["json5_rs", "serde/derive"]
23+
ura = ["gura"]
2324
convert-case = ["convert_case"]
2425
preserve_order = ["indexmap", "toml?/preserve_order", "serde_json?/preserve_order", "ron?/indexmap"]
2526
async = ["async-trait"]
@@ -36,6 +37,7 @@ yaml-rust = { version = "0.4", optional = true }
3637
rust-ini = { version = "0.19", optional = true }
3738
ron = { version = "0.8", optional = true }
3839
json5_rs = { version = "0.4", optional = true, package = "json5" }
40+
gura = { version = "0.3.0", optional = true, package = "gura" }
3941
indexmap = { version = "2.0.0", features = ["serde"], optional = true }
4042
convert_case = { version = "0.6", optional = true }
4143
pathdiff = "0.2"

src/file/format/gura.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use std::error::Error;
2+
3+
use gura::GuraType;
4+
5+
use crate::map::Map;
6+
use crate::value::{Value, ValueKind};
7+
8+
pub fn parse(
9+
uri: Option<&String>,
10+
text: &str,
11+
) -> Result<Map<String, Value>, Box<dyn Error + Send + Sync>> {
12+
let val = from_gura_value(uri, &gura::parse(text).unwrap());
13+
14+
match val.kind {
15+
ValueKind::Table(map) => Ok(map),
16+
17+
_ => Ok(Map::new())
18+
}
19+
}
20+
21+
fn from_gura_value(uri: Option<&String>, val: &GuraType) -> Value {
22+
match val {
23+
GuraType::Null => Value::new(uri, ValueKind::Nil),
24+
25+
GuraType::Object(ref table) => {
26+
let mut m = Map::new();
27+
28+
for (key, val) in table {
29+
m.insert(key.clone(), from_gura_value(uri, val));
30+
}
31+
32+
Value::new(uri, ValueKind::Table(m))
33+
}
34+
35+
GuraType::Bool(val) => Value::new(uri, ValueKind::Boolean(*val)),
36+
37+
GuraType::String(ref val) => Value::new(uri, ValueKind::String(val.clone())),
38+
39+
GuraType::Integer(val) => Value::new(uri, ValueKind::I64(*val as i64)),
40+
41+
GuraType::BigInteger(val) => Value::new(uri, ValueKind::I128(*val)),
42+
43+
GuraType::Float(val) => Value::new(uri, ValueKind::Float(*val)),
44+
45+
GuraType::Array(ref arr) => {
46+
let mut l = Vec::new();
47+
48+
for val in arr {
49+
l.push(from_gura_value(uri, val));
50+
}
51+
52+
Value::new(uri, ValueKind::Array(l))
53+
}
54+
55+
_ => Value::new(uri, ValueKind::Nil),
56+
}
57+
}

src/file/format/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ mod ron;
2727
#[cfg(feature = "json5")]
2828
mod json5;
2929

30+
#[cfg(feature = "ura")]
31+
mod gura;
32+
3033
/// File formats provided by the library.
3134
///
3235
/// Although it is possible to define custom formats using [`Format`] trait it is recommended to use FileFormat if possible.
@@ -55,6 +58,10 @@ pub enum FileFormat {
5558
/// JSON5 (parsed with json5)
5659
#[cfg(feature = "json5")]
5760
Json5,
61+
62+
/// GURA (parsed with ura)
63+
#[cfg(feature = "ura")]
64+
Gura,
5865
}
5966

6067
lazy_static! {
@@ -81,6 +88,9 @@ lazy_static! {
8188
#[cfg(feature = "json5")]
8289
formats.insert(FileFormat::Json5, vec!["json5"]);
8390

91+
#[cfg(feature = "ura")]
92+
formats.insert(FileFormat::Gura, vec!["ura"]);
93+
8494
formats
8595
};
8696
}
@@ -117,6 +127,9 @@ impl FileFormat {
117127
#[cfg(feature = "json5")]
118128
FileFormat::Json5 => json5::parse(uri, text),
119129

130+
#[cfg(feature = "ura")]
131+
FileFormat::Gura => gura::parse(uri, text),
132+
120133
#[cfg(all(
121134
not(feature = "toml"),
122135
not(feature = "json"),

tests/Settings-enum-test.ura

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# comment
2+
3+
bar: "bar is a lowercase param"

tests/Settings.ura

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#comment
2+
3+
debug: true
4+
debug_gura: true
5+
production: false
6+
arr: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
7+
place:
8+
name: "Torre di Pisa"
9+
longitude: 43.7224985
10+
latitude: 10.3970522
11+
favorite: false
12+
reviews: 3866
13+
rating: 4.5
14+
creator:
15+
name: "John Smith"
16+
username: "jsmith"
17+
email: "jsmith@localhost"
18+
19+
FOO: "FOO should be overridden"
20+
bar: "I am bar"

tests/file_gura.rs

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
#![cfg(feature = "json")]
2+
3+
use serde_derive::Deserialize;
4+
5+
use config::{Config, File, FileFormat, Map, Value};
6+
use float_cmp::ApproxEqUlps;
7+
8+
#[derive(Debug, Deserialize)]
9+
struct Place {
10+
name: String,
11+
longitude: f64,
12+
latitude: f64,
13+
favorite: bool,
14+
telephone: Option<String>,
15+
reviews: u64,
16+
creator: Map<String, Value>,
17+
rating: Option<f32>,
18+
}
19+
20+
#[derive(Debug, Deserialize)]
21+
struct Settings {
22+
debug: f64,
23+
production: Option<String>,
24+
place: Place,
25+
#[serde(rename = "arr")]
26+
elements: Vec<String>,
27+
}
28+
29+
fn make() -> Config {
30+
Config::builder()
31+
.add_source(File::new("tests/Settings", FileFormat::Gura))
32+
.build()
33+
.unwrap()
34+
}
35+
36+
#[test]
37+
fn test_file() {
38+
let c = make();
39+
40+
// Deserialize the entire file as single struct
41+
let s: Settings = c.try_deserialize().unwrap();
42+
43+
assert!(s.debug.approx_eq_ulps(&1.0, 2));
44+
assert_eq!(s.production, Some("false".to_string()));
45+
assert_eq!(s.place.name, "Torre di Pisa");
46+
assert!(s.place.longitude.approx_eq_ulps(&43.722_498_5, 2));
47+
assert!(s.place.latitude.approx_eq_ulps(&10.397_052_2, 2));
48+
assert!(!s.place.favorite);
49+
assert_eq!(s.place.reviews, 3866);
50+
assert_eq!(s.place.rating, Some(4.5));
51+
assert_eq!(s.place.telephone, None);
52+
assert_eq!(s.elements.len(), 10);
53+
assert_eq!(s.elements[3], "4".to_string());
54+
if cfg!(feature = "preserve_order") {
55+
assert_eq!(
56+
s.place
57+
.creator
58+
.into_iter()
59+
.collect::<Vec<(String, config::Value)>>(),
60+
vec![
61+
("name".to_string(), "John Smith".into()),
62+
("username".into(), "jsmith".into()),
63+
("email".into(), "jsmith@localhost".into()),
64+
]
65+
);
66+
} else {
67+
assert_eq!(
68+
s.place.creator["name"].clone().into_string().unwrap(),
69+
"John Smith".to_string()
70+
);
71+
}
72+
}
73+
74+
#[test]
75+
fn test_gura_vec() {
76+
let c = Config::builder()
77+
.add_source(File::from_str(
78+
r#"
79+
hosts: [
80+
"alpha",
81+
"omega"
82+
]
83+
"#,
84+
FileFormat::Gura,
85+
))
86+
.build()
87+
.unwrap();
88+
89+
let v = c.get_array("hosts").unwrap();
90+
let mut vi = v.into_iter();
91+
assert_eq!(vi.next().unwrap().into_string().unwrap(), "alpha");
92+
assert_eq!(vi.next().unwrap().into_string().unwrap(), "omega");
93+
assert!(vi.next().is_none());
94+
}
95+
96+
#[derive(Debug, Deserialize, PartialEq)]
97+
enum EnumSettings {
98+
Bar(String),
99+
}
100+
101+
#[derive(Debug, Deserialize, PartialEq)]
102+
struct StructSettings {
103+
foo: String,
104+
bar: String,
105+
}
106+
#[derive(Debug, Deserialize, PartialEq)]
107+
#[allow(non_snake_case)]
108+
struct CapSettings {
109+
FOO: String,
110+
}
111+
112+
#[test]
113+
fn test_override_uppercase_value_for_struct() {
114+
std::env::set_var("APP_FOO", "I HAVE BEEN OVERRIDDEN_WITH_UPPER_CASE");
115+
116+
let cfg = Config::builder()
117+
.add_source(File::new("tests/Settings", FileFormat::Gura))
118+
.add_source(config::Environment::with_prefix("APP").separator("_"))
119+
.build()
120+
.unwrap();
121+
122+
let cap_settings = cfg.clone().try_deserialize::<CapSettings>();
123+
let lower_settings = cfg.try_deserialize::<StructSettings>().unwrap();
124+
125+
match cap_settings {
126+
Ok(v) => {
127+
// this assertion will ensure that the map has only lowercase keys
128+
assert_ne!(v.FOO, "FOO should be overridden");
129+
assert_eq!(
130+
lower_settings.foo,
131+
"I HAVE BEEN OVERRIDDEN_WITH_UPPER_CASE".to_string()
132+
);
133+
}
134+
Err(e) => {
135+
if e.to_string().contains("missing field `FOO`") {
136+
println!("triggered error {:?}", e);
137+
assert_eq!(
138+
lower_settings.foo,
139+
"I HAVE BEEN OVERRIDDEN_WITH_UPPER_CASE".to_string()
140+
);
141+
} else {
142+
panic!("{}", e);
143+
}
144+
}
145+
}
146+
}
147+
148+
#[test]
149+
fn test_override_lowercase_value_for_struct() {
150+
std::env::set_var("config_foo", "I have been overridden_with_lower_case");
151+
152+
let cfg = Config::builder()
153+
.add_source(File::new("tests/Settings", FileFormat::Gura))
154+
.add_source(config::Environment::with_prefix("config").separator("_"))
155+
.build()
156+
.unwrap();
157+
158+
let values: StructSettings = cfg.try_deserialize().unwrap();
159+
assert_eq!(
160+
values.foo,
161+
"I have been overridden_with_lower_case".to_string()
162+
);
163+
assert_ne!(values.foo, "I am bar".to_string());
164+
}
165+
166+
#[test]
167+
fn test_override_uppercase_value_for_enums() {
168+
std::env::set_var("APPS_BAR", "I HAVE BEEN OVERRIDDEN_WITH_UPPER_CASE");
169+
170+
let cfg = Config::builder()
171+
.add_source(File::new("tests/Settings-enum-test", FileFormat::Gura))
172+
.add_source(config::Environment::with_prefix("APPS").separator("_"))
173+
.build()
174+
.unwrap();
175+
let val: EnumSettings = cfg.try_deserialize().unwrap();
176+
177+
assert_eq!(
178+
val,
179+
EnumSettings::Bar("I HAVE BEEN OVERRIDDEN_WITH_UPPER_CASE".to_string())
180+
);
181+
}
182+
183+
#[test]
184+
fn test_override_lowercase_value_for_enums() {
185+
std::env::set_var("test_bar", "I have been overridden_with_lower_case");
186+
187+
let cfg = Config::builder()
188+
.add_source(File::new("tests/Settings-enum-test", FileFormat::Json))
189+
.add_source(config::Environment::with_prefix("test").separator("_"))
190+
.build()
191+
.unwrap();
192+
193+
let param: EnumSettings = cfg.try_deserialize().unwrap();
194+
195+
assert_eq!(
196+
param,
197+
EnumSettings::Bar("I have been overridden_with_lower_case".to_string())
198+
);
199+
}

0 commit comments

Comments
 (0)