serializable_yaml/
lib.rs

1// Copyright (C) 2023 Enrico Guiraud
2//
3// This file is part of serializa-yaml, a Rust library.
4//
5// serialize-yaml is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// serialize-yaml is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with serialize-yaml. If not, see <http://www.gnu.org/licenses/>.
17
18//! This library makes [yaml-rust](https://crates.io/crates/yaml-rust)'s YAML enum serializable.
19//!
20//! Because of the orphan rule we cannot implement the necessary trait on the YAML enum directly,
21//! so instead this library provides its own serializable equivalent with the same name.
22//!
23//! For (de)serialization of custom Rust types to/from YAML see [serde_yaml](https://crates.io/crates/serde_yaml).
24//!
25//! ## Usage
26//!
27//! ```rust
28//! let input = "answer: 42";
29//! // load some YAML with yaml-rust
30//! let yaml = yaml_rust::YamlLoader::load_from_str(input).unwrap();
31//! // convert it to the serializable-yaml equivalent
32//! let yaml = serializable_yaml::from_vec(yaml);
33//! // you can now serialize tha YAML instance with serde_yaml
34//! let yaml_as_string = serde_yaml::to_string(&yaml).unwrap();
35//! ```
36//!
37//! Utility functions `from_vec` and `from_map` are also available.
38
39use linked_hash_map::LinkedHashMap;
40use serde::Serialize;
41use yaml_rust as yr;
42
43/// A serializable equivalent of the yaml_rust::Yaml enum.
44/// The main difference being that only strings are allowed as hashmap keys.
45#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
46#[serde(untagged)]
47pub enum Yaml {
48    Real(String),
49    Integer(i64),
50    String(String),
51    Boolean(bool),
52    Array(Vec<Yaml>),
53    Hash(LinkedHashMap<String, Yaml>),
54    Alias(usize),
55    Null,
56    BadValue,
57}
58
59impl From<yr::Yaml> for Yaml {
60    fn from(y: yr::Yaml) -> Self {
61        match y {
62            yr::Yaml::Real(s) => Self::Real(s),
63            yr::Yaml::Integer(i) => Self::Integer(i),
64            yr::Yaml::String(s) => Self::String(s),
65            yr::Yaml::Boolean(b) => Self::Boolean(b),
66            yr::Yaml::Array(a) => Self::Array(a.into_iter().map(Yaml::from).collect()),
67            yr::Yaml::Hash(m) => Yaml::Hash(m.into_iter().map(with_key_as_string).collect()),
68            yr::Yaml::Alias(a) => Self::Alias(a),
69            yr::Yaml::Null => Self::Null,
70            yr::Yaml::BadValue => Self::BadValue,
71        }
72    }
73}
74
75impl From<&yr::Yaml> for Yaml {
76    fn from(y: &yr::Yaml) -> Self {
77        Self::from(y.clone())
78    }
79}
80
81pub fn from_vec(v: Vec<yr::Yaml>) -> Vec<Yaml> {
82    v.into_iter().map(Yaml::from).collect()
83}
84
85pub fn from_slice(v: &[yr::Yaml]) -> Vec<Yaml> {
86    v.iter().map(Yaml::from).collect()
87}
88
89pub fn from_map(m: LinkedHashMap<yr::Yaml, yr::Yaml>) -> LinkedHashMap<String, Yaml> {
90    m.into_iter().map(with_key_as_string).collect()
91}
92
93pub fn from_map_ref(m: &LinkedHashMap<yr::Yaml, yr::Yaml>) -> LinkedHashMap<String, Yaml> {
94    m.clone().into_iter().map(with_key_as_string).collect()
95}
96
97fn with_key_as_string(t: (yr::Yaml, yr::Yaml)) -> (String, Yaml) {
98    (t.0.as_str().unwrap().to_owned(), Yaml::from(t.1))
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    /// Serialization of a typical YAML frontmatter
106    #[test]
107    fn frontmatter() {
108        let input = "
109            title: Home
110            tags: [c++, war-stories]
111        ";
112        let yaml = yaml_rust::YamlLoader::load_from_str(input).unwrap();
113        let yaml = from_vec(yaml);
114        let yaml_as_string = serde_yaml::to_string(&yaml).unwrap();
115        let expected = "- title: Home
116  tags:
117  - c++
118  - war-stories
119";
120        assert_eq!(yaml_as_string, expected);
121    }
122
123    #[test]
124    fn from_vec_ref() {
125        let input = "
126            title: Home
127            tags: [c++, war-stories]
128        ";
129        let yaml = yaml_rust::YamlLoader::load_from_str(input).unwrap();
130        let yaml = super::from_slice(&yaml);
131        let yaml_as_string = serde_yaml::to_string(&yaml).unwrap();
132        let expected = "- title: Home
133  tags:
134  - c++
135  - war-stories
136";
137        assert_eq!(yaml_as_string, expected);
138    }
139
140    /// Serialization of a LinkedHashMap<Yaml, Yaml>
141    #[test]
142    fn from_map() {
143        let input = "
144            key1: value1
145            key2: [value2, value3]
146        ";
147        let yaml = yaml_rust::YamlLoader::load_from_str(input)
148            .unwrap()
149            .pop()
150            .unwrap();
151        let yr::Yaml::Hash(yaml) = yaml else { panic!() };
152        let yaml = super::from_map(yaml);
153        let yaml_as_string = serde_yaml::to_string(&yaml).unwrap();
154        let expected = "key1: value1
155key2:
156- value2
157- value3
158";
159        assert_eq!(yaml_as_string, expected);
160    }
161
162    #[test]
163    fn from_map_ref() {
164        let input = "
165            key1: value1
166            key2: [42, 0]
167        ";
168        let yaml = yaml_rust::YamlLoader::load_from_str(input)
169            .unwrap()
170            .pop()
171            .unwrap();
172        let yr::Yaml::Hash(yaml) = yaml else { panic!() };
173        let yaml = super::from_map_ref(&yaml);
174        let yaml_as_yaml_string = serde_yaml::to_string(&yaml).unwrap();
175        let expected = "key1: value1
176key2:
177- 42
178- 0
179";
180        assert_eq!(yaml_as_yaml_string, expected);
181    }
182}