serializable-yaml 0.6.1

Serializable equivalent of yaml-rust
Documentation
// Copyright (C) 2023 Enrico Guiraud
//
// This file is part of serializa-yaml, a Rust library.
//
// serialize-yaml is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// serialize-yaml is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with serialize-yaml. If not, see <http://www.gnu.org/licenses/>.

//! This library makes [yaml-rust](https://crates.io/crates/yaml-rust)'s YAML enum serializable.
//!
//! Because of the orphan rule we cannot implement the necessary trait on the YAML enum directly,
//! so instead this library provides its own serializable equivalent with the same name.
//!
//! For (de)serialization of custom Rust types to/from YAML see [serde_yaml](https://crates.io/crates/serde_yaml).
//!
//! ## Usage
//!
//! ```rust
//! let input = "answer: 42";
//! // load some YAML with yaml-rust
//! let yaml = yaml_rust::YamlLoader::load_from_str(input).unwrap();
//! // convert it to the serializable-yaml equivalent
//! let yaml = serializable_yaml::from_vec(yaml);
//! // you can now serialize tha YAML instance with serde_yaml
//! let yaml_as_string = serde_yaml::to_string(&yaml).unwrap();
//! ```
//!
//! Utility functions `from_vec` and `from_map` are also available.

use linked_hash_map::LinkedHashMap;
use serde::Serialize;
use yaml_rust as yr;

/// A serializable equivalent of the yaml_rust::Yaml enum.
/// The main difference being that only strings are allowed as hashmap keys.
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
#[serde(untagged)]
pub enum Yaml {
    Real(String),
    Integer(i64),
    String(String),
    Boolean(bool),
    Array(Vec<Yaml>),
    Hash(LinkedHashMap<String, Yaml>),
    Alias(usize),
    Null,
    BadValue,
}

impl From<yr::Yaml> for Yaml {
    fn from(y: yr::Yaml) -> Self {
        match y {
            yr::Yaml::Real(s) => Self::Real(s),
            yr::Yaml::Integer(i) => Self::Integer(i),
            yr::Yaml::String(s) => Self::String(s),
            yr::Yaml::Boolean(b) => Self::Boolean(b),
            yr::Yaml::Array(a) => Self::Array(a.into_iter().map(Yaml::from).collect()),
            yr::Yaml::Hash(m) => Yaml::Hash(m.into_iter().map(with_key_as_string).collect()),
            yr::Yaml::Alias(a) => Self::Alias(a),
            yr::Yaml::Null => Self::Null,
            yr::Yaml::BadValue => Self::BadValue,
        }
    }
}

impl From<&yr::Yaml> for Yaml {
    fn from(y: &yr::Yaml) -> Self {
        Self::from(y.clone())
    }
}

pub fn from_vec(v: Vec<yr::Yaml>) -> Vec<Yaml> {
    v.into_iter().map(Yaml::from).collect()
}

pub fn from_slice(v: &[yr::Yaml]) -> Vec<Yaml> {
    v.iter().map(Yaml::from).collect()
}

pub fn from_map(m: LinkedHashMap<yr::Yaml, yr::Yaml>) -> LinkedHashMap<String, Yaml> {
    m.into_iter().map(with_key_as_string).collect()
}

pub fn from_map_ref(m: &LinkedHashMap<yr::Yaml, yr::Yaml>) -> LinkedHashMap<String, Yaml> {
    m.clone().into_iter().map(with_key_as_string).collect()
}

fn with_key_as_string(t: (yr::Yaml, yr::Yaml)) -> (String, Yaml) {
    (t.0.as_str().unwrap().to_owned(), Yaml::from(t.1))
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Serialization of a typical YAML frontmatter
    #[test]
    fn frontmatter() {
        let input = "
            title: Home
            tags: [c++, war-stories]
        ";
        let yaml = yaml_rust::YamlLoader::load_from_str(input).unwrap();
        let yaml = from_vec(yaml);
        let yaml_as_string = serde_yaml::to_string(&yaml).unwrap();
        let expected = "- title: Home
  tags:
  - c++
  - war-stories
";
        assert_eq!(yaml_as_string, expected);
    }

    #[test]
    fn from_vec_ref() {
        let input = "
            title: Home
            tags: [c++, war-stories]
        ";
        let yaml = yaml_rust::YamlLoader::load_from_str(input).unwrap();
        let yaml = super::from_slice(&yaml);
        let yaml_as_string = serde_yaml::to_string(&yaml).unwrap();
        let expected = "- title: Home
  tags:
  - c++
  - war-stories
";
        assert_eq!(yaml_as_string, expected);
    }

    /// Serialization of a LinkedHashMap<Yaml, Yaml>
    #[test]
    fn from_map() {
        let input = "
            key1: value1
            key2: [value2, value3]
        ";
        let yaml = yaml_rust::YamlLoader::load_from_str(input)
            .unwrap()
            .pop()
            .unwrap();
        let yr::Yaml::Hash(yaml) = yaml else { panic!() };
        let yaml = super::from_map(yaml);
        let yaml_as_string = serde_yaml::to_string(&yaml).unwrap();
        let expected = "key1: value1
key2:
- value2
- value3
";
        assert_eq!(yaml_as_string, expected);
    }

    #[test]
    fn from_map_ref() {
        let input = "
            key1: value1
            key2: [42, 0]
        ";
        let yaml = yaml_rust::YamlLoader::load_from_str(input)
            .unwrap()
            .pop()
            .unwrap();
        let yr::Yaml::Hash(yaml) = yaml else { panic!() };
        let yaml = super::from_map_ref(&yaml);
        let yaml_as_yaml_string = serde_yaml::to_string(&yaml).unwrap();
        let expected = "key1: value1
key2:
- 42
- 0
";
        assert_eq!(yaml_as_yaml_string, expected);
    }
}