use std::collections::BTreeMap;
use super::error::{Located, Problem};
use super::record::Val;
use crate::ext::{Ext, ExtValue, MAX_INTEGER};
use crate::names::Namespace;
use crate::yaml::{self, Out, Style, Value};
const PLAIN: &str = "an integer, `true`, `false`, or text in double quotes";
const INTEGER: &str = "an integer from -9007199254740991 to 9007199254740991";
pub(super) fn check(node: &yaml::Node) -> Result<Val, Located> {
let Value::Mapping(entries) = &node.value else {
return Err(located(node, Problem::Expected("a mapping of namespaces")));
};
let mut ext = Ext::new();
for entry in entries {
let namespace = Namespace::parse(&entry.key).map_err(|_| Located {
position: entry.key_position,
problem: Problem::Invalid {
value: entry.key.clone(),
expected: "an extension namespace",
},
})?;
ext.insert(namespace, value(&entry.value)?);
}
Ok(Val::Ext {
value: ext,
position: node.position,
})
}
fn value(node: &yaml::Node) -> Result<ExtValue, Located> {
match &node.value {
Value::Mapping(entries) => entries
.iter()
.map(|entry| Ok((entry.key.clone(), value(&entry.value)?)))
.collect::<Result<BTreeMap<_, _>, _>>()
.map(ExtValue::Map),
Value::Sequence(items) => items
.iter()
.map(value)
.collect::<Result<_, _>>()
.map(ExtValue::List),
Value::Scalar(scalar) if scalar.style != Style::Plain => {
Ok(ExtValue::Text(scalar.text.clone()))
}
Value::Scalar(scalar) => plain(node, &scalar.text),
Value::Empty => Err(located(node, Problem::Expected("a value"))),
}
}
fn plain(node: &yaml::Node, text: &str) -> Result<ExtValue, Located> {
let invalid = |expected| {
located(
node,
Problem::Invalid {
value: text.to_owned(),
expected,
},
)
};
match text {
"true" => return Ok(ExtValue::Boolean(true)),
"false" => return Ok(ExtValue::Boolean(false)),
_ => {}
}
let digits = text.strip_prefix('-').unwrap_or(text);
let decimal = !digits.is_empty()
&& digits.bytes().all(|byte| byte.is_ascii_digit())
&& (digits == "0" || !digits.starts_with('0'))
&& text != "-0";
if !decimal {
return Err(invalid(PLAIN));
}
text.parse::<i64>()
.ok()
.filter(|number| (-MAX_INTEGER..=MAX_INTEGER).contains(number))
.map(ExtValue::Integer)
.ok_or_else(|| invalid(INTEGER))
}
const fn located(node: &yaml::Node, problem: Problem) -> Located {
Located {
position: node.position,
problem,
}
}
pub(super) fn ext_out(ext: &Ext) -> Out {
Out::Map(
ext.iter()
.map(|(namespace, value)| (namespace.to_string(), value_out(value)))
.collect(),
)
}
fn value_out(value: &ExtValue) -> Out {
match value {
ExtValue::Text(text) => Out::Text(text.clone()),
ExtValue::Integer(number) => Out::Plain(number.to_string()),
ExtValue::Boolean(flag) => Out::Plain(flag.to_string()),
ExtValue::List(items) => Out::Sequence(items.iter().map(value_out).collect()),
ExtValue::Map(entries) => Out::Map(
entries
.iter()
.map(|(key, value)| (key.clone(), value_out(value)))
.collect(),
),
}
}