use std::ops::Range;
use serde::de::DeserializeOwned;
use toml::Spanned;
use toml::de::{DeTable, DeValue, Deserializer};
use super::limits::{Bounds, Exceeded, Raise};
use super::{At, FieldPath, Input, InputError, Problem, Step};
pub(super) fn parse<T: DeserializeOwned>(
input: &Input,
bounds: Bounds,
raise: Raise,
) -> Result<T, InputError> {
let root = DeTable::parse(&input.text)
.map_err(|error| refusal(input, error.span(), error.message()))?;
let mut measure = Measure {
max_depth: bounds.depth,
max_nodes: bounds.nodes,
nodes: 0,
raise,
};
measure
.table(&root.span(), root.get_ref(), 1)
.map_err(|refused| refusal(input, Some(refused.at), refused.exceeded.to_string()))?;
T::deserialize(Deserializer::from(root))
.map_err(|error| refusal(input, error.span(), error.message()))
}
pub(super) fn places(text: &str, paths: &[FieldPath], at: At) -> Vec<Option<(usize, usize)>> {
let root = DeTable::parse(text)
.ok()
.map(|table| Spanned::new(table.span(), DeValue::Table(table.into_inner())));
paths
.iter()
.map(|path| {
root.as_ref()
.map(|root| position(text, nearest(root, path.steps(), at).start))
})
.collect()
}
fn nearest(root: &Spanned<DeValue<'_>>, steps: &[Step], at: At) -> Range<usize> {
let mut found = root;
let mut key = None;
for step in steps {
let next = match (found.get_ref(), step) {
(DeValue::Table(table), Step::Key(name)) => table
.iter()
.find(|(entry, _)| entry.get_ref().as_ref() == name.as_str())
.map(|(entry, value)| (value, Some(entry.span()))),
(value, Step::Key(name)) => value.get(name.as_str()).map(|value| (value, None)),
(value, Step::Index(index)) => value.get(*index).map(|value| (value, None)),
};
let Some((next, named)) = next else { break };
(found, key) = (next, named);
}
match at {
At::Value => found.span(),
At::Key => key.unwrap_or_else(|| found.span()),
}
}
fn refusal(input: &Input, span: Option<Range<usize>>, message: impl Into<String>) -> InputError {
let place = span.map(|span| position(&input.text, span.start));
InputError::of(
input.name.clone(),
vec![Problem {
line: place.map(|(line, _)| line + input.lines_before),
column: place.map(|(_, column)| column),
field: String::new(),
message: message.into(),
}],
)
}
fn position(text: &str, offset: usize) -> (usize, usize) {
let before = String::from_utf8_lossy(&text.as_bytes()[..offset.min(text.len())]);
let line = before.split('\n').count();
let column = 1 + before
.rsplit('\n')
.next()
.map_or(0, |last| last.chars().count());
(line, column)
}
struct Refused {
exceeded: Exceeded,
at: Range<usize>,
}
struct Measure {
max_depth: usize,
max_nodes: usize,
nodes: usize,
raise: Raise,
}
impl Measure {
fn table(
&mut self,
span: &Range<usize>,
table: &DeTable<'_>,
depth: usize,
) -> Result<(), Refused> {
self.container(span, depth)?;
let mut entries: Vec<_> = table.iter().collect();
entries.sort_by_key(|(key, _)| key.span().start);
entries
.into_iter()
.try_for_each(|(_, value)| self.value(value, depth + 1))
}
fn value(&mut self, value: &Spanned<DeValue<'_>>, depth: usize) -> Result<(), Refused> {
let span = value.span();
match value.get_ref() {
DeValue::Table(table) => self.table(&span, table, depth),
DeValue::Array(items) => {
self.container(&span, depth)?;
items
.iter()
.try_for_each(|item| self.value(item, depth + 1))
}
DeValue::String(_)
| DeValue::Integer(_)
| DeValue::Float(_)
| DeValue::Boolean(_)
| DeValue::Datetime(_) => self.node(&span),
}
}
fn container(&mut self, span: &Range<usize>, depth: usize) -> Result<(), Refused> {
if depth > self.max_depth {
return Err(self.refuse("max_depth", self.max_depth, span));
}
self.node(span)
}
fn node(&mut self, span: &Range<usize>) -> Result<(), Refused> {
self.nodes += 1;
if self.nodes > self.max_nodes {
return Err(self.refuse("max_nodes", self.max_nodes, span));
}
Ok(())
}
fn refuse(&self, field: &'static str, limit: usize, span: &Range<usize>) -> Refused {
Refused {
exceeded: Exceeded::new(field, limit, self.raise),
at: span.clone(),
}
}
}