config_lib/parsers/
noml_parser.rs1#[cfg(not(feature = "noml"))]
13use crate::error::Error;
14use crate::error::Result;
15use crate::value::Value;
16use std::collections::BTreeMap;
17
18#[cfg(feature = "noml")]
20pub fn parse(source: &str) -> Result<Value> {
21 let document = noml::parse_string(source, None)?;
23
24 let mut resolver = noml::Resolver::new();
26 let resolved = resolver.resolve(&document)?;
27
28 convert_noml_value(resolved)
29}
30
31#[cfg(not(feature = "noml"))]
33pub fn parse(_source: &str) -> Result<Value> {
34 Err(Error::general(
35 "NOML parsing requires the 'noml' feature to be enabled",
36 ))
37}
38
39#[cfg(feature = "noml")]
41fn convert_noml_value(noml_value: noml::Value) -> Result<Value> {
42 match noml_value {
43 noml::Value::Null => Ok(Value::Null),
44 noml::Value::Bool(b) => Ok(Value::Bool(b)),
45 noml::Value::Integer(i) => Ok(Value::Integer(i)),
46 noml::Value::Float(f) => Ok(Value::Float(f)),
47 noml::Value::String(s) => Ok(Value::String(s)),
48 noml::Value::Array(arr) => {
49 let converted: Result<Vec<Value>> = arr.into_iter().map(convert_noml_value).collect();
50 Ok(Value::Array(converted?))
51 }
52 noml::Value::Table(table) => {
53 let mut converted = BTreeMap::new();
54 for (key, value) in table {
55 converted.insert(key, convert_noml_value(value)?);
56 }
57 Ok(Value::Table(converted))
58 }
59 #[cfg(feature = "chrono")]
60 noml::Value::DateTime(dt) => Ok(Value::DateTime(dt)),
61 #[cfg(not(feature = "chrono"))]
62 noml::Value::DateTime(_) => Ok(Value::String(
63 "datetime not supported without chrono feature".to_string(),
64 )),
65 noml::Value::Binary(_data) => {
66 #[cfg(feature = "base64")]
68 {
69 use base64::{engine::general_purpose, Engine as _};
70 Ok(Value::String(general_purpose::STANDARD.encode(_data)))
71 }
72 #[cfg(not(feature = "base64"))]
73 Ok(Value::String("<binary data>".to_string()))
74 }
75 noml::Value::Size(size) => {
76 Ok(Value::Integer(size as i64))
78 }
79 noml::Value::Duration(duration) => {
80 Ok(Value::Float(duration))
82 }
83 }
84}
85
86pub fn parse_with_preservation(source: &str) -> Result<(Value, noml::Document)> {
88 let document = noml::parse_string(source, None)?;
90
91 let mut resolver = noml::Resolver::new();
93 let resolved = resolver.resolve(&document)?;
94
95 let value = convert_noml_value(resolved)?;
97
98 Ok((value, document))
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn test_basic_noml() {
107 let config = parse(
108 r#"
109 name = "test"
110 port = 8080
111 debug = true
112 "#,
113 )
114 .unwrap();
115
116 assert_eq!(config.get("name").unwrap().as_string().unwrap(), "test");
117 assert_eq!(config.get("port").unwrap().as_integer().unwrap(), 8080);
118 assert!(config.get("debug").unwrap().as_bool().unwrap());
119 }
120
121 #[test]
122 fn test_noml_features() {
123 std::env::set_var("TEST_VAR", "hello");
124
125 let config = parse(
126 r#"
127 greeting = env("TEST_VAR", "world")
128 size = @size("10MB")
129 timeout = @duration("30s")
130 "#,
131 )
132 .unwrap();
133
134 assert_eq!(
135 config.get("greeting").unwrap().as_string().unwrap(),
136 "hello"
137 );
138 assert_eq!(config.get("size").unwrap().as_integer().unwrap(), 10485760);
140 assert_eq!(config.get("timeout").unwrap().as_float().unwrap(), 30.0);
142 }
143}