1 use linked_hash_map::LinkedHashMap;
2 use crate::parser::*;
3 use crate::scanner::{Marker, ScanError, TScalarStyle, TokenType};
4 use std::collections::BTreeMap;
5 use std::f64;
6 use std::i64;
7 use std::mem;
8 use std::ops::Index;
9 use std::string;
10 use 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)]
29 pub 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 
55 pub type Array = Vec<Yaml>;
56 pub type Hash = LinkedHashMap<Yaml, Yaml>;
57 
58 // parse f64 as Core schema
59 // See: https://github.com/chyh1990/yaml-rust/issues/51
parse_f64(v: &str) -> Option<f64>60 fn 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 
69 pub 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 
78 impl MarkedEventReceiver for YamlLoader {
on_event(&mut self, ev: Event, _: Marker)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 
160 impl YamlLoader {
insert_new_node(&mut self, node: (Yaml, usize))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 
load_from_str(source: &str) -> Result<Vec<Yaml>, ScanError>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 
202 macro_rules! define_as (
203     ($name:ident, $t:ident, $yt:ident) => (
204 pub fn $name(&self) -> Option<$t> {
205     match *self {
206         Yaml::$yt(v) => Some(v),
207         _ => None
208     }
209 }
210     );
211 );
212 
213 macro_rules! define_as_ref (
214     ($name:ident, $t:ty, $yt:ident) => (
215 pub fn $name(&self) -> Option<$t> {
216     match *self {
217         Yaml::$yt(ref v) => Some(v),
218         _ => None
219     }
220 }
221     );
222 );
223 
224 macro_rules! define_into (
225     ($name:ident, $t:ty, $yt:ident) => (
226 pub fn $name(self) -> Option<$t> {
227     match self {
228         Yaml::$yt(v) => Some(v),
229         _ => None
230     }
231 }
232     );
233 );
234 
235 impl 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 
is_null(&self) -> bool249     pub fn is_null(&self) -> bool {
250         match *self {
251             Yaml::Null => true,
252             _ => false,
253         }
254     }
255 
is_badvalue(&self) -> bool256     pub fn is_badvalue(&self) -> bool {
257         match *self {
258             Yaml::BadValue => true,
259             _ => false,
260         }
261     }
262 
is_array(&self) -> bool263     pub fn is_array(&self) -> bool {
264         match *self {
265             Yaml::Array(_) => true,
266             _ => false,
267         }
268     }
269 
as_f64(&self) -> Option<f64>270     pub fn as_f64(&self) -> Option<f64> {
271         match *self {
272             Yaml::Real(ref v) => parse_f64(v),
273             _ => None,
274         }
275     }
276 
into_f64(self) -> Option<f64>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))]
286 impl Yaml {
287     // Not implementing FromStr because there is no possibility of Error.
288     // This function falls back to Yaml::String if nothing else matches.
from_str(v: &str) -> Yaml289     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 
317 static BAD_VALUE: Yaml = Yaml::BadValue;
318 impl<'a> Index<&'a str> for Yaml {
319     type Output = Yaml;
320 
index(&self, idx: &'a str) -> &Yaml321     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 
330 impl Index<usize> for Yaml {
331     type Output = Yaml;
332 
index(&self, idx: usize) -> &Yaml333     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 
345 impl IntoIterator for Yaml {
346     type Item = Yaml;
347     type IntoIter = YamlIter;
348 
into_iter(self) -> Self::IntoIter349     fn into_iter(self) -> Self::IntoIter {
350         YamlIter {
351             yaml: self.into_vec().unwrap_or_else(Vec::new).into_iter(),
352         }
353     }
354 }
355 
356 pub struct YamlIter {
357     yaml: vec::IntoIter<Yaml>,
358 }
359 
360 impl Iterator for YamlIter {
361     type Item = Yaml;
362 
next(&mut self) -> Option<Yaml>363     fn next(&mut self) -> Option<Yaml> {
364         self.yaml.next()
365     }
366 }
367 
368 #[cfg(test)]
369 mod test {
370     use std::f64;
371     use crate::yaml::*;
372     #[test]
test_coerce()373     fn test_coerce() {
374         let s = "---
375 a: 1
376 b: 2.2
377 c: [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]
test_empty_doc()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]
test_parser()396     fn test_parser() {
397         let s: String = "
398 # comment
399 a0 bb: val
400 a1:
401     b1: 4
402     b2: d
403 a2: 4 # i'm comment
404 a3: [1, 2, 3]
405 a4:
406     - - a1
407       - a2
408     - 2
409 a5: 'single_quoted'
410 a6: \"double_quoted\"
411 a7: 你好
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]
test_multi_doc()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]
test_anchor()433     fn test_anchor() {
434         let s = "
435 a1: &DEFAULT
436     b1: 4
437     b2: d
438 a2: *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]
test_bad_anchor()446     fn test_bad_anchor() {
447         let s = "
448 a1: &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]
test_github_27()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]
test_plain_datatype()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]
test_bad_hyphen()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]
test_issue_65()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]
test_bad_docstart()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]
test_plain_datatype_with_into_methods()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]
test_hash_order()611     fn test_hash_order() {
612         let s = "---
613 b: ~
614 a: ~
615 c: ~
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]
test_integer_key()636     fn test_integer_key() {
637         let s = "
638 0:
639     important: true
640 1:
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]
test_indentation_equality()649     fn test_indentation_equality() {
650         let four_spaces = YamlLoader::load_from_str(
651             r#"
652 hash:
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#"
664 hash:
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#"
676 hash:
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#"
688 hash:
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]
test_two_space_indentations()704     fn test_two_space_indentations() {
705         // https://github.com/kbknapp/clap-rs/issues/965
706 
707         let s = r#"
708 subcommands:
709   - server:
710     about: server related commands
711 subcommands2:
712   - server:
713       about: server related commands
714 subcommands3:
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]
test_recursion_depth_check_objects()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]
test_recursion_depth_check_arrays()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 }
740