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 //
430 // Reserve at least 4 additional bytes to guarantee forward progress. A single
431 // UTF-8 scalar is at most 4 bytes, so reserving 4 ensures there is always room for
432 // at least one more character each iteration. Without this floor `input.len() / 10`
433 // is 0 for inputs shorter than 10 bytes, so the buffer never grows: when the
434 // decoded UTF-8 output is longer than the input (e.g. multi-byte UTF-16), the
435 // decoder keeps returning `OutputFull` with no capacity gained and the loop spins
436 // forever, pegging a CPU core (see Ethiraric/yaml-rust2#78).
437 output.reserve((input.len() / 10).max(4));
438 }
439 (DecoderResult::Malformed(malformed_len, bytes_after_malformed), bytes_read) => {
440 total_bytes_read += bytes_read;
441 match trap {
442 // Ignore (skip over) malformed character.
443 YAMLDecodingTrap::Ignore => {}
444 // Replace them with the Unicode REPLACEMENT CHARACTER.
445 YAMLDecodingTrap::Replace => {
446 output.push('\u{FFFD}');
447 }
448 // Otherwise error, getting as much context as possible.
449 YAMLDecodingTrap::Strict => {
450 let malformed_len = malformed_len as usize;
451 let bytes_after_malformed = bytes_after_malformed as usize;
452 let byte_idx =
453 total_bytes_read - (malformed_len + bytes_after_malformed);
454 let malformed_sequence = &input[byte_idx..byte_idx + malformed_len];
455
456 break Err(LoadError::Decode(Cow::Owned(format!(
457 "Invalid character sequence at {byte_idx}: {malformed_sequence:?}",
458 ))));
459 }
460 YAMLDecodingTrap::Call(callback) => {
461 let byte_idx = total_bytes_read
462 - ((malformed_len + bytes_after_malformed) as usize);
463 let malformed_sequence =
464 &input[byte_idx..byte_idx + malformed_len as usize];
465 if let ControlFlow::Break(error) = callback(
466 malformed_len,
467 bytes_after_malformed,
468 &input[byte_idx..],
469 output,
470 ) {
471 if error.is_empty() {
472 break Err(LoadError::Decode(Cow::Owned(format!(
473 "Invalid character sequence at {byte_idx}: {malformed_sequence:?}",
474 ))));
475 }
476 break Err(LoadError::Decode(error));
477 }
478 }
479 }
480 }
481 }
482 }
483 }
484
485 /// The encoding crate knows how to tell apart UTF-8 from UTF-16LE and utf-16BE, when the
486 /// bytestream starts with BOM codepoint.
487 /// However, it doesn't even attempt to guess the UTF-16 endianness of the input bytestream since
488 /// in the general case the bytestream could start with a codepoint that uses both bytes.
489 ///
490 /// The YAML-1.2 spec mandates that the first character of a YAML document is an ASCII character.
491 /// This allows the encoding to be deduced by the pattern of null (#x00) characters.
492 //
493 /// See spec at <https://yaml.org/spec/1.2/spec.html#id2771184>
494 fn detect_utf16_endianness(b: &[u8]) -> &'static Encoding {
495 if b.len() > 1 && (b[0] != b[1]) {
496 if b[0] == 0 {
497 return encoding_rs::UTF_16BE;
498 } else if b[1] == 0 {
499 return encoding_rs::UTF_16LE;
500 }
501 }
502 encoding_rs::UTF_8
503 }
504}
505
506macro_rules! define_as (
507 ($name:ident, $t:ident, $yt:ident) => (
508/// Get a copy of the inner object in the YAML enum if it is a `$t`.
509///
510/// # Return
511/// If the variant of `self` is `Yaml::$yt`, return `Some($t)` with a copy of the `$t` contained.
512/// Otherwise, return `None`.
513#[must_use]
514pub fn $name(&self) -> Option<$t> {
515 match *self {
516 Yaml::$yt(v) => Some(v),
517 _ => None
518 }
519}
520 );
521);
522
523macro_rules! define_as_ref (
524 ($name:ident, $t:ty, $yt:ident) => (
525/// Get a reference to the inner object in the YAML enum if it is a `$t`.
526///
527/// # Return
528/// If the variant of `self` is `Yaml::$yt`, return `Some(&$t)` with the `$t` contained. Otherwise,
529/// return `None`.
530#[must_use]
531pub fn $name(&self) -> Option<$t> {
532 match *self {
533 Yaml::$yt(ref v) => Some(v),
534 _ => None
535 }
536}
537 );
538);
539
540macro_rules! define_as_mut_ref (
541 ($name:ident, $t:ty, $yt:ident) => (
542/// Get a mutable reference to the inner object in the YAML enum if it is a `$t`.
543///
544/// # Return
545/// If the variant of `self` is `Yaml::$yt`, return `Some(&mut $t)` with the `$t` contained.
546/// Otherwise, return `None`.
547#[must_use]
548pub fn $name(&mut self) -> Option<$t> {
549 match *self {
550 Yaml::$yt(ref mut v) => Some(v),
551 _ => None
552 }
553}
554 );
555);
556
557macro_rules! define_into (
558 ($name:ident, $t:ty, $yt:ident) => (
559/// Get the inner object in the YAML enum if it is a `$t`.
560///
561/// # Return
562/// If the variant of `self` is `Yaml::$yt`, return `Some($t)` with the `$t` contained. Otherwise,
563/// return `None`.
564#[must_use]
565pub fn $name(self) -> Option<$t> {
566 match self {
567 Yaml::$yt(v) => Some(v),
568 _ => None
569 }
570}
571 );
572);
573
574impl Yaml {
575 define_as!(as_bool, bool, Boolean);
576 define_as!(as_i64, i64, Integer);
577
578 define_as_ref!(as_str, &str, String);
579 define_as_ref!(as_hash, &Hash, Hash);
580 define_as_ref!(as_vec, &Array, Array);
581
582 define_as_mut_ref!(as_mut_hash, &mut Hash, Hash);
583 define_as_mut_ref!(as_mut_vec, &mut Array, Array);
584
585 define_into!(into_bool, bool, Boolean);
586 define_into!(into_i64, i64, Integer);
587 define_into!(into_string, String, String);
588 define_into!(into_hash, Hash, Hash);
589 define_into!(into_vec, Array, Array);
590
591 /// Return whether `self` is a [`Yaml::Null`] node.
592 #[must_use]
593 pub fn is_null(&self) -> bool {
594 matches!(*self, Yaml::Null)
595 }
596
597 /// Return whether `self` is a [`Yaml::BadValue`] node.
598 #[must_use]
599 pub fn is_badvalue(&self) -> bool {
600 matches!(*self, Yaml::BadValue)
601 }
602
603 /// Return whether `self` is a [`Yaml::Array`] node.
604 #[must_use]
605 pub fn is_array(&self) -> bool {
606 matches!(*self, Yaml::Array(_))
607 }
608
609 /// Return whether `self` is a [`Yaml::Hash`] node.
610 #[must_use]
611 pub fn is_hash(&self) -> bool {
612 matches!(*self, Yaml::Hash(_))
613 }
614
615 /// Return the `f64` value contained in this YAML node.
616 ///
617 /// If the node is not a [`Yaml::Real`] YAML node or its contents is not a valid `f64` string,
618 /// `None` is returned.
619 #[must_use]
620 pub fn as_f64(&self) -> Option<f64> {
621 if let Yaml::Real(ref v) = self {
622 parse_f64(v)
623 } else {
624 None
625 }
626 }
627
628 /// Return the `f64` value contained in this YAML node.
629 ///
630 /// If the node is not a [`Yaml::Real`] YAML node or its contents is not a valid `f64` string,
631 /// `None` is returned.
632 #[must_use]
633 pub fn into_f64(self) -> Option<f64> {
634 self.as_f64()
635 }
636
637 /// If a value is null or otherwise bad (see variants), consume it and
638 /// replace it with a given value `other`. Otherwise, return self unchanged.
639 ///
640 /// ```
641 /// use yaml_rust2::yaml::Yaml;
642 ///
643 /// assert_eq!(Yaml::BadValue.or(Yaml::Integer(3)), Yaml::Integer(3));
644 /// assert_eq!(Yaml::Integer(3).or(Yaml::BadValue), Yaml::Integer(3));
645 /// ```
646 #[must_use]
647 pub fn or(self, other: Self) -> Self {
648 match self {
649 Yaml::BadValue | Yaml::Null => other,
650 this => this,
651 }
652 }
653
654 /// See `or` for behavior. This performs the same operations, but with
655 /// borrowed values for less linear pipelines.
656 #[must_use]
657 pub fn borrowed_or<'a>(&'a self, other: &'a Self) -> &'a Self {
658 match self {
659 Yaml::BadValue | Yaml::Null => other,
660 this => this,
661 }
662 }
663}
664
665#[allow(clippy::should_implement_trait)]
666impl Yaml {
667 /// Convert a string to a [`Yaml`] node.
668 ///
669 /// [`Yaml`] does not implement [`std::str::FromStr`] since conversion may not fail. This
670 /// function falls back to [`Yaml::String`] if nothing else matches.
671 ///
672 /// # Examples
673 /// ```
674 /// # use yaml_rust2::yaml::Yaml;
675 /// assert!(matches!(Yaml::from_str("42"), Yaml::Integer(42)));
676 /// assert!(matches!(Yaml::from_str("0x2A"), Yaml::Integer(42)));
677 /// assert!(matches!(Yaml::from_str("0o52"), Yaml::Integer(42)));
678 /// assert!(matches!(Yaml::from_str("~"), Yaml::Null));
679 /// assert!(matches!(Yaml::from_str("null"), Yaml::Null));
680 /// assert!(matches!(Yaml::from_str("true"), Yaml::Boolean(true)));
681 /// assert!(matches!(Yaml::from_str("True"), Yaml::Boolean(true)));
682 /// assert!(matches!(Yaml::from_str("TRUE"), Yaml::Boolean(true)));
683 /// assert!(matches!(Yaml::from_str("false"), Yaml::Boolean(false)));
684 /// assert!(matches!(Yaml::from_str("False"), Yaml::Boolean(false)));
685 /// assert!(matches!(Yaml::from_str("FALSE"), Yaml::Boolean(false)));
686 /// assert!(matches!(Yaml::from_str("3.14"), Yaml::Real(_)));
687 /// assert!(matches!(Yaml::from_str("foo"), Yaml::String(_)));
688 /// ```
689 #[must_use]
690 pub fn from_str(v: &str) -> Yaml {
691 if let Some(number) = v.strip_prefix("0x") {
692 if let Ok(i) = i64::from_str_radix(number, 16) {
693 return Yaml::Integer(i);
694 }
695 } else if let Some(number) = v.strip_prefix("0o") {
696 if let Ok(i) = i64::from_str_radix(number, 8) {
697 return Yaml::Integer(i);
698 }
699 } else if let Some(number) = v.strip_prefix('+') {
700 if let Ok(i) = number.parse::<i64>() {
701 return Yaml::Integer(i);
702 }
703 }
704 match v {
705 "" | "~" | "null" => Yaml::Null,
706 "true" | "True" | "TRUE" => Yaml::Boolean(true),
707 "false" | "False" | "FALSE" => Yaml::Boolean(false),
708 _ => {
709 if let Ok(integer) = v.parse::<i64>() {
710 Yaml::Integer(integer)
711 } else if parse_f64(v).is_some() {
712 Yaml::Real(v.to_owned())
713 } else {
714 Yaml::String(v.to_owned())
715 }
716 }
717 }
718 }
719}
720
721static BAD_VALUE: Yaml = Yaml::BadValue;
722impl<'a> Index<&'a str> for Yaml {
723 type Output = Yaml;
724
725 /// Perform indexing if `self` is a mapping.
726 ///
727 /// # Return
728 /// If `self` is a [`Yaml::Hash`], returns an immutable borrow to the value associated to the
729 /// given key in the hash.
730 ///
731 /// This function returns a [`Yaml::BadValue`] if the underlying [`type@Hash`] does not contain
732 /// [`Yaml::String`]`{idx}` as a key.
733 ///
734 /// This function also returns a [`Yaml::BadValue`] if `self` is not a [`Yaml::Hash`].
735 fn index(&self, idx: &'a str) -> &Yaml {
736 let key = Yaml::String(idx.to_owned());
737 match self.as_hash() {
738 Some(h) => h.get(&key).unwrap_or(&BAD_VALUE),
739 None => &BAD_VALUE,
740 }
741 }
742}
743
744impl<'a> IndexMut<&'a str> for Yaml {
745 /// Perform indexing if `self` is a mapping.
746 ///
747 /// Since we cannot return a mutable borrow to a static [`Yaml::BadValue`] as we return an
748 /// immutable one in [`Index<&'a str>`], this function panics on out of bounds.
749 ///
750 /// # Panics
751 /// This function panics if the given key is not contained in `self` (as per [`IndexMut`]).
752 ///
753 /// This function also panics if `self` is not a [`Yaml::Hash`].
754 fn index_mut(&mut self, idx: &'a str) -> &mut Yaml {
755 let key = Yaml::String(idx.to_owned());
756 match self.as_mut_hash() {
757 Some(h) => h.get_mut(&key).unwrap(),
758 None => panic!("Not a hash type"),
759 }
760 }
761}
762
763impl Index<usize> for Yaml {
764 type Output = Yaml;
765
766 /// Perform indexing if `self` is a sequence or a mapping.
767 ///
768 /// # Return
769 /// If `self` is a [`Yaml::Array`], returns an immutable borrow to the value located at the
770 /// given index in the array.
771 ///
772 /// Otherwise, if `self` is a [`Yaml::Hash`], returns a borrow to the value whose key is
773 /// [`Yaml::Integer`]`(idx)` (this would not work if the key is [`Yaml::String`]`("1")`.
774 ///
775 /// This function returns a [`Yaml::BadValue`] if the index given is out of range. If `self` is
776 /// a [`Yaml::Array`], this is when the index is bigger or equal to the length of the
777 /// underlying `Vec`. If `self` is a [`Yaml::Hash`], this is when the mapping sequence does not
778 /// contain [`Yaml::Integer`]`(idx)` as a key.
779 ///
780 /// This function also returns a [`Yaml::BadValue`] if `self` is not a [`Yaml::Array`] nor a
781 /// [`Yaml::Hash`].
782 fn index(&self, idx: usize) -> &Yaml {
783 if let Some(v) = self.as_vec() {
784 v.get(idx).unwrap_or(&BAD_VALUE)
785 } else if let Some(v) = self.as_hash() {
786 let key = Yaml::Integer(i64::try_from(idx).unwrap());
787 v.get(&key).unwrap_or(&BAD_VALUE)
788 } else {
789 &BAD_VALUE
790 }
791 }
792}
793
794impl IndexMut<usize> for Yaml {
795 /// Perform indexing if `self` is a sequence or a mapping.
796 ///
797 /// Since we cannot return a mutable borrow to a static [`Yaml::BadValue`] as we return an
798 /// immutable one in [`Index<usize>`], this function panics on out of bounds.
799 ///
800 /// # Panics
801 /// This function panics if the index given is out of range (as per [`IndexMut`]). If `self` is
802 /// a [`Yaml::Array`], this is when the index is bigger or equal to the length of the
803 /// underlying `Vec`. If `self` is a [`Yaml::Hash`], this is when the mapping sequence does not
804 /// contain [`Yaml::Integer`]`(idx)` as a key.
805 ///
806 /// This function also panics if `self` is not a [`Yaml::Array`] nor a [`Yaml::Hash`].
807 fn index_mut(&mut self, idx: usize) -> &mut Yaml {
808 match self {
809 Yaml::Array(sequence) => sequence.index_mut(idx),
810 Yaml::Hash(mapping) => {
811 let key = Yaml::Integer(i64::try_from(idx).unwrap());
812 mapping.get_mut(&key).unwrap()
813 }
814 _ => panic!("Attempting to index but `self` is not a sequence nor a mapping"),
815 }
816 }
817}
818
819impl IntoIterator for Yaml {
820 type Item = Yaml;
821 type IntoIter = YamlIter;
822
823 /// Extract the [`Array`] from `self` and iterate over it.
824 ///
825 /// If `self` is **not** of the [`Yaml::Array`] variant, this function will not panic or return
826 /// an error (as per the [`IntoIterator`] trait it cannot) but will instead return an iterator
827 /// over an empty [`Array`]. Callers have to ensure (using [`Yaml::is_array`], [`matches`] or
828 /// something similar) that the [`Yaml`] object is a [`Yaml::Array`] if they want to do error
829 /// handling.
830 ///
831 /// # Examples
832 /// ```
833 /// # use yaml_rust2::{Yaml, YamlLoader};
834 ///
835 /// // An array of 2 integers, 1 and 2.
836 /// let arr = &YamlLoader::load_from_str("- 1\n- 2").unwrap()[0];
837 ///
838 /// assert_eq!(arr.clone().into_iter().count(), 2);
839 /// assert_eq!(arr.clone().into_iter().next(), Some(Yaml::Integer(1)));
840 /// assert_eq!(arr.clone().into_iter().nth(1), Some(Yaml::Integer(2)));
841 ///
842 /// // An empty array returns an empty iterator.
843 /// let empty = Yaml::Array(vec![]);
844 /// assert_eq!(empty.into_iter().count(), 0);
845 ///
846 /// // A hash with 2 key-value pairs, `(a, b)` and `(c, d)`.
847 /// let hash = YamlLoader::load_from_str("a: b\nc: d").unwrap().remove(0);
848 /// // The hash has 2 elements.
849 /// assert_eq!(hash.as_hash().unwrap().iter().count(), 2);
850 /// // But since `into_iter` can't be used with a `Yaml::Hash`, `into_iter` returns an empty
851 /// // iterator.
852 /// assert_eq!(hash.into_iter().count(), 0);
853 /// ```
854 fn into_iter(self) -> Self::IntoIter {
855 YamlIter {
856 yaml: self.into_vec().unwrap_or_default().into_iter(),
857 }
858 }
859}
860
861/// An iterator over a [`Yaml`] node.
862pub struct YamlIter {
863 yaml: std::vec::IntoIter<Yaml>,
864}
865
866impl Iterator for YamlIter {
867 type Item = Yaml;
868
869 fn next(&mut self) -> Option<Yaml> {
870 self.yaml.next()
871 }
872}
873
874#[cfg(all(test, feature = "encoding"))]
875mod test {
876 use super::{YAMLDecodingTrap, Yaml, YamlDecoder};
877
878 #[test]
879 fn test_read_bom() {
880 let s = b"\xef\xbb\xbf---
881a: 1
882b: 2.2
883c: [1, 2]
884";
885 let out = YamlDecoder::read(s as &[u8]).decode().unwrap();
886 let doc = &out[0];
887 assert_eq!(doc["a"].as_i64().unwrap(), 1i64);
888 assert!((doc["b"].as_f64().unwrap() - 2.2f64).abs() <= f64::EPSILON);
889 assert_eq!(doc["c"][1].as_i64().unwrap(), 2i64);
890 assert!(doc["d"][0].is_badvalue());
891 }
892
893 #[test]
894 fn test_read_utf16le() {
895 let s = b"\xff\xfe-\x00-\x00-\x00
896\x00a\x00:\x00 \x001\x00
897\x00b\x00:\x00 \x002\x00.\x002\x00
898\x00c\x00:\x00 \x00[\x001\x00,\x00 \x002\x00]\x00
899\x00";
900 let out = YamlDecoder::read(s as &[u8]).decode().unwrap();
901 let doc = &out[0];
902 println!("GOT: {doc:?}");
903 assert_eq!(doc["a"].as_i64().unwrap(), 1i64);
904 assert!((doc["b"].as_f64().unwrap() - 2.2f64) <= f64::EPSILON);
905 assert_eq!(doc["c"][1].as_i64().unwrap(), 2i64);
906 assert!(doc["d"][0].is_badvalue());
907 }
908
909 #[test]
910 fn test_read_utf16be() {
911 let s = b"\xfe\xff\x00-\x00-\x00-\x00
912\x00a\x00:\x00 \x001\x00
913\x00b\x00:\x00 \x002\x00.\x002\x00
914\x00c\x00:\x00 \x00[\x001\x00,\x00 \x002\x00]\x00
915";
916 let out = YamlDecoder::read(s as &[u8]).decode().unwrap();
917 let doc = &out[0];
918 println!("GOT: {doc:?}");
919 assert_eq!(doc["a"].as_i64().unwrap(), 1i64);
920 assert!((doc["b"].as_f64().unwrap() - 2.2f64).abs() <= f64::EPSILON);
921 assert_eq!(doc["c"][1].as_i64().unwrap(), 2i64);
922 assert!(doc["d"][0].is_badvalue());
923 }
924
925 #[test]
926 fn test_read_utf16le_nobom() {
927 let s = b"-\x00-\x00-\x00
928\x00a\x00:\x00 \x001\x00
929\x00b\x00:\x00 \x002\x00.\x002\x00
930\x00c\x00:\x00 \x00[\x001\x00,\x00 \x002\x00]\x00
931\x00";
932 let out = YamlDecoder::read(s as &[u8]).decode().unwrap();
933 let doc = &out[0];
934 println!("GOT: {doc:?}");
935 assert_eq!(doc["a"].as_i64().unwrap(), 1i64);
936 assert!((doc["b"].as_f64().unwrap() - 2.2f64).abs() <= f64::EPSILON);
937 assert_eq!(doc["c"][1].as_i64().unwrap(), 2i64);
938 assert!(doc["d"][0].is_badvalue());
939 }
940
941 #[test]
942 fn test_read_trap() {
943 let s = b"---
944a\xa9: 1
945b: 2.2
946c: [1, 2]
947";
948 let out = YamlDecoder::read(s as &[u8])
949 .encoding_trap(YAMLDecodingTrap::Ignore)
950 .decode()
951 .unwrap();
952 let doc = &out[0];
953 println!("GOT: {doc:?}");
954 assert_eq!(doc["a"].as_i64().unwrap(), 1i64);
955 assert!((doc["b"].as_f64().unwrap() - 2.2f64).abs() <= f64::EPSILON);
956 assert_eq!(doc["c"][1].as_i64().unwrap(), 2i64);
957 assert!(doc["d"][0].is_badvalue());
958 }
959
960 #[test]
961 fn test_or() {
962 assert_eq!(Yaml::Null.or(Yaml::Integer(3)), Yaml::Integer(3));
963 assert_eq!(Yaml::Integer(3).or(Yaml::Integer(7)), Yaml::Integer(3));
964 }
965}