use serde_json::{Map, Value as Json};
use toml_edit::{Array, DocumentMut, InlineTable, Item, Table, Value as TomlValue};
use super::{WriteRefusal, not_a_number};
pub(super) fn value_of(raw: &str) -> Result<Json, WriteRefusal> {
let line = format!("value = {raw}");
if let Ok(doc) = line.parse::<DocumentMut>()
&& let Some(value) = doc.get("value").and_then(Item::as_value)
{
if let Some(path) = non_finite(value) {
return Err(WriteRefusal::Usage(not_a_number(&path)));
}
return Ok(value_json(value));
}
Ok(Json::String(raw.to_string()))
}
fn item_json(item: &Item) -> Option<Json> {
Some(match item {
Item::None => return None,
Item::Value(value) => value_json(value),
Item::Table(table) => Json::Object(table_json(table)),
Item::ArrayOfTables(tables) => Json::Array(
tables
.iter()
.map(|table| Json::Object(table_json(table)))
.collect(),
),
})
}
fn value_json(value: &TomlValue) -> Json {
if let Some(flag) = value.as_bool() {
return Json::Bool(flag);
}
if let Some(integer) = value.as_integer() {
return Json::from(integer);
}
if let Some(float) = value.as_float() {
if !float.is_finite() {
return Json::String(spelling(float).to_string());
}
return Json::from(float);
}
if let Some(text) = value.as_str() {
return Json::String(text.to_string());
}
if let Some(array) = value.as_array() {
return Json::Array(array.iter().map(value_json).collect());
}
if let Some(inline) = value.as_inline_table() {
return Json::Object(
inline
.iter()
.map(|(key, nested)| (key.to_string(), value_json(nested)))
.collect(),
);
}
if let Some(stamp) = value.as_datetime() {
return Json::String(stamp.to_string());
}
Json::Null
}
fn spelling(float: f64) -> &'static str {
if float.is_nan() {
"nan"
} else if float.is_sign_negative() {
"-inf"
} else {
"inf"
}
}
fn non_finite(value: &TomlValue) -> Option<String> {
under("", value).map(|path| path.join("."))
}
fn under(path: &str, value: &TomlValue) -> Option<Vec<String>> {
if let Some(float) = value.as_float() {
return (!float.is_finite()).then(|| vec![path.to_string()]);
}
if let Some(array) = value.as_array() {
return array
.iter()
.enumerate()
.find_map(|(index, nested)| under(&format!("{path}[{index}]"), nested));
}
if let Some(inline) = value.as_inline_table() {
return inline.iter().find_map(|(key, nested)| {
let step = if path.is_empty() {
key.to_string()
} else {
format!("{path}.{key}")
};
under(&step, nested)
});
}
None
}
pub(crate) fn table_json(table: &Table) -> Map<String, Json> {
table
.iter()
.filter_map(|(key, item)| item_json(item).map(|value| (key.to_string(), value)))
.collect()
}
pub(super) fn toml_value(value: &Json) -> Option<TomlValue> {
Some(match value {
Json::Null => return None,
Json::Bool(flag) => TomlValue::from(*flag),
Json::Number(number) => match number.as_i64() {
Some(integer) => TomlValue::from(integer),
None => TomlValue::from(number.as_f64()?),
},
Json::String(text) => TomlValue::from(text.as_str()),
Json::Array(list) => {
let mut array = Array::new();
for element in list {
array.push(toml_value(element)?);
}
TomlValue::Array(array)
}
Json::Object(map) => TomlValue::InlineTable(inline_table(map)),
})
}
pub(super) fn null_leaf(map: &Map<String, Json>) -> Option<String> {
map.iter()
.find_map(|(key, value)| leaf(key.clone(), value))
.map(|path| path.join("."))
}
fn leaf(path: String, value: &Json) -> Option<Vec<String>> {
match value {
Json::Null => Some(vec![path]),
Json::Object(map) => map
.iter()
.find_map(|(key, nested)| leaf(format!("{path}.{key}"), nested)),
Json::Array(list) => list
.iter()
.enumerate()
.find_map(|(index, nested)| leaf(format!("{path}[{index}]"), nested)),
_ => None,
}
}
pub(super) fn inline_table(map: &Map<String, Json>) -> InlineTable {
let mut inline = InlineTable::new();
for (key, value) in map {
if let Some(cell) = toml_value(value) {
inline.insert(key.as_str(), cell);
}
}
inline
}