yaml_schema/
utils.rs

1// Various utility functions
2use crate::Result;
3use hashlink::linked_hash_map;
4use saphyr::{MarkedYaml, Scalar, YamlData};
5use std::borrow::Cow;
6use std::collections::HashMap;
7use std::hash::Hash;
8
9/// Create and return a HashMap with a single key & value
10pub fn hash_map<K, V>(key: K, value: V) -> HashMap<K, V>
11where
12    K: Hash + Eq + Clone,
13{
14    let mut hash_map = HashMap::with_capacity(1);
15    hash_map.insert(key, value);
16    hash_map
17}
18
19/// Create and return a LinkedHashMap with a single key & value
20pub fn linked_hash_map<K, V>(key: K, value: V) -> linked_hash_map::LinkedHashMap<K, V>
21where
22    K: Hash + Eq + Clone,
23{
24    let mut linked_hash_map = linked_hash_map::LinkedHashMap::new();
25    linked_hash_map.insert(key, value);
26    linked_hash_map
27}
28
29/// Construct a saphyr::Yaml scalar value from a &str
30pub const fn saphyr_yaml_string(s: &str) -> saphyr::Yaml<'_> {
31    saphyr::Yaml::Value(saphyr::Scalar::String(Cow::Borrowed(s)))
32}
33
34/// Try to unwrap a saphyr::Scalar from a saphyr::Yaml
35pub fn try_unwrap_saphyr_scalar<'a>(yaml: &'a saphyr::Yaml) -> Result<&'a saphyr::Scalar<'a>> {
36    if let saphyr::Yaml::Value(scalar) = yaml {
37        Ok(scalar)
38    } else {
39        Err(expected_scalar!("Expected a scalar, got: {:?}", yaml))
40    }
41}
42
43/// Converts a saphyr::Scalar value to a String. Does NOT enclose Scalar::String values in
44/// double-quotes.
45pub fn scalar_to_string(scalar: &saphyr::Scalar) -> String {
46    match scalar {
47        saphyr::Scalar::Null => "null".to_string(),
48        saphyr::Scalar::Boolean(b) => b.to_string(),
49        saphyr::Scalar::Integer(i) => i.to_string(),
50        saphyr::Scalar::FloatingPoint(o) => o.to_string(),
51        saphyr::Scalar::String(s) => s.to_string(),
52    }
53}
54
55/// Formats a saphyr::Scalar as a string. Encloses Scalar::String values in double quotes (`"`)
56pub fn format_scalar(scalar: &saphyr::Scalar) -> String {
57    match scalar {
58        saphyr::Scalar::String(s) => format!("\"{s}\""),
59        _ => scalar_to_string(scalar),
60    }
61}
62
63/// Formats a saphyr::YamlData as a string
64pub fn format_yaml_data<'a>(data: &saphyr::YamlData<'a, saphyr::MarkedYaml<'a>>) -> String {
65    match data {
66        saphyr::YamlData::Value(scalar) => format_scalar(scalar),
67        saphyr::YamlData::Sequence(seq) => {
68            let items: Vec<String> = seq.iter().map(|v| format_yaml_data(&v.data)).collect();
69            format!("[{}]", items.join(", "))
70        }
71        saphyr::YamlData::Mapping(mapping) => {
72            let items: Vec<String> = mapping
73                .iter()
74                .map(|(k, v)| {
75                    format!(
76                        "{}: {}",
77                        format_yaml_data(&k.data),
78                        format_yaml_data(&v.data)
79                    )
80                })
81                .collect();
82            format!("[{}]", items.join(", "))
83        }
84        _ => format!("<unsupported type: {data:?}>"),
85    }
86}
87
88/// Formats a saphyr::Marker as a string. Displays the line and column as a pair of numbers, separated by a comma.
89pub fn format_marker(marker: &saphyr::Marker) -> String {
90    format!("[{}, {}]", marker.line(), marker.col())
91}
92
93/// Formats a vector of values as a string, by joining them with commas
94pub fn format_vec<V>(vec: &[V]) -> String
95where
96    V: std::fmt::Display,
97{
98    let items: Vec<String> = vec.iter().map(|v| format!("{v}")).collect();
99    format!("[{}]", items.join(", "))
100}
101
102/// Formats a HashMap as a string, ala JSON
103pub fn format_hash_map<K, V>(hash_map: &HashMap<K, V>) -> String
104where
105    K: std::fmt::Display,
106    V: std::fmt::Display,
107{
108    let items: Vec<String> = hash_map
109        .iter()
110        .map(|(k, v)| format!("{}: {}", k, v))
111        .collect();
112    format!("{{ {} }}", items.join(", "))
113}
114/// Collects the keys of a list of SchemaMetadata implementations into a single slice of strings.
115pub fn collect_keys(a: &'static [&'static str], b: &'static [&'static str]) -> Vec<&'static str> {
116    let mut keys = Vec::with_capacity(a.len() + b.len());
117    keys.extend_from_slice(a);
118    keys.extend_from_slice(b);
119    keys.sort();
120    keys.dedup();
121    keys
122}
123
124/// Filters a saphyr::Mapping and returns a new mapping with only the keys that are in the list.
125pub fn filter_mapping<'a>(
126    mapping: &saphyr::AnnotatedMapping<'a, saphyr::MarkedYaml<'a>>,
127    keys: Vec<&'static str>,
128    override_type: &'a str,
129) -> Result<saphyr::AnnotatedMapping<'a, saphyr::MarkedYaml<'a>>> {
130    let mut filtered_mapping = saphyr::AnnotatedMapping::new();
131    for (k, v) in mapping.iter() {
132        if let YamlData::Value(Scalar::String(key)) = &k.data {
133            if keys.contains(&key.as_ref()) {
134                match key.as_ref() {
135                    "type" => {
136                        filtered_mapping
137                            .insert(k.clone(), MarkedYaml::value_from_str(override_type));
138                    }
139                    _ => {
140                        filtered_mapping.insert(k.clone(), v.clone());
141                    }
142                }
143            }
144        } else {
145            return Err(expected_scalar!("Expected a string key, got: {:?}", k.data));
146        }
147    }
148    Ok(filtered_mapping.into_iter().collect())
149}
150
151#[cfg(test)]
152mod tests {
153    use crate::utils::{format_scalar, hash_map, scalar_to_string};
154    use ordered_float::OrderedFloat;
155    use std::collections::HashMap;
156
157    #[test]
158    fn test_hash_map() {
159        let expected = vec![("foo".to_string(), "bar".to_string())]
160            .into_iter()
161            .collect::<HashMap<String, String>>();
162
163        let actual = hash_map("foo".to_string(), "bar".to_string());
164        assert_eq!(expected, actual);
165    }
166
167    #[test]
168    fn test_scalar_to_string() {
169        assert_eq!("null", scalar_to_string(&saphyr::Scalar::Null));
170        assert_eq!("true", scalar_to_string(&saphyr::Scalar::Boolean(true)));
171        assert_eq!("false", scalar_to_string(&saphyr::Scalar::Boolean(false)));
172        assert_eq!("42", scalar_to_string(&saphyr::Scalar::Integer(42)));
173        assert_eq!("-1", scalar_to_string(&saphyr::Scalar::Integer(-1)));
174        assert_eq!(
175            "3.14",
176            scalar_to_string(&saphyr::Scalar::FloatingPoint(OrderedFloat::from(3.14)))
177        );
178        assert_eq!(
179            "foo",
180            scalar_to_string(&saphyr::Scalar::String("foo".into()))
181        );
182    }
183
184    #[test]
185    fn test_format_scalar() {
186        assert_eq!("null", format_scalar(&saphyr::Scalar::Null));
187        assert_eq!("true", format_scalar(&saphyr::Scalar::Boolean(true)));
188        assert_eq!("false", format_scalar(&saphyr::Scalar::Boolean(false)));
189        assert_eq!("42", format_scalar(&saphyr::Scalar::Integer(42)));
190        assert_eq!("-1", format_scalar(&saphyr::Scalar::Integer(-1)));
191        assert_eq!(
192            "3.14",
193            format_scalar(&saphyr::Scalar::FloatingPoint(OrderedFloat::from(3.14)))
194        );
195        assert_eq!(
196            "\"foo\"",
197            format_scalar(&saphyr::Scalar::String("foo".into()))
198        );
199    }
200}