Skip to content

Commit f3a70f4

Browse files
sphwmatthiasbeyer
authored andcommitted
feat: add serde_dhall support
1 parent 052c950 commit f3a70f4

File tree

5 files changed

+172
-1
lines changed

5 files changed

+172
-1
lines changed

Cargo.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@ edition = "2018"
1515
maintenance = { status = "actively-developed" }
1616

1717
[features]
18-
default = ["toml", "json", "yaml", "hjson", "ini", "ron", "json5"]
18+
default = ["toml", "json", "yaml", "hjson", "ini", "ron", "json5", "dhall"]
1919
json = ["serde_json"]
2020
yaml = ["yaml-rust"]
2121
hjson = ["serde-hjson"]
2222
ini = ["rust-ini"]
2323
json5 = ["json5_rs"]
24+
dhall = ["serde_dhall"]
2425
preserve_order = ["indexmap", "toml/preserve_order", "serde_json/preserve_order", "ron/indexmap"]
2526

2627
[dependencies]
@@ -37,6 +38,7 @@ rust-ini = { version = "0.17", optional = true }
3738
ron = { version = "0.6", optional = true }
3839
json5_rs = { version = "0.3", optional = true, package = "json5" }
3940
indexmap = { version = "1.7.0", features = ["serde-1"], optional = true}
41+
serde_dhall = { version = "0.10", optional = true }
4042

4143
[dev-dependencies]
4244
serde_derive = "1.0.8"

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

+13
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ mod ron;
2929
#[cfg(feature = "json5")]
3030
mod json5;
3131

32+
#[cfg(feature = "dhall")]
33+
mod dhall;
34+
3235
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
3336
pub enum FileFormat {
3437
/// TOML (parsed with toml)
@@ -58,6 +61,10 @@ pub enum FileFormat {
5861
/// JSON5 (parsed with json5)
5962
#[cfg(feature = "json5")]
6063
Json5,
64+
65+
/// Dhall (parsed with serde_dhall)
66+
#[cfg(feature = "dhall")]
67+
Dhall,
6168
}
6269

6370
lazy_static! {
@@ -87,6 +94,9 @@ lazy_static! {
8794
#[cfg(feature = "json5")]
8895
formats.insert(FileFormat::Json5, vec!["json5"]);
8996

97+
#[cfg(feature = "dhall")]
98+
formats.insert(FileFormat::Dhall, vec!["dhall"]);
99+
90100
formats
91101
};
92102
}
@@ -130,6 +140,9 @@ impl FileFormat {
130140

131141
#[cfg(feature = "json5")]
132142
FileFormat::Json5 => json5::parse(uri, text),
143+
144+
#[cfg(feature = "dhall")]
145+
FileFormat::Dhall => dhall::parse(uri, text),
133146
}
134147
}
135148
}

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)