Skip to content

Commit

Permalink
refactor: Use a common from_value method
Browse files Browse the repository at this point in the history
DRY: The `json`, `json5` and `toml` parsers all leverage `serde` and can share a common enum to deserialize data into, instead of individual methods performing roughly the same transformations.

- While `ron` improves their support for serde untagged enums with v0.9, it is still not compatible with this approach (_Their README details why_).
- The `yaml` support doesn't leverage `serde` thus is not compatible.

The new common method is based on the `json5` technique.
- It has been adjusted to reflect the `ValueKind` enum, which could not directly be used due to the `Table` and `Array` types using `Value` as their value storage type instead of self-referencing the enum.
- Very similar to a `impl From`, but supports the complimentary `uri` parameter for each `Value` derived.

Signed-off-by: Brennan Kinney <[email protected]>
  • Loading branch information
polarathene committed Oct 7, 2023
1 parent 55c464e commit 6ca707b
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 119 deletions.
46 changes: 3 additions & 43 deletions src/file/format/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,13 @@ use std::error::Error;

use crate::format;
use crate::map::Map;
use crate::value::{Value, ValueKind};
use crate::value::Value;

pub fn parse(
uri: Option<&String>,
text: &str,
) -> Result<Map<String, Value>, Box<dyn Error + Send + Sync>> {
// Parse a JSON object value from the text
let value = from_json_value(uri, &serde_json::from_str(text)?);
// Parse a JSON input from the provided text
let value = format::from_parsed_value(uri, serde_json::from_str(text)?);
format::extract_root_table(uri, value)
}

fn from_json_value(uri: Option<&String>, value: &serde_json::Value) -> Value {
match *value {
serde_json::Value::String(ref value) => Value::new(uri, ValueKind::String(value.clone())),

serde_json::Value::Number(ref value) => {
if let Some(value) = value.as_i64() {
Value::new(uri, ValueKind::I64(value))
} else if let Some(value) = value.as_f64() {
Value::new(uri, ValueKind::Float(value))
} else {
unreachable!();
}
}

serde_json::Value::Bool(value) => Value::new(uri, ValueKind::Boolean(value)),

serde_json::Value::Object(ref table) => {
let mut m = Map::new();

for (key, value) in table {
m.insert(key.clone(), from_json_value(uri, value));
}

Value::new(uri, ValueKind::Table(m))
}

serde_json::Value::Array(ref array) => {
let mut l = Vec::new();

for value in array {
l.push(from_json_value(uri, value));
}

Value::new(uri, ValueKind::Array(l))
}

serde_json::Value::Null => Value::new(uri, ValueKind::Nil),
}
}
46 changes: 3 additions & 43 deletions src/file/format/json5.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,13 @@ use std::error::Error;

use crate::format;
use crate::map::Map;
use crate::value::{Value, ValueKind};

#[derive(serde::Deserialize, Debug)]
#[serde(untagged)]
pub enum Val {
Null,
Boolean(bool),
Integer(i64),
Float(f64),
String(String),
Array(Vec<Self>),
Object(Map<String, Self>),
}
use crate::value::Value;

pub fn parse(
uri: Option<&String>,
text: &str,
) -> Result<Map<String, Value>, Box<dyn Error + Send + Sync>> {
let value = from_json5_value(uri, json5_rs::from_str::<Val>(text)?);
// Parse a JSON5 input from the provided text
let value = format::from_parsed_value(uri, json5_rs::from_str(text)?);
format::extract_root_table(uri, value)
}

fn from_json5_value(uri: Option<&String>, value: Val) -> Value {
let vk = match value {
Val::Null => ValueKind::Nil,
Val::String(v) => ValueKind::String(v),
Val::Integer(v) => ValueKind::I64(v),
Val::Float(v) => ValueKind::Float(v),
Val::Boolean(v) => ValueKind::Boolean(v),
Val::Object(table) => {
let m = table
.into_iter()
.map(|(k, v)| (k, from_json5_value(uri, v)))
.collect();

ValueKind::Table(m)
}

Val::Array(array) => {
let l = array
.into_iter()
.map(|v| from_json5_value(uri, v))
.collect();

ValueKind::Array(l)
}
};

Value::new(uri, vk)
}
35 changes: 2 additions & 33 deletions src/file/format/toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,7 @@ pub fn parse(
uri: Option<&String>,
text: &str,
) -> Result<Map<String, Value>, Box<dyn Error + Send + Sync>> {
// Parse a TOML value from the provided text
let value = from_toml_value(uri, &toml::from_str(text)?);
// Parse a TOML input from the provided text
let value = format::from_parsed_value(uri, toml::from_str(text)?);
format::extract_root_table(uri, value)
}

fn from_toml_value(uri: Option<&String>, value: &toml::Value) -> Value {
match *value {
toml::Value::String(ref value) => Value::new(uri, value.to_string()),
toml::Value::Float(value) => Value::new(uri, value),
toml::Value::Integer(value) => Value::new(uri, value),
toml::Value::Boolean(value) => Value::new(uri, value),

toml::Value::Table(ref table) => {
let mut m = Map::new();

for (key, value) in table {
m.insert(key.clone(), from_toml_value(uri, value));
}

Value::new(uri, m)
}

toml::Value::Array(ref array) => {
let mut l = Vec::new();

for value in array {
l.push(from_toml_value(uri, value));
}

Value::new(uri, l)
}

toml::Value::Datetime(ref datetime) => Value::new(uri, datetime.to_string()),
}
}
50 changes: 50 additions & 0 deletions src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,53 @@ pub fn extract_root_table(
.map_err(|err| ConfigError::invalid_root(uri, err))
.map_err(|err| Box::new(err) as Box<dyn Error + Send + Sync>)
}

// Equivalent to ValueKind, except Table + Array store the same enum
// Useful for serde to serialize values into, then convert to Value
#[derive(serde::Deserialize, Debug)]
#[serde(untagged)]
pub enum ParsedValue {
Nil,
Boolean(bool),
I64(i64),
I128(i128),
U64(u64),
U128(u128),
Float(f64),
String(String),
Table(Map<String, Self>),
Array(Vec<Self>),
}

// Value wrap ValueKind values, with optional uri (origin)
pub fn from_parsed_value(uri: Option<&String>, value: ParsedValue) -> Value {
let vk = match value {
ParsedValue::Nil => ValueKind::Nil,
ParsedValue::String(v) => ValueKind::String(v),
ParsedValue::I64(v) => ValueKind::I64(v),
ParsedValue::I128(v) => ValueKind::I128(v),
ParsedValue::U64(v) => ValueKind::U64(v),
ParsedValue::U128(v) => ValueKind::U128(v),
ParsedValue::Float(v) => ValueKind::Float(v),
ParsedValue::Boolean(v) => ValueKind::Boolean(v),
ParsedValue::Table(table) => {
let m = table
.into_iter()
.map(|(k, v)| (k, from_parsed_value(uri, v)))
.collect();

ValueKind::Table(m)
}

ParsedValue::Array(array) => {
let l = array
.into_iter()
.map(|v| from_parsed_value(uri, v))
.collect();

ValueKind::Array(l)
}
};

Value::new(uri, vk)
}

0 comments on commit 6ca707b

Please sign in to comment.