didp_yaml/
util.rs

1//! Utility functions for parsing YAML.
2
3use dypdl::variable_type;
4use dypdl::StateMetadata;
5use std::convert::TryFrom;
6use std::error;
7use std::fmt;
8use std::str;
9use yaml_rust::{yaml::Array, Yaml};
10
11/// Error representing that the format is invalid.
12#[derive(Debug, Clone)]
13pub struct YamlContentErr(String);
14
15impl YamlContentErr {
16    /// Returns a new error.
17    pub fn new(message: String) -> YamlContentErr {
18        YamlContentErr(format!("Error in yaml contents: {message}"))
19    }
20}
21
22impl fmt::Display for YamlContentErr {
23    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
24        write!(f, "{}", self.0)
25    }
26}
27
28impl error::Error for YamlContentErr {}
29
30/// Returns a map parsed from a YAML.
31///
32/// # Errors
33///
34/// If the YAML is not a map.
35pub fn get_map(
36    value: &Yaml,
37) -> Result<&linked_hash_map::LinkedHashMap<Yaml, Yaml>, YamlContentErr> {
38    match value {
39        Yaml::Hash(map) => Ok(map),
40        _ => Err(YamlContentErr::new(
41            format!("expected Hash, but {value:?}",),
42        )),
43    }
44}
45
46/// Returns a YAML from a map given a key.
47///
48/// # Errors
49///
50/// If no such key exists.
51pub fn get_yaml_by_key<'a>(
52    map: &'a linked_hash_map::LinkedHashMap<Yaml, Yaml>,
53    key: &str,
54) -> Result<&'a Yaml, YamlContentErr> {
55    match map.get(&Yaml::String(String::from(key))) {
56        Some(value) => Ok(value),
57        None => Err(YamlContentErr::new(format!("no such key `{key}` in yaml",))),
58    }
59}
60
61/// Returns a vector parsed from a YAML.
62///
63/// # Errors
64///
65/// If the YAML is not an array.
66pub fn get_array(value: &Yaml) -> Result<&Vec<Yaml>, YamlContentErr> {
67    match value {
68        Yaml::Array(array) => Ok(array),
69        _ => Err(YamlContentErr::new(format!(
70            "expected Array, but is `{value:?}`",
71        ))),
72    }
73}
74
75/// Returns a boolean parsed from a YAML.
76///
77/// # Errors
78///
79/// If it cannot be parsed.
80pub fn get_bool(value: &Yaml) -> Result<bool, YamlContentErr> {
81    match value {
82        Yaml::Boolean(value) => Ok(*value),
83        _ => Err(YamlContentErr::new(format!(
84            "expected Boolean, but is `{value:?}`",
85        ))),
86    }
87}
88
89/// Returns a boolean from a map given a key.
90///
91/// # Errors
92///
93/// If no such key exists or it cannot be parsed.
94pub fn get_bool_by_key(
95    map: &linked_hash_map::LinkedHashMap<Yaml, Yaml>,
96    key: &str,
97) -> Result<bool, YamlContentErr> {
98    match map.get(&Yaml::String(String::from(key))) {
99        Some(value) => get_bool(value),
100        None => Err(YamlContentErr::new(format!("key `{key}` not found"))),
101    }
102}
103
104/// Returns an usize parsed from a YAML.
105///
106/// # Errors
107///
108/// If it cannot be parsed.
109pub fn get_usize(value: &Yaml) -> Result<usize, YamlContentErr> {
110    if let Yaml::Integer(value) = value {
111        match variable_type::Element::try_from(*value) {
112            Ok(value) => Ok(value),
113            Err(e) => Err(YamlContentErr::new(format!(
114                "cannot convert {value} to usize: {e:?}",
115            ))),
116        }
117    } else {
118        Err(YamlContentErr::new(format!(
119            "expected Integer, but is `{value:?}`",
120        )))
121    }
122}
123
124/// Returns an array of usize parsed from a YAML.
125///
126/// # Errors
127///
128/// If it cannot be parsed.
129pub fn get_usize_array(value: &Yaml) -> Result<Vec<usize>, YamlContentErr> {
130    if let Yaml::Array(array) = value {
131        let mut result = Vec::with_capacity(array.len());
132        for value in array {
133            result.push(get_usize(value)?);
134        }
135        Ok(result)
136    } else {
137        Err(YamlContentErr::new(format!(
138            "expected Array, but is `{value:?}`",
139        )))
140    }
141}
142
143/// Returns an usize from a map given a key.
144///
145/// # Errors
146///
147/// If no such key exists or it cannot be parsed.
148pub fn get_usize_by_key(
149    map: &linked_hash_map::LinkedHashMap<Yaml, Yaml>,
150    key: &str,
151) -> Result<usize, YamlContentErr> {
152    match map.get(&Yaml::String(String::from(key))) {
153        Some(value) => get_usize(value),
154        None => Err(YamlContentErr::new(format!("key `{key}` not found"))),
155    }
156}
157
158/// Returns an array of usize from a map given a key.
159///
160/// # Errors
161///
162/// If no such key exists or it cannot be parsed.
163pub fn get_usize_array_by_key(
164    map: &linked_hash_map::LinkedHashMap<Yaml, Yaml>,
165    key: &str,
166) -> Result<Vec<usize>, YamlContentErr> {
167    match map.get(&Yaml::String(String::from(key))) {
168        Some(value) => get_usize_array(value),
169        None => Err(YamlContentErr::new(format!("key `{key}` not found"))),
170    }
171}
172
173/// Returns a numeric value parsed from a YAML.
174///
175/// # Errors
176///
177/// If it cannot be parsed.
178pub fn get_numeric<T: str::FromStr + num_traits::FromPrimitive>(
179    value: &Yaml,
180) -> Result<T, YamlContentErr>
181where
182    <T as str::FromStr>::Err: fmt::Debug,
183{
184    match value {
185        Yaml::Integer(value) => match T::from_i64(*value) {
186            Some(value) => Ok(value),
187            None => Err(YamlContentErr::new(format!(
188                "could not parse {} as a number",
189                *value,
190            ))),
191        },
192        Yaml::Real(value) => value.parse().map_err(|e| {
193            YamlContentErr::new(format!("could not parse {value} as a number: {e:?}"))
194        }),
195        _ => Err(YamlContentErr::new(format!(
196            "expected Integer or Real, but is {value:?}",
197        ))),
198    }
199}
200
201/// Returns a numeric value from a map given a key.
202///
203/// # Errors
204///
205/// If no such key exists or it cannot be parsed.
206pub fn get_numeric_by_key<T: str::FromStr + num_traits::FromPrimitive>(
207    map: &linked_hash_map::LinkedHashMap<Yaml, Yaml>,
208    key: &str,
209) -> Result<T, YamlContentErr>
210where
211    <T as str::FromStr>::Err: fmt::Debug,
212{
213    match map.get(&Yaml::String(String::from(key))) {
214        Some(value) => get_numeric(value),
215        None => Err(YamlContentErr::new(format!("key `{key}` not found"))),
216    }
217}
218
219/// Returns a string parsed from a YAML.
220///
221/// # Errors
222///
223/// If it cannot be parsed.
224pub fn get_string(value: &Yaml) -> Result<String, YamlContentErr> {
225    match value {
226        Yaml::String(string) => Ok(string.clone()),
227        _ => Err(YamlContentErr::new(format!(
228            "expected String, but {value:?}",
229        ))),
230    }
231}
232
233/// Returns an array of string parsed from a YAML.
234///
235/// # Errors
236///
237/// If it cannot be parsed.
238pub fn get_string_array(value: &Yaml) -> Result<Vec<String>, YamlContentErr> {
239    match value {
240        Yaml::Array(value) => parse_string_array(value),
241        _ => Err(YamlContentErr::new(format!(
242            "expected Array, but is `{value:?}`",
243        ))),
244    }
245}
246
247/// Returns an usize parsed from a YAML item.
248///
249/// The item can be String that stands for object name or integers.
250///
251/// # Errors
252///
253/// If it cannot be parsed.
254pub fn get_size_from_yaml(
255    item: &Yaml,
256    metadata: &StateMetadata,
257) -> Result<usize, Box<dyn error::Error>> {
258    match item {
259        Yaml::String(object) => {
260            if let Some(index) = metadata.name_to_object_type.get(object) {
261                Ok(metadata.object_numbers[*index])
262            } else {
263                Err(YamlContentErr::new(format!("no such object `{object}`",)).into())
264            }
265        }
266        Yaml::Integer(size) => Ok(usize::try_from(*size)?),
267        _ => Err(YamlContentErr::new("Invalid table arg elements".to_owned()).into()),
268    }
269}
270
271/// Returns an array of dimension sizes parsed from a YAML array.
272///
273/// The elements in Yaml array can be String that stands for object name or integers.
274///
275/// # Errors
276///
277/// If it cannot be parsed.
278pub fn get_table_arg_array(
279    arg_array: &Array,
280    metadata: &StateMetadata,
281) -> Result<Vec<usize>, Box<dyn error::Error>> {
282    arg_array
283        .iter()
284        .map(|item: &Yaml| get_size_from_yaml(item, metadata))
285        .collect()
286}
287
288/// Returns a string from a map given a key.
289///
290/// # Errors
291///
292/// If no such key exists or it cannot be parsed.
293pub fn get_string_by_key(
294    map: &linked_hash_map::LinkedHashMap<Yaml, Yaml>,
295    key: &str,
296) -> Result<String, YamlContentErr> {
297    match map.get(&Yaml::String(String::from(key))) {
298        Some(value) => get_string(value),
299        None => Err(YamlContentErr::new(format!("key `{key}` not found"))),
300    }
301}
302
303/// Returns an array of string from a map given a key.
304///
305/// # Errors
306///
307/// If no such key exists or it cannot be parsed.
308fn parse_string_array(array: &[Yaml]) -> Result<Vec<String>, YamlContentErr> {
309    let mut result = Vec::with_capacity(array.len());
310    for v in array {
311        result.push(get_string(v)?);
312    }
313    Ok(result)
314}
315
316#[cfg(test)]
317mod tests {
318    use rustc_hash::FxHashMap;
319
320    use super::*;
321
322    #[test]
323    fn get_map_ok() {
324        let mut map = linked_hash_map::LinkedHashMap::<Yaml, Yaml>::new();
325        map.insert(Yaml::Integer(0), Yaml::Integer(2));
326        let yaml = Yaml::Hash(map);
327        let map = get_map(&yaml);
328        assert!(map.is_ok());
329        let map = map.unwrap();
330        assert_eq!(map.len(), 1);
331        assert!(matches!(map.get(&Yaml::Integer(0)), Some(Yaml::Integer(2))));
332    }
333
334    #[test]
335    fn get_map_err() {
336        let yaml = Yaml::Array(vec![Yaml::Integer(0), Yaml::Integer(1), Yaml::Integer(2)]);
337        let map = get_map(&yaml);
338        assert!(map.is_err());
339    }
340
341    #[test]
342    fn get_yaml_by_key_ok() {
343        let mut map = linked_hash_map::LinkedHashMap::<Yaml, Yaml>::new();
344        map.insert(Yaml::String(String::from("yaml")), Yaml::Integer(2));
345        let yaml = get_yaml_by_key(&map, "yaml");
346        assert!(yaml.is_ok());
347        assert!(matches!(yaml.unwrap(), Yaml::Integer(2)));
348    }
349
350    #[test]
351    fn get_yaml_by_key_err() {
352        let mut map = linked_hash_map::LinkedHashMap::<Yaml, Yaml>::new();
353        map.insert(Yaml::String(String::from("yaml")), Yaml::Integer(2));
354        let yaml = get_yaml_by_key(&map, "json");
355        assert!(yaml.is_err());
356    }
357
358    #[test]
359    fn get_array_ok() {
360        let yaml = Yaml::Array(vec![Yaml::Integer(0), Yaml::Integer(1)]);
361        let array = get_array(&yaml);
362        assert!(array.is_ok());
363        let array = array.unwrap();
364        assert_eq!(array.len(), 2);
365        assert!(matches!(array[0], Yaml::Integer(0)));
366        assert!(matches!(array[1], Yaml::Integer(1)));
367    }
368
369    #[test]
370    fn get_array_err() {
371        let yaml = Yaml::Integer(0);
372        let array = get_array(&yaml);
373        assert!(array.is_err());
374    }
375
376    #[test]
377    fn get_bool_ok() {
378        let yaml = Yaml::Boolean(true);
379        let result = get_bool(&yaml);
380        assert!(result.is_ok());
381        assert!(result.unwrap());
382    }
383
384    #[test]
385    fn get_bool_err() {
386        let yaml = Yaml::Integer(0);
387        let result = get_bool(&yaml);
388        assert!(result.is_err());
389    }
390
391    #[test]
392    fn get_bool_by_key_ok() {
393        let mut map = linked_hash_map::LinkedHashMap::<Yaml, Yaml>::new();
394        map.insert(Yaml::String(String::from("bool")), Yaml::Boolean(true));
395        let result = get_bool_by_key(&map, "bool");
396        assert!(result.is_ok());
397        assert!(result.unwrap());
398    }
399
400    #[test]
401    fn get_bool_by_key_err() {
402        let mut map = linked_hash_map::LinkedHashMap::<Yaml, Yaml>::new();
403        map.insert(Yaml::String(String::from("integer")), Yaml::Integer(0));
404        let result = get_bool_by_key(&map, "array");
405        assert!(result.is_err());
406        let result = get_bool_by_key(&map, "integer");
407        assert!(result.is_err());
408    }
409
410    #[test]
411    fn get_usize_ok() {
412        let yaml = Yaml::Integer(0);
413        let result = get_usize(&yaml);
414        assert!(result.is_ok());
415        assert_eq!(result.unwrap(), 0);
416    }
417
418    #[test]
419    fn get_usize_err() {
420        let yaml = Yaml::Real(String::from("0.0"));
421        let result = get_usize(&yaml);
422        assert!(result.is_err());
423
424        let yaml = Yaml::Integer(-1);
425        let result = get_usize(&yaml);
426        assert!(result.is_err());
427    }
428
429    #[test]
430    fn get_usize_array_ok() {
431        let yaml = Yaml::Array(vec![Yaml::Integer(0), Yaml::Integer(1)]);
432        let array = get_usize_array(&yaml);
433        assert!(array.is_ok());
434        assert_eq!(array.unwrap(), vec![0, 1]);
435    }
436
437    #[test]
438    fn get_usize_array_err() {
439        let yaml = Yaml::Integer(0);
440        let array = get_usize_array(&yaml);
441        assert!(array.is_err());
442
443        let yaml = Yaml::Array(vec![Yaml::Integer(0), Yaml::Boolean(true)]);
444        let array = get_usize_array(&yaml);
445        assert!(array.is_err());
446
447        let yaml = Yaml::Array(vec![Yaml::Integer(0), Yaml::Integer(-1)]);
448        let array = get_usize_array(&yaml);
449        assert!(array.is_err());
450    }
451
452    #[test]
453    fn get_usize_by_key_ok() {
454        let mut map = linked_hash_map::LinkedHashMap::<Yaml, Yaml>::new();
455        map.insert(Yaml::String(String::from("usize")), Yaml::Integer(0));
456        let result = get_usize_by_key(&map, "usize");
457        assert!(result.is_ok());
458        assert_eq!(result.unwrap(), 0);
459    }
460
461    #[test]
462    fn get_usize_by_key_err() {
463        let mut map = linked_hash_map::LinkedHashMap::<Yaml, Yaml>::new();
464        map.insert(Yaml::String(String::from("bool")), Yaml::Boolean(true));
465        map.insert(Yaml::String(String::from("integer")), Yaml::Integer(-1));
466        let result = get_usize_by_key(&map, "array");
467        assert!(result.is_err());
468        let result = get_usize_by_key(&map, "bool");
469        assert!(result.is_err());
470        let result = get_usize_by_key(&map, "integer");
471        assert!(result.is_err());
472    }
473
474    #[test]
475    fn get_usize_array_by_key_ok() {
476        let mut map = linked_hash_map::LinkedHashMap::<Yaml, Yaml>::new();
477        map.insert(
478            Yaml::String(String::from("array")),
479            Yaml::Array(vec![Yaml::Integer(0), Yaml::Integer(1)]),
480        );
481        let array = get_usize_array_by_key(&map, "array");
482        assert!(array.is_ok());
483        assert_eq!(array.unwrap(), vec![0, 1]);
484    }
485
486    #[test]
487    fn get_usize_array_by_key_err() {
488        let mut map = linked_hash_map::LinkedHashMap::<Yaml, Yaml>::new();
489        map.insert(
490            Yaml::String(String::from("array1")),
491            Yaml::Array(vec![Yaml::Integer(0), Yaml::Boolean(true)]),
492        );
493        map.insert(
494            Yaml::String(String::from("array2")),
495            Yaml::Array(vec![Yaml::Integer(0), Yaml::Integer(-1)]),
496        );
497        let array = get_usize_array_by_key(&map, "array");
498        assert!(array.is_err());
499        let array = get_usize_array_by_key(&map, "array1");
500        assert!(array.is_err());
501        let array = get_usize_array_by_key(&map, "array2");
502        assert!(array.is_err());
503    }
504
505    #[test]
506    fn get_numeric_ok() {
507        let yaml = Yaml::Integer(0);
508        let result = get_numeric::<variable_type::Integer>(&yaml);
509        assert!(result.is_ok());
510        assert_eq!(result.unwrap(), 0);
511    }
512
513    #[test]
514    fn get_numeric_err() {
515        let yaml = Yaml::Real(String::from("0.5"));
516        let result = get_numeric::<variable_type::Integer>(&yaml);
517        assert!(result.is_err());
518        let yaml = Yaml::Boolean(true);
519        let result = get_numeric::<variable_type::Integer>(&yaml);
520        assert!(result.is_err());
521    }
522
523    #[test]
524    fn get_numeric_by_key_ok() {
525        let mut map = linked_hash_map::LinkedHashMap::<Yaml, Yaml>::new();
526        map.insert(Yaml::String(String::from("numeric")), Yaml::Integer(0));
527        let result = get_numeric_by_key::<variable_type::Integer>(&map, "numeric");
528        assert!(result.is_ok());
529        assert_eq!(result.unwrap(), 0);
530    }
531
532    #[test]
533    fn get_numeric_by_key_err() {
534        let mut map = linked_hash_map::LinkedHashMap::<Yaml, Yaml>::new();
535        map.insert(
536            Yaml::String(String::from("numeric")),
537            Yaml::Real(String::from("0.5")),
538        );
539        map.insert(Yaml::String(String::from("bool")), Yaml::Boolean(true));
540        let result = get_numeric_by_key::<variable_type::Integer>(&map, "integer");
541        assert!(result.is_err());
542        let result = get_numeric_by_key::<variable_type::Integer>(&map, "bool");
543        assert!(result.is_err());
544        let result = get_numeric_by_key::<variable_type::Integer>(&map, "numeric");
545        assert!(result.is_err());
546    }
547
548    #[test]
549    fn get_string_ok() {
550        let yaml = Yaml::String(String::from("string"));
551        let result = get_string(&yaml);
552        assert!(result.is_ok());
553        assert_eq!(result.unwrap(), String::from("string"));
554    }
555
556    #[test]
557    fn get_string_err() {
558        let yaml = Yaml::Real(String::from("0.0"));
559        let result = get_string(&yaml);
560        assert!(result.is_err());
561    }
562
563    #[test]
564    fn get_string_array_ok() {
565        let yaml = Yaml::Array(vec![
566            Yaml::String(String::from("0")),
567            Yaml::String(String::from("1")),
568        ]);
569        let array = get_string_array(&yaml);
570        assert!(array.is_ok());
571        assert_eq!(array.unwrap(), vec![String::from("0"), String::from("1")]);
572    }
573
574    #[test]
575    fn get_string_array_err() {
576        let yaml = Yaml::Integer(0);
577        let array = get_string_array(&yaml);
578        assert!(array.is_err());
579
580        let yaml = Yaml::Array(vec![Yaml::String(String::from("0")), Yaml::Boolean(true)]);
581        let array = get_string_array(&yaml);
582        assert!(array.is_err());
583    }
584
585    #[test]
586    fn get_size_from_yaml_ok() {
587        let yaml = Yaml::Integer(1);
588        let size = get_size_from_yaml(&yaml, &StateMetadata::default());
589        assert!(size.is_ok());
590        assert_eq!(size.unwrap(), 1);
591
592        let yaml = Yaml::String("object".to_owned());
593        let mut name_to_object_type = FxHashMap::default();
594        name_to_object_type.insert("object".to_owned(), 0);
595        let state_metadata = StateMetadata {
596            name_to_object_type,
597            object_numbers: vec![10],
598            ..Default::default()
599        };
600        let size = get_size_from_yaml(&yaml, &state_metadata);
601        assert!(size.is_ok());
602        assert_eq!(size.unwrap(), 10);
603    }
604
605    #[test]
606    fn get_size_from_yaml_err() {
607        let yaml = Yaml::Array(vec![]);
608        let size = get_size_from_yaml(&yaml, &StateMetadata::default());
609        assert!(size.is_err());
610
611        let yaml = Yaml::String("object".to_owned());
612        let size = get_size_from_yaml(&yaml, &StateMetadata::default());
613        assert!(size.is_err());
614    }
615
616    #[test]
617    fn get_table_arg_array_ok() {
618        let arg_array = vec![Yaml::Integer(1), Yaml::String("object".to_owned())];
619        let mut name_to_object_type = FxHashMap::default();
620        name_to_object_type.insert("object".to_owned(), 0);
621        let state_metadata = StateMetadata {
622            name_to_object_type,
623            object_numbers: vec![10],
624            ..Default::default()
625        };
626
627        let sizes = get_table_arg_array(&arg_array, &state_metadata);
628        assert!(sizes.is_ok());
629        assert_eq!(sizes.unwrap(), vec![1, 10]);
630    }
631
632    #[test]
633    fn get_table_arg_array_err() {
634        let arg_array = vec![
635            Yaml::Integer(1),
636            Yaml::String("non_exist_object".to_owned()),
637        ];
638        let mut name_to_object_type = FxHashMap::default();
639        name_to_object_type.insert("object".to_owned(), 0);
640        let state_metadata = StateMetadata {
641            name_to_object_type,
642            object_numbers: vec![10],
643            ..Default::default()
644        };
645
646        let sizes = get_table_arg_array(&arg_array, &state_metadata);
647        assert!(sizes.is_err());
648    }
649
650    #[test]
651    fn get_string_by_key_ok() {
652        let mut map = linked_hash_map::LinkedHashMap::<Yaml, Yaml>::new();
653        map.insert(
654            Yaml::String(String::from("string")),
655            Yaml::String(String::from("0")),
656        );
657        let result = get_string_by_key(&map, "string");
658        assert!(result.is_ok());
659        assert_eq!(result.unwrap(), String::from("0"));
660    }
661
662    #[test]
663    fn get_string_by_key_err() {
664        let mut map = linked_hash_map::LinkedHashMap::<Yaml, Yaml>::new();
665        map.insert(Yaml::String(String::from("bool")), Yaml::Boolean(true));
666        let result = get_string_by_key(&map, "array");
667        assert!(result.is_err());
668        let result = get_string_by_key(&map, "bool");
669        assert!(result.is_err());
670    }
671}