Fun with traits for better ergonomics of the JSONValue Enum

This commit is contained in:
Timothy Warren 2020-02-11 19:12:01 -05:00
parent a4cb3f9b0e
commit 97bfd16b32

View File

@ -7,6 +7,7 @@ use std::iter::FromIterator;
use std::{char, u16}; use std::{char, u16};
pub type JSONResult = Result<JSONValue, ParseError>; pub type JSONResult = Result<JSONValue, ParseError>;
pub type JSONArray = Vec<JSONValue>;
pub type JSONMap = HashMap<String, JSONValue>; pub type JSONMap = HashMap<String, JSONValue>;
/// The type of JSON value /// The type of JSON value
@ -34,6 +35,139 @@ pub enum JSONValue {
Null, Null,
} }
impl JSONValue {
/// Convert the wrapped JSONValue to its simpler rust value
///
/// Example:
/// ```
/// use naive_json_parser::JSONValue;
///
/// let str = "Four score and seven years ago...";
/// let wrapped = JSONValue::from(str);
///
/// // s is now the `String` that was in the `JSONValue` enum
/// let s: String = wrapped.unwrap();
///
/// # assert_eq!(str, &s);
/// ```
pub fn unwrap<T: From<JSONValue>>(self) -> T {
T::from(self)
}
}
impl From<JSONValue> for JSONMap {
/// Extracts the `HashMap` in the `JSONValue` enum, if it exists.
/// Otherwise, panics.
fn from(val: JSONValue) -> JSONMap {
match val {
JSONValue::Object(o) => o,
_ => panic!("Invalid type conversion")
}
}
}
impl From<JSONValue> for JSONArray{
/// Extracts the `Vec` in the `JSONValue` enum, if it exists.
/// Otherwise, panics.
fn from(val: JSONValue) -> JSONArray {
match val {
JSONValue::Array(a) => a,
_ => panic!("Invalid type conversion")
}
}
}
impl From<JSONValue> for f64 {
/// Extracts the `f64` in the `JSONValue` enum, if it exists.
/// Otherwise, panics.
fn from(val: JSONValue) -> f64 {
match val {
JSONValue::Number(n) => n,
_ => panic!("Invalid type conversion")
}
}
}
impl From<JSONValue> for String {
/// Extracts the `String` in the `JSONValue` enum, if it exists.
/// Otherwise, panics.
fn from(val: JSONValue) -> String {
match val {
JSONValue::String(s) => s,
_ => panic!("Invalid type conversion")
}
}
}
impl From<JSONValue> for bool {
/// Extracts the `bool` value from the `JSONValue` enum, if it exists.
/// Otherwise, panics.
fn from(val: JSONValue) -> bool {
match val {
JSONValue::True => true,
JSONValue::False => false,
_ => panic!("Invalid type conversion")
}
}
}
impl From<JSONValue> for () {
/// This will just swallow the enum value and return a unit tuple
fn from(_: JSONValue) -> () { () }
}
impl From<JSONMap> for JSONValue {
/// Wraps the `HashMap` in the `JSONValue` enum
fn from(val: JSONMap) -> JSONValue {
Self::Object(val)
}
}
impl From<JSONArray> for JSONValue {
/// Wraps the `Vec` in the `JSONValue` enum
fn from(val: JSONArray) -> JSONValue {
Self::Array(val)
}
}
impl From<bool> for JSONValue {
/// Sets the `JSONValue` enum to the `True` or `False` value
fn from(val: bool) -> Self {
match val {
true => Self::True,
false => Self::False,
}
}
}
impl From<f64> for JSONValue {
/// Wraps the `f64` in the `JSONValue` enum
fn from(n: f64) -> Self {
Self::Number(n)
}
}
impl From<()> for JSONValue {
/// Sets the `JSONValue` enum to the `Null` value
fn from(_s: ()) -> Self {
Self::Null
}
}
impl From<String> for JSONValue {
/// Wraps the `String` in the `JSONValue` enum
fn from(s: String) -> Self {
Self::String(s)
}
}
impl From<&str> for JSONValue {
/// Creates a `String` and wraps it in the `JSONValue` enum
fn from(s: &str) -> Self {
Self::String(String::from(s))
}
}
/// The type of error returned by the parser /// The type of error returned by the parser
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum ParseError { pub enum ParseError {
@ -111,9 +245,7 @@ impl JSON {
); );
// Every parser failed, so the syntax is probably incorrect // Every parser failed, so the syntax is probably incorrect
Err(ParseError::UnexpectedEndOfInput(String::from( Err(ParseError::UnexpectedEndOfInput(format!("Doesn't seem to be valid JSON")))
"Doesn't seem to be valid JSON",
)))
} }
/// See if there's a `JSONValue::Object` next in the JSON /// See if there's a `JSONValue::Object` next in the JSON
@ -315,7 +447,7 @@ impl JSON {
match str.parse::<f64>() { match str.parse::<f64>() {
Ok(number) => { Ok(number) => {
self.increment(str.len()); self.increment(str.len());
return Ok(Some(JSONValue::Number(number))); return Ok(Some(JSONValue::from(number)));
} }
Err(e) => Err(ParseError::ExpectedDigit(format!("'{}', {:#?}", str, e))), Err(e) => Err(ParseError::ExpectedDigit(format!("'{}', {:#?}", str, e))),
} }
@ -391,20 +523,39 @@ mod tests {
use super::JSONValue::{Array, False, Null, Number, Object, True}; use super::JSONValue::{Array, False, Null, Number, Object, True};
use super::*; use super::*;
impl JSONValue { #[test]
fn unwrap_object(self) -> JSONMap { fn value_conversion() {
match self { let map: JSONMap = HashMap::new();
JSONValue::Object(o) => o, let num = 9.380831539;
_ => panic!("Tried to unwrap a non-object"), let str = "applesauce";
} let arr: JSONArray = vec![JSONValue::from(map.clone()), JSONValue::from(num), JSONValue::from(str)];
}
fn unwrap_array(self) -> Vec<JSONValue> { assert_eq!(map.clone(), JSONMap::from(JSONValue::from(map.clone())));
match self { assert_eq!(num, f64::from(JSONValue::from(num)));
JSONValue::Array(a) => a, assert_eq!(String::from(str), String::from(JSONValue::from(str)));
_ => panic!("Tried to unwrap a non-array"), assert_eq!(arr.clone(), JSONArray::from(JSONValue::from(arr.clone())));
} assert_eq!(true, bool::from(JSONValue::from(true)));
} assert_eq!(false, bool::from(JSONValue::from(false)));
assert_eq!((), <()>::from(JSONValue::from(())));
}
#[test]
fn wrap_and_unwrap() {
let map: JSONMap = HashMap::new();
let num = 9.380831539;
let str = "applesauce";
let arr: JSONArray = vec![JSONValue::from(map.clone()), JSONValue::from(num), JSONValue::from(str)];
let s: String = JSONValue::from(str).unwrap();
let a: JSONArray = JSONValue::from(arr.clone()).unwrap();
assert_eq!(map.clone(), JSONValue::from(map.clone()).unwrap());
assert_eq!(num, JSONValue::from(num).unwrap());
assert_eq!(str, &s);
assert_eq!(arr.clone(), a);
assert_eq!(true, JSONValue::from(true).unwrap());
assert_eq!(false, JSONValue::from(false).unwrap());
assert_eq!((), JSONValue::from(()).unwrap());
} }
#[test] #[test]
@ -426,10 +577,10 @@ mod tests {
#[test] #[test]
fn parse_string() { fn parse_string() {
let res = JSON::new(r#""\t""#).parse_string(); let res = JSON::new(r#""\t""#).parse_string();
assert_eq!(res, Ok(Some(JSONValue::String(String::from("\t"))))); assert_eq!(res, Ok(Some(JSONValue::from("\t"))));
let res = JSON::new(r#""\u203d""#).parse_string(); let res = JSON::new(r#""\u203d""#).parse_string();
assert_eq!(res, Ok(Some(JSONValue::String(String::from(""))))); assert_eq!(res, Ok(Some(JSONValue::from(""))));
} }
#[test] #[test]
@ -468,7 +619,7 @@ mod tests {
let result = JSON::new(r#"{"foo": "bar"}"#).parse_object(); let result = JSON::new(r#"{"foo": "bar"}"#).parse_object();
let mut hash_map: JSONMap = HashMap::new(); let mut hash_map: JSONMap = HashMap::new();
hash_map.insert(String::from("foo"), JSONValue::String(String::from("bar"))); hash_map.insert(String::from("foo"), JSONValue::from("bar"));
assert_eq!(result, Ok(Some(JSONValue::Object(hash_map)))); assert_eq!(result, Ok(Some(JSONValue::Object(hash_map))));
} }
@ -491,7 +642,7 @@ mod tests {
let res = JSON::parse(r#""/^$/""#); let res = JSON::parse(r#""/^$/""#);
assert_eq!( assert_eq!(
res, res,
Ok(JSONValue::String(String::from("/^$/"))), Ok(JSONValue::from("/^$/")),
"Failed to parse string" "Failed to parse string"
); );
@ -542,16 +693,16 @@ mod tests {
let result = let result =
JSON::parse(r#"["\"", "\\", "/", "\b", "\f", "\n", "\r", "\t", "\u0001", "\uface"]"#); JSON::parse(r#"["\"", "\\", "/", "\b", "\f", "\n", "\r", "\t", "\u0001", "\uface"]"#);
let expected = Ok(Array(vec![ let expected = Ok(Array(vec![
JSONValue::String(String::from("\"")), JSONValue::from("\""),
JSONValue::String(String::from("\\")), JSONValue::from("\\"),
JSONValue::String(String::from("/")), JSONValue::from("/"),
JSONValue::String(String::from("\u{8}")), JSONValue::from("\u{8}"),
JSONValue::String(String::from("\x0C")), JSONValue::from("\x0C"),
JSONValue::String(String::from("\n")), JSONValue::from("\n"),
JSONValue::String(String::from("\r")), JSONValue::from("\r"),
JSONValue::String(String::from("\t")), JSONValue::from("\t"),
JSONValue::String(String::from("\u{1}")), JSONValue::from("\u{1}"),
JSONValue::String(String::from("\u{face}")), JSONValue::from("\u{face}"),
])); ]));
assert_eq!(result, expected); assert_eq!(result, expected);
@ -591,27 +742,29 @@ mod tests {
String::from("c"), String::from("c"),
Array(vec![Number(1f64), Number(2f64), Number(3f64)]), Array(vec![Number(1f64), Number(2f64), Number(3f64)]),
); );
map.insert(String::from("d"), JSONValue::String(String::from("foo"))); map.insert(String::from("d"), JSONValue::from("foo"));
map.insert(String::from("e"), Object(emap)); map.insert(String::from("e"), Object(emap));
map.insert( map.insert(
String::from("i"), String::from("i"),
Array(vec![ Array(vec![
JSONValue::String(String::from("\"")), JSONValue::from("\""),
JSONValue::String(String::from("\\")), JSONValue::from("\\"),
JSONValue::String(String::from("/")), JSONValue::from("/"),
JSONValue::String(String::from("\u{8}")), JSONValue::from("\u{8}"),
JSONValue::String(String::from("\x0C")), JSONValue::from("\x0C"),
JSONValue::String(String::from("\n")), JSONValue::from("\n"),
JSONValue::String(String::from("\r")), JSONValue::from("\r"),
JSONValue::String(String::from("\t")), JSONValue::from("\t"),
JSONValue::String(String::from("\u{1}")), JSONValue::from("\u{1}"),
JSONValue::String(String::from("\u{face}")), JSONValue::from("\u{face}"),
]), ]),
); );
assert!(result.is_ok(), format!("{:#?}", result)); assert!(result.is_ok(), format!("{:#?}", result));
let result_map = result.unwrap().unwrap_array()[0].clone().unwrap_object();
let outer_array: Vec<JSONValue> = result.unwrap().unwrap();
let result_map: JSONMap = outer_array[0].clone().unwrap();
for (k, v) in &map { for (k, v) in &map {
assert_eq!( assert_eq!(