Skip to content

Commit 92bcc9f

Browse files
committed
feat: add serde_dhall support
1 parent 74a0a80 commit 92bcc9f

File tree

5 files changed

+167
-1
lines changed

5 files changed

+167
-1
lines changed

Cargo.toml

Lines changed: 3 additions & 1 deletion
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

2526
[dependencies]
2627
async-trait = "0.1.50"
@@ -35,6 +36,7 @@ serde-hjson = { version = "0.9", default-features = false, optional = true }
3536
rust-ini = { version = "0.17", optional = true }
3637
ron = { version = "0.6", optional = true }
3738
json5_rs = { version = "0.3", optional = true, package = "json5" }
39+
serde_dhall = { version = "0.10", optional = true }
3840

3941
[dev-dependencies]
4042
serde_derive = "1.0.8"

src/file/format/dhall.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use std::collections::HashMap;
2+
use std::error::Error;
3+
4+
use crate::value::{Value, ValueKind};
5+
6+
pub fn parse(
7+
uri: Option<&String>,
8+
text: &str,
9+
) -> Result<HashMap<String, Value>, Box<dyn Error + Send + Sync>> {
10+
// Parse a Dhall object value from the text
11+
// TODO: Have a proper error fire if the root of a file is ever not a Table
12+
let value = from_dhall_value(uri, &serde_dhall::from_str(text).parse()?);
13+
match value.kind {
14+
ValueKind::Table(map) => Ok(map),
15+
16+
_ => Ok(HashMap::new()),
17+
}
18+
}
19+
20+
fn from_dhall_value(uri: Option<&String>, value: &serde_dhall::SimpleValue) -> Value {
21+
match value {
22+
serde_dhall::SimpleValue::Num(num) => match num {
23+
serde_dhall::NumKind::Bool(b) => Value::new(uri, ValueKind::Boolean(*b)),
24+
serde_dhall::NumKind::Natural(n) => Value::new(uri, ValueKind::Integer(*n as i64)),
25+
serde_dhall::NumKind::Integer(i) => Value::new(uri, ValueKind::Integer(*i)),
26+
serde_dhall::NumKind::Double(d) => Value::new(uri, ValueKind::Float(f64::from(*d))),
27+
},
28+
serde_dhall::SimpleValue::Text(string) => {
29+
Value::new(uri, ValueKind::String(string.clone()))
30+
}
31+
serde_dhall::SimpleValue::List(list) => Value::new(
32+
uri,
33+
ValueKind::Array(list.iter().map(|v| from_dhall_value(uri, v)).collect()),
34+
),
35+
serde_dhall::SimpleValue::Record(rec) => Value::new(
36+
uri,
37+
ValueKind::Table(
38+
rec.iter()
39+
.map(|(k, v)| (k.clone(), from_dhall_value(uri, v)))
40+
.collect(),
41+
),
42+
),
43+
serde_dhall::SimpleValue::Optional(Some(value))
44+
| serde_dhall::SimpleValue::Union(_, Some(value)) => from_dhall_value(uri, value.as_ref()),
45+
serde_dhall::SimpleValue::Optional(None) | serde_dhall::SimpleValue::Union(_, None) => {
46+
Value::new(uri, ValueKind::Nil)
47+
}
48+
}
49+
}

src/file/format/mod.rs

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

31+
#[cfg(feature = "dhall")]
32+
mod dhall;
33+
3134
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
3235
pub enum FileFormat {
3336
/// TOML (parsed with toml)
@@ -57,6 +60,10 @@ pub enum FileFormat {
5760
/// JSON5 (parsed with json5)
5861
#[cfg(feature = "json5")]
5962
Json5,
63+
64+
/// Dhall (parsed with serde_dhall)
65+
#[cfg(feature = "dhall")]
66+
Dhall,
6067
}
6168

6269
lazy_static! {
@@ -86,6 +93,9 @@ lazy_static! {
8693
#[cfg(feature = "json5")]
8794
formats.insert(FileFormat::Json5, vec!["json5"]);
8895

96+
#[cfg(feature = "dhall")]
97+
formats.insert(FileFormat::Dhall, vec!["dhall"]);
98+
8999
formats
90100
};
91101
}
@@ -129,6 +139,9 @@ impl FileFormat {
129139

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

tests/Settings.dhall

Lines changed: 15 additions & 0 deletions
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

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

0 commit comments

Comments
 (0)