dop 0.2.2

Process, transform and query JSON/YAML/TOML, from the shell.
use crate::common::*;
use crate::path::*;
use crate::types::{DataFormat, ToStrError};
use crate::value::*;
use std::collections::HashMap;
use toml::Value as TomlValue;

pub struct Toml {}

impl DataFormat for Toml {
    fn from_str(&self, s: &str) -> Option<Value> {
        to_value(&toml::from_str(s).ok()?)
    }
    fn to_str(&self, value: &Value, _: bool) -> Result<String, ToStrError> {
        Ok(
            toml::to_string(&to_toml_value(value, &[]).map_err(|err| match err {
                ToTomlValueError::UnsupportedType((value, path)) => {
                    ToStrError::UnsupportedType((value.type_encoded(), path))
                }
            })?)
            .map_err(to_parse_error)?,
        )
    }
}

fn to_value(value: &TomlValue) -> Option<Value> {
    if value.is_array() {
        return Some(Value::List(
            value
                .as_array()
                .unwrap()
                .iter()
                .map(|value| to_value(value).unwrap())
                .collect(),
        ));
    }

    if value.is_bool() {
        return Some(Value::Bool(value.as_bool().unwrap()));
    }

    if value.is_integer() {
        return Some(Value::Int(value.as_integer().unwrap()));
    }

    if value.is_float() {
        return Some(Value::Float(value.as_float().unwrap()));
    }

    if value.is_str() {
        return Some(Value::String(value.as_str().unwrap().to_owned()));
    }

    if value.is_table() {
        let mut obj: HashMap<Key, Value> = std::collections::HashMap::new();

        for (key, value) in value.as_table().unwrap().iter() {
            obj.insert(to_key(key), to_value(value).unwrap());
        }

        return Some(Value::Object(obj));
    }

    None
}

#[derive(Debug)]
enum ToTomlValueError {
    UnsupportedType((Value, Vec<PathEntry>)),
}

fn to_toml_value(value: &Value, path: &[PathEntry]) -> Result<TomlValue, ToTomlValueError> {
    match value {
        Value::String(value) => Ok(TomlValue::from(value.to_owned())),
        Value::Int(value) => Ok(TomlValue::from(*value)),
        Value::Float(value) => Ok(TomlValue::from(*value)),
        Value::Bool(value) => Ok(TomlValue::from(*value)),
        Value::List(list) => Ok(TomlValue::Array(
            list.iter()
                .enumerate()
                .map(|(i, value)| {
                    to_toml_value(value, &{
                        let mut path_new = path.to_owned();
                        path_new.push(PathEntry::Index(i));

                        path_new
                    })
                })
                .collect::<Result<Vec<TomlValue>, ToTomlValueError>>()?,
        )),
        Value::Object(value) => {
            let mut obj = toml::Table::new();

            for (key, value) in value {
                if let Value::Null = value {
                    return Err(ToTomlValueError::UnsupportedType((Value::Null, {
                        let mut path_new = path.to_owned();
                        path_new.push(PathEntry::Field(if let Key::String(s) = key {
                            s.to_owned()
                        } else {
                            panic!("Failed to parse key");
                        }));

                        path_new
                    })));
                }

                obj.insert(
                    key_to_string(key),
                    to_toml_value(value, &{
                        let mut path_new = path.to_owned();
                        path_new.push(match key {
                            Key::String(s) => PathEntry::Field(s.to_owned()),
                            Key::Int(i) => PathEntry::Index((*i).try_into().unwrap()),
                        });

                        path_new
                    })?,
                );
            }

            Ok(TomlValue::from(obj))
        }
        Value::Null => Err(ToTomlValueError::UnsupportedType((
            Value::Null,
            path.to_vec(),
        ))),
    }
}