config_lib/parsers/
toml_parser.rs1use crate::error::Result;
7use crate::value::Value;
8use std::collections::BTreeMap;
9
10#[cfg(feature = "noml")]
12pub fn parse(source: &str) -> Result<Value> {
13 let noml_value = noml::parse(source)?;
15 convert_noml_value(noml_value)
16}
17
18#[cfg(not(feature = "noml"))]
20pub fn parse(_source: &str) -> Result<Value> {
21 Err(Error::general(
22 "TOML parsing requires either the 'noml' feature or a dedicated TOML parser",
23 ))
24}
25
26#[cfg(feature = "noml")]
28pub fn parse_with_preservation(source: &str) -> Result<(Value, noml::Document)> {
29 let document = noml::parse_string(source, None)?;
31
32 let mut resolver = noml::Resolver::new();
34 let resolved = resolver.resolve(&document)?;
35
36 let value = convert_noml_value(resolved)?;
38
39 Ok((value, document))
40}
41
42#[cfg(not(feature = "noml"))]
44pub fn parse_with_preservation(_source: &str) -> Result<(Value, ())> {
45 Err(Error::general(
46 "TOML format preservation requires the 'noml' feature",
47 ))
48}
49
50#[cfg(feature = "noml")]
52fn convert_noml_value(noml_value: noml::Value) -> Result<Value> {
53 match noml_value {
54 noml::Value::Null => Ok(Value::Null),
55 noml::Value::Bool(b) => Ok(Value::Bool(b)),
56 noml::Value::Integer(i) => Ok(Value::Integer(i)),
57 noml::Value::Float(f) => Ok(Value::Float(f)),
58 noml::Value::String(s) => Ok(Value::String(s)),
59 noml::Value::Array(arr) => {
60 let converted: Result<Vec<Value>> = arr.into_iter().map(convert_noml_value).collect();
61 Ok(Value::Array(converted?))
62 }
63 noml::Value::Table(table) => {
64 let mut converted = BTreeMap::new();
65 for (key, value) in table {
66 converted.insert(key, convert_noml_value(value)?);
67 }
68 Ok(Value::Table(converted))
69 }
70 #[cfg(feature = "chrono")]
71 noml::Value::DateTime(dt) => Ok(Value::DateTime(dt)),
72 #[cfg(not(feature = "chrono"))]
73 noml::Value::DateTime(dt) => Ok(Value::String(dt.to_rfc3339())),
74 noml::Value::Binary(_) => Ok(Value::String("binary_data".to_string())),
76 noml::Value::Size(size) => Ok(Value::Integer(size as i64)),
77 noml::Value::Duration(duration) => Ok(Value::Float(duration)),
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn test_basic_toml() {
87 let config = parse(
88 r#"
89 name = "test"
90 port = 8080
91 debug = true
92
93 [database]
94 host = "localhost"
95 max_connections = 100
96 "#,
97 )
98 .unwrap();
99
100 assert_eq!(config.get("name").unwrap().as_string().unwrap(), "test");
101 assert_eq!(config.get("port").unwrap().as_integer().unwrap(), 8080);
102 assert_eq!(
103 config.get("database.host").unwrap().as_string().unwrap(),
104 "localhost"
105 );
106 }
107
108 #[test]
109 fn test_toml_arrays() {
110 let config = parse(
111 r#"
112 servers = ["alpha", "beta", "gamma"]
113 ports = [8001, 8002, 8003]
114 "#,
115 )
116 .unwrap();
117
118 let servers = config.get("servers").unwrap().as_array().unwrap();
119 assert_eq!(servers.len(), 3);
120 assert_eq!(servers[0].as_string().unwrap(), "alpha");
121
122 let ports = config.get("ports").unwrap().as_array().unwrap();
123 assert_eq!(ports[0].as_integer().unwrap(), 8001);
124 }
125}