From 9af3bd629ce1e87946ddeedfca371a4da92d92d1 Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Wed, 12 Feb 2020 11:22:02 -0500 Subject: [PATCH] Add more error checking --- src/lib.rs | 288 +++++++++++++++++------------------------------------ 1 file changed, 89 insertions(+), 199 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 05f44c9..879ccc7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,18 +61,18 @@ impl From for JSONMap { fn from(val: JSONValue) -> JSONMap { match val { JSONValue::Object(o) => o, - _ => panic!("Invalid type conversion") + _ => unreachable!(), } } } -impl From for JSONArray{ +impl From 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") + _ => unreachable!(), } } } @@ -83,7 +83,7 @@ impl From for f64 { fn from(val: JSONValue) -> f64 { match val { JSONValue::Number(n) => n, - _ => panic!("Invalid type conversion") + _ => unreachable!(), } } } @@ -94,7 +94,7 @@ impl From for String { fn from(val: JSONValue) -> String { match val { JSONValue::String(s) => s, - _ => panic!("Invalid type conversion") + _ => unreachable!(), } } } @@ -106,14 +106,16 @@ impl From for bool { match val { JSONValue::True => true, JSONValue::False => false, - _ => panic!("Invalid type conversion") + _ => unreachable!(), } } } impl From for () { /// This will just swallow the enum value and return a unit tuple - fn from(_: JSONValue) -> () { () } + fn from(_: JSONValue) -> () { + () + } } impl From for JSONValue { @@ -245,7 +247,9 @@ impl JSON { ); // Every parser failed, so the syntax is probably incorrect - Err(ParseError::UnexpectedEndOfInput(format!("Doesn't seem to be valid JSON"))) + Err(ParseError::UnexpectedEndOfInput(format!( + "Doesn't seem to be valid JSON" + ))) } /// See if there's a `JSONValue::Object` next in the JSON @@ -270,20 +274,21 @@ impl JSON { if initial == false { self.eat(',')?; self.skip_whitespace(); + } else { + self.skip_whitespace(); + } + + let maybe_key = self.parse_string()?; + if maybe_key.is_none() { + return Err(ParseError::ExpectedObjectKey(format!( + "Expected an object key. Does the object have a trailing comma?" + ))); } - self.skip_whitespace(); - - let key = match self.parse_string()? { - Some(value) => match value { - JSONValue::String(s) => s, - _ => panic!("parse_string returned non-string value"), - }, - None => String::new(), - }; - self.skip_whitespace(); self.eat(':')?; + + let key = maybe_key.unwrap().into(); let value = self.parse_value()?; result.insert(key, value); @@ -293,6 +298,7 @@ impl JSON { self.skip_whitespace(); } + self.expect_not_end('}')?; // Move to the next character: '}' self.increment(1); @@ -322,6 +328,7 @@ impl JSON { initial = false; } + self.expect_not_end(']')?; // move to next character: ']' self.increment(1); @@ -360,22 +367,31 @@ impl JSON { }; result.push(ch); self.increment(1); - } else if ch == 'u' - && self.chars[self.i + 2].is_ascii_hexdigit() - && self.chars[self.i + 3].is_ascii_hexdigit() - && self.chars[self.i + 4].is_ascii_hexdigit() - && self.chars[self.i + 5].is_ascii_hexdigit() - { - // Blech, parse out a JSON unicode (utf16) escape code. Handles surrogate pairs - // by giving you the replacement character...because...yeah - let char_str = String::from_iter(&self.chars[self.i + 2..=self.i + 5]); - let code = u16::from_str_radix(&char_str, 16) - .expect("Failed to parse unicode escape number"); - let string = String::from_utf16_lossy(&[code]); + } else if ch == 'u' { + if self.chars[self.i + 2].is_ascii_hexdigit() + && self.chars[self.i + 3].is_ascii_hexdigit() + && self.chars[self.i + 4].is_ascii_hexdigit() + && self.chars[self.i + 5].is_ascii_hexdigit() + { + // Blech, parse out a JSON unicode (utf16) escape code. Handles surrogate pairs + // by giving you the replacement character...because...yeah + let char_str = String::from_iter(&self.chars[self.i + 2..=self.i + 5]); + let code = u16::from_str_radix(&char_str, 16) + .expect("Failed to parse unicode escape number"); + let string = String::from_utf16_lossy(&[code]); - result.push_str(&string); + result.push_str(&string); - self.increment(5); + self.increment(5); + } else { + return Err(ParseError::ExpectedUnicodeEscape(format!( + "Expected a unicode escape sequence" + ))); + } + } else { + return Err(ParseError::ExpectedEscapeChar(format!( + "Expected an escape sequence" + ))); } } else { result.push(self.chars[self.i]); @@ -383,7 +399,8 @@ impl JSON { self.increment(1); } - self.eat('"')?; + self.expect_not_end('"')?; + self.increment(1); Ok(Some(JSONValue::from(result))) } @@ -404,6 +421,7 @@ impl JSON { // Minus sign if self.chars[n] == '-' && n < max { n += 1; + self.expect_digit(start, n)?; } // Integer Part @@ -414,6 +432,7 @@ impl JSON { // Decimal Part if self.chars[n] == '.' && n < max { n += 1; + self.expect_digit(start, n)?; while self.chars[n].is_ascii_digit() && n < max { n += 1; } @@ -428,6 +447,7 @@ impl JSON { } // Exponent base + self.expect_digit(start, n)?; while self.chars[n].is_ascii_digit() && n < max { n += 1; } @@ -512,6 +532,31 @@ impl JSON { } } + /// Check that the next character is a digit. If not, return ParseError. + fn expect_digit(&mut self, start: usize, end: usize) -> Result<(), ParseError> { + let current = String::from_iter(&self.chars[start..end]); + if !self.chars[end].is_ascii_digit() { + Err(ParseError::ExpectedDigit(format!( + "Expected a digit, received '{}' after numeric '{}'", + self.chars[end], current + ))) + } else { + Ok(()) + } + } + + /// Verify that we are not at the end of the input string + fn expect_not_end(&mut self, ch: char) -> Result<(), ParseError> { + if self.i == self.chars.len() { + Err(ParseError::UnexpectedEndOfInput(format!( + "Unexpected end of input. Expected '{}'", + ch + ))) + } else { + Ok(()) + } + } + /// Convert a `&str` containing JSON into a `Result` pub fn parse(json: &str) -> JSONResult { JSON::new(json).parse_value() @@ -520,7 +565,7 @@ impl JSON { #[cfg(test)] mod tests { - use super::JSONValue::{Array, False, Null, Number, Object, True}; + use super::JSONValue::{Array, Number, True}; use super::*; #[test] @@ -528,7 +573,11 @@ mod tests { 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 arr: JSONArray = vec![ + JSONValue::from(map.clone()), + JSONValue::from(num), + JSONValue::from(str), + ]; assert_eq!(map.clone(), JSONMap::from(JSONValue::from(map.clone()))); assert_eq!(num, f64::from(JSONValue::from(num))); @@ -544,7 +593,11 @@ mod tests { 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 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(); @@ -597,9 +650,6 @@ mod tests { let res = JSON::new(r#""foo""#).parse_number(); assert_eq!(res, Ok(None)); - let res = JSON::new("3.14159").parse_number(); - assert_eq!(res, Ok(Some(Number(3.14159f64)))); - let res = JSON::new("3e4").parse_number(); assert_eq!(res, Ok(Some(Number(3e4f64)))); @@ -607,13 +657,6 @@ mod tests { assert_eq!(res, Ok(Some(Number(1.234f64)))); } - #[test] - fn can_parse_array_of_keywords() { - let result = JSON::parse("[true,false,null]"); - - assert_eq!(result, Ok(Array(vec![True, False, Null]))); - } - #[test] fn parse_object() { let result = JSON::new(r#"{"foo": "bar"}"#).parse_object(); @@ -623,157 +666,4 @@ mod tests { assert_eq!(result, Ok(Some(JSONValue::Object(hash_map)))); } - - #[test] - fn parse_json_types() { - // Boolean / Null - let res = JSON::parse("true"); - assert_eq!(res, Ok(True)); - let res = JSON::parse("false"); - assert_eq!(res, Ok(False)); - let res = JSON::parse("null"); - assert_eq!(res, Ok(Null)); - - // Number - let res = JSON::parse("9.38083151965"); - assert_eq!(res, Ok(Number(9.38083151965)), "Failed to parse number"); - - // String - let res = JSON::parse(r#""/^$/""#); - assert_eq!( - res, - Ok(JSONValue::from("/^$/")), - "Failed to parse string" - ); - - // Number array - let res = JSON::parse("[1, 2, 3]"); - assert_eq!( - res, - Ok(Array(vec![Number(1f64), Number(2f64), Number(3f64)])) - ); - - // Object array - let result = JSON::parse("[{}]"); - assert_eq!(result, Ok(JSONValue::Array(vec![Object(HashMap::new())]))); - } - - #[test] - fn parse_nested_object() { - let res = JSON::parse(r#"{"a": {"b": []}}"#); - let mut outermap: JSONMap = HashMap::new(); - let mut innermap: JSONMap = HashMap::new(); - - innermap.insert(String::from("b"), Array(vec![])); - outermap.insert(String::from("a"), Object(innermap)); - - let expected = Ok(Object(outermap)); - - assert_eq!(res, expected); - } - - #[test] - fn parse_object_with_number_values() { - let result = JSON::parse(r#"[{ "a": 9.38083151965, "b": 4e3 }]"#); - let mut map: JSONMap = HashMap::new(); - map.insert(String::from("a"), Number(9.38083151965f64)); - map.insert(String::from("b"), Number(4e3f64)); - - let expected = Ok(Array(vec![Object(map)])); - - assert_eq!( - result, expected, - "Failed on just number values: {:#?}", - result - ); - } - - #[test] - fn parse_weird_character_array() { - let result = - JSON::parse(r#"["\"", "\\", "/", "\b", "\f", "\n", "\r", "\t", "\u0001", "\uface"]"#); - let expected = Ok(Array(vec![ - JSONValue::from("\""), - JSONValue::from("\\"), - JSONValue::from("/"), - JSONValue::from("\u{8}"), - JSONValue::from("\x0C"), - JSONValue::from("\n"), - JSONValue::from("\r"), - JSONValue::from("\t"), - JSONValue::from("\u{1}"), - JSONValue::from("\u{face}"), - ])); - - assert_eq!(result, expected); - } - - #[test] - fn parse_full_json_example() { - let result = JSON::parse( - r#"[{ - "a": 9.38083151965, - "b": 4e3, - "c": [1, 2, 3], - "d": "foo", - "e": { - "f": { - "g": { - "h": null - } - } - }, - "i": ["\"", "\\", "/", "\b", "\f", "\n", "\r", "\t", "\u0001", "\uface"] -}]"#, - ); - - let mut map: JSONMap = HashMap::new(); - let mut emap: JSONMap = HashMap::new(); - let mut fmap: JSONMap = HashMap::new(); - let mut gmap: JSONMap = HashMap::new(); - - gmap.insert(String::from("h"), Null); - fmap.insert(String::from("g"), Object(gmap)); - emap.insert(String::from("f"), Object(fmap)); - - map.insert(String::from("a"), Number(9.38083151965f64)); - map.insert(String::from("b"), Number(4e3f64)); - map.insert( - String::from("c"), - Array(vec![Number(1f64), Number(2f64), Number(3f64)]), - ); - map.insert(String::from("d"), JSONValue::from("foo")); - map.insert(String::from("e"), Object(emap)); - - map.insert( - String::from("i"), - Array(vec![ - JSONValue::from("\""), - JSONValue::from("\\"), - JSONValue::from("/"), - JSONValue::from("\u{8}"), - JSONValue::from("\x0C"), - JSONValue::from("\n"), - JSONValue::from("\r"), - JSONValue::from("\t"), - JSONValue::from("\u{1}"), - JSONValue::from("\u{face}"), - ]), - ); - - assert!(result.is_ok(), format!("{:#?}", result)); - - let outer_array: Vec = result.unwrap().unwrap(); - let result_map: JSONMap = outer_array[0].clone().unwrap(); - - for (k, v) in &map { - assert_eq!( - result_map.get(k).unwrap(), - v, - "HashMap Entry Differs: {:#?}, {:#?}", - result_map.get(k).unwrap(), - v - ); - } - } }