diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 7c6c345..35f870e 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -6,6 +6,8 @@ == [Unreleased] +- fix deserialization failing on arrays and objects in some cases + == [v0.2.1] - 2022-12-28 === Fixed diff --git a/src/de.rs b/src/de.rs index e3b2286..8d35613 100644 --- a/src/de.rs +++ b/src/de.rs @@ -53,6 +53,16 @@ impl<'de> Deserializer<'de> { Some(self.input.fragment().to_string()), ) } + + fn error_with_token(&self, code: ErrorCode, token: Token) -> Error { + Error::with_token( + code, + self.input.location_line(), + self.input.get_utf8_column(), + Some(self.input.fragment().to_string()), + token, + ) + } } pub fn from_str<'a, T>(input: &'a str) -> Result @@ -79,13 +89,15 @@ impl<'de, 'a> serde::de::Deserializer<'de> for &'a mut Deserializer<'de> { return Err(self.error(ErrorCode::ExpectedTopLevelObject)); } - match self.next_token()? { - Token::Boolean(val) => visitor.visit_bool::(val), - Token::Float(val) => visitor.visit_f64(val), - Token::Integer(val) => visitor.visit_i64(val), - Token::Null => visitor.visit_unit(), - Token::String(val) => visitor.visit_str(&val), - _ => Err(self.error(ErrorCode::ExpectedValue)), + match self.peek_token()? { + Token::Boolean(_) => self.deserialize_bool(visitor), + Token::Float(_) => self.deserialize_f64(visitor), + Token::Integer(_) => self.deserialize_i64(visitor), + Token::Null => self.deserialize_unit(visitor), + Token::String(_) => self.deserialize_str(visitor), + Token::ArrayStart => self.deserialize_seq(visitor), + Token::ObjectStart => self.deserialize_map(visitor), + token => Err(self.error_with_token(ErrorCode::ExpectedValue, token)), } } @@ -731,4 +743,53 @@ render_config = "core/rendering/renderer" let actual = from_str::<()>(json); assert_eq!(actual, Err(err)); } + + #[test] + fn deserialize_array() { + #[derive(Debug, Default, serde::Deserialize, PartialEq)] + struct Data { + array: Vec, + } + + let expected = Data { + array: vec![String::from("foo")], + }; + + let sjson = r#" +array = [ + "foo" +] +"#; + assert_ok!(Data, expected, sjson); + } + + // Regression test for #1 (https://git.sclu1034.dev/lucas/serde_sjson/issues/1) + #[test] + fn deserialize_dtmt_config() { + #[derive(Debug, Default, serde::Deserialize, PartialEq)] + struct DtmtConfig { + name: String, + #[serde(default)] + description: String, + version: Option, + } + + let sjson = r#" +name = "test-mod" +description = "A dummy project to test things with" +version = "0.1.0" + +packages = [ + "packages/test-mod" +] +"#; + + let expected = DtmtConfig { + name: String::from("test-mod"), + description: String::from("A dummy project to test things with"), + version: Some(String::from("0.1.0")), + }; + + assert_ok!(DtmtConfig, expected, sjson); + } } diff --git a/src/error.rs b/src/error.rs index 6e604b4..621ad60 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,7 @@ use std::fmt; +use crate::parser::Token; + pub type Result = std::result::Result; #[derive(PartialEq)] @@ -13,6 +15,7 @@ struct ErrorImpl { line: u32, column: usize, fragment: Option, + token: Option, } #[derive(PartialEq)] @@ -89,11 +92,12 @@ impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "Error({:?}, line: {}, column: {}, fragment: {:?})", + "Error({:?}, line: {}, column: {}, fragment: {:?}, token: {:?})", self.inner.code.to_string(), self.inner.line, self.inner.column, self.inner.fragment, + self.inner.token, ) } } @@ -108,6 +112,7 @@ impl serde::de::Error for Error { line: 0, column: 0, fragment: None, + token: None, }); Self { inner } } @@ -123,6 +128,7 @@ impl serde::ser::Error for Error { line: 0, column: 0, fragment: None, + token: None, }); Self { inner } } @@ -138,6 +144,24 @@ impl Error { line, column, fragment, + token: None, + }), + } + } + pub(crate) fn with_token( + code: ErrorCode, + line: u32, + column: usize, + fragment: Option, + token: Token, + ) -> Self { + Self { + inner: Box::new(ErrorImpl { + code, + line, + column, + fragment, + token: Some(token), }), } } diff --git a/src/parser.rs b/src/parser.rs index 0371881..e325108 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -203,6 +203,32 @@ mod test { }}; } + fn check_parse_result, T: AsRef<[Token]>>(input: S, tokens: T) { + let tokens = tokens.as_ref(); + let mut remaining = Span::from(input.as_ref()); + let mut i = 0; + + loop { + if remaining.fragment().is_empty() { + break; + } + + let (span, token) = + super::parse_next_token(remaining).expect("failed to parse next token"); + + assert_eq!(Some(&token), tokens.get(i)); + + remaining = span; + i = i + 1; + } + + assert_eq!( + tokens.len(), + i, + "tokens to check against were not exhausted" + ); + } + #[test] fn parse_optional() { assert_ok!("\n", whitespace, "", '\n'); @@ -265,6 +291,7 @@ mod test { assert_ok!(r#""foo123""#, delimited_string, "", "foo123"); assert_ok!(r#""123foo""#, delimited_string, "", "123foo"); assert_ok!(r#""foo\"bar""#, delimited_string, "", "foo\\\"bar"); + assert_ok!(r#""foo/bar""#, delimited_string, "", "foo/bar"); assert_err!("foo\"", delimited_string, ErrorKind::Char); @@ -302,4 +329,39 @@ mod test { assert_ok!("/* foo */", block_comment, "", " foo "); assert_ok!("/*\n\tfoo\nbar\n*/", block_comment, "", "\n\tfoo\nbar\n"); } + + // Regression test for #1 (https://git.sclu1034.dev/lucas/serde_sjson/issues/1) + #[test] + fn parse_dtmt_config() { + let sjson = r#" +name = "test-mod" +description = "A dummy project to test things with" +version = "0.1.0" + +packages = [ + "packages/test-mod" +] +"#; + + check_parse_result( + sjson, + [ + Token::String(String::from("name")), + Token::Equals, + Token::String(String::from("test-mod")), + Token::String(String::from("description")), + Token::Equals, + Token::String(String::from("A dummy project to test things with")), + Token::String(String::from("version")), + Token::Equals, + Token::String(String::from("0.1.0")), + Token::String(String::from("packages")), + Token::Equals, + Token::ArrayStart, + Token::String(String::from("packages/test-mod")), + Token::ArrayEnd, + Token::Eof, + ], + ); + } }