use ron::{ error::{Position, Span, SpannedError}, Error, }; use serde::Deserialize; #[derive(Debug, Deserialize, PartialEq)] struct BytesStruct { small: Vec, #[serde(with = "serde_bytes")] large: Vec, } #[test] fn rusty_byte_string() { assert_eq!( Ok(BytesStruct { small: vec![1, 2], large: vec![1, 2, 0, 4] }), ron::from_str("BytesStruct( small:[1, 2], large: b\"\\x01\\u{2}\\0\\x04\" )"), ); assert_eq!( ron::from_str::("\"Hello \\x01 \\u{2}!\"").unwrap(), "Hello \x01 \u{2}!", ); assert_eq!( &*ron::from_str::("b\"Hello \\x01 \\u{2}!\"").unwrap(), b"Hello \x01 \x02!", ); rusty_byte_string_roundtrip(b"hello", "b\"hello\"", "b\"hello\""); rusty_byte_string_roundtrip(b"\"", "b\"\\\"\"", "br#\"\"\"#"); rusty_byte_string_roundtrip(b"\"#", "b\"\\\"#\"", "br##\"\"#\"##"); rusty_byte_string_roundtrip(b"\n", "b\"\\n\"", "b\"\n\""); assert_eq!( ron::from_str::("b\"\\xf\"").unwrap_err(), SpannedError { code: Error::InvalidEscape("Non-hex digit found"), span: Span { start: Position { line: 1, col: 3 }, end: Position { line: 1, col: 7 }, } }, ); assert_eq!( ron::from_str::("b\"\\xf🦀\"").unwrap_err(), SpannedError { code: Error::InvalidEscape("Non-hex digit found"), span: Span { start: Position { line: 1, col: 3 }, end: Position { line: 1, col: 7 }, } }, ); let err = ron::from_str::("br#q\"").unwrap_err(); assert_eq!( err, SpannedError { code: Error::ExpectedByteString, span: Span { start: Position { line: 1, col: 3 }, end: Position { line: 1, col: 4 }, } }, ); assert_eq!(format!("{}", err.code), "Expected byte string",); assert_eq!( ron::from_str::("br#\"q").unwrap_err(), SpannedError { code: Error::ExpectedStringEnd, span: Span { start: Position { line: 1, col: 4 }, end: Position { line: 1, col: 5 }, } }, ); assert_eq!( ron::from_str::("r#q\"").unwrap_err(), SpannedError { code: Error::ExpectedString, span: Span { start: Position { line: 1, col: 2 }, end: Position { line: 1, col: 3 }, } }, ); assert_eq!( ron::from_str::("r#\"q").unwrap_err(), SpannedError { code: Error::ExpectedStringEnd, span: Span { start: Position { line: 1, col: 3 }, end: Position { line: 1, col: 4 }, } }, ); } fn rusty_byte_string_roundtrip(bytes: &[u8], ron: &str, ron_raw: &str) { let ser_list = ron::to_string(bytes).unwrap(); let de_list: Vec = ron::from_str(&ser_list).unwrap(); assert_eq!(de_list, bytes); let ser = ron::to_string(&bytes::Bytes::copy_from_slice(bytes)).unwrap(); assert_eq!(ser, ron); let ser_non_raw = ron::ser::to_string_pretty( &bytes::Bytes::copy_from_slice(bytes), ron::ser::PrettyConfig::default(), ) .unwrap(); assert_eq!(ser_non_raw, ron); let ser_raw = ron::ser::to_string_pretty( &bytes::Bytes::copy_from_slice(bytes), ron::ser::PrettyConfig::default().escape_strings(false), ) .unwrap(); assert_eq!(ser_raw, ron_raw); let de: bytes::Bytes = ron::from_str(&ser).unwrap(); assert_eq!(de, bytes); let de_raw: bytes::Bytes = ron::from_str(&ser_raw).unwrap(); assert_eq!(de_raw, bytes); } #[test] fn fuzzer_failures() { assert_eq!( ron::to_string(&bytes::Bytes::copy_from_slice(&[ 123, 0, 0, 0, 0, 214, 214, 214, 214, 214 ])) .unwrap(), r#"b"{\x00\x00\x00\x00\xd6\xd6\xd6\xd6\xd6""# ); // Need to fall back to escaping so no invalid UTF-8 is produced assert_eq!( ron::ser::to_string_pretty( &bytes::Bytes::copy_from_slice(&[123, 0, 0, 0, 0, 214, 214, 214, 214, 214]), ron::ser::PrettyConfig::default().escape_strings(false) ) .unwrap(), r#"b"{\x00\x00\x00\x00\xd6\xd6\xd6\xd6\xd6""# ); assert_eq!( ron::to_string(&bytes::Bytes::copy_from_slice(&[123, 0, 0, 0, 0])).unwrap(), r#"b"{\x00\x00\x00\x00""# ); assert_eq!( ron::ser::to_string_pretty( &bytes::Bytes::copy_from_slice(&[123, 0, 0, 0, 0]), ron::ser::PrettyConfig::default().escape_strings(false) ) .unwrap(), "b\"{\x00\x00\x00\x00\"" ); // `br#` should be parsed as the start of a byte string, not the identifier `br` and a `#` assert_eq!( ron::from_str(r##"br#"""#"##), Ok(ron::Value::Bytes(vec![34])) ); assert_eq!( ron::from_str(r##"{"error": br#"""#}"##), Ok(ron::Value::Map( [( ron::Value::String(String::from("error")), ron::Value::Bytes(vec![34]) )] .into_iter() .collect() )) ); assert_eq!( ron::from_str( r##"#![enable(unwrap_newtypes)] #![enable(unwrap_variant_newtypes)] Some({"error": br#"""#}) "## ), Ok(ron::Value::Option(Some(Box::new(ron::Value::Map( [( ron::Value::String(String::from("error")), ron::Value::Bytes(vec![34]) )] .into_iter() .collect() ))))) ); // `br"` should be parsed as the start of a byte string, not the identifier `br` and a `"` assert_eq!(ron::from_str(r#"br"""#), Ok(ron::Value::Bytes(vec![]))); assert_eq!( ron::from_str(r#"{"error": br""}"#), Ok(ron::Value::Map( [( ron::Value::String(String::from("error")), ron::Value::Bytes(vec![]) )] .into_iter() .collect() )) ); assert_eq!( ron::from_str( r#"#![enable(unwrap_newtypes)] #![enable(unwrap_variant_newtypes)] Some({"error": br""}) "# ), Ok(ron::Value::Option(Some(Box::new(ron::Value::Map( [( ron::Value::String(String::from("error")), ron::Value::Bytes(vec![]) )] .into_iter() .collect() ))))) ); // Test that the struct type check for newtype variant unwrapping does // not enter inside a byte string to find a bad comment start assert_eq!( ron::from_str::>( r#"#![enable(unwrap_variant_newtypes)] Some(b"\xff/not a comment")"# ) .unwrap(), Some(ron::Value::Bytes(b"\xff/not a comment".to_vec())) ); // `b'` should be parsed as the start of a byte literal, not the identifier `b` and a `'` assert_eq!( ron::from_str(r"b'\xff'"), Ok(ron::Value::Number(ron::value::Number::U8(b'\xff'))) ); // `b`, `br`, `bq`, and `brq` should all be parsed as identifiers for id in ["b", "br", "bq", "brq"] { assert_eq!(ron::from_str(id), Ok(ron::Value::Unit)); } } #[test] fn serialize_backslash_byte_string() { check_roundtrip('\\', r"'\\'", r"'\\'"); check_roundtrip( bytes::Bytes::copy_from_slice(b"\\"), r#"b"\\""#, "br#\"\\\"#", ); } fn check_roundtrip< T: PartialEq + core::fmt::Debug + serde::Serialize + serde::de::DeserializeOwned, >( val: T, cmp: &str, cmp_raw: &str, ) { let ron = ron::to_string(&val).unwrap(); assert_eq!(ron, cmp); let ron_escaped = ron::ser::to_string_pretty(&val, ron::ser::PrettyConfig::default().escape_strings(true)) .unwrap(); assert_eq!(ron_escaped, cmp); let ron_raw = ron::ser::to_string_pretty( &val, ron::ser::PrettyConfig::default().escape_strings(false), ) .unwrap(); assert_eq!(ron_raw, cmp_raw); let de = ron::from_str::(&ron).unwrap(); assert_eq!(de, val); let de_raw = ron::from_str::(&ron_raw).unwrap(); assert_eq!(de_raw, val); } #[test] fn test_weird_escapes() { assert_eq!( ron::from_str::(r#""\u{1F980}""#), Ok(String::from("\u{1F980}")) ); assert_eq!( ron::from_str::(r#"b"\xf0\x9f\xa6\x80""#), Ok(bytes::Bytes::copy_from_slice("\u{1F980}".as_bytes())) ); assert_eq!( ron::from_str::(r#""\xf0\x9f\xa6\x80""#), Ok(String::from("\u{1F980}")) ); assert_eq!( ron::from_str::(r#""\xf0""#), Err(SpannedError { code: Error::InvalidEscape("Not a valid byte-escaped Unicode character"), span: Span { start: Position { line: 1, col: 2 }, end: Position { line: 1, col: 6 } } }) ); assert_eq!( ron::from_str::(r#""\xf0\x9f""#), Err(SpannedError { code: Error::InvalidEscape("Not a valid byte-escaped Unicode character"), span: Span { start: Position { line: 1, col: 6 }, end: Position { line: 1, col: 10 } } }) ); assert_eq!( ron::from_str::(r#""\xf0\x9f\x40""#), Err(SpannedError { code: Error::InvalidEscape("Not a valid byte-escaped Unicode character"), span: Span { start: Position { line: 1, col: 10 }, end: Position { line: 1, col: 14 } } }) ); assert_eq!( ron::from_str::(r#""\xf0\x9f\xa6""#), Err(SpannedError { code: Error::InvalidEscape("Not a valid byte-escaped Unicode character"), span: Span { start: Position { line: 1, col: 10 }, end: Position { line: 1, col: 14 } } }) ); assert_eq!( ron::from_str::(r#""\xff\xff\xff\xff""#), Err(SpannedError { code: Error::InvalidEscape("Not a valid byte-escaped Unicode character"), span: Span { start: Position { line: 1, col: 14 }, end: Position { line: 1, col: 18 } } }) ); assert_eq!(ron::from_str::(r"'\u{1F980}'"), Ok('\u{1F980}')); assert_eq!( ron::from_str::(r"'\xf0\x9f\xa6\x80'"), Err(SpannedError { code: Error::InvalidEscape("Not a valid byte-escaped Unicode character"), span: Span { start: Position { line: 1, col: 1 }, end: Position { line: 1, col: 6 } } }) ); } #[test] fn byte_literal() { assert_eq!( ron::from_str("b'\0'"), Ok(ron::Value::Number(ron::value::Number::U8(0))) ); assert_eq!( ron::from_str("b'\\0'"), Ok(ron::Value::Number(ron::value::Number::U8(0))) ); for b in 0..=255_u8 { let default = std::ascii::escape_default(b) .map(char::from) .collect::(); let lower = format!(r"\x{:02x}", b); let upper = format!(r"\x{:02X}", b); assert_eq!( ron::from_str(&format!("b'{}'", default)), Ok(ron::Value::Number(ron::value::Number::U8(b))) ); assert_eq!( ron::from_str(&format!("b'{}'", lower)), Ok(ron::Value::Number(ron::value::Number::U8(b))) ); assert_eq!( ron::from_str(&format!("b'{}'", upper)), Ok(ron::Value::Number(ron::value::Number::U8(b))) ); } assert_eq!( ron::from_str::(r#"b'\u{0}'"#), Err(SpannedError { code: Error::InvalidEscape("Unexpected Unicode escape in byte literal"), span: Span { start: Position { line: 1, col: 7 }, end: Position { line: 1, col: 8 }, } }) ); let err = ron::from_str::(r#"b'🦀'"#).unwrap_err(); assert_eq!( err, SpannedError { code: Error::ExpectedByteLiteral, span: Span { start: Position { line: 1, col: 1 }, end: Position { line: 1, col: 4 }, } } ); assert_eq!(format!("{}", err.code), "Expected byte literal"); assert_eq!( ron::from_str::(r#"b'qq'"#).unwrap_err(), SpannedError { code: Error::ExpectedByteLiteral, span: Span { start: Position { line: 1, col: 1 }, end: Position { line: 1, col: 4 }, } } ); assert_eq!( ron::from_str::(r#"b'9'"#), Err(SpannedError { code: Error::InvalidValueForType { expected: String::from("an 8-bit signed integer"), found: String::from(r#"b'9'"#) }, span: Span { start: Position { line: 1, col: 4 }, end: Position { line: 1, col: 5 }, } }) ); } #[test] fn invalid_identifier() { #[allow(dead_code)] #[derive(Debug, Deserialize)] // GRCOV_EXCL_LINE struct Test { a: i32, } for id in ["b\"", "b'", "br#", "br\"", "r\"", "r#\"", "r##"] { assert_eq!( ron::from_str::(&format!("({}: 42)", id)).unwrap_err(), SpannedError { code: Error::ExpectedIdentifier, span: Span { start: Position { line: 1, col: 2 }, end: Position { line: 1, col: 2 }, } } ); } }