use std::{fs, path::PathBuf};
use serde::Serialize;
use crate::checkers::base::CheckError;
use super::generic::{Array, Mapping, MappingError, Value};
pub(crate) fn from_path(path: PathBuf) -> Result<Box<dyn Mapping>, CheckError> {
let file_contents = fs::read_to_string(path)?;
from_string(&file_contents)
}
pub(crate) fn from_string(doc: &str) -> Result<Box<dyn Mapping>, CheckError> {
if doc.trim().is_empty() {
return Ok(Box::new(serde_json::Map::new()));
}
let doc: serde_json::Value =
serde_json::from_str(doc).map_err(|e| CheckError::InvalidFileFormat(e.to_string()))?;
let doc = doc
.as_object()
.ok_or(CheckError::InvalidFileFormat("No object".to_string()))?;
Ok(Box::new(doc.clone()))
}
impl Mapping for serde_json::Map<String, serde_json::Value> {
fn to_string(&self, indent: usize) -> Result<String, CheckError> {
if self.is_empty() {
return Ok("".to_string());
}
let buf = Vec::new();
let indent = " ".repeat(indent);
let formatter = serde_json::ser::PrettyFormatter::with_indent(indent.as_bytes());
let mut ser = serde_json::Serializer::with_formatter(buf, formatter);
self.serialize(&mut ser).unwrap();
Ok(String::from_utf8(ser.into_inner()).unwrap() + "\n")
}
fn contains_key(&self, key: &str) -> bool {
self.contains_key(key)
}
fn get_mapping(
&mut self,
key: &str,
create_missing: bool,
) -> Result<&mut dyn Mapping, MappingError> {
if !self.contains_key(key) {
if !create_missing {
return Err(MappingError::MissingKey(key.to_string()));
}
self.insert(
key.to_string(),
serde_json::Value::Object(serde_json::Map::new()),
);
}
let value = self.get_mut(key).unwrap();
if !value.is_object() {
Err(MappingError::WrongType(format!("{key} is not a mapping")))
} else {
Ok(value.as_object_mut().unwrap())
}
}
fn get_array(
&mut self,
key: &str,
create_missing: bool,
) -> Result<&mut dyn Array, MappingError> {
if !self.contains_key(key) {
if !create_missing {
return Err(MappingError::MissingKey(key.to_string()));
}
self.insert(key.to_string(), serde_json::Value::Array(vec![]));
}
let value = self.get_mut(key).unwrap();
if !value.is_array() {
Err(MappingError::WrongType(format!("{key} is not an array")))
} else {
Ok(value)
}
}
fn get_string(&self, key: &str) -> Result<String, MappingError> {
if !self.contains_key(key) {
return Err(MappingError::MissingKey(key.to_string()));
}
let value = self.get(key).unwrap();
if !value.is_string() {
Err(MappingError::WrongType(format!("{key} is not a string")))
} else {
Ok(value.as_str().unwrap().to_string())
}
}
fn insert(&mut self, key: &toml_edit::Key, value: &toml_edit::Item) {
self.insert(key.to_string(), serde_json::Value::from_toml_value(value));
}
fn remove(&mut self, key: &str) {
if self.contains_key(key) {
self.remove(key);
}
}
}
impl Array for serde_json::Value {
fn insert_when_not_present(&mut self, value: &toml_edit::Item) {
let value = serde_json::Value::from_toml_value(value);
if !self.as_array().unwrap().contains(&value) {
self.as_array_mut().unwrap().push(value);
}
}
fn remove(&mut self, value: &toml_edit::Item) {
let value = serde_json::Value::from_toml_value(value);
let array = self.as_array_mut().unwrap();
for (idx, array_item) in array.iter().enumerate() {
if *array_item == value {
array.remove(idx);
return;
}
}
}
fn contains_item(&self, value: &toml_edit::Item) -> bool {
let value = serde_json::Value::from_toml_value(value);
self.as_array().unwrap().contains(&value)
}
}
impl Value for serde_json::Value {
fn from_toml_value(value: &toml_edit::Item) -> serde_json::Value {
match value {
toml_edit::Item::Value(toml_edit::Value::String(v)) => {
serde_json::Value::String(v.value().to_owned())
}
toml_edit::Item::Value(toml_edit::Value::Integer(v)) => {
serde_json::Value::Number(serde_json::Number::from(*v.value()))
}
toml_edit::Item::Value(toml_edit::Value::Float(v)) => {
serde_json::Value::Number(serde_json::Number::from_f64(*v.value()).unwrap())
}
toml_edit::Item::Value(toml_edit::Value::Boolean(v)) => {
serde_json::Value::Bool(*v.value())
}
toml_edit::Item::Value(toml_edit::Value::Datetime(v)) => {
serde_json::Value::String(v.value().to_string())
}
toml_edit::Item::Value(toml_edit::Value::Array(v)) => {
let mut a = vec![];
for v_item in v {
a.push(serde_json::Value::from_toml_value(&toml_edit::Item::Value(
v_item.to_owned(),
)))
}
serde_json::Value::Array(a)
}
toml_edit::Item::Table(v) => {
let mut a: serde_json::Map<String, serde_json::Value> = serde_json::Map::new();
for (k, v_item) in v {
a.insert(k.to_string(), serde_json::Value::from_toml_value(v_item));
}
serde_json::Value::Object(a)
}
toml_edit::Item::Value(toml_edit::Value::InlineTable(v)) => {
let mut a: serde_json::Map<String, serde_json::Value> = serde_json::Map::new();
for (k, v_item) in v {
a.insert(
k.to_string(),
serde_json::Value::from_toml_value(&toml_edit::Item::Value(
v_item.to_owned(),
)),
);
}
serde_json::Value::Object(a)
}
toml_edit::Item::ArrayOfTables(v) => {
let mut a = vec![];
for v_item in v {
a.push(serde_json::Value::from_toml_value(&toml_edit::Item::Table(
v_item.to_owned(),
)))
}
serde_json::Value::Array(a)
}
_ => serde_json::Value::Null,
}
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::super::generic::tests::get_test_table;
use super::super::generic::tests::test_mapping;
use super::*;
#[test]
fn test_access_map() {
let table = get_test_table();
let binding = serde_json::Value::from_toml_value(&table);
let mut mapping_to_check = binding.as_object().unwrap().to_owned();
test_mapping(Box::new(mapping_to_check.clone()));
assert_eq!(
mapping_to_check
.get_mapping("dict", false)
.expect("")
.to_string(4)
.unwrap(),
"{\n \"array\": [\n 1,\n 2\n ],\n \"bool\": true,\n \"float\": 1.1,\n \"int\": 1,\n \"str\": \"string\"\n}\n".to_string()
);
assert_eq!(
mapping_to_check
.get_mapping("dict", false)
.expect("")
.to_string(2)
.unwrap(),
"{\n \"array\": [\n 1,\n 2\n ],\n \"bool\": true,\n \"float\": 1.1,\n \"int\": 1,\n \"str\": \"string\"\n}\n".to_string()
);
}
#[test]
fn test_from_toml_value() {
let table = get_test_table();
let json_table = serde_json::Value::from_toml_value(&table);
assert_eq!(
json_table,
json!({ "str": "string", "int": 1, "float": 1.1, "bool": true, "array": [1, 2], "dict": {"str": "string", "int": 1, "float": 1.1, "bool": true, "array": [1, 2],
}})
);
}
}