use std::collections::VecDeque;
use serde::de::DeserializeOwned;
use serde::Serialize;
use toml::{map::Map, Value};
use toml_datetime::{Date, Datetime};
use super::{AsType, DynamicValueExt, PointerValueExt, ValueError, ValueResult};
impl AsType<'_, Value> for String {
fn from_value(value: &Value) -> ValueResult<Self, ValueError> {
Ok(String::from(
value.as_str().ok_or(ValueError::ValueNotType("str"))?,
))
}
}
impl<'a> AsType<'a, Value> for &'a str {
fn from_value(value: &'a Value) -> ValueResult<Self, ValueError> {
value.as_str().ok_or(ValueError::ValueNotType("str"))
}
}
impl AsType<'_, Value> for bool {
fn from_value(value: &Value) -> ValueResult<Self, ValueError> {
value.as_bool().ok_or(ValueError::ValueNotType("bool"))
}
}
impl AsType<'_, Value> for f64 {
fn from_value(value: &Value) -> ValueResult<Self, ValueError> {
value.as_float().ok_or(ValueError::ValueNotType("f64"))
}
}
impl AsType<'_, Value> for u32 {
fn from_value(value: &Value) -> ValueResult<Self, ValueError> {
value
.as_integer()
.map(i64::unsigned_abs)
.and_then(|v| u32::try_from(v).ok())
.ok_or(ValueError::ValueNotType("u32"))
}
}
impl AsType<'_, Value> for i32 {
fn from_value(value: &Value) -> ValueResult<Self, ValueError> {
value
.as_integer()
.and_then(|v| i32::try_from(v).ok())
.ok_or(ValueError::ValueNotType("i32"))
}
}
impl AsType<'_, Value> for i64 {
fn from_value(value: &Value) -> ValueResult<Self, ValueError> {
value.as_integer().ok_or(ValueError::ValueNotType("i64"))
}
}
impl AsType<'_, Value> for Date {
fn from_value(value: &Value) -> ValueResult<Self, ValueError> {
match value.as_datetime() {
Some(item) => match item.date {
Some(item_date) => Ok(item_date),
None => Err(ValueError::ValueNotType("date")),
},
None => Err(ValueError::ValueNotType("date")),
}
}
}
impl AsType<'_, Value> for Datetime {
fn from_value(value: &Value) -> ValueResult<Self, ValueError> {
value
.as_datetime()
.map(std::borrow::ToOwned::to_owned)
.ok_or(ValueError::ValueNotType("datetime"))
}
}
impl AsType<'_, Value> for u64 {
fn from_value(value: &Value) -> ValueResult<Self, ValueError> {
value
.as_integer()
.map(i64::unsigned_abs)
.ok_or(ValueError::ValueNotType("u64"))
}
}
#[derive(Debug, derive_more::From)]
pub enum TomlError {
#[from(ignore)]
Value(ValueError),
#[from(ignore)]
NotFound,
#[from(ignore)]
NotTable,
#[from(ignore)]
NoValueAt(String),
#[from(ignore)]
FailedDeserialization(toml::de::Error),
#[from(ignore)]
FailedSerialization(toml::ser::Error),
}
impl From<toml::ser::Error> for TomlError {
fn from(value: toml::ser::Error) -> Self {
Self::FailedSerialization(value)
}
}
impl From<toml::de::Error> for TomlError {
fn from(value: toml::de::Error) -> Self {
Self::FailedDeserialization(value)
}
}
impl From<ValueError> for TomlError {
fn from(value: ValueError) -> Self {
Self::Value(value)
}
}
impl TomlError {
pub fn value<V: Into<ValueError>>(val: V) -> Self {
Self::Value(val.into())
}
pub fn custom<T>(val: T) -> Self
where
T: std::fmt::Display,
{
Self::Value(ValueError::Custom(val.to_string()))
}
pub fn into_custom<T>(val: T) -> Self
where
T: Into<String>,
{
Self::Value(ValueError::Custom(val.into()))
}
}
impl std::error::Error for TomlError {}
impl core::fmt::Display for TomlError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
impl PointerValueExt for Value {
type Item = Value;
type Error = TomlError;
fn get_path(&self, name_or_pointer: &str) -> ValueResult<&Self::Item, Self::Error> {
if !name_or_pointer.contains('/') {
return self
.get(name_or_pointer)
.ok_or_else(|| ValueError::PropertyNotFound(name_or_pointer.to_string()).into());
}
let pointer_parts: Vec<&str> = name_or_pointer.split('/').skip(1).collect();
let mut current_value = self;
for &part in &pointer_parts[..pointer_parts.len() - 1] {
match current_value {
Value::Table(table) => {
if !table.contains_key(part) {
return Err(TomlError::NotFound);
}
match table.get(part) {
Some(item) => {
current_value = item;
}
None => return Err(TomlError::NoValueAt(String::from(part))),
}
}
_ => return Err(TomlError::NotTable),
}
}
if let Some(&last_part) = pointer_parts.last() {
match current_value {
Value::Table(table) => {
if !table.contains_key(last_part) {
return Err(TomlError::NotFound);
}
match table.get(last_part) {
Some(item) => {
return Ok(item);
}
None => return Err(TomlError::NoValueAt(String::from(last_part))),
}
}
_ => return Err(TomlError::NotTable),
}
}
Err(TomlError::custom("invalid path"))
}
fn take_path(&mut self, name_or_pointer: &str) -> ValueResult<Self::Item, Self::Error> {
if !name_or_pointer.contains('/') {
return self
.get(name_or_pointer)
.map(std::borrow::ToOwned::to_owned)
.ok_or_else(|| ValueError::PropertyNotFound(name_or_pointer.to_string()).into());
}
let pointer_parts: Vec<&str> = name_or_pointer.split('/').skip(1).collect();
let mut current_value = self;
for &part in &pointer_parts[..pointer_parts.len() - 1] {
match current_value {
Value::Table(table) => {
if !table.contains_key(part) {
return Err(TomlError::NotFound);
}
match table.get_mut(part) {
Some(item) => {
current_value = item;
}
None => return Err(TomlError::NoValueAt(String::from(part))),
}
}
_ => return Err(TomlError::NotTable),
}
}
if let Some(&last_part) = pointer_parts.last() {
match current_value {
Value::Table(table) => {
if !table.contains_key(last_part) {
return Err(TomlError::NotFound);
}
match table.get_mut(last_part) {
Some(item) => {
return Ok(item.to_owned());
}
None => return Err(TomlError::NoValueAt(String::from(last_part))),
}
}
_ => return Err(TomlError::NotTable),
}
}
Err(TomlError::custom("invalid path"))
}
}
pub trait TomlValueExt: DynamicValueExt {
fn d_walk<F>(&mut self, callback: F) -> bool
where
F: FnMut(&mut Map<String, Value>, &str) -> bool;
}
impl DynamicValueExt for Value {
type Item = Value;
type Error = TomlError;
fn d_new() -> Value {
Value::Table(Map::new())
}
fn d_get<T: DeserializeOwned>(&self, name_or_pointer: &str) -> ValueResult<T, Self::Error> {
let value = PointerValueExt::get_path(self, name_or_pointer)?;
T::deserialize(value.to_owned()).map_err(std::convert::Into::into)
}
fn d_get_as<'a, V: AsType<'a, Self::Item>>(
&'a self,
name_or_pointer: &str,
) -> ValueResult<V, Self::Error> {
let value = PointerValueExt::get_path(self, name_or_pointer)?;
V::from_value(value).map_err(std::convert::Into::into)
}
fn d_take<T: DeserializeOwned>(
&mut self,
name_or_pointer: &str,
) -> ValueResult<T, Self::Error> {
let value = PointerValueExt::take_path(self, name_or_pointer)?;
T::deserialize(value.clone()).map_err(std::convert::Into::into)
}
fn d_insert<T: Serialize>(
&mut self,
name_or_pointer: &str,
value: T,
) -> ValueResult<(), Self::Error> {
let new_value = Value::try_from(value)?;
if name_or_pointer.starts_with('/') {
let parts: Vec<&str> = name_or_pointer.split('/').skip(1).collect();
let mut current = self;
for &part in &parts[..parts.len() - 1] {
match current {
Value::Table(table) => {
current = table
.entry(part)
.or_insert_with(|| toml::Value::Table(Map::new()));
}
_ => return Err(TomlError::custom("Path does not point to an Table")),
}
}
if let Some(&last_part) = parts.last() {
match current {
Value::Table(map) => {
map.insert(last_part.to_string(), new_value);
Ok(())
}
_ => Err(TomlError::into_custom("Path does not point to an Table")),
}
} else {
Err(TomlError::into_custom("Invalid path"))
}
} else {
match self {
Value::Table(map) => {
map.insert(name_or_pointer.to_string(), new_value);
Ok(())
}
_ => Err(TomlError::custom("Value is not an Table, cannot d_insert")),
}
}
}
fn d_pretty(&self) -> ValueResult<String, Self::Error> {
let content = toml::to_string_pretty(self)?;
Ok(content)
}
}
impl TomlValueExt for Value {
fn d_walk<F>(&mut self, mut callback: F) -> bool
where
F: FnMut(&mut Map<String, Value>, &str) -> bool,
{
let mut queue = VecDeque::new();
queue.push_back(self);
while let Some(current) = queue.pop_front() {
if let Value::Table(map) = current {
for key in map.keys().cloned().collect::<Vec<_>>() {
let res = callback(map, &key);
if !res {
return false;
}
}
for (_, value) in map.iter_mut() {
if value.is_table() || value.is_array() {
queue.push_back(value);
}
}
} else if let Value::Array(arr) = current {
for value in arr.iter_mut() {
if value.is_table() || value.is_array() {
queue.push_back(value);
}
}
}
}
true
}
}