diff --git a/README.md b/README.md index 27c58d81..0425f57b 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,9 @@ fn main() -> Result<(), serde_yaml::Error> { map.insert("y".to_string(), 2.0); // Serialize it to a YAML string. + // y is quoted to avoid ambiguity in parsers that might read it as `true`. let yaml = serde_yaml::to_string(&map)?; - assert_eq!(yaml, "x: 1.0\ny: 2.0\n"); + assert_eq!(yaml, "x: 1.0\n'y': 2.0\n"); // Deserialize it back to a Rust type. let deserialized_map: BTreeMap = serde_yaml::from_str(&yaml)?; @@ -75,7 +76,7 @@ fn main() -> Result<(), serde_yaml::Error> { let point = Point { x: 1.0, y: 2.0 }; let yaml = serde_yaml::to_string(&point)?; - assert_eq!(yaml, "x: 1.0\ny: 2.0\n"); + assert_eq!(yaml, "x: 1.0\n'y': 2.0\n"); let deserialized_point: Point = serde_yaml::from_str(&yaml)?; assert_eq!(point, deserialized_point); diff --git a/src/de.rs b/src/de.rs index f0b467da..571ae235 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1101,13 +1101,20 @@ pub(crate) fn digits_but_not_number(scalar: &str) -> bool { /// serialize it with quotes to be safe. /// This avoids the norway problem https://hitchdev.com/strictyaml/why/implicit-typing-removed/ pub(crate) fn ambiguous_string(scalar: &str) -> bool { - parse_bool(scalar).is_some() - || parse_null(scalar.as_bytes()).is_some() - || scalar.len() == 0 - || scalar.bytes().nth(0).unwrap().is_ascii_digit() - || scalar.starts_with('-') - || scalar.starts_with('.') - || scalar.starts_with("+") + let lower_scalar = scalar.to_lowercase(); + parse_bool(&lower_scalar).is_some() + || parse_null(&lower_scalar.as_bytes()).is_some() + || lower_scalar.len() == 0 + || lower_scalar.bytes().nth(0).unwrap().is_ascii_digit() + || lower_scalar.starts_with('-') + || lower_scalar.starts_with('.') + || lower_scalar.starts_with("+") + // Things that we don't parse as bool but could be parsed as bool by + // other YAML parsers. + || lower_scalar == "y" + || lower_scalar == "yes" + || lower_scalar == "n" + || lower_scalar == "no" } pub(crate) fn visit_int<'de, V>(visitor: V, v: &str) -> Result, V> diff --git a/src/lib.rs b/src/lib.rs index f22cee9d..b1440bae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,8 +24,9 @@ //! map.insert("y".to_string(), 2.0); //! //! // Serialize it to a YAML string. +//! // 'y' is quoted to avoid ambiguity in parsers that might read it as `true`. //! let yaml = serde_yaml::to_string(&map)?; -//! assert_eq!(yaml, "x: 1.0\ny: 2.0\n"); +//! assert_eq!(yaml, "x: 1.0\n'y': 2.0\n"); //! //! // Deserialize it back to a Rust type. //! let deserialized_map: BTreeMap = serde_yaml::from_str(&yaml)?; @@ -55,7 +56,7 @@ //! let point = Point { x: 1.0, y: 2.0 }; //! //! let yaml = serde_yaml::to_string(&point)?; -//! assert_eq!(yaml, "x: 1.0\ny: 2.0\n"); +//! assert_eq!(yaml, "x: 1.0\n'y': 2.0\n"); //! //! let deserialized_point: Point = serde_yaml::from_str(&yaml)?; //! assert_eq!(point, deserialized_point); diff --git a/tests/test_serde.rs b/tests/test_serde.rs index 22651f08..40e93ba2 100644 --- a/tests/test_serde.rs +++ b/tests/test_serde.rs @@ -195,7 +195,7 @@ fn test_map() { thing.insert("y".to_owned(), 2); let yaml = indoc! {" x: 1 - y: 2 + 'y': 2 "}; test_serde(&thing, yaml); } @@ -238,7 +238,7 @@ fn test_basic_struct() { }; let yaml = indoc! {r#" x: -4 - y: "hi\tquoted" + 'y': "hi\tquoted" z: true "#}; test_serde(&thing, yaml); @@ -335,6 +335,23 @@ fn test_strings_needing_quote() { string: '' "}; test_serde(&thing3, yaml3); + + let thing4 = Struct2 { + string: " ".to_owned(), + }; + let yaml4 = indoc! {" + string: ' ' + "}; + test_serde(&thing4, yaml4); + + // The literal norway problem https://hitchdev.com/strictyaml/why/implicit-typing-removed/ + let thing5 = Struct2 { + string: "NO".to_owned(), + }; + let yaml5 = indoc! {" + string: 'NO' + "}; + test_serde(&thing5, yaml5); } #[test]