jaq_json/
toml.rs

1//! TOML support.
2use crate::{Map, Num, Tag, Val};
3use alloc::string::{String, ToString};
4use alloc::vec::Vec;
5use core::fmt::{self, Display, Formatter};
6use toml_edit::{Document, DocumentMut, Formatted, Item, Table, Value};
7
8/// Parse a TOML document from a string.
9pub fn parse(s: &str) -> Result<Val, PError> {
10    table(s.parse::<Document<String>>()?.into_table())
11}
12
13/// Serialisation error.
14#[derive(Debug)]
15pub enum SError {
16    /// non-string key in object
17    Key(Val),
18    /// non-table value as root
19    Root(Val),
20    /// null or too large integer as value
21    Val(Val),
22}
23
24impl fmt::Display for SError {
25    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
26        match self {
27            Self::Key(v) => write!(f, "TOML keys must be strings, found: {v}"),
28            Self::Root(v) => write!(f, "TOML root must be an object, found: {v}"),
29            Self::Val(v) => write!(f, "could not encode {v} as TOML value"),
30        }
31    }
32}
33
34/// Parse error.
35#[derive(Debug)]
36pub struct PError(toml_edit::TomlError);
37
38impl From<toml_edit::TomlError> for PError {
39    fn from(e: toml_edit::TomlError) -> Self {
40        Self(e)
41    }
42}
43
44impl fmt::Display for PError {
45    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
46        self.0.fmt(f)
47    }
48}
49
50impl std::error::Error for PError {}
51
52impl std::error::Error for SError {}
53
54fn value(v: Value) -> Result<Val, PError> {
55    Ok(match v {
56        Value::String(s) => Val::Str(s.into_value().into(), Tag::Utf8),
57        Value::Integer(i) => Val::Num(Num::from_integral(i.into_value())),
58        Value::Float(f) => Val::Num(Num::Float(f.into_value())),
59        Value::Boolean(b) => Val::Bool(b.into_value()),
60        Value::Array(a) => return a.into_iter().map(value).collect(),
61        Value::InlineTable(t) => return table(t.into_table()),
62        Value::Datetime(d) => Val::Str(d.into_value().to_string().into(), Tag::Utf8),
63    })
64}
65
66fn item(item: Item) -> Result<Val, PError> {
67    match item {
68        // TODO: what is this? can this be triggered?
69        Item::None => panic!(),
70        Item::Value(v) => value(v),
71        Item::Table(t) => table(t),
72        Item::ArrayOfTables(a) => a.into_array().into_iter().map(value).collect(),
73    }
74}
75
76fn table(t: Table) -> Result<Val, PError> {
77    t.into_iter()
78        .map(|(k, v)| Ok((k.into(), item(v)?)))
79        .collect::<Result<_, _>>()
80        .map(Val::obj)
81}
82
83/// TOML root value.
84pub struct Toml(DocumentMut);
85
86impl Display for Toml {
87    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
88        self.0.fmt(f)
89    }
90}
91
92impl TryFrom<&Val> for Toml {
93    type Error = SError;
94    fn try_from(v: &Val) -> Result<Self, Self::Error> {
95        let obj = val_obj(v).ok_or_else(|| SError::Root(v.clone()));
96        obj.and_then(obj_table).map(DocumentMut::from).map(Self)
97    }
98}
99
100fn obj_table(o: &Map) -> Result<Table, SError> {
101    use jaq_std::ValT;
102    let kvs = o.iter().map(|(k, v)| {
103        let k = k.as_utf8_bytes().ok_or_else(|| SError::Key(k.clone()))?;
104        Ok((String::from_utf8_lossy(k).into_owned(), val_item(v)?))
105    });
106    kvs.collect::<Result<_, _>>()
107}
108
109fn val_obj(v: &Val) -> Option<&Map> {
110    match v {
111        Val::Obj(o) => Some(o),
112        _ => None,
113    }
114}
115
116fn val_item(v: &Val) -> Result<Item, SError> {
117    if let Val::Obj(o) = v {
118        return obj_table(o).map(Item::Table);
119    } else if let Val::Arr(a) = v {
120        if let Some(objs) = a.iter().map(val_obj).collect::<Option<Vec<_>>>() {
121            let tables = objs.into_iter().map(obj_table);
122            return tables.collect::<Result<_, _>>().map(Item::ArrayOfTables);
123        }
124    }
125    val_value(v).map(Item::Value)
126}
127
128fn val_value(v: &Val) -> Result<Value, SError> {
129    let fail = || SError::Val(v.clone());
130    Ok(match v {
131        Val::Null | Val::Str(_, Tag::Bytes) => Err(fail())?,
132        Val::Bool(b) => Value::Boolean(Formatted::new(*b)),
133        Val::Str(s, Tag::Utf8) => {
134            Value::String(Formatted::new(String::from_utf8_lossy(s).into_owned()))
135        }
136        Val::Num(Num::Float(f)) => Value::Float(Formatted::new(*f)),
137        Val::Num(Num::Dec(n)) => val_value(&Val::Num(Num::from_dec_str(n)))?,
138        Val::Num(n @ (Num::Int(_) | Num::BigInt(_))) => {
139            let from_int = |n: &Num| n.as_isize()?.try_into().ok();
140            Value::Integer(Formatted::new(from_int(n).ok_or_else(fail)?))
141        }
142        Val::Arr(a) => a
143            .iter()
144            .map(val_value)
145            .collect::<Result<_, _>>()
146            .map(Value::Array)?,
147        Val::Obj(o) => obj_table(o).map(|t| Value::InlineTable(Table::into_inline_table(t)))?,
148    })
149}