summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSascha Wise <me@saschawise.com>2021-07-30 21:46:06 +1200
committerMatthias Beyer <mail@beyermatthias.de>2021-10-02 20:41:22 +0200
commitf3a70f42582a158738fb909a5b6cbdc1b6937e82 (patch)
tree3ad3e0fb20c3456f974561c8a991fa2049baa58a
parent052c9503c65ee66074d00e8885aa13e6831bf079 (diff)
feat: add serde_dhall supportadd-dhall-support
-rw-r--r--Cargo.toml4
-rw-r--r--src/file/format/dhall.rs55
-rw-r--r--src/file/format/mod.rs13
-rw-r--r--tests/Settings.dhall15
-rw-r--r--tests/file_dhall.rs86
5 files changed, 172 insertions, 1 deletions
diff --git a/Cargo.toml b/Cargo.toml
index fcc1c1e..e908355 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,12 +15,13 @@ edition = "2018"
maintenance = { status = "actively-developed" }
[features]
-default = ["toml", "json", "yaml", "hjson", "ini", "ron", "json5"]
+default = ["toml", "json", "yaml", "hjson", "ini", "ron", "json5", "dhall"]
json = ["serde_json"]
yaml = ["yaml-rust"]
hjson = ["serde-hjson"]
ini = ["rust-ini"]
json5 = ["json5_rs"]
+dhall = ["serde_dhall"]
preserve_order = ["indexmap", "toml/preserve_order", "serde_json/preserve_order", "ron/indexmap"]
[dependencies]
@@ -37,6 +38,7 @@ rust-ini = { version = "0.17", optional = true }
ron = { version = "0.6", optional = true }
json5_rs = { version = "0.3", optional = true, package = "json5" }
indexmap = { version = "1.7.0", features = ["serde-1"], optional = true}
+serde_dhall = { version = "0.10", optional = true }
[dev-dependencies]
serde_derive = "1.0.8"
diff --git a/src/file/format/dhall.rs b/src/file/format/dhall.rs
new file mode 100644
index 0000000..03bee38
--- /dev/null
+++ b/src/file/format/dhall.rs
@@ -0,0 +1,55 @@
+use std::collections::HashMap;
+use std::error::Error;
+
+use crate::{
+ error::Unexpected,
+ value::{Value, ValueKind},
+ ConfigError,
+};
+
+pub fn parse(
+ uri: Option<&String>,
+ text: &str,
+) -> Result<HashMap<String, Value>, Box<dyn Error + Send + Sync>> {
+ let value = from_dhall_value(uri, serde_dhall::from_str(text).parse()?);
+ match value.kind {
+ ValueKind::Table(map) => Ok(map),
+ ValueKind::Nil => Err(Unexpected::Unit),
+ ValueKind::Boolean(value) => Err(Unexpected::Bool(value)),
+ ValueKind::Integer(value) => Err(Unexpected::Integer(value)),
+ ValueKind::Float(value) => Err(Unexpected::Float(value)),
+ ValueKind::String(value) => Err(Unexpected::Str(value)),
+ ValueKind::Array(value) => Err(Unexpected::Seq),
+ }
+ .map_err(|err| ConfigError::invalid_root(uri, err))
+ .map_err(|err| Box::new(err) as Box<dyn Error + Send + Sync>)
+}
+
+fn from_dhall_value(uri: Option<&String>, value: serde_dhall::SimpleValue) -> Value {
+ match value {
+ serde_dhall::SimpleValue::Num(num) => match num {
+ serde_dhall::NumKind::Bool(b) => Value::new(uri, ValueKind::Boolean(b)),
+ serde_dhall::NumKind::Natural(n) => Value::new(uri, ValueKind::Integer(n as i64)),
+ serde_dhall::NumKind::Integer(i) => Value::new(uri, ValueKind::Integer(i)),
+ serde_dhall::NumKind::Double(d) => Value::new(uri, ValueKind::Float(f64::from(d))),
+ },
+ serde_dhall::SimpleValue::Text(string) => Value::new(uri, ValueKind::String(string)),
+ serde_dhall::SimpleValue::List(list) => Value::new(
+ uri,
+ ValueKind::Array(list.into_iter().map(|v| from_dhall_value(uri, v)).collect()),
+ ),
+ serde_dhall::SimpleValue::Record(rec) => Value::new(
+ uri,
+ ValueKind::Table(
+ rec.into_iter()
+ .map(|(k, v)| (k, from_dhall_value(uri, v)))
+ .collect(),
+ ),
+ ),
+ serde_dhall::SimpleValue::Optional(Some(value))
+ | serde_dhall::SimpleValue::Union(_, Some(value)) => from_dhall_value(uri, *value),
+ serde_dhall::SimpleValue::Optional(None) | serde_dhall::SimpleValue::Union(_, None) => {
+ Value::new(uri, ValueKind::Nil)
+ }
+ }
+}
diff --git a/src/file/format/mod.rs b/src/file/format/mod.rs
index 9f6d7d7..9c5b32a 100644
--- a/src/file/format/mod.rs
+++ b/src/file/format/mod.rs
@@ -29,6 +29,9 @@ mod ron;
#[cfg(feature = "json5")]
mod json5;
+#[cfg(feature = "dhall")]
+mod dhall;
+
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum FileFormat {
/// TOML (parsed with toml)
@@ -58,6 +61,10 @@ pub enum FileFormat {
/// JSON5 (parsed with json5)
#[cfg(feature = "json5")]
Json5,
+
+ /// Dhall (parsed with serde_dhall)
+ #[cfg(feature = "dhall")]
+ Dhall,
}
lazy_static! {
@@ -87,6 +94,9 @@ lazy_static! {
#[cfg(feature = "json5")]
formats.insert(FileFormat::Json5, vec!["json5"]);
+ #[cfg(feature = "dhall")]
+ formats.insert(FileFormat::Dhall, vec!["dhall"]);
+
formats
};
}
@@ -130,6 +140,9 @@ impl FileFormat {
#[cfg(feature = "json5")]
FileFormat::Json5 => json5::parse(uri, text),
+
+ #[cfg(feature = "dhall")]
+ FileFormat::Dhall => dhall::parse(uri, text),
}
}
}
diff --git a/tests/Settings.dhall b/tests/Settings.dhall
new file mode 100644
index 0000000..9b88caf
--- /dev/null
+++ b/tests/Settings.dhall
@@ -0,0 +1,15 @@
+{
+ debug = True
+, debug_json = True
+, production = False
+, arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+, place = {
+ name = "Torre di Pisa"
+, longitude = 43.7224985
+, latitude = 10.3970522
+, favorite = False
+, reviews = 3866
+, rating = 4.5
+, creator.name = "John Smith"
+ }
+}
diff --git a/tests/file_dhall.rs b/tests/file_dhall.rs
new file mode 100644
index 0000000..f45c854
--- /dev/null
+++ b/tests/file_dhall.rs
@@ -0,0 +1,86 @@
+#![cfg(feature = "dhall")]
+
+extern crate config;
+extern crate float_cmp;
+extern crate serde;
+
+#[macro_use]
+extern crate serde_derive;
+
+use std::collections::HashMap;
+
+use config::*;
+use float_cmp::ApproxEqUlps;
+
+#[derive(Debug, Deserialize)]
+struct Place {
+ name: String,
+ longitude: f64,
+ latitude: f64,
+ favorite: bool,
+ telephone: Option<String>,
+ reviews: u64,
+ creator: HashMap<String, Value>,
+ rating: Option<f32>,
+}
+
+#[derive(Debug, Deserialize)]
+struct Settings {
+ debug: f64,
+ production: Option<String>,
+ place: Place,
+ #[serde(rename = "arr")]
+ elements: Vec<String>,
+}
+
+fn make() -> Config {
+ Config::builder()
+ .add_source(File::new("tests/Settings", FileFormat::Dhall))
+ .build()
+ .unwrap()
+}
+
+#[test]
+fn test_file() {
+ let c = make();
+
+ // Deserialize the entire file as single struct
+ let s: Settings = c.try_into().unwrap();
+
+ assert!(s.debug.approx_eq_ulps(&1.0, 2));
+ assert_eq!(s.production, Some("false".to_string()));
+ assert_eq!(s.place.name, "Torre di Pisa");
+ assert!(s.place.longitude.approx_eq_ulps(&43.7224985, 2));
+ assert!(s.place.latitude.approx_eq_ulps(&10.3970522, 2));
+ assert_eq!(s.place.favorite, false);
+ assert_eq!(s.place.reviews, 3866);
+ assert_eq!(s.place.rating, Some(4.5));
+ assert_eq!(s.place.telephone, None);
+ assert_eq!(s.elements.len(), 10);
+ assert_eq!(s.elements[3], "4".to_string());
+ assert_eq!(
+ s.place.creator["name"].clone().into_string().unwrap(),
+ "John Smith".to_string()
+ );
+}
+
+#[test]
+fn test_dhall_vec() {
+ let c = Config::builder()
+ .add_source(File::from_str(
+ r#"
+ {
+ WASTE = ["example_dir1", "example_dir2"]
+ }
+ "#,
+ FileFormat::Dhall,
+ ))
+ .build()
+ .unwrap();
+
+ let v = c.get_array("WASTE").unwrap();
+ let mut vi = v.into_iter();
+ assert_eq!(vi.next().unwrap().into_string().unwrap(), "example_dir1");
+ assert_eq!(vi.next().unwrap().into_string().unwrap(), "example_dir2");
+ assert!(vi.next().is_none());
+}