Skip to main content

hocon/adapters/
toml.rs

1//! TOML documents as HOCON config.
2//!
3//! TOML's types line up with HOCON's apart from dates: HOCON has no datetime,
4//! so all four TOML date-time types become their RFC 3339 string forms, which
5//! is the honest representation rather than a lossy number (spec F4.2).
6
7use indexmap::IndexMap;
8use toml::Value as TomlValue;
9
10use super::{config_from_object, AdapterError};
11use crate::value::{HoconValue, ScalarValue};
12use crate::Config;
13
14/// Read TOML text.
15pub fn parse(input: &str, origin: Option<&str>) -> Result<Config, AdapterError> {
16    // A TOML document is a table; `Value: FromStr` parses a single value, so
17    // parse the table type to get document semantics (F0.3 comes for free —
18    // TOML has no other root shape).
19    let table: toml::Table = super::strip_bom(input)
20        .parse()
21        .map_err(|e| AdapterError::new(format!("toml: {e}")))?;
22    let doc = TomlValue::Table(table);
23    Ok(config_from_object(convert(&doc, "")?, origin))
24}
25
26/// Read a TOML file, using its path as the origin description.
27pub fn parse_file(path: impl AsRef<std::path::Path>) -> Result<Config, AdapterError> {
28    let path = path.as_ref();
29    let text = std::fs::read_to_string(path)
30        .map_err(|e| AdapterError::new(format!("toml: {}: {e}", path.display())))?;
31    parse(&text, Some(&path.display().to_string()))
32}
33
34fn convert(v: &TomlValue, at: &str) -> Result<HoconValue, AdapterError> {
35    match v {
36        TomlValue::Table(t) => {
37            let mut out: IndexMap<String, HoconValue> = IndexMap::new();
38            for (k, e) in t {
39                let path = if at.is_empty() {
40                    k.clone()
41                } else {
42                    format!("{at}.{k}")
43                };
44                out.insert(k.clone(), convert(e, &path)?);
45            }
46            Ok(HoconValue::Object(out))
47        }
48        TomlValue::Array(items) => {
49            let mut out = Vec::with_capacity(items.len());
50            for (i, e) in items.iter().enumerate() {
51                out.push(convert(e, &format!("{at}[{i}]"))?);
52            }
53            Ok(HoconValue::Array(out))
54        }
55        TomlValue::String(s) => Ok(HoconValue::Scalar(ScalarValue::string(s.clone()))),
56        TomlValue::Integer(i) => Ok(HoconValue::Scalar(ScalarValue::number(i.to_string()))),
57        TomlValue::Float(f) => {
58            if f.is_nan() || f.is_infinite() {
59                return Err(AdapterError::new(format!(
60                    "toml: at {at}: {f} is not representable in HOCON (spec F0.6)"
61                )));
62            }
63            Ok(HoconValue::Scalar(ScalarValue::number(f.to_string())))
64        }
65        TomlValue::Boolean(b) => Ok(HoconValue::Scalar(ScalarValue::boolean(*b))),
66        // All four date-time types render themselves in their own shape.
67        TomlValue::Datetime(dt) => Ok(HoconValue::Scalar(ScalarValue::string(dt.to_string()))),
68    }
69}