use std::collections::BTreeMap;
pub const MAX_INPUT_BYTES: usize = 1024 * 1024;
pub const MAX_NODES: usize = 16_384;
pub const MAX_DEPTH: usize = 32;
#[derive(Clone, Debug, PartialEq)]
pub enum Value {
String(String),
Integer(i64),
Float(f64),
Boolean(bool),
DateTime(String),
Array(Vec<Value>),
Table(BTreeMap<String, Value>),
}
#[derive(Clone, Debug, PartialEq)]
pub struct Document {
root: Value,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
pub enum ParseError {
#[error("configuration exceeds 1048576 UTF-8 source bytes")]
InputTooLarge,
#[error("invalid TOML document")]
InvalidSyntax,
#[error("configuration exceeds 16384 decoded values")]
TooManyNodes,
#[error("configuration exceeds value depth 32")]
TooDeep,
}
impl Document {
pub fn parse_toml(source: &str) -> Result<Self, ParseError> {
if source.len() > MAX_INPUT_BYTES {
return Err(ParseError::InputTooLarge);
}
let table = toml::from_str::<toml::Table>(source).map_err(|_| ParseError::InvalidSyntax)?;
let mut remaining = MAX_NODES;
Ok(Self {
root: convert(toml::Value::Table(table), 0, &mut remaining)?,
})
}
pub fn root(&self) -> &Value {
&self.root
}
}
fn convert(value: toml::Value, depth: usize, remaining: &mut usize) -> Result<Value, ParseError> {
if depth > MAX_DEPTH {
return Err(ParseError::TooDeep);
}
*remaining = remaining.checked_sub(1).ok_or(ParseError::TooManyNodes)?;
Ok(match value {
toml::Value::String(value) => Value::String(value),
toml::Value::Integer(value) => Value::Integer(value),
toml::Value::Float(value) => Value::Float(value),
toml::Value::Boolean(value) => Value::Boolean(value),
toml::Value::Datetime(value) => Value::DateTime(value.to_string()),
toml::Value::Array(values) => Value::Array(
values
.into_iter()
.map(|value| convert(value, depth + 1, remaining))
.collect::<Result<_, _>>()?,
),
toml::Value::Table(values) => Value::Table(
values
.into_iter()
.map(|(key, value)| Ok((key, convert(value, depth + 1, remaining)?)))
.collect::<Result<_, ParseError>>()?,
),
})
}