Skip to content

Commit fe1b043

Browse files
sphwpolarathene
authored andcommitted
feat: Add Dhall support
Signed-off-by: Brennan Kinney <[email protected]>
1 parent d7c1656 commit fe1b043

File tree

5 files changed

+173
-1
lines changed

5 files changed

+173
-1
lines changed

Cargo.toml

+3-1
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", "dhall", "convert-case", "async"]
1919
json = ["serde_json"]
2020
yaml = ["yaml-rust"]
2121
ini = ["rust-ini"]
2222
json5 = ["json5_rs", "serde/derive"]
23+
dhall = ["serde_dhall"]
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+
serde_dhall = { version = "0.10", optional = true }
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/dhall.rs

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use std::collections::HashMap;
2+
use std::error::Error;
3+
4+
use crate::{
5+
error::Unexpected,
6+
value::{Value, ValueKind},
7+
ConfigError,
8+
};
9+
10+
pub fn parse(
11+
uri: Option<&String>,
12+
text: &str,
13+
) -> Result<HashMap<String, Value>, Box<dyn Error + Send + Sync>> {
14+
let value = from_dhall_value(uri, serde_dhall::from_str(text).parse()?);
15+
match value.kind {
16+
ValueKind::Table(map) => Ok(map),
17+
ValueKind::Nil => Err(Unexpected::Unit),
18+
ValueKind::Boolean(value) => Err(Unexpected::Bool(value)),
19+
ValueKind::Integer(value) => Err(Unexpected::Integer(value)),
20+
ValueKind::Float(value) => Err(Unexpected::Float(value)),
21+
ValueKind::String(value) => Err(Unexpected::Str(value)),
22+
ValueKind::Array(value) => Err(Unexpected::Seq),
23+
}
24+
.map_err(|err| ConfigError::invalid_root(uri, err))
25+
.map_err(|err| Box::new(err) as Box<dyn Error + Send + Sync>)
26+
}
27+
28+
fn from_dhall_value(uri: Option<&String>, value: serde_dhall::SimpleValue) -> Value {
29+
match value {
30+
serde_dhall::SimpleValue::Num(num) => match num {
31+
serde_dhall::NumKind::Bool(b) => Value::new(uri, ValueKind::Boolean(b)),
32+
serde_dhall::NumKind::Natural(n) => Value::new(uri, ValueKind::Integer(n as i64)),
33+
serde_dhall::NumKind::Integer(i) => Value::new(uri, ValueKind::Integer(i)),
34+
serde_dhall::NumKind::Double(d) => Value::new(uri, ValueKind::Float(f64::from(d))),
35+
},
36+
serde_dhall::SimpleValue::Text(string) => Value::new(uri, ValueKind::String(string)),
37+
serde_dhall::SimpleValue::List(list) => Value::new(
38+
uri,
39+
ValueKind::Array(list.into_iter().map(|v| from_dhall_value(uri, v)).collect()),
40+
),
41+
serde_dhall::SimpleValue::Record(rec) => Value::new(
42+
uri,
43+
ValueKind::Table(
44+
rec.into_iter()
45+
.map(|(k, v)| (k, from_dhall_value(uri, v)))
46+
.collect(),
47+
),
48+
),
49+
serde_dhall::SimpleValue::Optional(Some(value))
50+
| serde_dhall::SimpleValue::Union(_, Some(value)) => from_dhall_value(uri, *value),
51+
serde_dhall::SimpleValue::Optional(None) | serde_dhall::SimpleValue::Union(_, None) => {
52+
Value::new(uri, ValueKind::Nil)
53+
}
54+
}
55+
}

src/file/format/mod.rs

