yaml_rust2/
yaml.rs

1//! YAML objects manipulation utilities.
2
3#![allow(clippy::module_name_repetitions)]
4
5use std::borrow::Cow;
6use std::{collections::BTreeMap, convert::TryFrom, mem, ops::Index, ops::IndexMut};
7
8use hashlink::LinkedHashMap;
9
10use crate::parser::{Event, MarkedEventReceiver, Parser, Tag};
11use crate::scanner::{Marker, ScanError, TScalarStyle};
12
13/// A YAML node is stored as this `Yaml` enumeration, which provides an easy way to
14/// access your YAML document.
15///
16/// # Examples
17///
18/// ```
19/// use yaml_rust2::Yaml;
20/// let foo = Yaml::from_str("-123"); // convert the string to the appropriate YAML type
21/// assert_eq!(foo.as_i64().unwrap(), -123);
22///
23/// // iterate over an Array
24/// let vec = Yaml::Array(vec![Yaml::Integer(1), Yaml::Integer(2)]);
25/// for v in vec.as_vec().unwrap() {
26///     assert!(v.as_i64().is_some());
27/// }
28/// ```
29#[derive(Clone, PartialEq, PartialOrd, Debug, Eq, Ord, Hash)]
30pub enum Yaml {
31    /// Float types are stored as String and parsed on demand.
32    /// Note that `f64` does NOT implement Eq trait and can NOT be stored in `BTreeMap`.
33    Real(String),
34    /// YAML int is stored as i64.
35    Integer(i64),
36    /// YAML scalar.
37    String(String),
38    /// YAML bool, e.g. `true` or `false`.
39    Boolean(bool),
40    /// YAML array, can be accessed as a [`Vec`].
41    Array(Array),
42    /// YAML hash, can be accessed as a [`LinkedHashMap`].
43    ///
44    /// Insertion order will match the order of insertion into the map.
45    Hash(Hash),
46    /// Alias, not fully supported yet.
47    Alias(usize),
48    /// YAML null, e.g. `null` or `~`.
49    Null,
50    /// Accessing a nonexistent node via the Index trait returns `BadValue`. This
51    /// simplifies error handling in the calling code. Invalid type conversion also
52    /// returns `BadValue`.
53    BadValue,
54}
55
56/// The type contained in the `Yaml::Array` variant. This corresponds to YAML sequences.
57pub type Array = Vec<Yaml>;
58/// The type contained in the `Yaml::Hash` variant. This corresponds to YAML mappings.
59pub type Hash = LinkedHashMap<Yaml, Yaml>;
60
61// parse f64 as Core schema
62// See: https://github.com/chyh1990/yaml-rust/issues/51
63fn parse_f64(v: &str) -> Option<f64> {
64    match v {
65        ".inf" | ".Inf" | ".INF" | "+.inf" | "+.Inf" | "+.INF" => Some(f64::INFINITY),
66        "-.inf" | "-.Inf" | "-.INF" => Some(f64::NEG_INFINITY),
67        ".nan" | ".NaN" | ".NAN" => Some(f64::NAN),
68        // Test that `v` contains a digit so as not to pass in strings like `inf`,
69        // which rust will parse as a float
70        _ if v.as_bytes().iter().any(u8::is_ascii_digit) => v.parse::<f64>().ok(),
71        _ => None,
72    }
73}
74
75/// Main structure for quickly parsing YAML.
76///
77/// See [`YamlLoader::load_from_str`].
78#[derive(Default)]
79pub struct YamlLoader {
80    /// The different YAML documents that are loaded.
81    docs: Vec<Yaml>,
82    // states
83    // (current node, anchor_id) tuple
84    doc_stack: Vec<(Yaml, usize)>,
85    key_stack: Vec<Yaml>,
86    anchor_map: BTreeMap<usize, Yaml>,
87    /// An error, if one was encountered.
88    error: Option<ScanError>,
89}
90
91impl MarkedEventReceiver for YamlLoader {
92    fn on_event(&mut self, ev: Event, mark: Marker) {
93        if self.error.is_some() {
94            return;
95        }
96        if let Err(e) = self.on_event_impl(ev, mark) {
97            self.error = Some(e);
98        }
99    }
100}
101
102/// An error that happened when loading a YAML document.
103#[derive(Debug)]
104pub enum LoadError {
105    /// An I/O error.
106    IO(std::io::Error),
107    /// An error within the scanner. This indicates a malformed YAML input.
108    Scan(ScanError),
109    /// A decoding error (e.g.: Invalid UTF-8).
110    Decode(Cow<'static, str>),
111}
112
113impl From<std::io::Error> for LoadError {
114    fn from(error: std::io::Error) -> Self {
115        LoadError::IO(error)
116    }
117}
118
119impl YamlLoader {
120    fn on_event_impl(&mut self, ev: Event, mark: Marker) -> Result<(), ScanError> {
121        // println!("EV {:?}", ev);
122        match ev {
123            Event::DocumentStart | Event::Nothing | Event::StreamStart | Event::StreamEnd => {
124                // do nothing
125            }
126            Event::DocumentEnd => {
127                match self.doc_stack.len() {
128                    // empty document
129                    0 => self.docs.push(Yaml::BadValue),
130                    1 => self.docs.push(self.doc_stack.pop().unwrap().0),
131                    _ => unreachable!(),
132                }
133            }
134            Event::SequenceStart(aid, _) => {
135                self.doc_stack.push((Yaml::Array(Vec::new()), aid));
136            }
137            Event::SequenceEnd => {
138                let node = self.doc_stack.pop().unwrap();
139                self.insert_new_node(node, mark)?;
140            }
141            Event::MappingStart(aid, _) => {
142                self.doc_stack.push((Yaml::Hash(Hash::new()), aid));
143                self.key_stack.push(Yaml::BadValue);
144            }
145            Event::MappingEnd => {
146                self.key_stack.pop().unwrap();
147                let node = self.doc_stack.pop().unwrap();
148                self.insert_new_node(node, mark)?;
149            }
150            Event::Scalar(v, style, aid, tag) => {
151                let node = if style != TScalarStyle::Plain {
152                    Yaml::String(v)
153                } else if let Some(Tag {
154                    ref handle,
155                    ref suffix,
156                }) = tag
157                {
158                    if handle == "tag:yaml.org,2002:" {
159                        match suffix.as_ref() {
160                            "bool" => match v.as_str() {
161                                "true" | "True" | "TRUE" => Yaml::Boolean(true),
162                                "false" | "False" | "FALSE" => Yaml::Boolean(false),
163                                _ => Yaml::BadValue,
164                            },
165                            "int" => match v.parse::<i64>() {
166                                Err(_) => Yaml::BadValue,
167                                Ok(v) => Yaml::Integer(v),
168                            },
169                            "float" => match parse_f64(&v) {
170                                Some(_) => Yaml::Real(v),
171                                None => Yaml::BadValue,
172                            },
173                            "null" => match v.as_ref() {
174                                "~" | "null" => Yaml::Null,
175                                _ => Yaml::BadValue,
176                            },
177                            _ => Yaml::String(v),
178                        }
179                    } else {
180                        Yaml::String(v)
181                    }
182                } else {
183                    // Datatype is not specified, or unrecognized
184                    Yaml::from_str(&v)
185                };
186
187                self.insert_new_node((node, aid), mark)?;
188            }
189            Event::Alias(id) => {
190                let n = match self.anchor_map.get(&id) {
191                    Some(v) => v.clone(),
192                    None => Yaml::BadValue,
193                };
194                self.insert_new_node((n, 0), mark)?;
195            }
196        }
197        // println!("DOC {:?}", self.doc_stack);
198        Ok(())
199    }
200
201    fn insert_new_node(&mut self, node: (Yaml, usize), mark: Marker) -> Result<(), ScanError> {
202        // valid anchor id starts from 1
203        if node.1 > 0 {
204            self.anchor_map.insert(node.1, node.0.clone());
205        }
206        if self.doc_stack.is_empty() {
207            self.doc_stack.push(node);
208        } else {
209            let parent = self.doc_stack.last_mut().unwrap();
210            match *parent {
211                (Yaml::Array(ref mut v), _) => v.push(node.0),
212                (Yaml::Hash(ref mut h), _) => {
213                    let cur_key = self.key_stack.last_mut().unwrap();
214                    // current node is a key
215                    if cur_key.is_badvalue() {
216                        *cur_key = node.0;
217                    // current node is a value
218                    } else {
219                        let mut newkey = Yaml::BadValue;
220                        mem::swap(&mut newkey, cur_key);
221                        if h.insert(newkey, node.0).is_some() {
222                            let inserted_key = h.back().unwrap().0;
223                            return Err(ScanError::new_string(
224                                mark,
225                                format!("{inserted_key:?}: duplicated key in mapping"),
226                            ));
227                        }
228                    }
229                }
230                _ => unreachable!(),
231            }
232        }
233        Ok(())
234    }
235
236    /// Load the given string as a set of YAML documents.
237    ///
238    /// The `source` is interpreted as YAML documents and is parsed. Parsing succeeds if and only
239    /// if all documents are parsed successfully. An error in a latter document prevents the former
240    /// from being returned.
241    /// # Errors
242    /// Returns `ScanError` when loading fails.
243    pub fn load_from_str(source: &str) -> Result<Vec<Yaml>, ScanError> {
244        Self::load_from_iter(source.chars())
245    }
246
247    /// Load the contents of the given iterator as a set of YAML documents.
248    ///
249    /// The `source` is interpreted as YAML documents and is parsed. Parsing succeeds if and only
250    /// if all documents are parsed successfully. An error in a latter document prevents the former
251    /// from being returned.
252    /// # Errors
253    /// Returns `ScanError` when loading fails.
254    pub fn load_from_iter<I: Iterator<Item = char>>(source: I) -> Result<Vec<Yaml>, ScanError> {
255        let mut parser = Parser::new(source);
256        Self::load_from_parser(&mut parser)
257    }
258
259    /// Load the contents from the specified Parser as a set of YAML documents.
260    ///
261    /// Parsing succeeds if and only if all documents are parsed successfully.
262    /// An error in a latter document prevents the former from being returned.
263    /// # Errors
264    /// Returns `ScanError` when loading fails.
265    pub fn load_from_parser<I: Iterator<Item = char>>(
266        parser: &mut Parser<I>,
267    ) -> Result<Vec<Yaml>, ScanError> {
268        let mut loader = YamlLoader::default();
269        parser.load(&mut loader, true)?;
270        if let Some(e) = loader.error {
271            Err(e)
272        } else {
273            Ok(loader.docs)
274        }
275    }
276
277    /// Return a reference to the parsed Yaml documents.
278    #[must_use]
279    pub fn documents(&self) -> &[Yaml] {
280        &self.docs
281    }
282}
283
284#[cfg(feature = "encoding")]
285pub use encoding::{YAMLDecodingTrap, YAMLDecodingTrapFn, YamlDecoder};
286
287#[cfg(feature = "encoding")]
288mod encoding {
289    use std::{borrow::Cow, ops::ControlFlow};
290
291    use encoding_rs::{Decoder, DecoderResult, Encoding};
292
293    use crate::yaml::{LoadError, Yaml, YamlLoader};
294
295    /// The signature of the function to call when using [`YAMLDecodingTrap::Call`].
296    ///
297    /// The arguments are as follows:
298    ///  * `malformation_length`: The length of the sequence the decoder failed to decode.
299    ///  * `bytes_read_after_malformation`: The number of lookahead bytes the decoder consumed after
300    ///    the malformation.
301    ///  * `input_at_malformation`: What the input buffer is at the malformation.
302    ///    This is the buffer starting at the malformation. The first `malformation_length` bytes are
303    ///    the problematic sequence. The following `bytes_read_after_malformation` are already stored
304    ///    in the decoder and will not be re-fed.
305    ///  * `output`: The output string.
306    ///
307    /// The function must modify `output` as it feels is best. For instance, one could recreate the
308    /// behavior of [`YAMLDecodingTrap::Ignore`] with an empty function, [`YAMLDecodingTrap::Replace`]
309    /// by pushing a `\u{FFFD}` into `output` and [`YAMLDecodingTrap::Strict`] by returning
310    /// [`ControlFlow::Break`].
311    ///
312    /// # Returns
313    /// The function must return [`ControlFlow::Continue`] if decoding may continue or
314    /// [`ControlFlow::Break`] if decoding must be aborted. An optional error string may be supplied.
315    pub type YAMLDecodingTrapFn = fn(
316        malformation_length: u8,
317        bytes_read_after_malformation: u8,
318        input_at_malformation: &[u8],
319        output: &mut String,
320    ) -> ControlFlow<Cow<'static, str>>;
321
322    /// The behavior [`YamlDecoder`] must have when an decoding error occurs.
323    #[derive(Copy, Clone)]
324    pub enum YAMLDecodingTrap {
325        /// Ignore the offending bytes, remove them from the output.
326        Ignore,
327        /// Error out.
328        Strict,
329        /// Replace them with the Unicode REPLACEMENT CHARACTER.
330        Replace,
331        /// Call the user-supplied function upon decoding malformation.
332        Call(YAMLDecodingTrapFn),
333    }
334
335    impl PartialEq for YAMLDecodingTrap {
336        fn eq(&self, other: &YAMLDecodingTrap) -> bool {
337            match (self, other) {
338                (YAMLDecodingTrap::Call(self_fn), YAMLDecodingTrap::Call(other_fn)) => {
339                    *self_fn as usize == *other_fn as usize
340                }
341                (x, y) => x == y,
342            }
343        }
344    }
345
346    impl Eq for YAMLDecodingTrap {}
347
348    /// `YamlDecoder` is a `YamlLoader` builder that allows you to supply your own encoding error trap.
349    /// For example, to read a YAML file while ignoring Unicode decoding errors you can set the
350    /// `encoding_trap` to `encoding::DecoderTrap::Ignore`.
351    /// ```rust
352    /// use yaml_rust2::yaml::{YamlDecoder, YAMLDecodingTrap};
353    ///
354    /// let string = b"---
355    /// a\xa9: 1
356    /// b: 2.2
357    /// c: [1, 2]
358    /// ";
359    /// let out = YamlDecoder::read(string as &[u8])
360    ///     .encoding_trap(YAMLDecodingTrap::Ignore)
361    ///     .decode()
362    ///     .unwrap();
363    /// ```
364    pub struct YamlDecoder<T: std::io::Read> {
365        source: T,
366        trap: YAMLDecodingTrap,
367    }
368
369    impl<T: std::io::Read> YamlDecoder<T> {
370        /// Create a `YamlDecoder` decoding the given source.
371        pub fn read(source: T) -> YamlDecoder<T> {
372            YamlDecoder {
373                source,
374                trap: YAMLDecodingTrap::Strict,
375            }
376        }
377
378        /// Set the behavior of the decoder when the encoding is invalid.
379        pub fn encoding_trap(&mut self, trap: YAMLDecodingTrap) -> &mut Self {
380            self.trap = trap;
381            self
382        }
383
384        /// Run the decode operation with the source and trap the `YamlDecoder` was built with.
385        ///
386        /// # Errors
387        /// Returns `LoadError` when decoding fails.
388        pub fn decode(&mut self) -> Result<Vec<Yaml>, LoadError> {
389            let mut buffer = Vec::new();
390            self.source.read_to_end(&mut buffer)?;
391
392            // Check if the `encoding` library can detect encoding from the BOM, otherwise use
393            // `detect_utf16_endianness`.
394            let (encoding, _) =
395                Encoding::for_bom(&buffer).unwrap_or_else(|| (detect_utf16_endianness(&buffer), 2));
396            let mut decoder = encoding.new_decoder();
397            let mut output = String::new();
398
399            // Decode the input buffer.
400            decode_loop(&buffer, &mut output, &mut decoder, self.trap)?;
401
402            YamlLoader::load_from_str(&output).map_err(LoadError::Scan)
403        }
404    }
405
406    /// Perform a loop of [`Decoder::decode_to_string`], reallocating `output` if needed.
407    fn decode_loop(
408        input: &[u8],
409        output: &mut String,
410        decoder: &mut Decoder,
411        trap: YAMLDecodingTrap,
412    ) -> Result<(), LoadError> {
413        output.reserve(input.len());
414        let mut total_bytes_read = 0;
415
416        loop {
417            match decoder.decode_to_string_without_replacement(
418                &input[total_bytes_read..],
419                output,
420                true,
421            ) {
422                // If the input is empty, we processed the whole input.
423                (DecoderResult::InputEmpty, _) => break Ok(()),
424                // If the output is full, we must reallocate.
425                (DecoderResult::OutputFull, bytes_read) => {
426                    total_bytes_read += bytes_read;
427                    // The output is already reserved to the size of the input. We slowly resize. Here,
428                    // we're expecting that 10% of bytes will double in size when converting to UTF-8.
429                    output.reserve(input.len() / 10);
430                }
431                (DecoderResult::Malformed(malformed_len, bytes_after_malformed), bytes_read) => {
432                    total_bytes_read += bytes_read;
433                    match trap {
434                        // Ignore (skip over) malformed character.
435                        YAMLDecodingTrap::Ignore => {}
436                        // Replace them with the Unicode REPLACEMENT CHARACTER.
437                        YAMLDecodingTrap::Replace => {
438                            output.push('\u{FFFD}');
439                        }
440                        // Otherwise error, getting as much context as possible.
441                        YAMLDecodingTrap::Strict => {
442                            let malformed_len = malformed_len as usize;
443                            let bytes_after_malformed = bytes_after_malformed as usize;
444                            let byte_idx =
445                                total_bytes_read - (malformed_len + bytes_after_malformed);
446                            let malformed_sequence = &input[byte_idx..byte_idx + malformed_len];
447
448                            break Err(LoadError::Decode(Cow::Owned(format!(
449                                "Invalid character sequence at {byte_idx}: {malformed_sequence:?}",
450                            ))));
451                        }
452                        YAMLDecodingTrap::Call(callback) => {
453                            let byte_idx = total_bytes_read
454                                - ((malformed_len + bytes_after_malformed) as usize);
455                            let malformed_sequence =
456                                &input[byte_idx..byte_idx + malformed_len as usize];
457                            if let ControlFlow::Break(error) = callback(
458                                malformed_len,
459                                bytes_after_malformed,
460                                &input[byte_idx..],
461                                output,
462                            ) {
463                                if error.is_empty() {
464                                    break Err(LoadError::Decode(Cow::Owned(format!(
465                                    "Invalid character sequence at {byte_idx}: {malformed_sequence:?}",
466                                ))));
467                                }
468                                break Err(LoadError::Decode(error));
469                            }
470                        }
471                    }
472                }
473            }
474        }
475    }
476
477    /// The encoding crate knows how to tell apart UTF-8 from UTF-16LE and utf-16BE, when the
478    /// bytestream starts with BOM codepoint.
479    /// However, it doesn't even attempt to guess the UTF-16 endianness of the input bytestream since
480    /// in the general case the bytestream could start with a codepoint that uses both bytes.
481    ///
482    /// The YAML-1.2 spec mandates that the first character of a YAML document is an ASCII character.
483    /// This allows the encoding to be deduced by the pattern of null (#x00) characters.
484    //
485    /// See spec at <https://yaml.org/spec/1.2/spec.html#id2771184>
486    fn detect_utf16_endianness(b: &[u8]) -> &'static Encoding {
487        if b.len() > 1 && (b[0] != b[1]) {
488            if b[0] == 0 {
489                return encoding_rs::UTF_16BE;
490            } else if b[1] == 0 {
491                return encoding_rs::UTF_16LE;
492            }
493        }
494        encoding_rs::UTF_8
495    }
496}
497
498macro_rules! define_as (
499    ($name:ident, $t:ident, $yt:ident) => (
500/// Get a copy of the inner object in the YAML enum if it is a `$t`.
501///
502/// # Return
503/// If the variant of `self` is `Yaml::$yt`, return `Some($t)` with a copy of the `$t` contained.
504/// Otherwise, return `None`.
505#[must_use]
506pub fn $name(&self) -> Option<$t> {
507    match *self {
508        Yaml::$yt(v) => Some(v),
509        _ => None
510    }
511}
512    );
513);
514
515macro_rules! define_as_ref (
516    ($name:ident, $t:ty, $yt:ident) => (
517/// Get a reference to the inner object in the YAML enum if it is a `$t`.
518///
519/// # Return
520/// If the variant of `self` is `Yaml::$yt`, return `Some(&$t)` with the `$t` contained. Otherwise,
521/// return `None`.
522#[must_use]
523pub fn $name(&self) -> Option<$t> {
524    match *self {
525        Yaml::$yt(ref v) => Some(v),
526        _ => None
527    }
528}
529    );
530);
531
532macro_rules! define_as_mut_ref (
533    ($name:ident, $t:ty, $yt:ident) => (
534/// Get a mutable reference to the inner object in the YAML enum if it is a `$t`.
535///
536/// # Return
537/// If the variant of `self` is `Yaml::$yt`, return `Some(&mut $t)` with the `$t` contained.
538/// Otherwise, return `None`.
539#[must_use]
540pub fn $name(&mut self) -> Option<$t> {
541    match *self {
542        Yaml::$yt(ref mut v) => Some(v),
543        _ => None
544    }
545}
546    );
547);
548
549macro_rules! define_into (
550    ($name:ident, $t:ty, $yt:ident) => (
551/// Get the inner object in the YAML enum if it is a `$t`.
552///
553/// # Return
554/// If the variant of `self` is `Yaml::$yt`, return `Some($t)` with the `$t` contained. Otherwise,
555/// return `None`.
556#[must_use]
557pub fn $name(self) -> Option<$t> {
558    match self {
559        Yaml::$yt(v) => Some(v),
560        _ => None
561    }
562}
563    );
564);
565
566impl Yaml {
567    define_as!(as_bool, bool, Boolean);
568    define_as!(as_i64, i64, Integer);
569
570    define_as_ref!(as_str, &str, String);
571    define_as_ref!(as_hash, &Hash, Hash);
572    define_as_ref!(as_vec, &Array, Array);
573
574    define_as_mut_ref!(as_mut_hash, &mut Hash, Hash);
575    define_as_mut_ref!(as_mut_vec, &mut Array, Array);
576
577    define_into!(into_bool, bool, Boolean);
578    define_into!(into_i64, i64, Integer);
579    define_into!(into_string, String, String);
580    define_into!(into_hash, Hash, Hash);
581    define_into!(into_vec, Array, Array);
582
583    /// Return whether `self` is a [`Yaml::Null`] node.
584    #[must_use]
585    pub fn is_null(&self) -> bool {
586        matches!(*self, Yaml::Null)
587    }
588
589    /// Return whether `self` is a [`Yaml::BadValue`] node.
590    #[must_use]
591    pub fn is_badvalue(&self) -> bool {
592        matches!(*self, Yaml::BadValue)
593    }
594
595    /// Return whether `self` is a [`Yaml::Array`] node.
596    #[must_use]
597    pub fn is_array(&self) -> bool {
598        matches!(*self, Yaml::Array(_))
599    }
600
601    /// Return whether `self` is a [`Yaml::Hash`] node.
602    #[must_use]
603    pub fn is_hash(&self) -> bool {
604        matches!(*self, Yaml::Hash(_))
605    }
606
607    /// Return the `f64` value contained in this YAML node.
608    ///
609    /// If the node is not a [`Yaml::Real`] YAML node or its contents is not a valid `f64` string,
610    /// `None` is returned.
611    #[must_use]
612    pub fn as_f64(&self) -> Option<f64> {
613        if let Yaml::Real(ref v) = self {
614            parse_f64(v)
615        } else {
616            None
617        }
618    }
619
620    /// Return the `f64` value contained in this YAML node.
621    ///
622    /// If the node is not a [`Yaml::Real`] YAML node or its contents is not a valid `f64` string,
623    /// `None` is returned.
624    #[must_use]
625    pub fn into_f64(self) -> Option<f64> {
626        self.as_f64()
627    }
628
629    /// If a value is null or otherwise bad (see variants), consume it and
630    /// replace it with a given value `other`. Otherwise, return self unchanged.
631    ///
632    /// ```
633    /// use yaml_rust2::yaml::Yaml;
634    ///
635    /// assert_eq!(Yaml::BadValue.or(Yaml::Integer(3)),  Yaml::Integer(3));
636    /// assert_eq!(Yaml::Integer(3).or(Yaml::BadValue),  Yaml::Integer(3));
637    /// ```
638    #[must_use]
639    pub fn or(self, other: Self) -> Self {
640        match self {
641            Yaml::BadValue | Yaml::Null => other,
642            this => this,
643        }
644    }
645
646    /// See `or` for behavior. This performs the same operations, but with
647    /// borrowed values for less linear pipelines.
648    #[must_use]
649    pub fn borrowed_or<'a>(&'a self, other: &'a Self) -> &'a Self {
650        match self {
651            Yaml::BadValue | Yaml::Null => other,
652            this => this,
653        }
654    }
655}
656
657#[allow(clippy::should_implement_trait)]
658impl Yaml {
659    /// Convert a string to a [`Yaml`] node.
660    ///
661    /// [`Yaml`] does not implement [`std::str::FromStr`] since conversion may not fail. This
662    /// function falls back to [`Yaml::String`] if nothing else matches.
663    ///
664    /// # Examples
665    /// ```
666    /// # use yaml_rust2::yaml::Yaml;
667    /// assert!(matches!(Yaml::from_str("42"), Yaml::Integer(42)));
668    /// assert!(matches!(Yaml::from_str("0x2A"), Yaml::Integer(42)));
669    /// assert!(matches!(Yaml::from_str("0o52"), Yaml::Integer(42)));
670    /// assert!(matches!(Yaml::from_str("~"), Yaml::Null));
671    /// assert!(matches!(Yaml::from_str("null"), Yaml::Null));
672    /// assert!(matches!(Yaml::from_str("true"), Yaml::Boolean(true)));
673    /// assert!(matches!(Yaml::from_str("True"), Yaml::Boolean(true)));
674    /// assert!(matches!(Yaml::from_str("TRUE"), Yaml::Boolean(true)));
675    /// assert!(matches!(Yaml::from_str("false"), Yaml::Boolean(false)));
676    /// assert!(matches!(Yaml::from_str("False"), Yaml::Boolean(false)));
677    /// assert!(matches!(Yaml::from_str("FALSE"), Yaml::Boolean(false)));
678    /// assert!(matches!(Yaml::from_str("3.14"), Yaml::Real(_)));
679    /// assert!(matches!(Yaml::from_str("foo"), Yaml::String(_)));
680    /// ```
681    #[must_use]
682    pub fn from_str(v: &str) -> Yaml {
683        if let Some(number) = v.strip_prefix("0x") {
684            if let Ok(i) = i64::from_str_radix(number, 16) {
685                return Yaml::Integer(i);
686            }
687        } else if let Some(number) = v.strip_prefix("0o") {
688            if let Ok(i) = i64::from_str_radix(number, 8) {
689                return Yaml::Integer(i);
690            }
691        } else if let Some(number) = v.strip_prefix('+') {
692            if let Ok(i) = number.parse::<i64>() {
693                return Yaml::Integer(i);
694            }
695        }
696        match v {
697            "" | "~" | "null" => Yaml::Null,
698            "true" | "True" | "TRUE" => Yaml::Boolean(true),
699            "false" | "False" | "FALSE" => Yaml::Boolean(false),
700            _ => {
701                if let Ok(integer) = v.parse::<i64>() {
702                    Yaml::Integer(integer)
703                } else if parse_f64(v).is_some() {
704                    Yaml::Real(v.to_owned())
705                } else {
706                    Yaml::String(v.to_owned())
707                }
708            }
709        }
710    }
711}
712
713static BAD_VALUE: Yaml = Yaml::BadValue;
714impl<'a> Index<&'a str> for Yaml {
715    type Output = Yaml;
716
717    /// Perform indexing if `self` is a mapping.
718    ///
719    /// # Return
720    /// If `self` is a [`Yaml::Hash`], returns an immutable borrow to the value associated to the
721    /// given key in the hash.
722    ///
723    /// This function returns a [`Yaml::BadValue`] if the underlying [`type@Hash`] does not contain
724    /// [`Yaml::String`]`{idx}` as a key.
725    ///
726    /// This function also returns a [`Yaml::BadValue`] if `self` is not a [`Yaml::Hash`].
727    fn index(&self, idx: &'a str) -> &Yaml {
728        let key = Yaml::String(idx.to_owned());
729        match self.as_hash() {
730            Some(h) => h.get(&key).unwrap_or(&BAD_VALUE),
731            None => &BAD_VALUE,
732        }
733    }
734}
735
736impl<'a> IndexMut<&'a str> for Yaml {
737    /// Perform indexing if `self` is a mapping.
738    ///
739    /// Since we cannot return a mutable borrow to a static [`Yaml::BadValue`] as we return an
740    /// immutable one in [`Index<&'a str>`], this function panics on out of bounds.
741    ///
742    /// # Panics
743    /// This function panics if the given key is not contained in `self` (as per [`IndexMut`]).
744    ///
745    /// This function also panics if `self` is not a [`Yaml::Hash`].
746    fn index_mut(&mut self, idx: &'a str) -> &mut Yaml {
747        let key = Yaml::String(idx.to_owned());
748        match self.as_mut_hash() {
749            Some(h) => h.get_mut(&key).unwrap(),
750            None => panic!("Not a hash type"),
751        }
752    }
753}
754
755impl Index<usize> for Yaml {
756    type Output = Yaml;
757
758    /// Perform indexing if `self` is a sequence or a mapping.
759    ///
760    /// # Return
761    /// If `self` is a [`Yaml::Array`], returns an immutable borrow to the value located at the
762    /// given index in the array.
763    ///
764    /// Otherwise, if `self` is a [`Yaml::Hash`], returns a borrow to the value whose key is
765    /// [`Yaml::Integer`]`(idx)` (this would not work if the key is [`Yaml::String`]`("1")`.
766    ///
767    /// This function returns a [`Yaml::BadValue`] if the index given is out of range. If `self` is
768    /// a [`Yaml::Array`], this is when the index is bigger or equal to the length of the
769    /// underlying `Vec`. If `self` is a [`Yaml::Hash`], this is when the mapping sequence does not
770    /// contain [`Yaml::Integer`]`(idx)` as a key.
771    ///
772    /// This function also returns a [`Yaml::BadValue`] if `self` is not a [`Yaml::Array`] nor a
773    /// [`Yaml::Hash`].
774    fn index(&self, idx: usize) -> &Yaml {
775        if let Some(v) = self.as_vec() {
776            v.get(idx).unwrap_or(&BAD_VALUE)
777        } else if let Some(v) = self.as_hash() {
778            let key = Yaml::Integer(i64::try_from(idx).unwrap());
779            v.get(&key).unwrap_or(&BAD_VALUE)
780        } else {
781            &BAD_VALUE
782        }
783    }
784}
785
786impl IndexMut<usize> for Yaml {
787    /// Perform indexing if `self` is a sequence or a mapping.
788    ///
789    /// Since we cannot return a mutable borrow to a static [`Yaml::BadValue`] as we return an
790    /// immutable one in [`Index<usize>`], this function panics on out of bounds.
791    ///
792    /// # Panics
793    /// This function panics if the index given is out of range (as per [`IndexMut`]). If `self` is
794    /// a [`Yaml::Array`], this is when the index is bigger or equal to the length of the
795    /// underlying `Vec`. If `self` is a [`Yaml::Hash`], this is when the mapping sequence does not
796    /// contain [`Yaml::Integer`]`(idx)` as a key.
797    ///
798    /// This function also panics if `self` is not a [`Yaml::Array`] nor a [`Yaml::Hash`].
799    fn index_mut(&mut self, idx: usize) -> &mut Yaml {
800        match self {
801            Yaml::Array(sequence) => sequence.index_mut(idx),
802            Yaml::Hash(mapping) => {
803                let key = Yaml::Integer(i64::try_from(idx).unwrap());
804                mapping.get_mut(&key).unwrap()
805            }
806            _ => panic!("Attempting to index but `self` is not a sequence nor a mapping"),
807        }
808    }
809}
810
811impl IntoIterator for Yaml {
812    type Item = Yaml;
813    type IntoIter = YamlIter;
814
815    /// Extract the [`Array`] from `self` and iterate over it.
816    ///
817    /// If `self` is **not** of the [`Yaml::Array`] variant, this function will not panic or return
818    /// an error (as per the [`IntoIterator`] trait it cannot) but will instead return an iterator
819    /// over an empty [`Array`]. Callers have to ensure (using [`Yaml::is_array`], [`matches`] or
820    /// something similar) that the [`Yaml`] object is a [`Yaml::Array`] if they want to do error
821    /// handling.
822    ///
823    /// # Examples
824    /// ```
825    /// # use yaml_rust2::{Yaml, YamlLoader};
826    ///
827    /// // An array of 2 integers, 1 and 2.
828    /// let arr = &YamlLoader::load_from_str("- 1\n- 2").unwrap()[0];
829    ///
830    /// assert_eq!(arr.clone().into_iter().count(), 2);
831    /// assert_eq!(arr.clone().into_iter().next(), Some(Yaml::Integer(1)));
832    /// assert_eq!(arr.clone().into_iter().nth(1), Some(Yaml::Integer(2)));
833    ///
834    /// // An empty array returns an empty iterator.
835    /// let empty = Yaml::Array(vec![]);
836    /// assert_eq!(empty.into_iter().count(), 0);
837    ///
838    /// // A hash with 2 key-value pairs, `(a, b)` and `(c, d)`.
839    /// let hash = YamlLoader::load_from_str("a: b\nc: d").unwrap().remove(0);
840    /// // The hash has 2 elements.
841    /// assert_eq!(hash.as_hash().unwrap().iter().count(), 2);
842    /// // But since `into_iter` can't be used with a `Yaml::Hash`, `into_iter` returns an empty
843    /// // iterator.
844    /// assert_eq!(hash.into_iter().count(), 0);
845    /// ```
846    fn into_iter(self) -> Self::IntoIter {
847        YamlIter {
848            yaml: self.into_vec().unwrap_or_default().into_iter(),
849        }
850    }
851}
852
853/// An iterator over a [`Yaml`] node.
854pub struct YamlIter {
855    yaml: std::vec::IntoIter<Yaml>,
856}
857
858impl Iterator for YamlIter {
859    type Item = Yaml;
860
861    fn next(&mut self) -> Option<Yaml> {
862        self.yaml.next()
863    }
864}
865
866#[cfg(all(test, feature = "encoding"))]
867mod test {
868    use super::{YAMLDecodingTrap, Yaml, YamlDecoder};
869
870    #[test]
871    fn test_read_bom() {
872        let s = b"\xef\xbb\xbf---
873a: 1
874b: 2.2
875c: [1, 2]
876";
877        let out = YamlDecoder::read(s as &[u8]).decode().unwrap();
878        let doc = &out[0];
879        assert_eq!(doc["a"].as_i64().unwrap(), 1i64);
880        assert!((doc["b"].as_f64().unwrap() - 2.2f64).abs() <= f64::EPSILON);
881        assert_eq!(doc["c"][1].as_i64().unwrap(), 2i64);
882        assert!(doc["d"][0].is_badvalue());
883    }
884
885    #[test]
886    fn test_read_utf16le() {
887        let s = b"\xff\xfe-\x00-\x00-\x00
888\x00a\x00:\x00 \x001\x00
889\x00b\x00:\x00 \x002\x00.\x002\x00
890\x00c\x00:\x00 \x00[\x001\x00,\x00 \x002\x00]\x00
891\x00";
892        let out = YamlDecoder::read(s as &[u8]).decode().unwrap();
893        let doc = &out[0];
894        println!("GOT: {doc:?}");
895        assert_eq!(doc["a"].as_i64().unwrap(), 1i64);
896        assert!((doc["b"].as_f64().unwrap() - 2.2f64) <= f64::EPSILON);
897        assert_eq!(doc["c"][1].as_i64().unwrap(), 2i64);
898        assert!(doc["d"][0].is_badvalue());
899    }
900
901    #[test]
902    fn test_read_utf16be() {
903        let s = b"\xfe\xff\x00-\x00-\x00-\x00
904\x00a\x00:\x00 \x001\x00
905\x00b\x00:\x00 \x002\x00.\x002\x00
906\x00c\x00:\x00 \x00[\x001\x00,\x00 \x002\x00]\x00
907";
908        let out = YamlDecoder::read(s as &[u8]).decode().unwrap();
909        let doc = &out[0];
910        println!("GOT: {doc:?}");
911        assert_eq!(doc["a"].as_i64().unwrap(), 1i64);
912        assert!((doc["b"].as_f64().unwrap() - 2.2f64).abs() <= f64::EPSILON);
913        assert_eq!(doc["c"][1].as_i64().unwrap(), 2i64);
914        assert!(doc["d"][0].is_badvalue());
915    }
916
917    #[test]
918    fn test_read_utf16le_nobom() {
919        let s = b"-\x00-\x00-\x00
920\x00a\x00:\x00 \x001\x00
921\x00b\x00:\x00 \x002\x00.\x002\x00
922\x00c\x00:\x00 \x00[\x001\x00,\x00 \x002\x00]\x00
923\x00";
924        let out = YamlDecoder::read(s as &[u8]).decode().unwrap();
925        let doc = &out[0];
926        println!("GOT: {doc:?}");
927        assert_eq!(doc["a"].as_i64().unwrap(), 1i64);
928        assert!((doc["b"].as_f64().unwrap() - 2.2f64).abs() <= f64::EPSILON);
929        assert_eq!(doc["c"][1].as_i64().unwrap(), 2i64);
930        assert!(doc["d"][0].is_badvalue());
931    }
932
933    #[test]
934    fn test_read_trap() {
935        let s = b"---
936a\xa9: 1
937b: 2.2
938c: [1, 2]
939";
940        let out = YamlDecoder::read(s as &[u8])
941            .encoding_trap(YAMLDecodingTrap::Ignore)
942            .decode()
943            .unwrap();
944        let doc = &out[0];
945        println!("GOT: {doc:?}");
946        assert_eq!(doc["a"].as_i64().unwrap(), 1i64);
947        assert!((doc["b"].as_f64().unwrap() - 2.2f64).abs() <= f64::EPSILON);
948        assert_eq!(doc["c"][1].as_i64().unwrap(), 2i64);
949        assert!(doc["d"][0].is_badvalue());
950    }
951
952    #[test]
953    fn test_or() {
954        assert_eq!(Yaml::Null.or(Yaml::Integer(3)), Yaml::Integer(3));
955        assert_eq!(Yaml::Integer(3).or(Yaml::Integer(7)), Yaml::Integer(3));
956    }
957}