serde-saphyr 1.0.0

YAML (de)serializer for Serde, emphasizing panic-free parsing, fast builds and good error reporting. Supports properties, comments, include
Documentation
#![cfg(all(feature = "serialize", feature = "deserialize"))]
mod tests {
    use serde_json::Value;
    use serde_saphyr::Error;
    use serde_saphyr::from_str_with_options;
    use serde_saphyr::options::{DuplicateKeyPolicy, MergeKeyPolicy};
    use std::collections::BTreeMap;

    /// Parse a YAML mapping into a `BTreeMap<String, serde_json::Value>`,
    /// configuring the deserializer to use `DuplicateKeyPolicy::FirstWins`.
    ///
    /// Params:
    /// - `yaml`: the YAML input as &str.
    ///
    /// Returns:
    /// - `Result<BTreeMap<String, Value>, Error>` with parsed map or a deserialization error.
    fn parse_first_wins_map(yaml: &str) -> Result<BTreeMap<String, Value>, Error> {
        let opts = serde_saphyr::options! {
            duplicate_keys: DuplicateKeyPolicy::FirstWins,
        };
        from_str_with_options::<BTreeMap<String, Value>>(yaml, opts)
    }

    fn parse_first_wins_map_rejecting_merge_keys(
        yaml: &str,
    ) -> Result<BTreeMap<String, Value>, Error> {
        let opts = serde_saphyr::options! {
            duplicate_keys: DuplicateKeyPolicy::FirstWins,
            merge_keys: MergeKeyPolicy::Error,
        };
        from_str_with_options::<BTreeMap<String, Value>>(yaml, opts)
    }

    #[test]
    fn first_wins_skips_scalar_value_of_duplicate_key() {
        let yaml = r#"
a: 1
a: 2
b: 3
"#;
        let map = parse_first_wins_map(yaml).expect("parse ok");
        assert_eq!(map.get("a"), Some(&Value::from(1)));
        assert_eq!(map.get("b"), Some(&Value::from(3)));
        assert_eq!(map.len(), 2);
    }

    #[test]
    fn first_wins_skips_sequence_value_of_duplicate_key() {
        let yaml = r#"
a: [1, 2]
a: [9, 9, 9]   # should be skipped entirely
b: 3
"#;
        let map = parse_first_wins_map(yaml).expect("parse ok");
        assert_eq!(map.get("a"), Some(&Value::from(vec![1, 2])));
        assert_eq!(map.get("b"), Some(&Value::from(3)));
        assert_eq!(map.len(), 2);
    }

    #[test]
    fn first_wins_skips_mapping_value_of_duplicate_key_including_nested() {
        let yaml = r#"
a:
  x: 1
  y:
    z: 2
a:
  X: 9         # whole mapping must be skipped
  Y: { Z: 10 } # including nested mapping
b: 42
"#;
        let map = parse_first_wins_map(yaml).expect("parse ok");
        // The original 'a' must remain unchanged
        let a = map.get("a").expect("key a present");
        assert_eq!(
            a,
            &serde_json::json!({
                "x": 1,
                "y": { "z": 2 }
            })
        );
        // And 'b' must still be read correctly after skipping
        assert_eq!(map.get("b"), Some(&Value::from(42)));
        assert_eq!(map.len(), 2);
    }

    #[test]
    fn first_wins_handles_duplicate_in_middle_and_keeps_following_keys() {
        let yaml = r#"
k1: start
dup:
  inner: [1, 2, { q: 9 }]
dup:
  should: be-ignored
  and: "this too"
k2: end
"#;
        let map = parse_first_wins_map(yaml).expect("parse ok");
        assert_eq!(map.get("k1"), Some(&Value::from("start")));
        assert_eq!(
            map.get("dup"),
            Some(&serde_json::json!({
                "inner": [1, 2, { "q": 9 }]
            }))
        );
        // Ensure parser stayed in sync and k2 was parsed after skipping the second 'dup' value.
        assert_eq!(map.get("k2"), Some(&Value::from("end")));
        assert_eq!(map.len(), 3);
    }

    #[test]
    fn first_wins_skips_nullish_scalar_duplicate_as_one_node_only() {
        // Even when the duplicate value is a null-ish scalar, it should skip exactly that node.
        let yaml = r#"
a: { keep: true }
a: null
b: ok
"#;
        let map = parse_first_wins_map(yaml).expect("parse ok");
        assert_eq!(map.get("a"), Some(&serde_json::json!({ "keep": true })));
        assert_eq!(map.get("b"), Some(&Value::from("ok")));
        assert_eq!(map.len(), 2);
    }

    #[test]
    fn first_wins_rejects_merge_key_inside_skipped_duplicate_value() {
        let yaml = r#"
a: { keep: true }
a:
  nested:
    <<: { admin: true }
b: ok
"#;

        let err = parse_first_wins_map_rejecting_merge_keys(yaml)
            .expect_err("merge key in skipped duplicate value must be rejected");

        assert!(matches!(
            err.without_snippet(),
            Error::MergeKeyNotAllowed { .. }
        ));
    }

    #[test]
    fn first_wins_allows_quoted_merge_key_inside_skipped_duplicate_value() {
        let yaml = r#"
a: { keep: true }
a:
  nested:
    "<<": { admin: true }
b: ok
"#;

        let map = parse_first_wins_map_rejecting_merge_keys(yaml).expect("quoted key is literal");

        assert_eq!(map.get("a"), Some(&serde_json::json!({ "keep": true })));
        assert_eq!(map.get("b"), Some(&Value::from("ok")));
        assert_eq!(map.len(), 2);
    }
}