yaml_rust/
yaml.rs

1use linked_hash_map::LinkedHashMap;
2use crate::parser::*;
3use crate::scanner::{Marker, ScanError, TScalarStyle, TokenType};
4use std::collections::BTreeMap;
5use std::f64;
6use std::i64;
7use std::mem;
8use std::ops::Index;
9use std::string;
10use std::vec;
11
12/// A YAML node is stored as this `Yaml` enumeration, which provides an easy way to
13/// access your YAML document.
14///
15/// # Examples
16///
17/// ```
18/// use yaml_rust::Yaml;
19/// let foo = Yaml::from_str("-123"); // convert the string to the appropriate YAML type
20/// assert_eq!(foo.as_i64().unwrap(), -123);
21///
22/// // iterate over an Array
23/// let vec = Yaml::Array(vec![Yaml::Integer(1), Yaml::Integer(2)]);
24/// for v in vec.as_vec().unwrap() {
25///     assert!(v.as_i64().is_some());
26/// }
27/// ```
28#[derive(Clone, PartialEq, PartialOrd, Debug, Eq, Ord, Hash)]
29pub enum Yaml {
30    /// Float types are stored as String and parsed on demand.
31    /// Note that f64 does NOT implement Eq trait and can NOT be stored in BTreeMap.
32    Real(string::String),
33    /// YAML int is stored as i64.
34    Integer(i64),
35    /// YAML scalar.
36    String(string::String),
37    /// YAML bool, e.g. `true` or `false`.
38    Boolean(bool),
39    /// YAML array, can be accessed as a `Vec`.
40    Array(self::Array),
41    /// YAML hash, can be accessed as a `LinkedHashMap`.
42    ///
43    /// Insertion order will match the order of insertion into the map.
44    Hash(self::Hash),
45    /// Alias, not fully supported yet.
46    Alias(usize),
47    /// YAML null, e.g. `null` or `~`.
48    Null,
49    /// Accessing a nonexistent node via the Index trait returns `BadValue`. This
50    /// simplifies error handling in the calling code. Invalid type conversion also
51    /// returns `BadValue`.
52    BadValue,
53}
54
55pub type Array = Vec<Yaml>;
56pub type Hash = LinkedHashMap<Yaml, Yaml>;
57
58// parse f64 as Core schema
59// See: https://github.com/chyh1990/yaml-rust/issues/51
60fn parse_f64(v: &str) -> Option<f64> {
61    match v {
62        ".inf" | ".Inf" | ".INF" | "+.inf" | "+.Inf" | "+.INF" => Some(f64::INFINITY),
63        "-.inf" | "-.Inf" | "-.INF" => Some(f64::NEG_INFINITY),
64        ".nan" | "NaN" | ".NAN" => Some(f64::NAN),
65        _ => v.parse::<f64>().ok(),
66    }
67}
68
69pub struct YamlLoader {
70    docs: Vec<Yaml>,
71    // states
72    // (current node, anchor_id) tuple
73    doc_stack: Vec<(Yaml, usize)>,
74    key_stack: Vec<Yaml>,
75    anchor_map: BTreeMap<usize, Yaml>,
76}
77
78impl MarkedEventReceiver for YamlLoader {
79    fn on_event(&mut self, ev: Event, _: Marker) {
80        // println!("EV {:?}", ev);
81        match ev {
82            Event::DocumentStart => {
83                // do nothing
84            }
85            Event::DocumentEnd => {
86                match self.doc_stack.len() {
87                    // empty document
88                    0 => self.docs.push(Yaml::BadValue),
89                    1 => self.docs.push(self.doc_stack.pop().unwrap().0),
90                    _ => unreachable!(),
91                }
92            }
93            Event::SequenceStart(aid) => {
94                self.doc_stack.push((Yaml::Array(Vec::new()), aid));
95            }
96            Event::SequenceEnd => {
97                let node = self.doc_stack.pop().unwrap();
98                self.insert_new_node(node);
99            }
100            Event::MappingStart(aid) => {
101                self.doc_stack.push((Yaml::Hash(Hash::new()), aid));
102                self.key_stack.push(Yaml::BadValue);
103            }
104            Event::MappingEnd => {
105                self.key_stack.pop().unwrap();
106                let node = self.doc_stack.pop().unwrap();
107                self.insert_new_node(node);
108            }
109            Event::Scalar(v, style, aid, tag) => {
110                let node = if style != TScalarStyle::Plain {
111                    Yaml::String(v)
112                } else if let Some(TokenType::Tag(ref handle, ref suffix)) = tag {
113                    // XXX tag:yaml.org,2002:
114                    if handle == "!!" {
115                        match suffix.as_ref() {
116                            "bool" => {
117                                // "true" or "false"
118                                match v.parse::<bool>() {
119                                    Err(_) => Yaml::BadValue,
120                                    Ok(v) => Yaml::Boolean(v),
121                                }
122                            }
123                            "int" => match v.parse::<i64>() {
124                                Err(_) => Yaml::BadValue,
125                                Ok(v) => Yaml::Integer(v),
126                            },
127                            "float" => match parse_f64(&v) {
128                                Some(_) => Yaml::Real(v),
129                                None => Yaml::BadValue,
130                            },
131                            "null" => match v.as_ref() {
132                                "~" | "null" => Yaml::Null,
133                                _ => Yaml::BadValue,
134                            },
135                            _ => Yaml::String(v),
136                        }
137                    } else {
138                        Yaml::String(v)
139                    }
140                } else {
141                    // Datatype is not specified, or unrecognized
142                    Yaml::from_str(&v)
143                };
144
145                self.insert_new_node((node, aid));
146            }
147            Event::Alias(id) => {
148                let n = match self.anchor_map.get(&id) {
149                    Some(v) => v.clone(),
150                    None => Yaml::BadValue,
151                };
152                self.insert_new_node((n, 0));
153            }
154            _ => { /* ignore */ }
155        }
156        // println!("DOC {:?}", self.doc_stack);
157    }
158}
159
160impl YamlLoader {
161    fn insert_new_node(&mut self, node: (Yaml, usize)) {
162        // valid anchor id starts from 1
163        if node.1 > 0 {
164            self.anchor_map.insert(node.1, node.0.clone());
165        }
166        if self.doc_stack.is_empty() {
167            self.doc_stack.push(node);
168        } else {
169            let parent = self.doc_stack.last_mut().unwrap();
170            match *parent {
171                (Yaml::Array(ref mut v), _) => v.push(node.0),
172                (Yaml::Hash(ref mut h), _) => {
173                    let cur_key = self.key_stack.last_mut().unwrap();
174                    // current node is a key
175                    if cur_key.is_badvalue() {
176                        *cur_key = node.0;
177                    // current node is a value
178                    } else {
179                        let mut newkey = Yaml::BadValue;
180                        mem::swap(&mut newkey, cur_key);
181                        h.insert(newkey, node.0);
182                    }
183                }
184                _ => unreachable!(),
185            }
186        }
187    }
188
189    pub fn load_from_str(source: &str) -> Result<Vec<Yaml>, ScanError> {
190        let mut loader = YamlLoader {
191            docs: Vec::new(),
192            doc_stack: Vec::new(),
193            key_stack: Vec::new(),
194            anchor_map: BTreeMap::new(),
195        };
196        let mut parser = Parser::new(source.chars());
197        parser.load(&mut loader, true)?;
198        Ok(loader.docs)
199    }
200}
201
202macro_rules! define_as (
203    ($name:ident, $t:ident, $yt:ident) => (
204pub fn $name(&self) -> Option<$t> {
205    match *self {
206        Yaml::$yt(v) => Some(v),
207        _ => None
208    }
209}
210    );
211);
212
213macro_rules! define_as_ref (
214    ($name:ident, $t:ty, $yt:ident) => (
215pub fn $name(&self) -> Option<$t> {
216    match *self {
217        Yaml::$yt(ref v) => Some(v),
218        _ => None
219    }
220}
221    );
222);
223
224macro_rules! define_into (
225    ($name:ident, $t:ty, $yt:ident) => (
226pub fn $name(self) -> Option<$t> {
227    match self {
228        Yaml::$yt(v) => Some(v),
229        _ => None
230    }
231}
232    );
233);
234
235impl Yaml {
236    define_as!(as_bool, bool, Boolean);
237    define_as!(as_i64, i64, Integer);
238
239    define_as_ref!(as_str, &str, String);
240    define_as_ref!(as_hash, &Hash, Hash);
241    define_as_ref!(as_vec, &Array, Array);
242
243    define_into!(into_bool, bool, Boolean);
244    define_into!(into_i64, i64, Integer);
245    define_into!(into_string, String, String);
246    define_into!(into_hash, Hash, Hash);
247    define_into!(into_vec, Array, Array);
248
249    pub fn is_null(&self) -> bool {
250        match *self {
251            Yaml::Null => true,
252            _ => false,
253        }
254    }
255
256    pub fn is_badvalue(&self) -> bool {
257        match *self {
258            Yaml::BadValue => true,
259            _ => false,
260        }
261    }
262
263    pub fn is_array(&self) -> bool {
264        match *self {
265            Yaml::Array(_) => true,
266            _ => false,
267        }
268    }
269
270    pub fn as_f64(&self) -> Option<f64> {
271        match *self {
272            Yaml::Real(ref v) => parse_f64(v),
273            _ => None,
274        }
275    }
276
277    pub fn into_f64(self) -> Option<f64> {
278        match self {
279            Yaml::Real(ref v) => parse_f64(v),
280            _ => None,
281        }
282    }
283}
284
285#[cfg_attr(feature = "cargo-clippy", allow(should_implement_trait))]
286impl Yaml {
287    // Not implementing FromStr because there is no possibility of Error.
288    // This function falls back to Yaml::String if nothing else matches.
289    pub fn from_str(v: &str) -> Yaml {
290        if v.starts_with("0x") {
291            if let Ok(i) = i64::from_str_radix(&v[2..], 16) {
292                return Yaml::Integer(i);
293            }
294        }
295        if v.starts_with("0o") {
296            if let Ok(i) = i64::from_str_radix(&v[2..], 8) {
297                return Yaml::Integer(i);
298            }
299        }
300        if v.starts_with('+') {
301            if let Ok(i) = v[1..].parse::<i64>() {
302                return Yaml::Integer(i);
303            }
304        }
305        match v {
306            "~" | "null" => Yaml::Null,
307            "true" => Yaml::Boolean(true),
308            "false" => Yaml::Boolean(false),
309            _ if v.parse::<i64>().is_ok() => Yaml::Integer(v.parse::<i64>().unwrap()),
310            // try parsing as f64
311            _ if parse_f64(v).is_some() => Yaml::Real(v.to_owned()),
312            _ => Yaml::String(v.to_owned()),
313        }
314    }
315}
316
317static BAD_VALUE: Yaml = Yaml::BadValue;
318impl<'a> Index<&'a str> for Yaml {
319    type Output = Yaml;
320
321    fn index(&self, idx: &'a str) -> &Yaml {
322        let key = Yaml::String(idx.to_owned());
323        match self.as_hash() {
324            Some(h) => h.get(&key).unwrap_or(&BAD_VALUE),
325            None => &BAD_VALUE,
326        }
327    }
328}
329
330impl Index<usize> for Yaml {
331    type Output = Yaml;
332
333    fn index(&self, idx: usize) -> &Yaml {
334        if let Some(v) = self.as_vec() {
335            v.get(idx).unwrap_or(&BAD_VALUE)
336        } else if let Some(v) = self.as_hash() {
337            let key = Yaml::Integer(idx as i64);
338            v.get(&key).unwrap_or(&BAD_VALUE)
339        } else {
340            &BAD_VALUE
341        }
342    }
343}
344
345impl IntoIterator for Yaml {
346    type Item = Yaml;
347    type IntoIter = YamlIter;
348
349    fn into_iter(self) -> Self::IntoIter {
350        YamlIter {
351            yaml: self.into_vec().unwrap_or_else(Vec::new).into_iter(),
352        }
353    }
354}
355
356pub struct YamlIter {
357    yaml: vec::IntoIter<Yaml>,
358}
359
360impl Iterator for YamlIter {
361    type Item = Yaml;
362
363    fn next(&mut self) -> Option<Yaml> {
364        self.yaml.next()
365    }
366}
367
368#[cfg(test)]
369mod test {
370    use std::f64;
371    use crate::yaml::*;
372    #[test]
373    fn test_coerce() {
374        let s = "---
375a: 1
376b: 2.2
377c: [1, 2]
378";
379        let out = YamlLoader::load_from_str(&s).unwrap();
380        let doc = &out[0];
381        assert_eq!(doc["a"].as_i64().unwrap(), 1i64);
382        assert_eq!(doc["b"].as_f64().unwrap(), 2.2f64);
383        assert_eq!(doc["c"][1].as_i64().unwrap(), 2i64);
384        assert!(doc["d"][0].is_badvalue());
385    }
386
387    #[test]
388    fn test_empty_doc() {
389        let s: String = "".to_owned();
390        YamlLoader::load_from_str(&s).unwrap();
391        let s: String = "---".to_owned();
392        assert_eq!(YamlLoader::load_from_str(&s).unwrap()[0], Yaml::Null);
393    }
394
395    #[test]
396    fn test_parser() {
397        let s: String = "
398# comment
399a0 bb: val
400a1:
401    b1: 4
402    b2: d
403a2: 4 # i'm comment
404a3: [1, 2, 3]
405a4:
406    - - a1
407      - a2
408    - 2
409a5: 'single_quoted'
410a6: \"double_quoted\"
411a7: 你好
412"
413        .to_owned();
414        let out = YamlLoader::load_from_str(&s).unwrap();
415        let doc = &out[0];
416        assert_eq!(doc["a7"].as_str().unwrap(), "你好");
417    }
418
419    #[test]
420    fn test_multi_doc() {
421        let s = "
422'a scalar'
423---
424'a scalar'
425---
426'a scalar'
427";
428        let out = YamlLoader::load_from_str(&s).unwrap();
429        assert_eq!(out.len(), 3);
430    }
431
432    #[test]
433    fn test_anchor() {
434        let s = "
435a1: &DEFAULT
436    b1: 4
437    b2: d
438a2: *DEFAULT
439";
440        let out = YamlLoader::load_from_str(&s).unwrap();
441        let doc = &out[0];
442        assert_eq!(doc["a2"]["b1"].as_i64().unwrap(), 4);
443    }
444
445    #[test]
446    fn test_bad_anchor() {
447        let s = "
448a1: &DEFAULT
449    b1: 4
450    b2: *DEFAULT
451";
452        let out = YamlLoader::load_from_str(&s).unwrap();
453        let doc = &out[0];
454        assert_eq!(doc["a1"]["b2"], Yaml::BadValue);
455    }
456
457    #[test]
458    fn test_github_27() {
459        // https://github.com/chyh1990/yaml-rust/issues/27
460        let s = "&a";
461        let out = YamlLoader::load_from_str(&s).unwrap();
462        let doc = &out[0];
463        assert_eq!(doc.as_str().unwrap(), "");
464    }
465
466    #[test]
467    fn test_plain_datatype() {
468        let s = "
469- 'string'
470- \"string\"
471- string
472- 123
473- -321
474- 1.23
475- -1e4
476- ~
477- null
478- true
479- false
480- !!str 0
481- !!int 100
482- !!float 2
483- !!null ~
484- !!bool true
485- !!bool false
486- 0xFF
487# bad values
488- !!int string
489- !!float string
490- !!bool null
491- !!null val
492- 0o77
493- [ 0xF, 0xF ]
494- +12345
495- [ true, false ]
496";
497        let out = YamlLoader::load_from_str(&s).unwrap();
498        let doc = &out[0];
499
500        assert_eq!(doc[0].as_str().unwrap(), "string");
501        assert_eq!(doc[1].as_str().unwrap(), "string");
502        assert_eq!(doc[2].as_str().unwrap(), "string");
503        assert_eq!(doc[3].as_i64().unwrap(), 123);
504        assert_eq!(doc[4].as_i64().unwrap(), -321);
505        assert_eq!(doc[5].as_f64().unwrap(), 1.23);
506        assert_eq!(doc[6].as_f64().unwrap(), -1e4);
507        assert!(doc[7].is_null());
508        assert!(doc[8].is_null());
509        assert_eq!(doc[9].as_bool().unwrap(), true);
510        assert_eq!(doc[10].as_bool().unwrap(), false);
511        assert_eq!(doc[11].as_str().unwrap(), "0");
512        assert_eq!(doc[12].as_i64().unwrap(), 100);
513        assert_eq!(doc[13].as_f64().unwrap(), 2.0);
514        assert!(doc[14].is_null());
515        assert_eq!(doc[15].as_bool().unwrap(), true);
516        assert_eq!(doc[16].as_bool().unwrap(), false);
517        assert_eq!(doc[17].as_i64().unwrap(), 255);
518        assert!(doc[18].is_badvalue());
519        assert!(doc[19].is_badvalue());
520        assert!(doc[20].is_badvalue());
521        assert!(doc[21].is_badvalue());
522        assert_eq!(doc[22].as_i64().unwrap(), 63);
523        assert_eq!(doc[23][0].as_i64().unwrap(), 15);
524        assert_eq!(doc[23][1].as_i64().unwrap(), 15);
525        assert_eq!(doc[24].as_i64().unwrap(), 12345);
526        assert!(doc[25][0].as_bool().unwrap());
527        assert!(!doc[25][1].as_bool().unwrap());
528    }
529
530    #[test]
531    fn test_bad_hyphen() {
532        // See: https://github.com/chyh1990/yaml-rust/issues/23
533        let s = "{-";
534        assert!(YamlLoader::load_from_str(&s).is_err());
535    }
536
537    #[test]
538    fn test_issue_65() {
539        // See: https://github.com/chyh1990/yaml-rust/issues/65
540        let b = "\n\"ll\\\"ll\\\r\n\"ll\\\"ll\\\r\r\r\rU\r\r\rU";
541        assert!(YamlLoader::load_from_str(&b).is_err());
542    }
543
544    #[test]
545    fn test_bad_docstart() {
546        assert!(YamlLoader::load_from_str("---This used to cause an infinite loop").is_ok());
547        assert_eq!(
548            YamlLoader::load_from_str("----"),
549            Ok(vec![Yaml::String(String::from("----"))])
550        );
551        assert_eq!(
552            YamlLoader::load_from_str("--- #here goes a comment"),
553            Ok(vec![Yaml::Null])
554        );
555        assert_eq!(
556            YamlLoader::load_from_str("---- #here goes a comment"),
557            Ok(vec![Yaml::String(String::from("----"))])
558        );
559    }
560
561    #[test]
562    fn test_plain_datatype_with_into_methods() {
563        let s = "
564- 'string'
565- \"string\"
566- string
567- 123
568- -321
569- 1.23
570- -1e4
571- true
572- false
573- !!str 0
574- !!int 100
575- !!float 2
576- !!bool true
577- !!bool false
578- 0xFF
579- 0o77
580- +12345
581- -.INF
582- .NAN
583- !!float .INF
584";
585        let mut out = YamlLoader::load_from_str(&s).unwrap().into_iter();
586        let mut doc = out.next().unwrap().into_iter();
587
588        assert_eq!(doc.next().unwrap().into_string().unwrap(), "string");
589        assert_eq!(doc.next().unwrap().into_string().unwrap(), "string");
590        assert_eq!(doc.next().unwrap().into_string().unwrap(), "string");
591        assert_eq!(doc.next().unwrap().into_i64().unwrap(), 123);
592        assert_eq!(doc.next().unwrap().into_i64().unwrap(), -321);
593        assert_eq!(doc.next().unwrap().into_f64().unwrap(), 1.23);
594        assert_eq!(doc.next().unwrap().into_f64().unwrap(), -1e4);
595        assert_eq!(doc.next().unwrap().into_bool().unwrap(), true);
596        assert_eq!(doc.next().unwrap().into_bool().unwrap(), false);
597        assert_eq!(doc.next().unwrap().into_string().unwrap(), "0");
598        assert_eq!(doc.next().unwrap().into_i64().unwrap(), 100);
599        assert_eq!(doc.next().unwrap().into_f64().unwrap(), 2.0);
600        assert_eq!(doc.next().unwrap().into_bool().unwrap(), true);
601        assert_eq!(doc.next().unwrap().into_bool().unwrap(), false);
602        assert_eq!(doc.next().unwrap().into_i64().unwrap(), 255);
603        assert_eq!(doc.next().unwrap().into_i64().unwrap(), 63);
604        assert_eq!(doc.next().unwrap().into_i64().unwrap(), 12345);
605        assert_eq!(doc.next().unwrap().into_f64().unwrap(), f64::NEG_INFINITY);
606        assert!(doc.next().unwrap().into_f64().is_some());
607        assert_eq!(doc.next().unwrap().into_f64().unwrap(), f64::INFINITY);
608    }
609
610    #[test]
611    fn test_hash_order() {
612        let s = "---
613b: ~
614a: ~
615c: ~
616";
617        let out = YamlLoader::load_from_str(&s).unwrap();
618        let first = out.into_iter().next().unwrap();
619        let mut iter = first.into_hash().unwrap().into_iter();
620        assert_eq!(
621            Some((Yaml::String("b".to_owned()), Yaml::Null)),
622            iter.next()
623        );
624        assert_eq!(
625            Some((Yaml::String("a".to_owned()), Yaml::Null)),
626            iter.next()
627        );
628        assert_eq!(
629            Some((Yaml::String("c".to_owned()), Yaml::Null)),
630            iter.next()
631        );
632        assert_eq!(None, iter.next());
633    }
634
635    #[test]
636    fn test_integer_key() {
637        let s = "
6380:
639    important: true
6401:
641    important: false
642";
643        let out = YamlLoader::load_from_str(&s).unwrap();
644        let first = out.into_iter().next().unwrap();
645        assert_eq!(first[0]["important"].as_bool().unwrap(), true);
646    }
647
648    #[test]
649    fn test_indentation_equality() {
650        let four_spaces = YamlLoader::load_from_str(
651            r#"
652hash:
653    with:
654        indentations
655"#,
656        )
657        .unwrap()
658        .into_iter()
659        .next()
660        .unwrap();
661
662        let two_spaces = YamlLoader::load_from_str(
663            r#"
664hash:
665  with:
666    indentations
667"#,
668        )
669        .unwrap()
670        .into_iter()
671        .next()
672        .unwrap();
673
674        let one_space = YamlLoader::load_from_str(
675            r#"
676hash:
677 with:
678  indentations
679"#,
680        )
681        .unwrap()
682        .into_iter()
683        .next()
684        .unwrap();
685
686        let mixed_spaces = YamlLoader::load_from_str(
687            r#"
688hash:
689     with:
690               indentations
691"#,
692        )
693        .unwrap()
694        .into_iter()
695        .next()
696        .unwrap();
697
698        assert_eq!(four_spaces, two_spaces);
699        assert_eq!(two_spaces, one_space);
700        assert_eq!(four_spaces, mixed_spaces);
701    }
702
703    #[test]
704    fn test_two_space_indentations() {
705        // https://github.com/kbknapp/clap-rs/issues/965
706
707        let s = r#"
708subcommands:
709  - server:
710    about: server related commands
711subcommands2:
712  - server:
713      about: server related commands
714subcommands3:
715 - server:
716    about: server related commands
717            "#;
718
719        let out = YamlLoader::load_from_str(&s).unwrap();
720        let doc = &out.into_iter().next().unwrap();
721
722        println!("{:#?}", doc);
723        assert_eq!(doc["subcommands"][0]["server"], Yaml::Null);
724        assert!(doc["subcommands2"][0]["server"].as_hash().is_some());
725        assert!(doc["subcommands3"][0]["server"].as_hash().is_some());
726    }
727
728    #[test]
729    fn test_recursion_depth_check_objects() {
730        let s = "{a:".repeat(10_000) + &"}".repeat(10_000);
731        assert!(YamlLoader::load_from_str(&s).is_err());
732    }
733
734    #[test]
735    fn test_recursion_depth_check_arrays() {
736        let s = "[".repeat(10_000) + &"]".repeat(10_000);
737        assert!(YamlLoader::load_from_str(&s).is_err());
738    }
739}