summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRaphael Cohn <raphael.cohn@stormmq.com>2017-09-01 09:06:27 +0100
committerRaphael Cohn <raphael.cohn@stormmq.com>2017-09-01 09:06:27 +0100
commit9826b2cda730116e148120dafe0fa89bd389626e (patch)
tree164597d8e40017ca690c259c75eacee9134f6f5d
parent13151213a7b368296c616e0a770fb2c238fff1a0 (diff)
Added HJSON (Human-Readable JSON) as a config file format
-rw-r--r--Cargo.toml4
-rw-r--r--README.md4
-rw-r--r--src/file/format/hjson.rs55
-rw-r--r--src/file/format/mod.rs13
-rw-r--r--src/lib.rs5
-rw-r--r--tests/Settings-invalid.hjson4
-rw-r--r--tests/Settings.hjson16
-rw-r--r--tests/datetime.rs19
-rw-r--r--tests/file_hjson.rs75
9 files changed, 192 insertions, 3 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 1317a27..c3789ab 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,9 +14,10 @@ license = "MIT/Apache-2.0"
travis-ci = { repository = "mehcode/config-rs" }
[features]
-default = ["toml", "json", "yaml"]
+default = ["toml", "json", "yaml", "hjson"]
json = ["serde_json"]
yaml = ["yaml-rust"]
+hjson = ["serde-hjson"]
[dependencies]
lazy_static = "0.2"
@@ -26,6 +27,7 @@ nom = "^3.0.0"
toml = { version = "^0.4.1", optional = true }
serde_json = { version = "^1.0.2", optional = true }
yaml-rust = { version = "^0.3.5", optional = true }
+serde-hjson = { version = "^0.8.1", optional = true }
[dev-dependencies]
serde_derive = "^1.0.8"
diff --git a/README.md b/README.md
index 066e68e..9beaac6 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
- Set defaults
- Set explicit values (to programmatically override)
- - Read from [JSON], [TOML], and [YAML] files
+ - Read from [JSON], [TOML], [YAML] and [HJSON] files
- Read from environment
- Loosely typed — Configuration values may be read in any supported type, as long as there exists a reasonable conversion
- Access nested fields using a formatted path — Uses a subset of JSONPath; currently supports the child ( `redis.port` ) and subscript operators ( `databases[0].name` )
@@ -18,6 +18,7 @@
[JSON]: https://github.com/serde-rs/json
[TOML]: https://github.com/toml-lang/toml
[YAML]: https://github.com/chyh1990/yaml-rust
+[HJSON]: https://github.com/hjson/hjson-rust
## Usage
@@ -27,6 +28,7 @@ config = "0.6"
```
- `json` - Adds support for reading JSON files
+ - `hjson` - Adds support for reading HJSON files
- `yaml` - Adds support for reading YAML files
- `toml` - Adds support for reading TOML files (included by default)
diff --git a/src/file/format/hjson.rs b/src/file/format/hjson.rs
new file mode 100644
index 0000000..3d4ad1b
--- /dev/null
+++ b/src/file/format/hjson.rs
@@ -0,0 +1,55 @@
+use serde_hjson;
+use source::Source;
+use std::collections::HashMap;
+use std::error::Error;
+use value::{Value, ValueKind};
+
+pub fn parse(
+ uri: Option<&String>,
+ text: &str,
+) -> Result<HashMap<String, Value>, Box<Error + Send + Sync>> {
+ // Parse a JSON object value from the text
+ // TODO: Have a proper error fire if the root of a file is ever not a Table
+ let value = from_hjson_value(uri, &serde_hjson::from_str(text)?);
+ match value.kind {
+ ValueKind::Table(map) => Ok(map),
+
+ _ => Ok(HashMap::new()),
+ }
+}
+
+fn from_hjson_value(uri: Option<&String>, value: &serde_hjson::Value) -> Value {
+ match *value {
+ serde_hjson::Value::String(ref value) => Value::new(uri, ValueKind::String(value.clone())),
+
+ serde_hjson::Value::I64(value) => Value::new(uri, ValueKind::Integer(value)),
+
+ serde_hjson::Value::U64(value) => Value::new(uri, ValueKind::Integer(value as i64)),
+
+ serde_hjson::Value::F64(value) => Value::new(uri, ValueKind::Float(value)),
+
+ serde_hjson::Value::Bool(value) => Value::new(uri, ValueKind::Boolean(value)),
+
+ serde_hjson::Value::Object(ref table) => {
+ let mut m = HashMap::new();
+
+ for (key, value) in table {
+ m.insert(key.to_lowercase().clone(), from_hjson_value(uri, value));
+ }
+
+ Value::new(uri, ValueKind::Table(m))
+ }
+
+ serde_hjson::Value::Array(ref array) => {
+ let mut l = Vec::new();
+
+ for value in array {
+ l.push(from_hjson_value(uri, value));
+ }
+
+ Value::new(uri, ValueKind::Array(l))
+ }
+
+ serde_hjson::Value::Null => Value::new(uri, ValueKind::Nil),
+ }
+}
diff --git a/src/file/format/mod.rs b/src/file/format/mod.rs
index a90dfda..5dfdfde 100644
--- a/src/file/format/mod.rs
+++ b/src/file/format/mod.rs
@@ -16,6 +16,9 @@ mod json;
#[cfg(feature = "yaml")]
mod yaml;
+#[cfg(feature = "hjson")]
+mod hjson;
+
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum FileFormat {
/// TOML (parsed with toml)
@@ -29,6 +32,10 @@ pub enum FileFormat {
/// YAML (parsed with yaml_rust)
#[cfg(feature = "yaml")]
Yaml,
+
+ /// HJSON (parsed with serde_hjson)
+ #[cfg(feature = "hjson")]
+ Hjson,
}
lazy_static! {
@@ -46,6 +53,9 @@ lazy_static! {
#[cfg(feature = "yaml")]
formats.insert(FileFormat::Yaml, vec!["yaml", "yml"]);
+ #[cfg(feature = "hjson")]
+ formats.insert(FileFormat::Hjson, vec!["hjson"]);
+
formats
};
}
@@ -77,6 +87,9 @@ impl FileFormat {
#[cfg(feature = "yaml")]
FileFormat::Yaml => yaml::parse(uri, text),
+
+ #[cfg(feature = "hjson")]
+ FileFormat::Hjson => hjson::parse(uri, text),
}
}
}
diff --git a/src/lib.rs b/src/lib.rs
index bed60b4..b6eb0f6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -5,7 +5,7 @@
//! - Environment variables
//! - Another Config instance
//! - Remote configuration: etcd, Consul
-//! - Files: JSON, YAML, TOML
+//! - Files: JSON, YAML, TOML, HJSON
//! - Manual, programmatic override (via a `.set` method on the Config instance)
//!
//! Additionally, Config supports:
@@ -38,6 +38,9 @@ extern crate serde_json;
#[cfg(feature = "yaml")]
extern crate yaml_rust;
+#[cfg(feature = "hjson")]
+extern crate serde_hjson;
+
mod error;
mod value;
mod de;
diff --git a/tests/Settings-invalid.hjson b/tests/Settings-invalid.hjson
new file mode 100644
index 0000000..7e31ec3
--- /dev/null
+++ b/tests/Settings-invalid.hjson
@@ -0,0 +1,4 @@
+{
+ ok: true,
+ error
+}
diff --git a/tests/Settings.hjson b/tests/Settings.hjson
new file mode 100644
index 0000000..3e04ccf
--- /dev/null
+++ b/tests/Settings.hjson
@@ -0,0 +1,16 @@
+{
+ debug: 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/datetime.rs b/tests/datetime.rs
index 8a50c01..89a34a1 100644
--- a/tests/datetime.rs
+++ b/tests/datetime.rs
@@ -29,6 +29,15 @@ fn make() -> Config {
FileFormat::Toml,
))
.unwrap()
+ .merge(File::from_str(
+ r#"
+ {
+ "hjson_datetime": "2017-05-10T02:14:53Z"
+ }
+ "#,
+ FileFormat::Hjson,
+ ))
+ .unwrap()
.clone()
}
@@ -50,6 +59,11 @@ fn test_datetime_string() {
let date: String = s.get("yaml_datetime").unwrap();
assert_eq!(&date, "2017-06-12T10:58:30Z");
+
+ // HJSON
+ let date: String = s.get("hjson_datetime").unwrap();
+
+ assert_eq!(&date, "2017-05-10T02:14:53Z");
}
#[test]
@@ -70,4 +84,9 @@ fn test_datetime() {
let date: DateTime<Utc> = s.get("yaml_datetime").unwrap();
assert_eq!(date, Utc.ymd(2017, 6, 12).and_hms(10, 58, 30));
+
+ // HJSON
+ let date: DateTime<Utc> = s.get("hjson_datetime").unwrap();
+
+ assert_eq!(date, Utc.ymd(2017, 5, 10).and_hms(2, 14, 53));
}
diff --git a/tests/file_hjson.rs b/tests/file_hjson.rs
new file mode 100644
index 0000000..f36843a
--- /dev/null
+++ b/tests/file_hjson.rs
@@ -0,0 +1,75 @@
+extern crate config;
+extern crate serde;
+extern crate float_cmp;
+
+#[macro_use]
+extern crate serde_derive;
+
+use std::collections::HashMap;
+use float_cmp::ApproxEqUlps;
+use config::*;
+
+#[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 {
+ let mut c = Config::default();
+ c.merge(File::new("tests/Settings", FileFormat::Hjson))
+ .unwrap();
+
+ c
+}
+
+#[test]
+fn test_file() {
+ let c = make();
+
+ // Deserialize the entire file as single struct
+ let s: Settings = c.deserialize().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_str().unwrap(),
+ "John Smith".to_string()
+ );
+}
+
+#[test]
+fn test_error_parse() {
+ let mut c = Config::default();
+ let res = c.merge(File::new("tests/Settings-invalid", FileFormat::Hjson));
+
+ assert!(res.is_err());
+ assert_eq!(
+ res.unwrap_err().to_string(),
+ "Found a punctuator where a key name was expected (check your syntax or use quotes if the key name includes {}[],: or whitespace) at line 1 column 1 in tests/Settings-invalid.hjson".to_string()
+ );
+}