+14
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 = "dhall")]
31+
mod dhall;
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+
/// Dhall (parsed with serde_dhall)
63+
#[cfg(feature = "dhall")]
64+
Dhall,
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 = "dhall")]
92+
formats.insert(FileFormat::Dhall, vec!["dhall"]);
93+
8494
formats
8595
};
8696
}
@@ -117,13 +127,17 @@ impl FileFormat {
117127
#[cfg(feature = "json5")]
118128
FileFormat::Json5 => json5::parse(uri, text),
119129

130+
#[cfg(feature = "dhall")]
131+
FileFormat::Dhall => dhall::parse(uri, text),
132+
120133
#[cfg(all(
121134
not(feature = "toml"),
122135
not(feature = "json"),
123136
not(feature = "yaml"),
124137
not(feature = "ini"),
125138
not(feature = "ron"),
126139
not(feature = "json5"),
140+
not(feature = "dhall"),
127141
))]
128142
_ => unreachable!("No features are enabled, this library won't work without features"),
129143
}

tests/Settings.dhall

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
debug = True
3+
, debug_json = True
4+
, production = False
5+
, arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
6+
, place = {
7+
name = "Torre di Pisa"
8+
, longitude = 43.7224985
9+
, latitude = 10.3970522
10+
, favorite = False
11+
, reviews = 3866
12+
, rating = 4.5
13+
, creator.name = "John Smith"
14+
}
15+
}

tests/file_dhall.rs

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#![cfg(feature = "dhall")]
2+
3+
extern crate config;
4+
extern crate float_cmp;
5+
extern crate serde;
6+
7+
#[macro_use]
8+
extern crate serde_derive;
9+
10+
use std::collections::HashMap;
11+
12+
use config::*;
13+
use float_cmp::ApproxEqUlps;
14+
15+
#[derive(Debug, Deserialize)]
16+
struct Place {
17+
name: String,
18+
longitude: f64,
19+
latitude: f64,
20+
favorite: bool,
21+
telephone: Option<String>,
22+
reviews: u64,
23+
creator: HashMap<String, Value>,
24+
rating: Option<f32>,
25+
}
26+
27+
#[derive(Debug, Deserialize)]
28+
struct Settings {
29+
debug: f64,
30+
production: Option<String>,
31+
place: Place,
32+
#[serde(rename = "arr")]
33+
elements: Vec<String>,
34+
}
35+
36+
fn make() -> Config {
37+
Config::builder()
38+
.add_source(File::new("tests/Settings", FileFormat::Dhall))
39+
.build()
40+
.unwrap()
41+
}
42+
43+
#[test]
44+
fn test_file() {
45+
let c = make();
46+
47+
// Deserialize the entire file as single struct
48+
let s: Settings = c.try_into().unwrap();
49+
50+
assert!(s.debug.approx_eq_ulps(&1.0, 2));
51+
assert_eq!(s.production, Some("false".to_string()));
52+
assert_eq!(s.place.name, "Torre di Pisa");
53+
assert!(s.place.longitude.approx_eq_ulps(&43.7224985, 2));
54+
assert!(s.place.latitude.approx_eq_ulps(&10.3970522, 2));
55+
assert_eq!(s.place.favorite, false);
56+
assert_eq!(s.place.reviews, 3866);
57+
assert_eq!(s.place.rating, Some(4.5));
58+
assert_eq!(s.place.telephone, None);
59+
assert_eq!(s.elements.len(), 10);
60+
assert_eq!(s.elements[3], "4".to_string());
61+
assert_eq!(
62+
s.place.creator["name"].clone().into_string().unwrap(),
63+
"John Smith".to_string()
64+
);
65+
}
66+
67+
#[test]
68+
fn test_dhall_vec() {
69+
let c = Config::builder()
70+
.add_source(File::from_str(
71+
r#"
72+
{
73+
WASTE = ["example_dir1", "example_dir2"]
74+
}
75+
"#,
76+
FileFormat::Dhall,
77+
))
78+
.build()
79+
.unwrap();
80+
81+
let v = c.get_array("WASTE").unwrap();
82+
let mut vi = v.into_iter();
83+
assert_eq!(vi.next().unwrap().into_string().unwrap(), "example_dir1");
84+
assert_eq!(vi.next().unwrap().into_string().unwrap(), "example_dir2");
85+
assert!(vi.next().is_none());
86+
}

0 commit comments

Comments
 (0)