Skip to main content

noyalib/de/
deserializer.rs

1//! The serde `Deserializer` over a `&Value` and its access types.
2
3// SPDX-License-Identifier: MIT OR Apache-2.0
4// Copyright (c) 2026 Noyalib. All rights reserved.
5
6use crate::error::{Error, Result};
7use crate::prelude::f64_fract;
8use crate::prelude::*;
9use crate::span_context;
10use crate::value::{Number, Value};
11
12/// A YAML deserializer.
13///
14/// # Examples
15///
16/// ```
17/// use noyalib::{Deserializer, Value};
18/// let v = Value::from(42_i64);
19/// let de = Deserializer::new(&v);
20/// let n: i32 = serde_core::Deserialize::deserialize(de).unwrap();
21/// assert_eq!(n, 42);
22/// ```
23#[derive(Debug, Clone, Copy)]
24pub struct Deserializer<'de> {
25    pub(crate) value: &'de Value,
26    pub(crate) span_ctx: Option<&'de span_context::SpanContext>,
27    /// Per-call flag mirroring
28    /// [`ParserConfig::ignore_binary_tag_for_string`]. When `true`,
29    /// `!!binary "ABCD"` deserializes into `String` as the literal
30    /// `"ABCD"` (no base64 decode). Default `false` preserves YAML
31    /// 1.2 semantics.
32    pub(crate) ignore_binary_tag_for_string: bool,
33    /// Per-call flag mirroring
34    /// [`ParserConfig::plain_scalar_strings`] (refs #344). When
35    /// `true`, a `String`/`char` target accepts a non-string scalar
36    /// (`Value::Bool`, `Value::Null`, `Value::Number`) and receives
37    /// its formatted text. Default `false` preserves the historical
38    /// refusal.
39    pub(crate) plain_scalar_strings: bool,
40}
41
42impl<'de> Deserializer<'de> {
43    /// Create a new deserializer from a value.
44    ///
45    /// # Examples
46    ///
47    /// ```
48    /// use noyalib::{Deserializer, Value};
49    /// let v = Value::from(1_i64);
50    /// let _de = Deserializer::new(&v);
51    /// ```
52    #[must_use]
53    pub fn new(value: &'de Value) -> Self {
54        Deserializer {
55            value,
56            span_ctx: None,
57            ignore_binary_tag_for_string: false,
58            plain_scalar_strings: false,
59        }
60    }
61
62    /// Create a new deserializer from a value with an associated span context.
63    ///
64    /// The span context carries source-location information used to attach
65    /// line/column details to errors and `Spanned<T>` fields. This
66    /// constructor is primarily used internally by `from_str`; most callers
67    /// should prefer [`Deserializer::new`].
68    ///
69    /// # Examples
70    ///
71    /// ```ignore
72    /// // Constructed internally by from_str — external callers use Deserializer::new.
73    /// use noyalib::Deserializer;
74    /// # let value = unimplemented!();
75    /// # let span_ctx = unimplemented!();
76    /// let _de = Deserializer::with_span_context(value, span_ctx);
77    /// ```
78    #[must_use]
79    pub fn with_span_context(value: &'de Value, span_ctx: &'de span_context::SpanContext) -> Self {
80        Deserializer {
81            value,
82            span_ctx: Some(span_ctx),
83            ignore_binary_tag_for_string: false,
84            plain_scalar_strings: false,
85        }
86    }
87
88    /// Pass-through constructor for the
89    /// [`crate::ParserConfig::ignore_binary_tag_for_string`] and
90    /// [`crate::ParserConfig::plain_scalar_strings`] flags. Used
91    /// internally by [`from_str_with_config`] when the caller has
92    /// opted in to either.
93    pub(crate) fn with_options(
94        value: &'de Value,
95        span_ctx: Option<&'de span_context::SpanContext>,
96        ignore_binary_tag_for_string: bool,
97        plain_scalar_strings: bool,
98    ) -> Self {
99        Deserializer {
100            value,
101            span_ctx,
102            ignore_binary_tag_for_string,
103            plain_scalar_strings,
104        }
105    }
106
107    /// Construct a child deserializer for `value`, propagating the
108    /// span context and every per-call config toggle from `self`.
109    /// Used by every descent site (struct field, sequence element,
110    /// tagged inner value) so the toggles survive the walk.
111    pub(crate) fn descend(&self, value: &'de Value) -> Self {
112        Deserializer {
113            value,
114            span_ctx: self.span_ctx,
115            ignore_binary_tag_for_string: self.ignore_binary_tag_for_string,
116            plain_scalar_strings: self.plain_scalar_strings,
117        }
118    }
119
120    /// Attach the source location of the value being deserialized to an
121    /// error that does not carry one yet.
122    ///
123    /// Five error shapes reach this point without a location when a typed
124    /// target rejects a value: [`Error::Deserialize`] (noyalib's own
125    /// deserialization failures), [`Error::TypeMismatch`] (the catch-all
126    /// arms of the typed `deserialize_*` methods), [`Error::Custom`]
127    /// (serde's `invalid_type` / `invalid_value` / `custom`, raised by the
128    /// caller's visitor -- the path a `#[serde(flatten)]` field or any
129    /// `deserialize_any` descent takes), and [`Error::UnknownField`] /
130    /// [`Error::MissingField`] (serde's `unknown_field` / `missing_field`
131    /// -- a `deny_unknown_fields` derive raises the first while reading
132    /// the key, so it is located at that entry; a hand-written `visit_map`
133    /// raises either against the mapping, so it is located there). All
134    /// five are re-wrapped as
135    /// [`Error::DeserializeWithLocation`] when the deserializer was built
136    /// from text (`from_str`, which records a span per value); the message
137    /// is the original error's text. Deserializers built without a span
138    /// context (`from_value`, [`Deserializer::new`]) return the error as
139    /// is, so `matches!(err, Error::TypeMismatch { .. })` keeps holding on
140    /// that path.
141    fn wrap_err<T>(&self, res: Result<T>) -> Result<T> {
142        let err = match res {
143            Ok(value) => return Ok(value),
144            Err(err) => err,
145        };
146        let Some(ctx) = self.span_ctx else {
147            return Err(err);
148        };
149        let ptr: *const Value = self.value;
150        let addr = ptr as usize;
151        let Some(span) = ctx.spans.get(&addr) else {
152            return Err(err);
153        };
154        let message = match err {
155            Error::Deserialize(msg) | Error::Custom(msg) => msg,
156            worded @ (Error::TypeMismatch { .. }
157            | Error::UnknownField(_)
158            | Error::MissingField(_)) => worded.to_string(),
159            other => return Err(other),
160        };
161        // Only the innermost wrap reaches this line (an already-wrapped
162        // error returns through the `other` arm above), so the recorded
163        // node is the one the failure is about. `from_str` walks the
164        // root value to turn it into a field path (#353).
165        #[cfg(feature = "std")]
166        span_context::record_error_node(addr);
167        Err(Error::deserialize_at(message, &ctx.source, span.0))
168    }
169}
170
171impl<'de> serde_core::Deserializer<'de> for Deserializer<'de> {
172    type Error = Error;
173
174    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
175    where
176        V: serde_core::de::Visitor<'de>,
177    {
178        match self.value {
179            Value::Null => self.wrap_err(visitor.visit_none()),
180            Value::Bool(b) => self.wrap_err(visitor.visit_bool(*b)),
181            Value::Number(Number::Integer(n)) => self.wrap_err(visitor.visit_i64(*n)),
182            #[cfg(feature = "lossless-u64")]
183            Value::Number(Number::Unsigned(n)) => self.wrap_err(visitor.visit_u64(*n)),
184            Value::Number(Number::Float(n)) => self.wrap_err(visitor.visit_f64(*n)),
185            Value::String(s) => self.wrap_err(visitor.visit_str(s)),
186            Value::Sequence(_) => self.deserialize_seq(visitor),
187            Value::Mapping(_) => self.deserialize_map(visitor),
188            Value::Tagged(tagged) => {
189                // `deserialize_any` is the "self-describing" entry point —
190                // reached by `Value`'s own `Deserialize` impl (directly, or
191                // nested inside `Mapping`, `Sequence`/`Vec<Value>`, or a
192                // struct field of type `Value`) and by serde's untagged-enum
193                // content buffering. Typed struct/map targets never reach
194                // here for a tagged node — `deserialize_map`/
195                // `deserialize_struct` have their own arm (below) that sees
196                // through the tag transparently, e.g.
197                // `#[derive(serde::Deserialize)] struct Foo { x: i32 }`
198                // against `!Foo {x: 1}` yields `Foo { x: 1 }`.
199                //
200                // So a tagged node reaching *this* arm is a `Value` being
201                // reconstructed, and it must keep its tag (see #350; the
202                // top-level `Value` target already did, via the AST
203                // loader's `parse_one_value` bypass in
204                // `from_str_with_config` / `from_value`'s clone fast path —
205                // this arm is what nested `Value`s go through instead).
206                // Hand the tag/inner-value pair to the visitor as an enum
207                // (variant name = tag, payload = the untagged value) —
208                // `ValueVisitor::visit_enum` reassembles `Value::Tagged`.
209                self.wrap_err(visitor.visit_enum(EnumAccess {
210                    variant: tagged.tag().as_str(),
211                    value: tagged.value(),
212                    span_ctx: self.span_ctx,
213                }))
214            }
215        }
216    }
217
218    fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value>
219    where
220        V: serde_core::de::Visitor<'de>,
221    {
222        match self.value {
223            Value::Bool(b) => self.wrap_err(visitor.visit_bool(*b)),
224            _ => self.wrap_err(Err(Error::TypeMismatch {
225                expected: "bool",
226                found: type_name(self.value),
227            })),
228        }
229    }
230
231    fn deserialize_i8<V>(self, visitor: V) -> Result<V::Value>
232    where
233        V: serde_core::de::Visitor<'de>,
234    {
235        self.deserialize_i64(visitor)
236    }
237
238    fn deserialize_i16<V>(self, visitor: V) -> Result<V::Value>
239    where
240        V: serde_core::de::Visitor<'de>,
241    {
242        self.deserialize_i64(visitor)
243    }
244
245    fn deserialize_i32<V>(self, visitor: V) -> Result<V::Value>
246    where
247        V: serde_core::de::Visitor<'de>,
248    {
249        self.deserialize_i64(visitor)
250    }
251
252    fn deserialize_i64<V>(self, visitor: V) -> Result<V::Value>
253    where
254        V: serde_core::de::Visitor<'de>,
255    {
256        match self.value {
257            Value::Number(Number::Integer(n)) => self.wrap_err(visitor.visit_i64(*n)),
258            #[cfg(feature = "lossless-u64")]
259            Value::Number(Number::Unsigned(n)) => match i64::try_from(*n) {
260                Ok(n) => self.wrap_err(visitor.visit_i64(n)),
261                // `type_name` reports both `Integer` and `Unsigned` as
262                // "integer", so reusing it here would render the useless
263                // "expected integer, found integer". Name the actual
264                // problem instead: the value is an integer, it just does
265                // not fit the signed target.
266                Err(_) => self.wrap_err(Err(Error::TypeMismatch {
267                    expected: "signed integer (i64)",
268                    found: format!("unsigned integer {n}, above i64::MAX"),
269                })),
270            },
271            Value::Number(Number::Float(n))
272                if f64_fract(*n) == 0.0
273                    && *n >= i64::MIN as f64
274                    && *n <= i64::MAX as f64
275                    && !n.is_nan() =>
276            {
277                self.wrap_err(visitor.visit_i64(*n as i64))
278            }
279            _ => self.wrap_err(Err(Error::TypeMismatch {
280                expected: "integer",
281                found: type_name(self.value),
282            })),
283        }
284    }
285
286    fn deserialize_u8<V>(self, visitor: V) -> Result<V::Value>
287    where
288        V: serde_core::de::Visitor<'de>,
289    {
290        self.deserialize_u64(visitor)
291    }
292
293    fn deserialize_u16<V>(self, visitor: V) -> Result<V::Value>
294    where
295        V: serde_core::de::Visitor<'de>,
296    {
297        self.deserialize_u64(visitor)
298    }
299
300    fn deserialize_u32<V>(self, visitor: V) -> Result<V::Value>
301    where
302        V: serde_core::de::Visitor<'de>,
303    {
304        self.deserialize_u64(visitor)
305    }
306
307    fn deserialize_u64<V>(self, visitor: V) -> Result<V::Value>
308    where
309        V: serde_core::de::Visitor<'de>,
310    {
311        match self.value {
312            Value::Number(Number::Integer(n)) if *n >= 0 => {
313                self.wrap_err(visitor.visit_u64(*n as u64))
314            }
315            // Mirror of the `Unsigned` -> i64 case in `deserialize_i64`:
316            // falling through to the catch-all would report "expected
317            // unsigned integer, found integer", which does not tell the
318            // caller that the problem is the sign.
319            Value::Number(Number::Integer(n)) => self.wrap_err(Err(Error::TypeMismatch {
320                expected: "unsigned integer",
321                found: format!("negative integer {n}"),
322            })),
323            #[cfg(feature = "lossless-u64")]
324            Value::Number(Number::Unsigned(n)) => self.wrap_err(visitor.visit_u64(*n)),
325            Value::Number(Number::Float(n))
326                if f64_fract(*n) == 0.0 && *n >= 0.0 && *n <= u64::MAX as f64 && !n.is_nan() =>
327            {
328                self.wrap_err(visitor.visit_u64(*n as u64))
329            }
330            _ => self.wrap_err(Err(Error::TypeMismatch {
331                expected: "unsigned integer",
332                found: type_name(self.value),
333            })),
334        }
335    }
336
337    fn deserialize_f32<V>(self, visitor: V) -> Result<V::Value>
338    where
339        V: serde_core::de::Visitor<'de>,
340    {
341        self.deserialize_f64(visitor)
342    }
343
344    fn deserialize_f64<V>(self, visitor: V) -> Result<V::Value>
345    where
346        V: serde_core::de::Visitor<'de>,
347    {
348        match self.value {
349            Value::Number(Number::Float(n)) => self.wrap_err(visitor.visit_f64(*n)),
350            Value::Number(Number::Integer(n)) => self.wrap_err(visitor.visit_f64(*n as f64)),
351            #[cfg(feature = "lossless-u64")]
352            Value::Number(Number::Unsigned(n)) => self.wrap_err(visitor.visit_f64(*n as f64)),
353            _ => self.wrap_err(Err(Error::TypeMismatch {
354                expected: "float",
355                found: type_name(self.value),
356            })),
357        }
358    }
359
360    fn deserialize_char<V>(self, visitor: V) -> Result<V::Value>
361    where
362        V: serde_core::de::Visitor<'de>,
363    {
364        match self.value {
365            Value::String(s) if s.chars().count() == 1 => {
366                self.wrap_err(visitor.visit_char(s.chars().next().unwrap()))
367            }
368            // Opt-in (refs #344, `plain_scalar_strings`): a `char`
369            // field sees the same literal text a `String` field
370            // would for a number, bool, or null — see
371            // `scalar_as_text` — further constrained to exactly one
372            // character, same as the arm above. Off by default.
373            _ if self.plain_scalar_strings => match scalar_as_text(self.value) {
374                Some(text) if text.chars().count() == 1 => {
375                    self.wrap_err(visitor.visit_char(text.chars().next().unwrap()))
376                }
377                _ => self.wrap_err(Err(Error::TypeMismatch {
378                    expected: "char",
379                    found: type_name(self.value),
380                })),
381            },
382            _ => self.wrap_err(Err(Error::TypeMismatch {
383                expected: "char",
384                found: type_name(self.value),
385            })),
386        }
387    }
388
389    fn deserialize_str<V>(self, visitor: V) -> Result<V::Value>
390    where
391        V: serde_core::de::Visitor<'de>,
392    {
393        match self.value {
394            Value::String(s) => self.wrap_err(visitor.visit_str(s)),
395            // Migration helper: when the source declared
396            // `!!binary "ABCD"` and the caller opted in to
397            // `ignore_binary_tag_for_string`, surface the literal
398            // source string rather than rejecting on tag mismatch.
399            // The base64 encoding stays as the user-facing value;
400            // the application layer can decode (or not) as it
401            // sees fit.
402            Value::Tagged(boxed)
403                if self.ignore_binary_tag_for_string && is_binary_tag(boxed.tag().as_str()) =>
404            {
405                match boxed.value() {
406                    Value::String(s) => self.wrap_err(visitor.visit_str(s)),
407                    other => self.wrap_err(Err(Error::TypeMismatch {
408                        expected: "string-shaped !!binary content",
409                        found: type_name(other),
410                    })),
411                }
412            }
413            // Opt-in (refs #344, `plain_scalar_strings`): a `String`
414            // target receives a number/bool/null scalar's text —
415            // implicit typing is a fallback used by
416            // `deserialize_any` for untyped targets, not a
417            // constraint on an explicitly-typed `String` field. Off
418            // by default — the catch-all below is the historical
419            // refusal.
420            _ if self.plain_scalar_strings => match scalar_as_text(self.value) {
421                Some(text) => self.wrap_err(visitor.visit_str(&text)),
422                None => self.wrap_err(Err(Error::TypeMismatch {
423                    expected: "string",
424                    found: type_name(self.value),
425                })),
426            },
427            _ => self.wrap_err(Err(Error::TypeMismatch {
428                expected: "string",
429                found: type_name(self.value),
430            })),
431        }
432    }
433
434    fn deserialize_string<V>(self, visitor: V) -> Result<V::Value>
435    where
436        V: serde_core::de::Visitor<'de>,
437    {
438        self.deserialize_str(visitor)
439    }
440
441    fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value>
442    where
443        V: serde_core::de::Visitor<'de>,
444    {
445        match self.value {
446            Value::String(s) => self.wrap_err(visitor.visit_bytes(s.as_bytes())),
447            // YAML 1.2.2 §10.4: `!!binary` carries an RFC 4648
448            // base64-encoded payload. Decode on demand when a serde
449            // target asks for bytes / a byte buffer (Vec<u8>,
450            // serde_bytes::ByteBuf, &[u8] via owned visit).
451            Value::Tagged(boxed) if is_binary_tag(boxed.tag().as_str()) => match boxed.value() {
452                Value::String(s) => match crate::base64::decode(s) {
453                    Ok(bytes) => self.wrap_err(visitor.visit_byte_buf(bytes)),
454                    Err(why) => self.wrap_err(Err(Error::Deserialize(format!("!!binary: {why}")))),
455                },
456                other => self.wrap_err(Err(Error::TypeMismatch {
457                    expected: "string-shaped !!binary content",
458                    found: type_name(other),
459                })),
460            },
461            _ => self.wrap_err(Err(Error::TypeMismatch {
462                expected: "bytes",
463                found: type_name(self.value),
464            })),
465        }
466    }
467
468    fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value>
469    where
470        V: serde_core::de::Visitor<'de>,
471    {
472        self.deserialize_bytes(visitor)
473    }
474
475    fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
476    where
477        V: serde_core::de::Visitor<'de>,
478    {
479        match self.value {
480            Value::Null => self.wrap_err(visitor.visit_none()),
481            _ => self.wrap_err(visitor.visit_some(self)),
482        }
483    }
484
485    fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value>
486    where
487        V: serde_core::de::Visitor<'de>,
488    {
489        match self.value {
490            Value::Null => self.wrap_err(visitor.visit_unit()),
491            _ => self.wrap_err(Err(Error::TypeMismatch {
492                expected: "null",
493                found: type_name(self.value),
494            })),
495        }
496    }
497
498    fn deserialize_unit_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value>
499    where
500        V: serde_core::de::Visitor<'de>,
501    {
502        self.deserialize_unit(visitor)
503    }
504
505    fn deserialize_newtype_struct<V>(self, name: &'static str, visitor: V) -> Result<V::Value>
506    where
507        V: serde_core::de::Visitor<'de>,
508    {
509        if name == crate::spanned::SPANNED_TYPE_NAME {
510            return visitor.visit_map(SpannedMapAccess::new(
511                self.value,
512                self.span_ctx,
513                self.ignore_binary_tag_for_string,
514                self.plain_scalar_strings,
515            ));
516        }
517        self.wrap_err(visitor.visit_newtype_struct(self))
518    }
519
520    fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value>
521    where
522        V: serde_core::de::Visitor<'de>,
523    {
524        match self.value {
525            Value::Sequence(seq) => {
526                self.wrap_err(visitor.visit_seq(ValueSeqAccess::from_de(&self, seq)))
527            }
528            // Tagged values are transparent for typed `deserialize_*`
529            // calls — `Vec<T>::deserialize` against `!List [1, 2, 3]`
530            // (which now surfaces as `Tagged(Sequence(...))` per the
531            // tag-preserving loader) sees through the wrapper.
532            Value::Tagged(tagged) => self.descend(tagged.value()).deserialize_seq(visitor),
533            _ => self.wrap_err(Err(Error::TypeMismatch {
534                expected: "sequence",
535                found: type_name(self.value),
536            })),
537        }
538    }
539
540    fn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value>
541    where
542        V: serde_core::de::Visitor<'de>,
543    {
544        self.deserialize_seq(visitor)
545    }
546
547    fn deserialize_tuple_struct<V>(
548        self,
549        _name: &'static str,
550        _len: usize,
551        visitor: V,
552    ) -> Result<V::Value>
553    where
554        V: serde_core::de::Visitor<'de>,
555    {
556        self.deserialize_seq(visitor)
557    }
558
559    fn deserialize_map<V>(self, visitor: V) -> Result<V::Value>
560    where
561        V: serde_core::de::Visitor<'de>,
562    {
563        match self.value {
564            Value::Mapping(map) => {
565                self.wrap_err(visitor.visit_map(ValueMapAccess::from_de(&self, map)))
566            }
567            // The null document (empty / whitespace-only / comment-only
568            // input, or a bare `---`) has "no entries" for a map or
569            // `#[serde(default)]` struct target. See #349.
570            Value::Null => self.wrap_err(visitor.visit_map(EmptyMapAccess)),
571            // Tagged values are transparent for typed
572            // `deserialize_*` calls — `HashMap::deserialize`
573            // against `!!set { Mark, Sammy }` (which now surfaces
574            // as `Tagged(Mapping(...))` per the tag-preserving
575            // loader) sees through the wrapper.
576            Value::Tagged(tagged) => self.descend(tagged.value()).deserialize_map(visitor),
577            _ => self.wrap_err(Err(Error::TypeMismatch {
578                expected: "mapping",
579                found: type_name(self.value),
580            })),
581        }
582    }
583
584    fn deserialize_struct<V>(
585        self,
586        name: &'static str,
587        _fields: &'static [&'static str],
588        visitor: V,
589    ) -> Result<V::Value>
590    where
591        V: serde_core::de::Visitor<'de>,
592    {
593        if name == crate::spanned::SPANNED_TYPE_NAME {
594            return visitor.visit_map(SpannedMapAccess::new(
595                self.value,
596                self.span_ctx,
597                self.ignore_binary_tag_for_string,
598                self.plain_scalar_strings,
599            ));
600        }
601        self.deserialize_map(visitor)
602    }
603
604    fn deserialize_enum<V>(
605        self,
606        _name: &'static str,
607        _variants: &'static [&'static str],
608        visitor: V,
609    ) -> Result<V::Value>
610    where
611        V: serde_core::de::Visitor<'de>,
612    {
613        match self.value {
614            Value::String(variant) => {
615                let de =
616                    serde_core::de::value::StrDeserializer::<'de, Error>::new(variant.as_str());
617                self.wrap_err(visitor.visit_enum(de))
618            }
619            Value::Mapping(map) if map.len() == 1 => {
620                let (variant, value) = map.iter().next().unwrap();
621                self.wrap_err(visitor.visit_enum(EnumAccess {
622                    variant,
623                    value,
624                    span_ctx: self.span_ctx,
625                }))
626            }
627            _ => self.wrap_err(Err(Error::TypeMismatch {
628                expected: "string or single-key mapping",
629                found: type_name(self.value),
630            })),
631        }
632    }
633
634    fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value>
635    where
636        V: serde_core::de::Visitor<'de>,
637    {
638        match self.value {
639            Value::String(s) => self.wrap_err(visitor.visit_str(s)),
640            _ => self.deserialize_any(visitor),
641        }
642    }
643
644    fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value>
645    where
646        V: serde_core::de::Visitor<'de>,
647    {
648        self.wrap_err(visitor.visit_unit())
649    }
650}
651
652pub(crate) struct ValueSeqAccess<'de> {
653    iter: core::slice::Iter<'de, Value>,
654    span_ctx: Option<&'de span_context::SpanContext>,
655    ignore_binary_tag_for_string: bool,
656    plain_scalar_strings: bool,
657}
658
659impl<'de> ValueSeqAccess<'de> {
660    pub(crate) fn from_de(de: &Deserializer<'de>, seq: &'de [Value]) -> Self {
661        ValueSeqAccess {
662            iter: seq.iter(),
663            span_ctx: de.span_ctx,
664            ignore_binary_tag_for_string: de.ignore_binary_tag_for_string,
665            plain_scalar_strings: de.plain_scalar_strings,
666        }
667    }
668}
669
670impl<'de> serde_core::de::SeqAccess<'de> for ValueSeqAccess<'de> {
671    type Error = Error;
672
673    fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
674    where
675        T: serde_core::de::DeserializeSeed<'de>,
676    {
677        match self.iter.next() {
678            Some(value) => {
679                let de = Deserializer {
680                    value,
681                    span_ctx: self.span_ctx,
682                    ignore_binary_tag_for_string: self.ignore_binary_tag_for_string,
683                    plain_scalar_strings: self.plain_scalar_strings,
684                };
685                seed.deserialize(de).map(Some)
686            }
687            None => Ok(None),
688        }
689    }
690}
691
692pub(crate) struct ValueMapAccess<'de> {
693    iter: indexmap::map::Iter<'de, String, Value>,
694    value: Option<&'de Value>,
695    span_ctx: Option<&'de span_context::SpanContext>,
696    ignore_binary_tag_for_string: bool,
697    plain_scalar_strings: bool,
698}
699
700impl<'de> ValueMapAccess<'de> {
701    pub(crate) fn from_de(de: &Deserializer<'de>, map: &'de crate::value::Mapping) -> Self {
702        ValueMapAccess {
703            iter: map.iter(),
704            value: None,
705            span_ctx: de.span_ctx,
706            ignore_binary_tag_for_string: de.ignore_binary_tag_for_string,
707            plain_scalar_strings: de.plain_scalar_strings,
708        }
709    }
710
711    /// Build the child [`Deserializer`] used to read each map
712    /// value — propagates every per-call toggle.
713    fn child_de(&self, value: &'de Value) -> Deserializer<'de> {
714        Deserializer {
715            value,
716            span_ctx: self.span_ctx,
717            ignore_binary_tag_for_string: self.ignore_binary_tag_for_string,
718            plain_scalar_strings: self.plain_scalar_strings,
719        }
720    }
721}
722
723impl<'de> serde_core::de::MapAccess<'de> for ValueMapAccess<'de> {
724    type Error = Error;
725
726    fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>>
727    where
728        K: serde_core::de::DeserializeSeed<'de>,
729    {
730        match self.iter.next() {
731            Some((key, value)) => {
732                self.value = Some(value);
733                let de = self.child_de(value);
734                // A key the loader canonicalised reads as written, as on
735                // the streaming path; a `Value` keeps the canonical form.
736                let text = self
737                    .span_ctx
738                    .and_then(|ctx| ctx.key_text(key))
739                    .unwrap_or(key.as_str());
740                let key_de = serde_core::de::value::StrDeserializer::<'de, Error>::new(text);
741                de.wrap_err(seed.deserialize(key_de).map(Some))
742            }
743            None => Ok(None),
744        }
745    }
746
747    fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value>
748    where
749        V: serde_core::de::DeserializeSeed<'de>,
750    {
751        match self.value.take() {
752            Some(value) => {
753                let de = self.child_de(value);
754                let res = seed.deserialize(de);
755                de.wrap_err(res)
756            }
757            None => Err(serde_core::de::Error::custom("value is missing")),
758        }
759    }
760}
761
762/// `MapAccess` that immediately reports "no entries".
763///
764/// An empty, whitespace-only, or comment-only document (and a bare
765/// `---` with no content) parses as the YAML null document
766/// (`Value::Null`). `Mapping` and `#[serde(default)]` struct targets
767/// treat that the same way `serde_yaml` does — as a map with no
768/// entries — instead of a type-mismatch error. `deserialize_any`
769/// still visits `Value::Null` as `None`; only the map/struct entry
770/// points route through here. See #349.
771pub(crate) struct EmptyMapAccess;
772
773impl<'de> serde_core::de::MapAccess<'de> for EmptyMapAccess {
774    type Error = Error;
775
776    fn next_key_seed<K>(&mut self, _seed: K) -> Result<Option<K::Value>>
777    where
778        K: serde_core::de::DeserializeSeed<'de>,
779    {
780        Ok(None)
781    }
782
783    fn next_value_seed<V>(&mut self, _seed: V) -> Result<V::Value>
784    where
785        V: serde_core::de::DeserializeSeed<'de>,
786    {
787        Err(serde_core::de::Error::custom("value is missing"))
788    }
789}
790
791struct EnumAccess<'de> {
792    variant: &'de str,
793    value: &'de Value,
794    span_ctx: Option<&'de span_context::SpanContext>,
795}
796
797impl<'de> serde_core::de::EnumAccess<'de> for EnumAccess<'de> {
798    type Error = Error;
799    type Variant = VariantAccess<'de>;
800
801    fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant)>
802    where
803        V: serde_core::de::DeserializeSeed<'de>,
804    {
805        let de = serde_core::de::value::StrDeserializer::<'de, Error>::new(self.variant);
806        let variant = seed.deserialize(de)?;
807        let visitor = VariantAccess {
808            value: self.value,
809            span_ctx: self.span_ctx,
810        };
811        Ok((variant, visitor))
812    }
813}
814
815struct VariantAccess<'de> {
816    value: &'de Value,
817    span_ctx: Option<&'de span_context::SpanContext>,
818}
819
820impl<'de> serde_core::de::VariantAccess<'de> for VariantAccess<'de> {
821    type Error = Error;
822
823    fn unit_variant(self) -> Result<()> {
824        let de = if let Some(ctx) = self.span_ctx {
825            Deserializer::with_span_context(self.value, ctx)
826        } else {
827            Deserializer::new(self.value)
828        };
829        serde_core::Deserialize::deserialize(de)
830    }
831
832    fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value>
833    where
834        T: serde_core::de::DeserializeSeed<'de>,
835    {
836        let de = if let Some(ctx) = self.span_ctx {
837            Deserializer::with_span_context(self.value, ctx)
838        } else {
839            Deserializer::new(self.value)
840        };
841        seed.deserialize(de)
842    }
843
844    fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value>
845    where
846        V: serde_core::de::Visitor<'de>,
847    {
848        let de = if let Some(ctx) = self.span_ctx {
849            Deserializer::with_span_context(self.value, ctx)
850        } else {
851            Deserializer::new(self.value)
852        };
853        serde_core::Deserializer::deserialize_seq(de, visitor)
854    }
855
856    fn struct_variant<V>(self, _fields: &'static [&'static str], visitor: V) -> Result<V::Value>
857    where
858        V: serde_core::de::Visitor<'de>,
859    {
860        let de = if let Some(ctx) = self.span_ctx {
861            Deserializer::with_span_context(self.value, ctx)
862        } else {
863            Deserializer::new(self.value)
864        };
865        serde_core::Deserializer::deserialize_map(de, visitor)
866    }
867}
868
869pub(crate) struct SpannedMapAccess<'de> {
870    value: &'de Value,
871    span_ctx: Option<&'de span_context::SpanContext>,
872    /// Carried from the enclosing deserializer. `Spanned<T>` is a
873    /// descent like any other, so the per-call toggles have to survive
874    /// it — building the inner deserializer with `new` dropped them,
875    /// and a `Spanned<String>` field then refused a plain scalar that a
876    /// bare `String` field accepted.
877    ignore_binary_tag_for_string: bool,
878    plain_scalar_strings: bool,
879    fields: core::slice::Iter<'static, &'static str>,
880    /// Start and end locations, computed on the first location field
881    /// and reused for the other five.
882    locations: Option<(crate::error::Location, crate::error::Location)>,
883}
884
885impl<'de> SpannedMapAccess<'de> {
886    pub(crate) fn new(
887        value: &'de Value,
888        span_ctx: Option<&'de span_context::SpanContext>,
889        ignore_binary_tag_for_string: bool,
890        plain_scalar_strings: bool,
891    ) -> Self {
892        SpannedMapAccess {
893            value,
894            span_ctx,
895            ignore_binary_tag_for_string,
896            plain_scalar_strings,
897            fields: crate::spanned::SPANNED_FIELDS.iter(),
898            locations: None,
899        }
900    }
901
902    /// The value's start and end locations, looked up once.
903    fn locations(&mut self) -> (crate::error::Location, crate::error::Location) {
904        let (value, span_ctx) = (self.value, self.span_ctx);
905        *self.locations.get_or_insert_with(|| {
906            let ptr: *const Value = value;
907            let addr = ptr as usize;
908            // The context's line index answers each lookup with a
909            // binary search; scanning from byte 0 per field made a
910            // document of many `Spanned` values quadratic.
911            span_ctx
912                .and_then(|ctx| {
913                    ctx.spans
914                        .get(&addr)
915                        .map(|&(start, end)| (ctx.location(start), ctx.location(end)))
916                })
917                .unwrap_or_default()
918        })
919    }
920}
921
922impl<'de> serde_core::de::MapAccess<'de> for SpannedMapAccess<'de> {
923    type Error = Error;
924
925    fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>>
926    where
927        K: serde_core::de::DeserializeSeed<'de>,
928    {
929        match self.fields.next() {
930            Some(field) => {
931                let de = serde_core::de::value::BorrowedStrDeserializer::<'_, Error>::new(field);
932                seed.deserialize(de).map(Some)
933            }
934            None => Ok(None),
935        }
936    }
937
938    fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value>
939    where
940        V: serde_core::de::DeserializeSeed<'de>,
941    {
942        use crate::spanned::{
943            SPANNED_FIELD_END_COLUMN, SPANNED_FIELD_END_INDEX, SPANNED_FIELD_END_LINE,
944            SPANNED_FIELD_START_COLUMN, SPANNED_FIELD_START_INDEX, SPANNED_FIELD_START_LINE,
945            SPANNED_FIELD_VALUE, SPANNED_FIELDS,
946        };
947        let last_field = SPANNED_FIELDS[SPANNED_FIELDS.len() - 1 - (self.fields.len())];
948
949        if last_field == SPANNED_FIELD_VALUE {
950            let de = Deserializer::with_options(
951                self.value,
952                self.span_ctx,
953                self.ignore_binary_tag_for_string,
954                self.plain_scalar_strings,
955            );
956            return de.wrap_err(seed.deserialize(de));
957        }
958
959        let (loc, end_loc) = self.locations();
960
961        let val = match last_field {
962            SPANNED_FIELD_START_LINE => loc.line(),
963            SPANNED_FIELD_START_COLUMN => loc.column(),
964            SPANNED_FIELD_START_INDEX => loc.index(),
965            SPANNED_FIELD_END_LINE => end_loc.line(),
966            SPANNED_FIELD_END_COLUMN => end_loc.column(),
967            SPANNED_FIELD_END_INDEX => end_loc.index(),
968            _ => crate::error::invariant_violated(
969                "spanned-field index outside the SPANNED_FIELDS array",
970            ),
971        };
972
973        seed.deserialize(serde_core::de::IntoDeserializer::into_deserializer(val))
974    }
975}
976
977/// True if `tag` names the YAML 1.2 binary tag, in any of the forms
978/// the scanner / loader may produce: shorthand `!!binary`, suffix
979/// `binary` (post-handle-stripping), or the canonical full URI
980/// `tag:yaml.org,2002:binary`. Stripping the leading `!` on the
981/// shorthand keeps `Tag::new("!!binary") == Tag::new("binary")` —
982/// which noyalib's `Tag` already considers equal — both matching.
983pub(crate) fn is_binary_tag(tag: &str) -> bool {
984    matches!(
985        tag,
986        "!!binary" | "binary" | "tag:yaml.org,2002:binary" | "!<tag:yaml.org,2002:binary>"
987    )
988}
989
990fn type_name(value: &Value) -> String {
991    match value {
992        Value::Null => "null".to_owned(),
993        Value::Bool(_) => "bool".to_owned(),
994        Value::Number(Number::Integer(_)) => "integer".to_owned(),
995        #[cfg(feature = "lossless-u64")]
996        Value::Number(Number::Unsigned(_)) => "integer".to_owned(),
997        Value::Number(Number::Float(_)) => "float".to_owned(),
998        Value::String(_) => "string".to_owned(),
999        Value::Sequence(_) => "sequence".to_owned(),
1000        Value::Mapping(_) => "mapping".to_owned(),
1001        Value::Tagged(tagged) => format!("tagged value (!{})", tagged.tag().as_str()),
1002    }
1003}
1004
1005/// The literal text a scalar `Value` prints as, for the scalar kinds
1006/// that are not already a `Value::String` — `bool`, `null`, and the
1007/// `Number` variants. Returns `None` for `Value::String` (callers
1008/// already hold the text directly), `Value::Sequence`,
1009/// `Value::Mapping`, and `Value::Tagged` (handled at each call site).
1010///
1011/// Refs #344: a `String` (or one-character `char`) target receives a
1012/// scalar's literal text even when it would otherwise resolve as a
1013/// number, bool, or null for an untyped target — implicit typing is
1014/// a fallback `deserialize_any` uses, not a constraint on an
1015/// explicitly-typed field. Two caveats follow from `Value` no longer
1016/// holding the original source text:
1017///
1018/// - `Value::Null` carries no text of its own, so this yields `""`.
1019///   The *streaming* deserializer — which typed parses take by
1020///   default — instead keeps a written `~`, `null`, or empty scalar
1021///   verbatim; only a caller that forces the AST path (a non-default
1022///   `ParserConfig`, or the `Value` target itself) sees `""` here.
1023/// - An integer literal's original spelling is not preserved: `0x1F`
1024///   parses to `Number::Integer(31)`, which formats back as `"31"`,
1025///   not `"0x1F"`. The streaming path gives the byte-exact source
1026///   text instead.
1027fn scalar_as_text(value: &Value) -> Option<Cow<'_, str>> {
1028    match value {
1029        Value::Bool(b) => Some(Cow::Borrowed(if *b { "true" } else { "false" })),
1030        Value::Null => Some(Cow::Borrowed("")),
1031        Value::Number(Number::Integer(n)) => Some(Cow::Owned(n.to_string())),
1032        #[cfg(feature = "lossless-u64")]
1033        Value::Number(Number::Unsigned(n)) => Some(Cow::Owned(n.to_string())),
1034        Value::Number(Number::Float(n)) => Some(Cow::Owned(format_float_for_string(*n))),
1035        _ => None,
1036    }
1037}
1038
1039/// Format a float exactly as the emitter (`ser.rs`'s `write_value`)
1040/// would print it as a YAML plain scalar — deliberately not
1041/// `Number`'s own `Display` impl, which prints `4.0` as `4` (Rust's
1042/// default float `Display` suppresses a redundant `.0`). Keeping the
1043/// two in step means a `String` field reading a `Value::Number(Float)`
1044/// back sees the same digits the value would serialize as.
1045fn format_float_for_string(n: f64) -> String {
1046    if n.is_nan() {
1047        return ".nan".to_owned();
1048    }
1049    if n.is_infinite() {
1050        return if n > 0.0 {
1051            ".inf".to_owned()
1052        } else {
1053            "-.inf".to_owned()
1054        };
1055    }
1056    #[cfg(feature = "fast-float")]
1057    {
1058        ryu::Buffer::new().format(n).to_owned()
1059    }
1060    #[cfg(not(feature = "fast-float"))]
1061    {
1062        format!("{n:?}")
1063    }
1064}