From f41036ca6f7e3876db8c2f9df44047d12fd643c6 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Wed, 14 Sep 2022 01:41:43 -0700 Subject: [PATCH 1/3] Add regression test for issue 327 Currently fails with: ---- test_python_safe_dump stdout ---- thread 'test_python_safe_dump' panicked at 'called `Result::unwrap()` on an `Err` value: Error("foo: invalid type: integer `7200`, expected u32", line: 1, column: 8)', tests/test_de.rs:17:54 --- tests/test_de.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_de.rs b/tests/test_de.rs index 0d16d5e7..fea81007 100644 --- a/tests/test_de.rs +++ b/tests/test_de.rs @@ -569,3 +569,28 @@ fn test_empty_scalar() { }; test_de(yaml, &expected); } + +#[test] +fn test_python_safe_dump() { + #[derive(Deserialize, PartialEq, Debug)] + struct Frob { + foo: u32, + } + + // This matches output produced by PyYAML's `yaml.safe_dump` when using the + // default_style parameter. + // + // >>> import yaml + // >>> d = {"foo": 7200} + // >>> print(yaml.safe_dump(d, default_style="|")) + // "foo": !!int |- + // 7200 + // + let yaml = indoc! {r#" + "foo": !!int |- + 7200 + "#}; + + let expected = Frob { foo: 7200 }; + test_de(yaml, &expected); +} From bfff6c193f3d39f3408c4313dd4f59aba725d0b1 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Wed, 14 Sep 2022 01:34:01 -0700 Subject: [PATCH 2/3] Support deserializing tagged literal scalar into primitive --- src/de.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/src/de.rs b/src/de.rs index 37c72ddf..517a2222 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1149,6 +1149,26 @@ where } } +fn is_plain_or_tagged_literal_scalar( + expected: &str, + scalar: &Scalar, + tagged_already: bool, +) -> bool { + if scalar.style == ScalarStyle::Plain { + return true; + } + if tagged_already { + return false; + } + if scalar.style != ScalarStyle::Literal { + return false; + } + if let Some(tag) = &scalar.tag { + return tag == expected; + } + false +} + fn invalid_type(event: &Event, exp: &dyn Expected) -> Error { enum Void {} @@ -1250,11 +1270,14 @@ impl<'de, 'document> de::Deserializer<'de> for &mut DeserializerFromEvents<'de, where V: Visitor<'de>, { + let tagged_already = self.current_enum.is_some(); let (next, mark) = self.next_event_mark()?; loop { match next { Event::Alias(mut pos) => break self.jump(&mut pos)?.deserialize_bool(visitor), - Event::Scalar(scalar) if scalar.style == ScalarStyle::Plain => { + Event::Scalar(scalar) + if is_plain_or_tagged_literal_scalar(Tag::BOOL, scalar, tagged_already) => + { if let Ok(value) = str::from_utf8(&scalar.value) { if let Some(boolean) = parse_bool(value) { break visitor.visit_bool(boolean); @@ -1293,11 +1316,14 @@ impl<'de, 'document> de::Deserializer<'de> for &mut DeserializerFromEvents<'de, where V: Visitor<'de>, { + let tagged_already = self.current_enum.is_some(); let (next, mark) = self.next_event_mark()?; loop { match next { Event::Alias(mut pos) => break self.jump(&mut pos)?.deserialize_i64(visitor), - Event::Scalar(scalar) if scalar.style == ScalarStyle::Plain => { + Event::Scalar(scalar) + if is_plain_or_tagged_literal_scalar(Tag::INT, scalar, tagged_already) => + { if let Ok(value) = str::from_utf8(&scalar.value) { if let Some(int) = parse_signed_int(value, i64::from_str_radix) { break visitor.visit_i64(int); @@ -1315,11 +1341,14 @@ impl<'de, 'document> de::Deserializer<'de> for &mut DeserializerFromEvents<'de, where V: Visitor<'de>, { + let tagged_already = self.current_enum.is_some(); let (next, mark) = self.next_event_mark()?; loop { match next { Event::Alias(mut pos) => break self.jump(&mut pos)?.deserialize_i128(visitor), - Event::Scalar(scalar) if scalar.style == ScalarStyle::Plain => { + Event::Scalar(scalar) + if is_plain_or_tagged_literal_scalar(Tag::INT, scalar, tagged_already) => + { if let Ok(value) = str::from_utf8(&scalar.value) { if let Some(int) = parse_signed_int(value, i128::from_str_radix) { break visitor.visit_i128(int); @@ -1358,11 +1387,14 @@ impl<'de, 'document> de::Deserializer<'de> for &mut DeserializerFromEvents<'de, where V: Visitor<'de>, { + let tagged_already = self.current_enum.is_some(); let (next, mark) = self.next_event_mark()?; loop { match next { Event::Alias(mut pos) => break self.jump(&mut pos)?.deserialize_u64(visitor), - Event::Scalar(scalar) if scalar.style == ScalarStyle::Plain => { + Event::Scalar(scalar) + if is_plain_or_tagged_literal_scalar(Tag::INT, scalar, tagged_already) => + { if let Ok(value) = str::from_utf8(&scalar.value) { if let Some(int) = parse_unsigned_int(value, u64::from_str_radix) { break visitor.visit_u64(int); @@ -1380,11 +1412,14 @@ impl<'de, 'document> de::Deserializer<'de> for &mut DeserializerFromEvents<'de, where V: Visitor<'de>, { + let tagged_already = self.current_enum.is_some(); let (next, mark) = self.next_event_mark()?; loop { match next { Event::Alias(mut pos) => break self.jump(&mut pos)?.deserialize_u128(visitor), - Event::Scalar(scalar) if scalar.style == ScalarStyle::Plain => { + Event::Scalar(scalar) + if is_plain_or_tagged_literal_scalar(Tag::INT, scalar, tagged_already) => + { if let Ok(value) = str::from_utf8(&scalar.value) { if let Some(int) = parse_unsigned_int(value, u128::from_str_radix) { break visitor.visit_u128(int); @@ -1409,11 +1444,14 @@ impl<'de, 'document> de::Deserializer<'de> for &mut DeserializerFromEvents<'de, where V: Visitor<'de>, { + let tagged_already = self.current_enum.is_some(); let (next, mark) = self.next_event_mark()?; loop { match next { Event::Alias(mut pos) => break self.jump(&mut pos)?.deserialize_f64(visitor), - Event::Scalar(scalar) if scalar.style == ScalarStyle::Plain => { + Event::Scalar(scalar) + if is_plain_or_tagged_literal_scalar(Tag::FLOAT, scalar, tagged_already) => + { if let Ok(value) = str::from_utf8(&scalar.value) { if let Some(float) = parse_f64(value) { break visitor.visit_f64(float); From c68127fad55d47e41f2133820cb368f92025116e Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Wed, 14 Sep 2022 01:44:06 -0700 Subject: [PATCH 3/3] Clean up tagged literal matching --- src/de.rs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/de.rs b/src/de.rs index 517a2222..7500bdec 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1154,19 +1154,11 @@ fn is_plain_or_tagged_literal_scalar( scalar: &Scalar, tagged_already: bool, ) -> bool { - if scalar.style == ScalarStyle::Plain { - return true; + match (scalar.style, &scalar.tag, tagged_already) { + (ScalarStyle::Plain, _, _) => true, + (ScalarStyle::Literal, Some(tag), false) => tag == expected, + _ => false, } - if tagged_already { - return false; - } - if scalar.style != ScalarStyle::Literal { - return false; - } - if let Some(tag) = &scalar.tag { - return tag == expected; - } - false } fn invalid_type(event: &Event, exp: &dyn Expected) -> Error {