Skip to main content

yaml_rt_serde/value/
mod.rs

1//! A loosely typed, presentation-independent YAML value model.
2
3mod de;
4mod index;
5mod mapping;
6mod number;
7mod ser;
8
9use std::borrow::Cow;
10use std::fmt;
11
12use serde::{Deserialize, Deserializer, Serialize};
13
14use crate::{Error, Result};
15
16pub use index::Index;
17pub use mapping::{
18    Entry, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Mapping, OccupiedEntry,
19    VacantEntry, Values, ValuesMut,
20};
21pub use number::Number;
22pub use ser::{Serializer, from_value, to_value};
23
24pub(crate) const TAGGED_VALUE_TOKEN: &str = "$yaml_rt::private::TaggedValue";
25
26/// A YAML sequence whose elements are [`Value`]s.
27pub type Sequence = Vec<Value>;
28
29/// A loosely typed YAML value.
30#[derive(Clone, Default, PartialEq, PartialOrd, Hash)]
31pub enum Value {
32    /// YAML null.
33    #[default]
34    Null,
35    /// YAML boolean.
36    Bool(bool),
37    /// YAML integer or floating-point number.
38    Number(Number),
39    /// YAML string.
40    String(String),
41    /// YAML sequence.
42    Sequence(Sequence),
43    /// YAML mapping.
44    Mapping(Mapping),
45    /// A locally tagged YAML value.
46    Tagged(Box<TaggedValue>),
47}
48
49impl Eq for Value {}
50
51impl fmt::Debug for Value {
52    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
53        match self {
54            Self::Null => formatter.write_str("Null"),
55            Self::Bool(value) => formatter.debug_tuple("Bool").field(value).finish(),
56            Self::Number(value) => formatter.debug_tuple("Number").field(value).finish(),
57            Self::String(value) => formatter.debug_tuple("String").field(value).finish(),
58            Self::Sequence(value) => formatter.debug_tuple("Sequence").field(value).finish(),
59            Self::Mapping(value) => formatter.debug_tuple("Mapping").field(value).finish(),
60            Self::Tagged(value) => formatter.debug_tuple("Tagged").field(value).finish(),
61        }
62    }
63}
64
65impl Value {
66    /// Returns the child selected by `index`.
67    #[must_use]
68    pub fn get<I>(&self, index: I) -> Option<&Value>
69    where
70        I: Index,
71    {
72        index.index_into(self)
73    }
74
75    /// Returns the mutable child selected by `index`.
76    pub fn get_mut<I>(&mut self, index: I) -> Option<&mut Value>
77    where
78        I: Index,
79    {
80        index.index_into_mut(self)
81    }
82
83    pub(crate) fn untag_ref(&self) -> &Self {
84        let mut value = self;
85        while let Self::Tagged(tagged) = value {
86            value = &tagged.value;
87        }
88        value
89    }
90
91    pub(crate) fn untag_mut(&mut self) -> &mut Self {
92        let mut value = self;
93        while let Self::Tagged(tagged) = value {
94            value = &mut tagged.value;
95        }
96        value
97    }
98
99    /// Returns true for null, including a tagged null.
100    #[must_use]
101    pub fn is_null(&self) -> bool {
102        matches!(self.untag_ref(), Self::Null)
103    }
104
105    /// Returns `Some(())` for null.
106    #[must_use]
107    pub fn as_null(&self) -> Option<()> {
108        self.is_null().then_some(())
109    }
110
111    /// Returns true for a boolean.
112    #[must_use]
113    pub fn is_bool(&self) -> bool {
114        self.as_bool().is_some()
115    }
116
117    /// Returns the boolean value.
118    #[must_use]
119    pub fn as_bool(&self) -> Option<bool> {
120        match self.untag_ref() {
121            Self::Bool(value) => Some(*value),
122            _ => None,
123        }
124    }
125
126    /// Returns true for a number.
127    #[must_use]
128    pub fn is_number(&self) -> bool {
129        matches!(self.untag_ref(), Self::Number(_))
130    }
131
132    /// Returns true for an integer representable as `i64`.
133    #[must_use]
134    pub fn is_i64(&self) -> bool {
135        self.as_i64().is_some()
136    }
137
138    /// Returns true for an integer representable as `u64`.
139    #[must_use]
140    pub fn is_u64(&self) -> bool {
141        self.as_u64().is_some()
142    }
143
144    /// Returns true for a floating-point number.
145    #[must_use]
146    pub fn is_f64(&self) -> bool {
147        matches!(self.untag_ref(), Self::Number(number) if number.is_f64())
148    }
149
150    /// Returns true for an integer representable as `i128`.
151    #[must_use]
152    pub fn is_i128(&self) -> bool {
153        self.as_i128().is_some()
154    }
155
156    /// Returns true for an integer representable as `u128`.
157    #[must_use]
158    pub fn is_u128(&self) -> bool {
159        self.as_u128().is_some()
160    }
161
162    /// Returns the number as `i64` when possible.
163    #[must_use]
164    pub fn as_i64(&self) -> Option<i64> {
165        match self.untag_ref() {
166            Self::Number(number) => number.as_i64(),
167            _ => None,
168        }
169    }
170
171    /// Returns the number as `u64` when possible.
172    #[must_use]
173    pub fn as_u64(&self) -> Option<u64> {
174        match self.untag_ref() {
175            Self::Number(number) => number.as_u64(),
176            _ => None,
177        }
178    }
179
180    /// Returns the number as `i128` when possible.
181    #[must_use]
182    pub fn as_i128(&self) -> Option<i128> {
183        match self.untag_ref() {
184            Self::Number(number) => number.as_i128(),
185            _ => None,
186        }
187    }
188
189    /// Returns the number as `u128` when possible.
190    #[must_use]
191    pub fn as_u128(&self) -> Option<u128> {
192        match self.untag_ref() {
193            Self::Number(number) => number.as_u128(),
194            _ => None,
195        }
196    }
197
198    /// Returns the number as `f64` when possible.
199    #[must_use]
200    pub fn as_f64(&self) -> Option<f64> {
201        match self.untag_ref() {
202            Self::Number(number) => number.as_f64(),
203            _ => None,
204        }
205    }
206
207    /// Returns true for a string.
208    #[must_use]
209    pub fn is_string(&self) -> bool {
210        self.as_str().is_some()
211    }
212
213    /// Returns the string value.
214    #[must_use]
215    pub fn as_str(&self) -> Option<&str> {
216        match self.untag_ref() {
217            Self::String(value) => Some(value),
218            _ => None,
219        }
220    }
221
222    /// Returns true for a sequence.
223    #[must_use]
224    pub fn is_sequence(&self) -> bool {
225        self.as_sequence().is_some()
226    }
227
228    /// Returns the sequence.
229    #[must_use]
230    pub fn as_sequence(&self) -> Option<&Sequence> {
231        match self.untag_ref() {
232            Self::Sequence(value) => Some(value),
233            _ => None,
234        }
235    }
236
237    /// Returns the mutable sequence.
238    pub fn as_sequence_mut(&mut self) -> Option<&mut Sequence> {
239        match self.untag_mut() {
240            Self::Sequence(value) => Some(value),
241            _ => None,
242        }
243    }
244
245    /// Returns true for a mapping.
246    #[must_use]
247    pub fn is_mapping(&self) -> bool {
248        self.as_mapping().is_some()
249    }
250
251    /// Returns the mapping.
252    #[must_use]
253    pub fn as_mapping(&self) -> Option<&Mapping> {
254        match self.untag_ref() {
255            Self::Mapping(value) => Some(value),
256            _ => None,
257        }
258    }
259
260    /// Returns the mutable mapping.
261    pub fn as_mapping_mut(&mut self) -> Option<&mut Mapping> {
262        match self.untag_mut() {
263            Self::Mapping(value) => Some(value),
264            _ => None,
265        }
266    }
267
268    /// Recursively expands YAML merge (`<<`) entries.
269    ///
270    /// # Errors
271    ///
272    /// Returns an error when a merge operand is not a mapping or a sequence of
273    /// mappings.
274    pub fn apply_merge(&mut self) -> Result<()> {
275        match self {
276            Self::Sequence(values) => {
277                for value in values {
278                    value.apply_merge()?;
279                }
280            }
281            Self::Mapping(mapping) => apply_mapping_merge(mapping)?,
282            Self::Tagged(tagged) => tagged.value.apply_merge()?,
283            Self::Null | Self::Bool(_) | Self::Number(_) | Self::String(_) => {}
284        }
285        Ok(())
286    }
287}
288
289fn apply_mapping_merge(mapping: &mut Mapping) -> Result<()> {
290    for (_, value) in &mut *mapping {
291        value.apply_merge()?;
292    }
293
294    let Some(position) = mapping
295        .entries
296        .iter()
297        .position(|(key, _)| matches!(key.untag_ref(), Value::String(key) if key == "<<"))
298    else {
299        return Ok(());
300    };
301
302    let (_, source) = mapping.entries.remove(position);
303    let explicit = std::mem::take(mapping);
304    let mut merged = Mapping::new();
305    merge_source(&mut merged, source.untag_ref())?;
306    for (key, value) in explicit {
307        merged.insert(key, value);
308    }
309    *mapping = merged;
310    Ok(())
311}
312
313fn merge_source(target: &mut Mapping, source: &Value) -> Result<()> {
314    match source {
315        Value::Mapping(mapping) => {
316            merge_mapping(target, mapping);
317            Ok(())
318        }
319        Value::Sequence(sequence) => {
320            for value in sequence {
321                let Value::Mapping(mapping) = value.untag_ref() else {
322                    return Err(Error::message("expected a mapping in YAML merge sequence"));
323                };
324                merge_mapping(target, mapping);
325            }
326            Ok(())
327        }
328        _ => Err(Error::message(
329            "expected a mapping or sequence of mappings for YAML merge",
330        )),
331    }
332}
333
334fn merge_mapping(target: &mut Mapping, source: &Mapping) {
335    for (key, value) in source {
336        if !target.contains_key(key) {
337            target.insert(key.clone(), value.clone());
338        }
339    }
340}
341
342/// A normalized YAML local tag.
343#[derive(Clone, Eq, Ord, PartialEq, PartialOrd, Hash)]
344pub struct Tag {
345    pub(crate) string: String,
346}
347
348impl Tag {
349    /// Creates a tag. A leading `!` is optional and not significant.
350    ///
351    /// # Panics
352    ///
353    /// Panics for an empty tag.
354    #[must_use]
355    pub fn new(value: impl Into<String>) -> Self {
356        let value = value.into();
357        let value = value.strip_prefix('!').unwrap_or(&value);
358        assert!(!value.is_empty(), "YAML tags cannot be empty");
359        Self {
360            string: value.to_owned(),
361        }
362    }
363
364    pub(crate) fn as_suffix(&self) -> &str {
365        &self.string
366    }
367}
368
369impl fmt::Display for Tag {
370    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
371        write!(formatter, "!{}", self.string)
372    }
373}
374
375impl fmt::Debug for Tag {
376    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
377        formatter
378            .debug_tuple("Tag")
379            .field(&self.to_string())
380            .finish()
381    }
382}
383
384impl PartialEq<str> for Tag {
385    fn eq(&self, other: &str) -> bool {
386        self.string == other.strip_prefix('!').unwrap_or(other)
387    }
388}
389
390impl PartialEq<&str> for Tag {
391    fn eq(&self, other: &&str) -> bool {
392        self == *other
393    }
394}
395
396impl PartialEq<String> for Tag {
397    fn eq(&self, other: &String) -> bool {
398        self == other.as_str()
399    }
400}
401
402impl Serialize for Tag {
403    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
404    where
405        S: serde::Serializer,
406    {
407        serializer.serialize_str(&self.to_string())
408    }
409}
410
411impl<'de> Deserialize<'de> for Tag {
412    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
413    where
414        D: Deserializer<'de>,
415    {
416        String::deserialize(deserializer).map(Self::new)
417    }
418}
419
420/// A YAML tag and its associated value.
421#[derive(Clone, Debug, PartialEq, PartialOrd, Hash)]
422pub struct TaggedValue {
423    /// The YAML tag.
424    pub tag: Tag,
425    /// The tagged value.
426    pub value: Value,
427}
428
429impl Eq for TaggedValue {}
430
431impl Serialize for TaggedValue {
432    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
433    where
434        S: serde::Serializer,
435    {
436        Value::Tagged(Box::new(self.clone())).serialize(serializer)
437    }
438}
439
440impl<'de> Deserialize<'de> for TaggedValue {
441    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
442    where
443        D: Deserializer<'de>,
444    {
445        match Value::deserialize(deserializer)? {
446            Value::Tagged(value) => Ok(*value),
447            _ => Err(serde::de::Error::custom("expected a tagged YAML value")),
448        }
449    }
450}
451
452impl From<Mapping> for Value {
453    fn from(value: Mapping) -> Self {
454        Self::Mapping(value)
455    }
456}
457
458impl From<String> for Value {
459    fn from(value: String) -> Self {
460        Self::String(value)
461    }
462}
463
464impl From<&str> for Value {
465    fn from(value: &str) -> Self {
466        Self::String(value.to_owned())
467    }
468}
469
470impl From<Cow<'_, str>> for Value {
471    fn from(value: Cow<'_, str>) -> Self {
472        Self::String(value.into_owned())
473    }
474}
475
476impl From<bool> for Value {
477    fn from(value: bool) -> Self {
478        Self::Bool(value)
479    }
480}
481
482macro_rules! number_conversions {
483    ($($ty:ty),+ $(,)?) => {
484        $(
485            impl From<$ty> for Value {
486                fn from(value: $ty) -> Self {
487                    Self::Number(Number::from(value))
488                }
489            }
490
491            impl PartialEq<$ty> for Value {
492                fn eq(&self, other: &$ty) -> bool {
493                    self == &Self::from(*other)
494                }
495            }
496
497            impl PartialEq<Value> for $ty {
498                fn eq(&self, other: &Value) -> bool {
499                    &Value::from(*self) == other
500                }
501            }
502        )+
503    };
504}
505
506number_conversions!(
507    i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64,
508);
509
510impl<T> From<Vec<T>> for Value
511where
512    T: Into<Value>,
513{
514    fn from(values: Vec<T>) -> Self {
515        Self::Sequence(values.into_iter().map(Into::into).collect())
516    }
517}
518
519impl<T> From<&[T]> for Value
520where
521    T: Clone + Into<Value>,
522{
523    fn from(values: &[T]) -> Self {
524        Self::Sequence(values.iter().cloned().map(Into::into).collect())
525    }
526}
527
528impl<T> FromIterator<T> for Value
529where
530    T: Into<Value>,
531{
532    fn from_iter<I>(iter: I) -> Self
533    where
534        I: IntoIterator<Item = T>,
535    {
536        Self::Sequence(iter.into_iter().map(Into::into).collect())
537    }
538}
539
540impl PartialEq<str> for Value {
541    fn eq(&self, other: &str) -> bool {
542        self.as_str().is_some_and(|value| value == other)
543    }
544}
545
546impl PartialEq<&str> for Value {
547    fn eq(&self, other: &&str) -> bool {
548        self == *other
549    }
550}
551
552impl PartialEq<String> for Value {
553    fn eq(&self, other: &String) -> bool {
554        self == other.as_str()
555    }
556}
557
558impl PartialEq<bool> for Value {
559    fn eq(&self, other: &bool) -> bool {
560        self.as_bool().is_some_and(|value| value == *other)
561    }
562}
563
564impl PartialEq<Value> for bool {
565    fn eq(&self, other: &Value) -> bool {
566        other == self
567    }
568}
569
570impl PartialEq<Value> for str {
571    fn eq(&self, other: &Value) -> bool {
572        other == self
573    }
574}
575
576impl PartialEq<Value> for String {
577    fn eq(&self, other: &Value) -> bool {
578        other == self
579    }
580}