Skip to main content

noyalib/
streaming.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright (c) 2026 Noyalib. All rights reserved.
3
4//! Streaming YAML deserializer that operates directly on parser events.
5//!
6//! Bypasses the intermediate `Value` AST for typed deserialization,
7//! eliminating all intermediate allocations. Anchors and aliases are
8//! handled natively via event buffering and replay. The common
9//! `<<: *anchor` merge-key pattern is expanded natively.
10//!
11//! # Where events come from
12//!
13//! Every event reaches the deserializer through one of two sources, and
14//! the distinction is the source of this module's sharpest edges:
15//!
16//! * **the parser**, for text not yet read; and
17//! * **the replay stack**, for buffered events being re-emitted — the
18//!   contents of an anchored node standing in for an alias, or a mapping
19//!   injected by a `<<:` merge.
20//!
21//! Anything that reads an event must handle *both*. Alias resolution once
22//! ran on the parser branch only, so an alias arriving through replay was
23//! stored as though it were fully processed while still being an
24//! `Event::Alias`, and deserialised as the literal anchor name. The replay
25//! stack only exists after a merge injects something, which is why the bug
26//! reproduced solely when an alias-valued entry followed a `<<:` key and
27//! vanished when the same entry was written above it (#301).
28//!
29//! [`StreamingDeserializer::process_event`] is now the single place that
30//! resolves an alias, and [`StreamingDeserializer::anchor_and_record`] the
31//! single place that does anchor bookkeeping, so a future branch cannot
32//! quietly acquire a partial copy.
33//!
34//! # The lookahead slot
35//!
36//! One event of lookahead is parked in `current`, and it may hold either a
37//! **raw** parser event or a **processed** one — see [`Lookahead`]. The two
38//! are not interchangeable:
39//!
40//! * the merge-key path fills the slot deliberately raw, because it must
41//!   see `<<: *base` as an unresolved `Event::Alias` in order to treat it
42//!   as a merge instruction rather than a value; while
43//! * a processed event must never be put through processing again, because
44//!   recording is append-only — a second pass puts the event into an
45//!   in-flight anchor buffer twice, and every alias to that anchor then
46//!   replays a duplicated stream.
47//!
48//! Both consumers used to infer which kind they held, in opposite
49//! directions. The state now travels with the event.
50
51use crate::prelude::*;
52use crate::prelude::{f64_fract, f64_mul_add};
53
54use crate::prelude::{FxHashMap, FxHashSet};
55use smallvec::SmallVec;
56
57use crate::error::{BudgetBreach, Error, Result, closest_name};
58use crate::parser::budget;
59use crate::parser::meter::{AliasCost, CostTally, Meter};
60use crate::parser::{Event, ParseConfig, Parser, ScalarStyle};
61use crate::value::Value;
62use core::fmt;
63
64/// Sentinel error message used to signal fallback to the Value-based path.
65const FALLBACK_SENTINEL: &str = "$__noyalib_streaming_fallback";
66
67/// Buffer size for small collections, tuned for wasm-opt.
68#[cfg(feature = "wasm-opt")]
69const SMALL_VEC_SIZE: usize = 4;
70#[cfg(not(feature = "wasm-opt"))]
71const SMALL_VEC_SIZE: usize = 16;
72
73#[derive(Debug, Clone)]
74enum BufferedEvent {
75    Scalar { value: String, style: ScalarStyle },
76    SeqStart,
77    SeqEnd,
78    MapStart,
79    MapEnd,
80    Alias { anchor: String },
81}
82
83#[derive(Debug, Clone)]
84pub(crate) enum Scalar<'a> {
85    Null,
86    Bool(bool),
87    Int(i64),
88    #[cfg(feature = "lossless-u64")]
89    Uint(u64),
90    Float(f64),
91    Str(Cow<'a, str>),
92}
93
94/// A streaming YAML deserializer operating directly on parser events.
95///
96/// Bypasses the intermediate `Value` AST for the supported subset of
97/// YAML. See the module-level documentation for when to use this type
98/// versus [`crate::from_str`].
99///
100/// # Examples
101///
102/// ```
103/// use noyalib::StreamingDeserializer;
104/// use serde_core::Deserialize as _;
105///
106/// #[derive(serde::Deserialize)]
107/// struct Doc { k: i32 }
108///
109/// let mut de = StreamingDeserializer::new("k: 42\n");
110/// let doc = Doc::deserialize(&mut de).unwrap();
111/// assert_eq!(doc.k, 42);
112/// ```
113pub struct StreamingDeserializer<'a> {
114    parser: Parser<'a>,
115    input: &'a str,
116    config: ParseConfig,
117    tag_registry: Option<Arc<crate::TagRegistry>>,
118    depth: usize,
119    /// One-event lookahead. See [`Lookahead`] — the slot holds either a
120    /// raw parser event or a fully processed one, and which it is has to
121    /// travel with the event rather than be inferred by the consumer.
122    current: Option<Lookahead<'a>>,
123    raw_str_mode: bool,
124    anchor_events: FxHashMap<String, SmallVec<[BufferedEvent; SMALL_VEC_SIZE]>>,
125    anchor_def_spans: FxHashMap<String, usize>,
126    replay_stack: Vec<SmallVec<[BufferedEvent; SMALL_VEC_SIZE]>>,
127    recording: Option<(String, usize, SmallVec<[BufferedEvent; SMALL_VEC_SIZE]>)>,
128    /// Measured cost of each recorded anchor, kept beside
129    /// `anchor_events` so an alias is charged before it is replayed.
130    anchor_costs: FxHashMap<String, AliasCost>,
131    /// Every budget counter, charged in the order the loaders use.
132    meter: Meter,
133    /// Whether the first event has been pulled. The document-length
134    /// check runs then, so a deserializer built directly with
135    /// [`Self::with_config`] enforces `max_document_length` too.
136    started: bool,
137}
138
139impl fmt::Debug for StreamingDeserializer<'_> {
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141        f.debug_struct("StreamingDeserializer")
142            .field("input_len", &self.input.len())
143            .field("config", &self.config)
144            .field("depth", &self.depth)
145            .field("raw_str_mode", &self.raw_str_mode)
146            .field("anchor_events_len", &self.anchor_events.len())
147            .field("replay_stack_len", &self.replay_stack.len())
148            .field("is_recording", &self.recording.is_some())
149            .finish()
150    }
151}
152
153/// What state the one-event lookahead slot is in.
154///
155/// The slot is written by three different paths and read by two, and the
156/// two kinds of event are not interchangeable:
157///
158/// * `Raw` is straight off the parser. An `Event::Alias` is still visible
159///   *as an alias*, which the merge-key path depends on — it needs to see
160///   `<<: *base` before anything resolves it.
161/// * `Processed` has been through alias resolution, [`handle_anchor`] and
162///   [`maybe_record`]. Putting it through those again is not idempotent:
163///   a second `maybe_record` pushes the event into the in-flight anchor
164///   buffer twice, so every alias to that anchor replays a corrupted
165///   stream.
166///
167/// Before this existed both kinds shared one `Option<Event>` field, and
168/// each consumer guessed. `next_event` assumed processed and returned raw
169/// events unresolved — which is why an alias used as a merge-key override
170/// value (`y: *other` beside `<<: *b`) came back unresolved (#301, found
171/// by @mathstuf). `next_parser_event` assumed the opposite and would
172/// double-record an already-processed one.
173///
174/// Making the state travel with the event turns both of those from a
175/// guess into a match arm.
176#[derive(Debug)]
177enum Lookahead<'a> {
178    /// Straight from the parser: aliases unresolved, not yet recorded.
179    Raw(Event<'a>),
180    /// Alias-resolved, anchor-handled and recorded. Do not reprocess.
181    Processed(Event<'a>),
182}
183
184impl<'a> Lookahead<'a> {
185    fn event(&self) -> &Event<'a> {
186        match self {
187            Self::Raw(ev) | Self::Processed(ev) => ev,
188        }
189    }
190
191    fn event_mut(&mut self) -> &mut Event<'a> {
192        match self {
193            Self::Raw(ev) | Self::Processed(ev) => ev,
194        }
195    }
196
197    /// Take the event out, whichever state the slot was in.
198    ///
199    /// `next_event` hands a parked event back unchanged in both states,
200    /// though for different reasons: a `Processed` one has already been
201    /// resolved, anchored and recorded, and doing that again would
202    /// double-record it; a `Raw` one is parked deliberately by the
203    /// merge-key path via `peek_parser_event` and then dropped with
204    /// `skip_event`, so resolving it on the way out would expand a
205    /// `<<: *base` that is meant to be consumed whole.
206    ///
207    /// Same action, different justifications — so this is one method
208    /// rather than two match arms that look like a distinction.
209    fn into_inner(self) -> Event<'a> {
210        match self {
211            Self::Raw(ev) | Self::Processed(ev) => ev,
212        }
213    }
214}
215
216impl<'a> StreamingDeserializer<'a> {
217    /// Create a streaming deserializer over the given YAML input using
218    /// default parser settings.
219    #[must_use]
220    pub fn new(input: &'a str) -> Self {
221        Self::with_config(input, ParseConfig::default())
222    }
223
224    /// Create a streaming deserializer with a custom parser configuration.
225    ///
226    /// Accepts anything convertible to the internal `ParseConfig` —
227    /// most callers pass a `&crate::ParserConfig`. Use this to tighten
228    /// security limits (`max_depth`, `max_document_length`, alias
229    /// expansion caps) for untrusted input.
230    pub fn with_config<C>(input: &'a str, config: C) -> Self
231    where
232        C: Into<ParseConfig>,
233    {
234        let config: ParseConfig = config.into();
235        let meter = Meter::new(&config);
236        StreamingDeserializer {
237            parser: Parser::with_max_events(input, config.max_events),
238            input,
239            config,
240            tag_registry: None,
241            depth: 0,
242            current: None,
243            raw_str_mode: false,
244            anchor_events: FxHashMap::default(),
245            anchor_def_spans: FxHashMap::default(),
246            replay_stack: Vec::new(),
247            recording: None,
248            anchor_costs: FxHashMap::default(),
249            meter,
250            started: false,
251        }
252    }
253
254    /// Install a [`TagRegistry`](crate::TagRegistry) on this
255    /// deserializer. Custom tags listed in the registry will be
256    /// stripped on the streaming path instead of forcing a fallback to
257    /// the `Value` AST.
258    ///
259    /// # Examples
260    ///
261    /// ```
262    /// use noyalib::{StreamingDeserializer, TagRegistry};
263    /// use serde_core::Deserialize as _;
264    /// use std::sync::Arc;
265    ///
266    /// #[derive(serde::Deserialize)]
267    /// struct Temp(f64);
268    ///
269    /// let reg = Arc::new(TagRegistry::new().with("!Celsius"));
270    /// let mut de = StreamingDeserializer::new("!Celsius 42.0")
271    ///     .with_tag_registry(reg);
272    /// let t = Temp::deserialize(&mut de).unwrap();
273    /// assert_eq!(t.0, 42.0);
274    /// ```
275    #[must_use]
276    pub fn with_tag_registry(mut self, registry: Arc<crate::TagRegistry>) -> Self {
277        self.tag_registry = Some(registry);
278        self
279    }
280
281    /// Require that the input holds nothing after the value just
282    /// deserialized: no second document, no trailing syntax error.
283    ///
284    /// `T::deserialize(&mut de)` stops as soon as `T` is complete, so a
285    /// caller driving the deserializer directly must call this after it,
286    /// as [`crate::from_str`] does, or bytes past the first document are
287    /// never read. Without it, `a: 1\n---\na: 2` deserializes as `1`
288    /// where `from_str` refuses the stream.
289    ///
290    /// # Errors
291    ///
292    /// [`Error::MoreThanOneDocument`] when another document follows, or
293    /// the parse error of whatever follows the value.
294    ///
295    /// # Examples
296    ///
297    /// ```
298    /// use noyalib::StreamingDeserializer;
299    /// use serde_core::Deserialize as _;
300    ///
301    /// let mut de = StreamingDeserializer::new("1\n---\n2\n");
302    /// let first = i32::deserialize(&mut de).unwrap();
303    /// assert_eq!(first, 1);
304    /// assert!(de.end().is_err());
305    /// ```
306    pub fn end(&mut self) -> Result<()> {
307        // Stop *at* `StreamEnd`: querying past it returns a benign
308        // "parser has already finished" error.
309        loop {
310            match self.next_event()? {
311                Event::StreamEnd => return Ok(()),
312                Event::DocumentEnd | Event::StreamStart => {}
313                Event::DocumentStart => return Err(Error::MoreThanOneDocument),
314                _ => return Ok(()),
315            }
316        }
317    }
318
319    /// The one place a raw event leaves the parser. Every budget the
320    /// loaders charge per parser event is charged here, so a budget
321    /// cannot exist on the loader paths without existing on this one.
322    /// Until v0.0.53 four of them (`max_events`, `max_nodes`,
323    /// `max_total_scalar_bytes`, `max_merge_keys`) and the alias ratio
324    /// were loader-only, and typed targets never saw them.
325    fn pull_parser_event(&mut self) -> Result<Event<'a>> {
326        if !self.started {
327            self.started = true;
328            if self.input.len() > self.config.max_document_length {
329                return Err(document_too_long(self.config.max_document_length));
330            }
331        }
332        let ev = self
333            .parser
334            .next_event()
335            .map_err(|e| e.into_error(self.input, self.config.max_events))?;
336        self.charge_event(&ev)?;
337        Ok(ev)
338    }
339
340    /// The loader prologue, from the meter all loaders share. Replayed
341    /// alias events are charged through the alias budgets instead.
342    fn charge_event(&mut self, ev: &Event<'_>) -> Result<()> {
343        self.meter.charge_event(ev, &self.config)
344    }
345
346    /// Peek without resolving anything. The merge-key path calls this so an
347    /// `Event::Alias` is still visible as an alias.
348    fn peek_parser_event(&mut self) -> Result<&Event<'a>> {
349        if self.current.is_none() {
350            let event = self.pull_parser_event()?;
351            self.current = Some(Lookahead::Raw(event));
352        }
353        Ok(self.current.as_ref().unwrap().event())
354    }
355
356    fn next_parser_event(&mut self) -> Result<Event<'a>> {
357        match self.current.take() {
358            // Already anchored and recorded — doing it again would push a
359            // duplicate into any in-flight anchor buffer.
360            Some(Lookahead::Processed(ev)) => Ok(ev),
361            Some(Lookahead::Raw(mut ev)) => {
362                self.anchor_and_record(&mut ev);
363                Ok(ev)
364            }
365            None => {
366                let mut ev = self.pull_parser_event()?;
367                self.anchor_and_record(&mut ev);
368                Ok(ev)
369            }
370        }
371    }
372
373    fn peek_event(&mut self) -> Result<&Event<'a>> {
374        // A Raw slot is deliberately left alone here. The merge-key path
375        // fills it via `peek_parser_event` precisely so it can see an
376        // unresolved `Event::Alias`, and then consumes it itself; resolving
377        // it on the way past defeats that and breaks merge handling.
378        if self.current.is_none() {
379            // Drain empty replay frames in a loop, not via tail-recursion.
380            // Arbitrary YAML inputs can create deep empty-frame chains
381            // during alias/merge expansion that blow the stack on recursion.
382            let mut ev_opt = None;
383            while let Some(buf) = self.replay_stack.last_mut() {
384                if let Some(be) = buf.pop() {
385                    ev_opt = Some(self.buffered_to_event(be));
386                    break;
387                }
388                let _ = self.replay_stack.pop();
389            }
390            // Alias resolution has to happen whichever side the event came
391            // from. It used to run only on the parser branch, so an alias
392            // replayed out of injected merge content was stored as though it
393            // were processed while still being an `Event::Alias` — which is
394            // exactly the `y: *other` beside `<<: *b` case in #301.
395            let raw = if let Some(ev) = ev_opt {
396                ev
397            } else {
398                self.pull_parser_event()?
399            };
400            let ev = self.process_event(raw)?;
401            self.current = Some(Lookahead::Processed(ev));
402        }
403        Ok(self.current.as_ref().unwrap().event())
404    }
405
406    /// Anchor bookkeeping + recording, without alias resolution.
407    ///
408    /// Split out because `next_parser_event` needs exactly this pair and must
409    /// *not* resolve: it is the raw accessor the merge-key path relies on to
410    /// see an `Event::Alias` as an alias. Three call sites carried their own
411    /// copy before, which is the same duplication that let alias resolution
412    /// go missing from one branch and produce #301.
413    ///
414    /// Not idempotent: `maybe_record` appends, so calling it twice on one
415    /// event puts that event into the in-flight anchor buffer twice.
416    fn anchor_and_record(&mut self, ev: &mut Event<'a>) {
417        self.handle_anchor(ev);
418        self.maybe_record(ev);
419    }
420
421    /// Alias resolution + [`Self::anchor_and_record`], in one place so the
422    /// callers cannot drift apart. Only ever apply to a [`Lookahead::Raw`]
423    /// event.
424    fn process_event(&mut self, mut ev: Event<'a>) -> Result<Event<'a>> {
425        if let Event::Alias {
426            ref anchor,
427            ref span,
428        } = ev
429        {
430            let start = span.start;
431            ev = self.resolve_alias(anchor, start)?;
432        }
433        self.anchor_and_record(&mut ev);
434        Ok(ev)
435    }
436
437    fn next_event(&mut self) -> Result<Event<'a>> {
438        // Both states hand the event back unchanged — see
439        // [`Lookahead::into_inner`] for why each does.
440        if let Some(slot) = self.current.take() {
441            return Ok(slot.into_inner());
442        }
443
444        let mut ev_opt = None;
445        while let Some(buf) = self.replay_stack.last_mut() {
446            if let Some(be) = buf.pop() {
447                ev_opt = Some(self.buffered_to_event(be));
448                break;
449            }
450            let _ = self.replay_stack.pop();
451        }
452        if let Some(ev) = ev_opt {
453            // Same gap as `peek_event` had: a replayed event can still be an
454            // unresolved alias.
455            return self.process_event(ev);
456        }
457
458        let ev = self.pull_parser_event()?;
459        self.process_event(ev)
460    }
461
462    fn buffered_to_event(&self, be: BufferedEvent) -> Event<'a> {
463        let dummy_span = crate::parser::scanner_span_default();
464        match be {
465            BufferedEvent::Scalar { value, style } => Event::Scalar {
466                value: Cow::Owned(value),
467                style,
468                anchor: None,
469                tag: None,
470                span: dummy_span,
471            },
472            BufferedEvent::SeqStart => Event::SequenceStart {
473                anchor: None,
474                tag: None,
475                span: dummy_span,
476            },
477            BufferedEvent::SeqEnd => Event::SequenceEnd { span: dummy_span },
478            BufferedEvent::MapStart => Event::MappingStart {
479                anchor: None,
480                tag: None,
481                span: dummy_span,
482            },
483            BufferedEvent::MapEnd => Event::MappingEnd { span: dummy_span },
484            BufferedEvent::Alias { anchor } => Event::Alias {
485                anchor,
486                span: dummy_span,
487            },
488        }
489    }
490
491    fn handle_anchor(&mut self, ev: &mut Event<'_>) {
492        if self.recording.is_some() {
493            return;
494        }
495        let def_start = match ev {
496            Event::Scalar { span, .. }
497            | Event::SequenceStart { span, .. }
498            | Event::MappingStart { span, .. } => Some(span.start),
499            _ => None,
500        };
501        let anchor_name = match ev {
502            Event::Scalar { anchor, .. }
503            | Event::SequenceStart { anchor, .. }
504            | Event::MappingStart { anchor, .. } => anchor.take(),
505            _ => None,
506        };
507        if let Some(name) = anchor_name {
508            if let Some(start) = def_start {
509                let _ = self.anchor_def_spans.insert(name.clone(), start);
510            }
511            self.recording = Some((name, 0, SmallVec::new()));
512        }
513    }
514
515    fn maybe_record(&mut self, ev: &Event<'_>) {
516        if let Some((_, ref mut depth, ref mut buf)) = self.recording {
517            match ev {
518                Event::Scalar { value, style, .. } => {
519                    buf.push(BufferedEvent::Scalar {
520                        value: value.to_string(),
521                        style: *style,
522                    });
523                    if *depth == 0 {
524                        self.finish_recording();
525                    }
526                }
527                Event::SequenceStart { .. } => {
528                    buf.push(BufferedEvent::SeqStart);
529                    *depth += 1;
530                }
531                Event::SequenceEnd { .. } => {
532                    buf.push(BufferedEvent::SeqEnd);
533                    *depth -= 1;
534                    if *depth == 0 {
535                        self.finish_recording();
536                    }
537                }
538                Event::MappingStart { .. } => {
539                    buf.push(BufferedEvent::MapStart);
540                    *depth += 1;
541                }
542                Event::MappingEnd { .. } => {
543                    buf.push(BufferedEvent::MapEnd);
544                    *depth -= 1;
545                    if *depth == 0 {
546                        self.finish_recording();
547                    }
548                }
549                Event::Alias { anchor, .. } => {
550                    buf.push(BufferedEvent::Alias {
551                        anchor: anchor.clone(),
552                    });
553                    if *depth == 0 {
554                        self.finish_recording();
555                    }
556                }
557                _ => {}
558            }
559        }
560    }
561
562    /// Store the anchor being recorded, with its measured cost.
563    fn finish_recording(&mut self) {
564        if let Some((name, _, events)) = self.recording.take() {
565            let _ = self.anchor_costs.insert(name.clone(), buffer_cost(&events));
566            let _ = self.anchor_events.insert(name, events);
567        }
568    }
569
570    /// Alias budgets from the shared meter: the occurrence charges, then
571    /// the expansion cost of the recorded buffer, measured by the same
572    /// estimator the loaders use. An unknown anchor is charged as an
573    /// occurrence and refused by the caller.
574    fn charge_alias(&mut self, name: &str) -> Result<()> {
575        self.meter.charge_alias(&self.config)?;
576        match self.anchor_costs.get(name) {
577            Some(cost) => self.meter.charge_expansion(cost, self.depth, &self.config),
578            None => Ok(()),
579        }
580    }
581
582    fn resolve_alias(&mut self, name: &str, alias_start: usize) -> Result<Event<'a>> {
583        self.charge_alias(name)?;
584        let buf = self
585            .anchor_events
586            .get(name)
587            .cloned()
588            .ok_or_else(|| self.build_unknown_anchor(name, alias_start))?;
589        if buf.is_empty() {
590            return Err(self.build_unknown_anchor(name, alias_start));
591        }
592        let mut reversed = buf;
593        reversed.reverse();
594        let first = reversed.pop().unwrap();
595        if !reversed.is_empty() {
596            self.replay_stack.push(reversed);
597        }
598        Ok(self.buffered_to_event(first))
599    }
600
601    fn inject_multi_merge_mapping_contents(&mut self, sources: &[(String, usize)]) -> Result<()> {
602        self.meter.charge_merge_key(&self.config)?;
603        let local_buf = self.buffer_rest_of_mapping()?;
604        let mut seen_keys = extract_local_keys(&local_buf);
605        let mut filtered_sources: SmallVec<[SmallVec<[BufferedEvent; SMALL_VEC_SIZE]>; 2]> =
606            SmallVec::new();
607        for (name, start) in sources {
608            let target_buf = self
609                .anchor_events
610                .get(name)
611                .cloned()
612                .ok_or_else(|| self.build_unknown_anchor(name, *start))?;
613            let body = extract_mapping_body(&target_buf).ok_or_else(|| self.fallback())?;
614            let filtered = filter_merge_entries(body, &seen_keys).ok_or_else(|| self.fallback())?;
615            collect_keys(body, &mut seen_keys).ok_or_else(|| self.fallback())?;
616            filtered_sources.push(filtered);
617        }
618        if !local_buf.is_empty() {
619            let mut rev = local_buf;
620            rev.reverse();
621            self.replay_stack.push(rev);
622        }
623        for mut filtered in filtered_sources.into_iter().rev() {
624            if !filtered.is_empty() {
625                filtered.reverse();
626                self.replay_stack.push(filtered);
627            }
628        }
629        Ok(())
630    }
631
632    fn buffer_rest_of_mapping(&mut self) -> Result<SmallVec<[BufferedEvent; SMALL_VEC_SIZE]>> {
633        let mut buf = SmallVec::new();
634        let mut depth: usize = 0;
635        loop {
636            let ev = self.next_parser_event()?;
637            match ev {
638                Event::MappingEnd { .. } => {
639                    buf.push(BufferedEvent::MapEnd);
640                    if depth == 0 {
641                        return Ok(buf);
642                    }
643                    depth -= 1;
644                }
645                Event::MappingStart { .. } => {
646                    buf.push(BufferedEvent::MapStart);
647                    depth += 1;
648                }
649                Event::SequenceStart { .. } => {
650                    buf.push(BufferedEvent::SeqStart);
651                    depth += 1;
652                }
653                Event::SequenceEnd { .. } => {
654                    buf.push(BufferedEvent::SeqEnd);
655                    depth = depth.saturating_sub(1);
656                }
657                Event::Scalar { value, style, .. } => buf.push(BufferedEvent::Scalar {
658                    value: value.into_owned(),
659                    style,
660                }),
661                Event::Alias { anchor, .. } => buf.push(BufferedEvent::Alias { anchor }),
662                _ => {}
663            }
664        }
665    }
666
667    fn build_unknown_anchor(&self, name: &str, alias_start: usize) -> Error {
668        let loc = crate::error::Location::from_index(self.input, alias_start);
669        let suggestion = closest_name(name, self.anchor_def_spans.keys().map(|s| s.as_str()))
670            .and_then(|s| {
671                self.anchor_def_spans.get(s).map(|&idx| {
672                    (
673                        s.to_string(),
674                        crate::error::Location::from_index(self.input, idx),
675                    )
676                })
677            });
678        Error::UnknownAnchorAt {
679            name: name.to_owned(),
680            location: loc,
681            suggestion,
682        }
683    }
684
685    fn fallback(&self) -> Error {
686        Error::Custom(FALLBACK_SENTINEL.to_owned())
687    }
688
689    fn skip_event(&mut self) -> Result<()> {
690        let _ = self.next_event()?;
691        Ok(())
692    }
693
694    fn skip_to_content(&mut self) -> Result<()> {
695        loop {
696            match self.peek_event()? {
697                Event::StreamStart | Event::DocumentStart => {
698                    self.skip_event()?;
699                }
700                _ => return Ok(()),
701            }
702        }
703    }
704
705    /// Consume one whole value without building it (`IgnoredAny`, unknown
706    /// fields, the rest of an abandoned collection).
707    ///
708    /// Skipped content is charged like content that is deserialized:
709    /// nesting against `max_depth`, entries against `max_sequence_length`
710    /// and `max_mapping_keys`, plain `<<` keys against `max_merge_keys`,
711    /// and repeated keys under `DuplicateKeyPolicy::Error`. Iterative, so
712    /// a deep value cannot exhaust the stack.
713    fn skip_value(&mut self) -> Result<()> {
714        let base = self.depth;
715        let result = self.skip_value_levels();
716        // Restore on Ok and Err alike (issue #46).
717        self.depth = base;
718        result
719    }
720
721    fn skip_value_levels(&mut self) -> Result<()> {
722        let mut levels: Vec<SkipLevel> = Vec::new();
723        loop {
724            let ev = self.next_event()?;
725            let opens = match &ev {
726                Event::Scalar { value, style, .. } => {
727                    self.skip_note_node(levels.last_mut(), Some((value, *style)))?;
728                    None
729                }
730                Event::SequenceStart { .. } => Some(false),
731                Event::MappingStart { .. } => Some(true),
732                Event::SequenceEnd { .. } | Event::MappingEnd { .. } => {
733                    let _ = levels.pop();
734                    self.depth = self.depth.saturating_sub(1);
735                    None
736                }
737                _ => continue,
738            };
739            if let Some(is_map) = opens {
740                self.skip_note_node(levels.last_mut(), None)?;
741                self.depth += 1;
742                if budget::depth_exceeded(self.depth, self.config.max_depth) {
743                    return Err(Error::RecursionLimitExceeded { depth: self.depth });
744                }
745                levels.push(SkipLevel::new(is_map));
746            }
747            if levels.is_empty() {
748                return Ok(());
749            }
750        }
751    }
752
753    /// Charge one skipped node to the collection it sits in: an entry of a
754    /// sequence, or a key or value of a mapping. `scalar` is the node's
755    /// text and style when it is a scalar.
756    fn skip_note_node(
757        &mut self,
758        level: Option<&mut SkipLevel>,
759        scalar: Option<(&Cow<'_, str>, ScalarStyle)>,
760    ) -> Result<()> {
761        let Some(level) = level else {
762            return Ok(());
763        };
764        if !level.is_map {
765            level.entries += 1;
766            if level.entries > self.config.max_sequence_length {
767                return Err(Error::Budget(BudgetBreach::MaxSequenceLength {
768                    limit: self.config.max_sequence_length,
769                    observed: level.entries,
770                }));
771            }
772            return Ok(());
773        }
774        level.expecting_key = !level.expecting_key;
775        if level.expecting_key {
776            // That node was the value; the next one is a key.
777            return Ok(());
778        }
779        level.entries += 1;
780        if level.entries > self.config.max_mapping_keys {
781            return Err(Error::Budget(BudgetBreach::MaxMappingKeys {
782                limit: self.config.max_mapping_keys,
783                observed: level.entries,
784            }));
785        }
786        let Some((key, style)) = scalar else {
787            return Ok(());
788        };
789        if style == ScalarStyle::Plain && key == "<<" {
790            self.meter.charge_merge_key(&self.config)?;
791        }
792        if self.config.duplicate_key_policy == crate::parser::InternalDuplicateKeyPolicy::Error
793            && !level.keys.insert(key.to_string())
794        {
795            return Err(Error::DuplicateKey(key.to_string()));
796        }
797        Ok(())
798    }
799
800    /// Peek the next event and, if it carries a tag, take it out of the
801    /// cached event so subsequent calls see the untagged value. Returns
802    /// `None` if no event / no tag. Critical for tag handling: without
803    /// this, `StreamingTagMapAccess` would recurse infinitely when its
804    /// `next_value_seed` reroutes back through `deserialize_any`.
805    fn take_tag_from_current(&mut self) -> Option<(String, String)> {
806        let _ = self.peek_event().ok()?;
807        match self.current.as_mut().map(Lookahead::event_mut) {
808            Some(
809                Event::Scalar { tag, .. }
810                | Event::SequenceStart { tag, .. }
811                | Event::MappingStart { tag, .. },
812            ) => tag.take(),
813            _ => None,
814        }
815    }
816
817    /// Is `(handle, suffix)` registered in the tag registry as
818    /// strip-through? Matches against the reconstructed full tag
819    /// string (`"{handle}{suffix}"`, so `!Celsius` for `("!",
820    /// "Celsius")`).
821    ///
822    /// Core YAML 1.2 tags (`!!str`, `!!int`, `!!bool`, `!!float`,
823    /// `!!null`, `!!seq`, `!!map`) are never stripped — their tag
824    /// carries semantic information (e.g. `!!str 42` forces string
825    /// resolution) that the AST resolver must see. Registering one of
826    /// those tags is a no-op.
827    fn tag_in_registry(&self, tag: &(String, String)) -> bool {
828        self.tag_registry
829            .as_ref()
830            .is_some_and(|r| tag_is_registry_stripped(&tag.0, &tag.1, r))
831    }
832
833    /// Put a tag back onto the currently-cached event. Used to restore
834    /// state before returning a fallback error so the AST path sees the
835    /// tagged node unchanged.
836    fn restore_tag_to_current(&mut self, t: (String, String)) {
837        if let Some(
838            Event::Scalar { tag, .. }
839            | Event::SequenceStart { tag, .. }
840            | Event::MappingStart { tag, .. },
841        ) = self.current.as_mut().map(Lookahead::event_mut)
842        {
843            *tag = Some(t);
844        }
845    }
846
847    fn resolve_scalar<'s>(&self, value: &'s str, style: ScalarStyle) -> Scalar<'s> {
848        if style == ScalarStyle::Plain {
849            resolve_plain_ext(
850                value,
851                self.config.strict_booleans,
852                self.config.legacy_booleans,
853                self.config.no_schema,
854                self.config.legacy_octal_numbers,
855                self.config.legacy_sexagesimal,
856                self.config.lossless_u64_integers(),
857            )
858        } else {
859            Scalar::Str(Cow::Borrowed(value))
860        }
861    }
862}
863
864impl<'de> serde_core::Deserializer<'de> for &mut StreamingDeserializer<'de> {
865    type Error = Error;
866
867    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
868    where
869        V: serde_core::de::Visitor<'de>,
870    {
871        self.skip_to_content()?;
872        // `deserialize_any` is used by `Value` and other type-erased
873        // visitors — they rely on the AST tag resolver to produce the
874        // correct `Value::Tagged(...)` / `Value::String` / `Value::Number`
875        // variant for tagged scalars. Restore the tag and fall back.
876        if let Some(t) = self.take_tag_from_current() {
877            // TagRegistry opt-in: if the tag is registered as
878            // strip-through, proceed without restoring so the inner
879            // value deserializes directly into the target type.
880            if !self.tag_in_registry(&t) {
881                self.restore_tag_to_current(t);
882                return Err(self.fallback());
883            }
884        }
885        match self.next_event()? {
886            Event::Scalar { value, style, .. } => match value {
887                Cow::Borrowed(s) => match self.resolve_scalar(s, style) {
888                    Scalar::Null => visitor.visit_none(),
889                    Scalar::Bool(b) => visitor.visit_bool(b),
890                    Scalar::Int(n) => visitor.visit_i64(n),
891                    #[cfg(feature = "lossless-u64")]
892                    Scalar::Uint(n) => visitor.visit_u64(n),
893                    Scalar::Float(f) => visitor.visit_f64(f),
894                    Scalar::Str(Cow::Borrowed(b)) => visitor.visit_borrowed_str(b),
895                    Scalar::Str(Cow::Owned(o)) => visitor.visit_string(o),
896                },
897                Cow::Owned(s) => match self.resolve_scalar(&s, style) {
898                    Scalar::Null => visitor.visit_none(),
899                    Scalar::Bool(b) => visitor.visit_bool(b),
900                    Scalar::Int(n) => visitor.visit_i64(n),
901                    #[cfg(feature = "lossless-u64")]
902                    Scalar::Uint(n) => visitor.visit_u64(n),
903                    Scalar::Float(f) => visitor.visit_f64(f),
904                    Scalar::Str(_) => visitor.visit_string(s),
905                },
906            },
907            Event::SequenceStart { .. } => {
908                self.depth += 1;
909                if budget::depth_exceeded(self.depth, self.config.max_depth) {
910                    return Err(Error::RecursionLimitExceeded { depth: self.depth });
911                }
912                let res = visitor.visit_seq(StreamingSeqAccess {
913                    de: self,
914                    finished: false,
915                    count: 0,
916                });
917                // Decrement on both Ok and Err so a failed inner
918                // visit_seq doesn't leak depth (issue #46).
919                self.depth = self.depth.saturating_sub(1);
920                res
921            }
922            Event::MappingStart { .. } => {
923                self.depth += 1;
924                if budget::depth_exceeded(self.depth, self.config.max_depth) {
925                    return Err(Error::RecursionLimitExceeded { depth: self.depth });
926                }
927                let res = visitor.visit_map(StreamingMapAccess {
928                    de: self,
929                    finished: false,
930                    has_emitted_key: false,
931                    key_count: 0,
932                    seen_keys: FxHashSet::default(),
933                    seen_typed: FxHashMap::default(),
934                });
935                // Decrement on both Ok and Err so a failed inner
936                // visit_map doesn't leak a depth count into the
937                // outer scope (issue #46).
938                self.depth = self.depth.saturating_sub(1);
939                res
940            }
941            _ => Err(self.fallback()),
942        }
943    }
944
945    fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value>
946    where
947        V: serde_core::de::Visitor<'de>,
948    {
949        self.skip_to_content()?;
950        if let Event::Scalar { value, style, .. } = self.next_event()?
951            && let Scalar::Bool(b) = self.resolve_scalar(&value, style)
952        {
953            return visitor.visit_bool(b);
954        }
955        Err(Error::TypeMismatch {
956            expected: "bool",
957            found: "other".into(),
958        })
959    }
960
961    fn deserialize_i64<V>(self, visitor: V) -> Result<V::Value>
962    where
963        V: serde_core::de::Visitor<'de>,
964    {
965        self.skip_to_content()?;
966        if let Event::Scalar { value, style, .. } = self.next_event()? {
967            match self.resolve_scalar(&value, style) {
968                Scalar::Int(n) => return visitor.visit_i64(n),
969                // Accept whole-number floats ("42.0") as integers so
970                // typed struct fields can consume them.
971                Scalar::Float(f)
972                    if f.is_finite()
973                        && f64_fract(f) == 0.0
974                        && f >= i64::MIN as f64
975                        && f <= i64::MAX as f64 =>
976                {
977                    return visitor.visit_i64(f as i64);
978                }
979                _ => {}
980            }
981        }
982        Err(Error::TypeMismatch {
983            expected: "integer",
984            found: "other".into(),
985        })
986    }
987
988    fn deserialize_u64<V>(self, visitor: V) -> Result<V::Value>
989    where
990        V: serde_core::de::Visitor<'de>,
991    {
992        self.skip_to_content()?;
993        if let Event::Scalar { value, style, .. } = self.next_event()? {
994            match self.resolve_scalar(&value, style) {
995                Scalar::Int(n) if n >= 0 => return visitor.visit_u64(n as u64),
996                #[cfg(feature = "lossless-u64")]
997                Scalar::Uint(n) => return visitor.visit_u64(n),
998                Scalar::Float(f)
999                    if f.is_finite() && f64_fract(f) == 0.0 && f >= 0.0 && f <= u64::MAX as f64 =>
1000                {
1001                    return visitor.visit_u64(f as u64);
1002                }
1003                _ => {}
1004            }
1005        }
1006        Err(Error::TypeMismatch {
1007            expected: "unsigned integer",
1008            found: "other".into(),
1009        })
1010    }
1011
1012    fn deserialize_f64<V>(self, visitor: V) -> Result<V::Value>
1013    where
1014        V: serde_core::de::Visitor<'de>,
1015    {
1016        self.skip_to_content()?;
1017        if let Event::Scalar { value, style, .. } = self.next_event()? {
1018            match self.resolve_scalar(&value, style) {
1019                Scalar::Float(f) => return visitor.visit_f64(f),
1020                Scalar::Int(n) => return visitor.visit_f64(n as f64),
1021                #[cfg(feature = "lossless-u64")]
1022                Scalar::Uint(n) => return visitor.visit_f64(n as f64),
1023                _ => {}
1024            }
1025        }
1026        Err(Error::TypeMismatch {
1027            expected: "float",
1028            found: "other".into(),
1029        })
1030    }
1031
1032    fn deserialize_str<V>(self, visitor: V) -> Result<V::Value>
1033    where
1034        V: serde_core::de::Visitor<'de>,
1035    {
1036        self.skip_to_content()?;
1037        // A tag (`!!str`, `!custom`, …) routes through the AST so the
1038        // tagged string is resolved correctly.
1039        if let Some(t) = self.take_tag_from_current()
1040            && !self.tag_in_registry(&t)
1041        {
1042            self.restore_tag_to_current(t);
1043            return Err(self.fallback());
1044        }
1045        match self.peek_event()? {
1046            Event::Scalar { .. } => {
1047                if let Event::Scalar { value, style, .. } = self.next_event()? {
1048                    // When the scalar borrows directly from the input
1049                    // (`Cow::Borrowed`), preserve the `'de` lifetime by
1050                    // calling `visit_borrowed_str`. This unlocks
1051                    // zero-copy `Deserialize<'de> for &'de str` without
1052                    // routing through the `Value` AST.
1053                    if self.raw_str_mode {
1054                        return match value {
1055                            Cow::Borrowed(s) => visitor.visit_borrowed_str(s),
1056                            Cow::Owned(s) => visitor.visit_string(s),
1057                        };
1058                    }
1059                    if style != ScalarStyle::Plain {
1060                        return match value {
1061                            Cow::Borrowed(s) => visitor.visit_borrowed_str(s),
1062                            Cow::Owned(s) => visitor.visit_string(s),
1063                        };
1064                    }
1065                    // Opt-in (refs #344,
1066                    // `ParserConfig::plain_scalar_strings`): a `String`
1067                    // target receives a plain scalar's literal text
1068                    // unchanged, even where the resolver below would
1069                    // classify it as a number, bool, or null. Off by
1070                    // default — see the non-string-scalar refusal a
1071                    // few lines down, which is the historical contract.
1072                    if self.config.plain_scalar_strings {
1073                        return match value {
1074                            Cow::Borrowed(s) => visitor.visit_borrowed_str(s),
1075                            Cow::Owned(s) => visitor.visit_string(s),
1076                        };
1077                    }
1078                    match value {
1079                        Cow::Borrowed(s) => match self.resolve_scalar(s, style) {
1080                            Scalar::Str(Cow::Borrowed(b)) => visitor.visit_borrowed_str(b),
1081                            Scalar::Str(Cow::Owned(o)) => visitor.visit_string(o),
1082                            _ => Err(Error::TypeMismatch {
1083                                expected: "string",
1084                                found: "non-string scalar".into(),
1085                            }),
1086                        },
1087                        Cow::Owned(s) => match self.resolve_scalar(&s, style) {
1088                            Scalar::Str(_) => visitor.visit_string(s),
1089                            _ => Err(Error::TypeMismatch {
1090                                expected: "string",
1091                                found: "non-string scalar".into(),
1092                            }),
1093                        },
1094                    }
1095                } else {
1096                    Err(Error::TypeMismatch {
1097                        expected: "string",
1098                        found: "other".into(),
1099                    })
1100                }
1101            }
1102            // A complex key (sequence / mapping / alias) cannot be
1103            // delivered as a string through the streaming path without
1104            // first materialising it — bail so the AST fallback (which
1105            // stringifies complex keys) takes over.
1106            _ => Err(self.fallback()),
1107        }
1108    }
1109
1110    fn deserialize_string<V>(self, visitor: V) -> Result<V::Value>
1111    where
1112        V: serde_core::de::Visitor<'de>,
1113    {
1114        self.deserialize_str(visitor)
1115    }
1116
1117    fn deserialize_char<V>(self, visitor: V) -> Result<V::Value>
1118    where
1119        V: serde_core::de::Visitor<'de>,
1120    {
1121        // Opt-in (refs #344): with `plain_scalar_strings` on, `char`'s
1122        // `Visitor` accepts a one-character string via `visit_str`
1123        // (falling back from `visit_char`), so routing through
1124        // `deserialize_str` gives it the same literal-text treatment
1125        // as a `String` target — a single-digit plain scalar is a
1126        // valid one-character string. Off by default: fall back to
1127        // `deserialize_any`, the historical (`forward_to_deserialize_any`)
1128        // behaviour.
1129        if self.config.plain_scalar_strings {
1130            return self.deserialize_str(visitor);
1131        }
1132        self.deserialize_any(visitor)
1133    }
1134
1135    fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
1136    where
1137        V: serde_core::de::Visitor<'de>,
1138    {
1139        self.skip_to_content()?;
1140        if let Event::Scalar { value, style, .. } = self.peek_event()?
1141            && *style == ScalarStyle::Plain
1142        {
1143            match &**value {
1144                "" | "~" | "null" | "Null" | "NULL" => {
1145                    self.skip_event()?;
1146                    return visitor.visit_none();
1147                }
1148                _ => {}
1149            }
1150        }
1151        visitor.visit_some(self)
1152    }
1153
1154    fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value>
1155    where
1156        V: serde_core::de::Visitor<'de>,
1157    {
1158        self.skip_to_content()?;
1159        if let Event::Scalar { value, style, .. } = self.next_event()?
1160            && matches!(self.resolve_scalar(&value, style), Scalar::Null)
1161        {
1162            return visitor.visit_unit();
1163        }
1164        Err(Error::TypeMismatch {
1165            expected: "null",
1166            found: "other".into(),
1167        })
1168    }
1169
1170    fn deserialize_newtype_struct<V>(self, name: &'static str, visitor: V) -> Result<V::Value>
1171    where
1172        V: serde_core::de::Visitor<'de>,
1173    {
1174        if name == crate::spanned::SPANNED_TYPE_NAME {
1175            return Err(self.fallback());
1176        }
1177        self.skip_to_content()?;
1178        if let Some(t) = self.take_tag_from_current() {
1179            if let ("!!", "int" | "float" | "str" | "bool" | "null" | "seq" | "map") =
1180                (t.0.as_str(), t.1.as_str())
1181            {
1182            } else {
1183                // TagRegistry opt-in: drop the tag and let the
1184                // inner value deserialize straight into the
1185                // newtype's target type.
1186                if self.tag_in_registry(&t) {
1187                    return visitor.visit_newtype_struct(self);
1188                }
1189                return visitor.visit_map(StreamingTagMapAccess {
1190                    de: self,
1191                    tag: t,
1192                    done: false,
1193                });
1194            }
1195        }
1196        visitor.visit_newtype_struct(self)
1197    }
1198
1199    fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value>
1200    where
1201        V: serde_core::de::Visitor<'de>,
1202    {
1203        self.skip_to_content()?;
1204        // Tagged sequences route through the AST fallback so the tag is
1205        // preserved on the resulting `Value::Tagged(...)`. The registry
1206        // opts a specific tag out of that behaviour.
1207        if let Some(t) = self.take_tag_from_current()
1208            && !self.tag_in_registry(&t)
1209        {
1210            self.restore_tag_to_current(t);
1211            return Err(self.fallback());
1212        }
1213        if let Event::SequenceStart { .. } = self.next_event()? {
1214            self.depth += 1;
1215            if budget::depth_exceeded(self.depth, self.config.max_depth) {
1216                return Err(Error::RecursionLimitExceeded { depth: self.depth });
1217            }
1218            let res = visitor.visit_seq(StreamingSeqAccess {
1219                de: self,
1220                finished: false,
1221                count: 0,
1222            });
1223            // Decrement on Ok and Err (issue #46).
1224            self.depth = self.depth.saturating_sub(1);
1225            res
1226        } else {
1227            Err(Error::TypeMismatch {
1228                expected: "sequence",
1229                found: "other".into(),
1230            })
1231        }
1232    }
1233
1234    fn deserialize_map<V>(self, visitor: V) -> Result<V::Value>
1235    where
1236        V: serde_core::de::Visitor<'de>,
1237    {
1238        self.skip_to_content()?;
1239        // An empty / whitespace-only / comment-only document never opens
1240        // a document at all — after `skip_to_content` the very next event
1241        // is `StreamEnd`, with no `Scalar` to resolve. Treat it as the
1242        // null document's "no entries" the same way the `Scalar` arm
1243        // below handles an explicit `---` with nothing after it. Peeked,
1244        // not consumed: the top-level drain loop in `from_str_streaming`
1245        // expects to see `StreamEnd` itself once deserialisation is done.
1246        // See #349.
1247        if matches!(self.peek_event()?, Event::StreamEnd) {
1248            return visitor.visit_map(crate::de::EmptyMapAccess);
1249        }
1250        // Tagged mappings route through the AST fallback so the tag is
1251        // preserved on the resulting `Value::Tagged(...)`. The registry
1252        // opts a specific tag out of that behaviour.
1253        if let Some(t) = self.take_tag_from_current()
1254            && !self.tag_in_registry(&t)
1255        {
1256            self.restore_tag_to_current(t);
1257            return Err(self.fallback());
1258        }
1259        match self.next_event()? {
1260            Event::MappingStart { .. } => {
1261                self.depth += 1;
1262                if budget::depth_exceeded(self.depth, self.config.max_depth) {
1263                    return Err(Error::RecursionLimitExceeded { depth: self.depth });
1264                }
1265                let res = visitor.visit_map(StreamingMapAccess {
1266                    de: self,
1267                    finished: false,
1268                    has_emitted_key: false,
1269                    key_count: 0,
1270                    seen_keys: FxHashSet::default(),
1271                    seen_typed: FxHashMap::default(),
1272                });
1273                // Decrement on Ok and Err (issue #46).
1274                self.depth = self.depth.saturating_sub(1);
1275                res
1276            }
1277            // A bare `---` with nothing after it resolves to an implicit
1278            // null scalar — the same null document as the truly-empty
1279            // case above, just with an explicit document marker. Mirrors
1280            // the AST path's `Value::Null` arm in `de::deserializer`.
1281            // See #349.
1282            Event::Scalar { value, style, .. }
1283                if matches!(self.resolve_scalar(&value, style), Scalar::Null) =>
1284            {
1285                visitor.visit_map(crate::de::EmptyMapAccess)
1286            }
1287            _ => Err(Error::TypeMismatch {
1288                expected: "mapping",
1289                found: "other".into(),
1290            }),
1291        }
1292    }
1293
1294    fn deserialize_enum<V>(
1295        self,
1296        _name: &'static str,
1297        _variants: &'static [&'static str],
1298        visitor: V,
1299    ) -> Result<V::Value>
1300    where
1301        V: serde_core::de::Visitor<'de>,
1302    {
1303        self.skip_to_content()?;
1304        if let Some(t) = self.take_tag_from_current() {
1305            match (t.0.as_str(), t.1.as_str()) {
1306                ("!!", "int" | "float" | "str" | "bool" | "null" | "seq" | "map") => {}
1307                _ => {
1308                    return visitor.visit_enum(StreamingTagEnumAccess { de: self, tag: t });
1309                }
1310            }
1311        }
1312        match self.next_event()? {
1313            Event::Scalar { value, .. } => visitor.visit_enum(
1314                serde_core::de::IntoDeserializer::into_deserializer(value.into_owned()),
1315            ),
1316            Event::MappingStart { .. } => {
1317                if let Event::Scalar { value, .. } = self.next_event()? {
1318                    visitor.visit_enum(StreamingEnumAccess {
1319                        de: self,
1320                        variant: value.into_owned(),
1321                    })
1322                } else {
1323                    Err(Error::TypeMismatch {
1324                        expected: "variant name",
1325                        found: "non-scalar".into(),
1326                    })
1327                }
1328            }
1329            _ => Err(Error::TypeMismatch {
1330                expected: "enum",
1331                found: "other".into(),
1332            }),
1333        }
1334    }
1335
1336    fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value>
1337    where
1338        V: serde_core::de::Visitor<'de>,
1339    {
1340        self.skip_to_content()?;
1341        if let Event::Scalar { value, .. } = self.next_event()? {
1342            return match value {
1343                Cow::Borrowed(s) => visitor.visit_borrowed_str(s),
1344                Cow::Owned(s) => visitor.visit_string(s),
1345            };
1346        }
1347        Err(Error::TypeMismatch {
1348            expected: "identifier",
1349            found: "non-scalar".into(),
1350        })
1351    }
1352
1353    fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value>
1354    where
1355        V: serde_core::de::Visitor<'de>,
1356    {
1357        self.skip_value()?;
1358        visitor.visit_unit()
1359    }
1360
1361    fn deserialize_unit_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value>
1362    where
1363        V: serde_core::de::Visitor<'de>,
1364    {
1365        self.skip_to_content()?;
1366        // Accept `null` / `~` / empty scalar — delegate to deserialize_unit.
1367        self.deserialize_unit(visitor)
1368    }
1369
1370    fn deserialize_struct<V>(
1371        self,
1372        name: &'static str,
1373        _fields: &'static [&'static str],
1374        visitor: V,
1375    ) -> Result<V::Value>
1376    where
1377        V: serde_core::de::Visitor<'de>,
1378    {
1379        // `Spanned<T>` is routed through this method by its Deserialize
1380        // impl. The streaming path does not carry source-span context,
1381        // so bail to the AST fallback for any Spanned deserialisation.
1382        if name == crate::spanned::SPANNED_TYPE_NAME {
1383            return Err(self.fallback());
1384        }
1385        self.deserialize_map(visitor)
1386    }
1387
1388    fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value>
1389    where
1390        V: serde_core::de::Visitor<'de>,
1391    {
1392        self.skip_to_content()?;
1393        // YAML 1.2.2 §10.4: a `!!binary`-tagged scalar carries an
1394        // RFC 4648 base64 payload. Recognise the tag on the current
1395        // event, decode without buffering, and hand the bytes to the
1396        // visitor — the AST fallback path is unnecessary here.
1397        let is_binary = match self.current.as_ref().map(Lookahead::event) {
1398            Some(Event::Scalar { tag: Some(t), .. }) => {
1399                let full = format!("{}{}", t.0, t.1);
1400                crate::de::is_binary_tag(&full)
1401            }
1402            _ => false,
1403        };
1404        if is_binary {
1405            let _ = self.take_tag_from_current();
1406            if let Event::Scalar { value, .. } = self.next_event()? {
1407                return match crate::base64::decode(&value) {
1408                    Ok(bytes) => visitor.visit_byte_buf(bytes),
1409                    Err(why) => Err(Error::Deserialize(format!("!!binary: {why}"))),
1410                };
1411            }
1412            return Err(Error::TypeMismatch {
1413                expected: "string-shaped !!binary content",
1414                found: "non-scalar".into(),
1415            });
1416        }
1417        if let Event::Scalar { value, style, .. } = self.next_event()? {
1418            // Mirror the AST path: only string-shaped scalars are
1419            // accepted as bytes. A plain scalar that resolves to an
1420            // int / float / bool / null is a type error — the caller
1421            // wanted bytes, not a number's UTF-8 representation.
1422            return match self.resolve_scalar(&value, style) {
1423                Scalar::Str(s) => visitor.visit_bytes(s.as_bytes()),
1424                Scalar::Null => Err(Error::TypeMismatch {
1425                    expected: "bytes",
1426                    found: "null".into(),
1427                }),
1428                Scalar::Bool(_) => Err(Error::TypeMismatch {
1429                    expected: "bytes",
1430                    found: "bool".into(),
1431                }),
1432                Scalar::Int(_) => Err(Error::TypeMismatch {
1433                    expected: "bytes",
1434                    found: "integer".into(),
1435                }),
1436                #[cfg(feature = "lossless-u64")]
1437                Scalar::Uint(_) => Err(Error::TypeMismatch {
1438                    expected: "bytes",
1439                    found: "integer".into(),
1440                }),
1441                Scalar::Float(_) => Err(Error::TypeMismatch {
1442                    expected: "bytes",
1443                    found: "float".into(),
1444                }),
1445            };
1446        }
1447        Err(Error::TypeMismatch {
1448            expected: "bytes",
1449            found: "non-scalar".into(),
1450        })
1451    }
1452
1453    fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value>
1454    where
1455        V: serde_core::de::Visitor<'de>,
1456    {
1457        self.deserialize_bytes(visitor)
1458    }
1459
1460    serde_core::forward_to_deserialize_any! {
1461        i8 i16 i32 u8 u16 u32 f32
1462        tuple tuple_struct
1463    }
1464}
1465
1466struct StreamingSeqAccess<'a, 'de> {
1467    de: &'a mut StreamingDeserializer<'de>,
1468    finished: bool,
1469    count: usize,
1470}
1471
1472impl<'de> serde_core::de::SeqAccess<'de> for StreamingSeqAccess<'_, 'de> {
1473    type Error = Error;
1474    fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
1475    where
1476        T: serde_core::de::DeserializeSeed<'de>,
1477    {
1478        // Symmetric guard with `StreamingMapAccess::next_key_seed`
1479        // — callers that re-invoke `next_element` after the
1480        // sequence is exhausted must not consume events from the
1481        // parent context. See issue #46 for the matching map-side
1482        // failure mode.
1483        if self.finished {
1484            return Ok(None);
1485        }
1486        if matches!(self.de.peek_event()?, Event::SequenceEnd { .. }) {
1487            self.de.skip_event()?;
1488            self.finished = true;
1489            return Ok(None);
1490        }
1491        self.count += 1;
1492        if self.count > self.de.config.max_sequence_length {
1493            return Err(Error::Budget(BudgetBreach::MaxSequenceLength {
1494                limit: self.de.config.max_sequence_length,
1495                observed: self.count,
1496            }));
1497        }
1498        seed.deserialize(&mut *self.de).map(Some)
1499    }
1500}
1501
1502impl Drop for StreamingSeqAccess<'_, '_> {
1503    fn drop(&mut self) {
1504        if !self.finished {
1505            loop {
1506                match self.de.peek_event() {
1507                    Ok(Event::SequenceEnd { .. }) => {
1508                        let _ = self.de.skip_event();
1509                        break;
1510                    }
1511                    Ok(_) => {
1512                        if self.de.skip_value().is_err() {
1513                            break;
1514                        }
1515                    }
1516                    Err(_) => break,
1517                }
1518            }
1519        }
1520    }
1521}
1522
1523/// Convert a resolved streaming `Scalar` into an owned `Value` — used only to
1524/// canonicalise a mapping key for distinct-typed collision detection, matching
1525/// the AST loader's untagged-scalar resolution.
1526fn scalar_to_value(s: Scalar<'_>) -> Value {
1527    match s {
1528        Scalar::Null => Value::Null,
1529        Scalar::Bool(b) => Value::Bool(b),
1530        Scalar::Int(n) => Value::Number(crate::value::Number::Integer(n)),
1531        #[cfg(feature = "lossless-u64")]
1532        Scalar::Uint(n) => Value::Number(crate::value::Number::Unsigned(n)),
1533        Scalar::Float(f) => Value::Number(crate::value::Number::Float(f)),
1534        Scalar::Str(s) => Value::String(s.into_owned()),
1535    }
1536}
1537
1538struct StreamingMapAccess<'a, 'de> {
1539    de: &'a mut StreamingDeserializer<'de>,
1540    finished: bool,
1541    has_emitted_key: bool,
1542    key_count: usize,
1543    /// Track keys seen at this mapping's top level to enforce
1544    /// `DuplicateKeyPolicy`. Populated lazily; bypassed when the policy
1545    /// is `First` (the visitor's own insertion order already matches).
1546    seen_keys: FxHashSet<String>,
1547    /// Canonical key string → the typed key `Value` first seen for it, to
1548    /// detect distinct-typed key collisions (`1` vs `"1"`, `~` vs `"null"`)
1549    /// in parity with the AST loader. Separate from `DuplicateKeyPolicy`:
1550    /// a same-string/different-type clash is always a `KeyCollision` error.
1551    seen_typed: FxHashMap<String, Value>,
1552}
1553
1554impl StreamingMapAccess<'_, '_> {
1555    /// Expand a plain `<<` key: its value (an alias or a sequence of
1556    /// aliases) is spliced into this mapping through the replay stack.
1557    fn expand_merge_key(&mut self) -> Result<()> {
1558        self.de.skip_event()?;
1559        // The value event is raw-read from the parser so an
1560        // `Event::Alias` is visible without auto-resolution.
1561        match self.de.peek_parser_event()? {
1562            Event::Alias { anchor, span } => {
1563                let name = anchor.clone();
1564                let start = span.start;
1565                self.de.current = None;
1566                self.de
1567                    .inject_multi_merge_mapping_contents(&[(name, start)])?;
1568            }
1569            Event::SequenceStart { .. } => {
1570                self.de.skip_event()?;
1571                let mut sources = Vec::new();
1572                loop {
1573                    match self.de.peek_parser_event()? {
1574                        Event::SequenceEnd { .. } => {
1575                            self.de.skip_event()?;
1576                            break;
1577                        }
1578                        Event::Alias { anchor, span } => {
1579                            sources.push((anchor.clone(), span.start));
1580                            self.de.skip_event()?;
1581                        }
1582                        _ => return Err(self.de.fallback()),
1583                    }
1584                }
1585                self.de.inject_multi_merge_mapping_contents(&sources)?;
1586            }
1587            _ => return Err(self.de.fallback()),
1588        }
1589        Ok(())
1590    }
1591
1592    /// Apply the key-collision guard and `DuplicateKeyPolicy` to the
1593    /// next key. Returns `false` when the policy skipped the entry.
1594    fn admit_key(&mut self, key_info: Option<(String, ScalarStyle, bool)>) -> Result<bool> {
1595        let Some((raw, style, untagged)) = key_info else {
1596            return Ok(true);
1597        };
1598        // Distinct-typed key-collision guard (parity with the AST loader):
1599        // two keys whose canonical map-key string matches but whose typed
1600        // Value differs (`1` vs `"1"`, `~` vs `"null"`, `true` vs `"true"`)
1601        // are a `KeyCollision`, independent of `DuplicateKeyPolicy`. This is
1602        // the guard the streaming path lacked, so `from_str::<map/struct>`
1603        // and `from_str_borrowing::<Value>` silently collapsed such keys.
1604        // Untagged scalar keys only; tagged keys keep their prior behaviour.
1605        // Duplicates are judged on the same canonical form, so `~` and
1606        // `null` are one key, as the AST loader judges them; the key
1607        // string the visitor receives is still the text as written.
1608        let mut identity = None;
1609        if untagged {
1610            let typed = scalar_to_value(self.de.resolve_scalar(&raw, style));
1611            if let Some(canon) = crate::parser::value_to_key_string(typed.clone()) {
1612                match self.seen_typed.get(&canon) {
1613                    Some(prev) if *prev != typed => {
1614                        return Err(Error::KeyCollision(canon));
1615                    }
1616                    Some(_) => {}
1617                    None => {
1618                        let _ = self.seen_typed.insert(canon.clone(), typed);
1619                    }
1620                }
1621                identity = Some(canon);
1622            }
1623        }
1624        let policy = self.de.config.duplicate_key_policy;
1625        if policy == crate::parser::InternalDuplicateKeyPolicy::Last {
1626            return Ok(true);
1627        }
1628        let identity = identity.unwrap_or_else(|| raw.clone());
1629        if self.seen_keys.insert(identity) {
1630            return Ok(true);
1631        }
1632        match policy {
1633            crate::parser::InternalDuplicateKeyPolicy::Error => Err(Error::DuplicateKey(raw)),
1634            crate::parser::InternalDuplicateKeyPolicy::First => {
1635                // Skip duplicate key + value.
1636                self.de.skip_value()?;
1637                self.de.skip_value()?;
1638                Ok(false)
1639            }
1640            _ => Ok(true),
1641        }
1642    }
1643}
1644
1645impl<'de> serde_core::de::MapAccess<'de> for StreamingMapAccess<'_, 'de> {
1646    type Error = Error;
1647    fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>>
1648    where
1649        K: serde_core::de::DeserializeSeed<'de>,
1650    {
1651        // Guard against serde visitors (e.g. `noyalib::Value`'s
1652        // `ValueVisitor::visit_map`) that call `next_key` again
1653        // after the previous call returned `Ok(None)`. Without
1654        // this guard the loop reads events from the *parent*
1655        // mapping and treats them as belonging to the now-empty
1656        // child — the recursive `deserialize_any` on each spilled
1657        // value inflates `self.depth` by one per entry and
1658        // triggers `RecursionLimitExceeded` on otherwise-shallow
1659        // documents (issue #46).
1660        if self.finished {
1661            return Ok(None);
1662        }
1663        loop {
1664            // Use `peek_event` so we see events queued on the replay stack
1665            // by a prior merge expansion — `peek_parser_event` bypasses it
1666            // and would read the next raw parser event, skipping the merge.
1667            let ev = self.de.peek_event()?;
1668            if matches!(ev, Event::MappingEnd { .. }) {
1669                self.de.skip_event()?;
1670                self.finished = true;
1671                return Ok(None);
1672            }
1673            if matches!(ev, Event::Scalar { value, style: ScalarStyle::Plain, .. } if value == "<<")
1674            {
1675                if self.has_emitted_key {
1676                    return Err(self.de.fallback());
1677                }
1678                self.expand_merge_key()?;
1679                continue;
1680            }
1681            // Enforce duplicate-key policy when not `Last` (the serde
1682            // default). The key is peeked as a raw scalar string so policy
1683            // is applied before the visitor sees it. Extract the key
1684            // eagerly so the `ev` borrow is released before we touch
1685            // `self.de` again.
1686            let key_info = if let Event::Scalar {
1687                value: key_val,
1688                style,
1689                tag,
1690                ..
1691            } = ev
1692            {
1693                Some((key_val.to_string(), *style, tag.is_none()))
1694            } else {
1695                None
1696            };
1697            if !self.admit_key(key_info)? {
1698                continue;
1699            }
1700            self.key_count += 1;
1701            if self.key_count > self.de.config.max_mapping_keys {
1702                return Err(Error::Budget(BudgetBreach::MaxMappingKeys {
1703                    limit: self.de.config.max_mapping_keys,
1704                    observed: self.key_count,
1705                }));
1706            }
1707            self.de.raw_str_mode = true;
1708            let res = seed.deserialize(&mut *self.de).map(Some);
1709            self.de.raw_str_mode = false;
1710            if res.is_ok() {
1711                self.has_emitted_key = true;
1712            }
1713            return res;
1714        }
1715    }
1716    fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value>
1717    where
1718        V: serde_core::de::DeserializeSeed<'de>,
1719    {
1720        // Symmetric guard with `next_key_seed`. Callers that
1721        // misuse the MapAccess contract (e.g. invoking
1722        // `next_value` after the keys are exhausted) would
1723        // otherwise read into the parent mapping's value events
1724        // — the same `depth` leak documented in issue #46.
1725        if self.finished {
1726            return Err(Error::Custom(
1727                "next_value_seed called after MapAccess exhausted".into(),
1728            ));
1729        }
1730        seed.deserialize(&mut *self.de)
1731    }
1732}
1733
1734impl Drop for StreamingMapAccess<'_, '_> {
1735    fn drop(&mut self) {
1736        if !self.finished {
1737            loop {
1738                match self.de.peek_event() {
1739                    Ok(Event::MappingEnd { .. }) => {
1740                        let _ = self.de.skip_event();
1741                        break;
1742                    }
1743                    Ok(_) => {
1744                        if self.de.skip_value().is_err() {
1745                            break;
1746                        }
1747                        if self.de.skip_value().is_err() {
1748                            break;
1749                        }
1750                    }
1751                    Err(_) => break,
1752                }
1753            }
1754        }
1755    }
1756}
1757
1758struct StreamingEnumAccess<'a, 'de> {
1759    de: &'a mut StreamingDeserializer<'de>,
1760    variant: String,
1761}
1762impl<'a, 'de> serde_core::de::EnumAccess<'de> for StreamingEnumAccess<'a, 'de> {
1763    type Error = Error;
1764    type Variant = StreamingVariantAccess<'a, 'de>;
1765    fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant)>
1766    where
1767        V: serde_core::de::DeserializeSeed<'de>,
1768    {
1769        let de = serde_core::de::value::StringDeserializer::<Error>::new(self.variant);
1770        let variant = seed.deserialize(de)?;
1771        Ok((variant, StreamingVariantAccess { de: self.de }))
1772    }
1773}
1774
1775struct StreamingVariantAccess<'a, 'de> {
1776    de: &'a mut StreamingDeserializer<'de>,
1777}
1778impl<'de> serde_core::de::VariantAccess<'de> for StreamingVariantAccess<'_, 'de> {
1779    type Error = Error;
1780    fn unit_variant(self) -> Result<()> {
1781        let ev = self.de.next_event()?;
1782        if !matches!(ev, Event::MappingEnd { .. }) {
1783            self.de.current = Some(Lookahead::Processed(ev));
1784            self.de.skip_value()?;
1785            if !matches!(self.de.next_event()?, Event::MappingEnd { .. }) {
1786                return Err(Error::Invalid("expected mapping end".into()));
1787            }
1788        }
1789        Ok(())
1790    }
1791    fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value>
1792    where
1793        T: serde_core::de::DeserializeSeed<'de>,
1794    {
1795        let res = seed.deserialize(&mut *self.de)?;
1796        if !matches!(self.de.next_event()?, Event::MappingEnd { .. }) {
1797            return Err(Error::Invalid("expected mapping end".into()));
1798        }
1799        Ok(res)
1800    }
1801    fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value>
1802    where
1803        V: serde_core::de::Visitor<'de>,
1804    {
1805        let res = serde_core::Deserializer::deserialize_seq(&mut *self.de, visitor)?;
1806        if !matches!(self.de.next_event()?, Event::MappingEnd { .. }) {
1807            return Err(Error::Invalid("expected mapping end".into()));
1808        }
1809        Ok(res)
1810    }
1811    fn struct_variant<V>(self, _fields: &'static [&'static str], visitor: V) -> Result<V::Value>
1812    where
1813        V: serde_core::de::Visitor<'de>,
1814    {
1815        let res = serde_core::Deserializer::deserialize_map(&mut *self.de, visitor)?;
1816        if !matches!(self.de.next_event()?, Event::MappingEnd { .. }) {
1817            return Err(Error::Invalid("expected mapping end".into()));
1818        }
1819        Ok(res)
1820    }
1821}
1822
1823#[allow(dead_code)]
1824struct StreamingTagMapAccess<'a, 'de> {
1825    de: &'a mut StreamingDeserializer<'de>,
1826    tag: (String, String),
1827    done: bool,
1828}
1829impl<'de> serde_core::de::MapAccess<'de> for StreamingTagMapAccess<'_, 'de> {
1830    type Error = Error;
1831    fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>>
1832    where
1833        K: serde_core::de::DeserializeSeed<'de>,
1834    {
1835        if self.done {
1836            Ok(None)
1837        } else {
1838            let full = if self.tag.0 == "!" {
1839                format!("!{}", self.tag.1)
1840            } else {
1841                format!("{}{}", self.tag.0, self.tag.1)
1842            };
1843            let de = serde_core::de::value::StringDeserializer::<Error>::new(full);
1844            seed.deserialize(de).map(Some)
1845        }
1846    }
1847    fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value>
1848    where
1849        V: serde_core::de::DeserializeSeed<'de>,
1850    {
1851        self.done = true;
1852        seed.deserialize(&mut *self.de)
1853    }
1854}
1855
1856#[allow(dead_code)]
1857struct StreamingTagEnumAccess<'a, 'de> {
1858    de: &'a mut StreamingDeserializer<'de>,
1859    tag: (String, String),
1860}
1861impl<'a, 'de> serde_core::de::EnumAccess<'de> for StreamingTagEnumAccess<'a, 'de> {
1862    type Error = Error;
1863    type Variant = StreamingTagVariantAccess<'a, 'de>;
1864    fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant)>
1865    where
1866        V: serde_core::de::DeserializeSeed<'de>,
1867    {
1868        let full = if self.tag.0 == "!" {
1869            format!("!{}", self.tag.1)
1870        } else {
1871            format!("{}{}", self.tag.0, self.tag.1)
1872        };
1873        let de = serde_core::de::value::StringDeserializer::<Error>::new(full);
1874        let variant = seed.deserialize(de)?;
1875        Ok((variant, StreamingTagVariantAccess { de: self.de }))
1876    }
1877}
1878
1879#[allow(dead_code)]
1880struct StreamingTagVariantAccess<'a, 'de> {
1881    de: &'a mut StreamingDeserializer<'de>,
1882}
1883impl<'de> serde_core::de::VariantAccess<'de> for StreamingTagVariantAccess<'_, 'de> {
1884    type Error = Error;
1885    fn unit_variant(self) -> Result<()> {
1886        self.de.skip_value()
1887    }
1888    fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value>
1889    where
1890        T: serde_core::de::DeserializeSeed<'de>,
1891    {
1892        seed.deserialize(self.de)
1893    }
1894    fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value>
1895    where
1896        V: serde_core::de::Visitor<'de>,
1897    {
1898        serde_core::Deserializer::deserialize_seq(self.de, visitor)
1899    }
1900    fn struct_variant<V>(self, _fields: &'static [&'static str], visitor: V) -> Result<V::Value>
1901    where
1902        V: serde_core::de::Visitor<'de>,
1903    {
1904        serde_core::Deserializer::deserialize_map(self.de, visitor)
1905    }
1906}
1907
1908pub(crate) fn from_str_streaming<T>(s: &str, config: &crate::de::ParserConfig) -> Option<Result<T>>
1909where
1910    T: for<'de> serde_core::Deserialize<'de>,
1911{
1912    let parse_config = ParseConfig::from(config);
1913    if s.len() > parse_config.max_document_length {
1914        return Some(Err(document_too_long(parse_config.max_document_length)));
1915    }
1916    let mut de = StreamingDeserializer::with_config(s, parse_config);
1917    if let Some(registry) = config.tag_registry.as_ref() {
1918        de = de.with_tag_registry(Arc::clone(registry));
1919    }
1920    let res = T::deserialize(&mut de);
1921    match res {
1922        Ok(val) => {
1923            // Drain remaining events to surface any errors lurking past
1924            // the point at which `T::deserialize` was satisfied. Without
1925            // this, lenient inputs like `--- key1: value1\n    key2: value2`
1926            // would lazily yield `Value::String("key1")` because the
1927            // serde visitor stops at the first complete node — the
1928            // subsequent tokens that violate the spec (e.g. "mapping
1929            // values are not allowed in this context") would never be
1930            // fetched. Stop *at* `StreamEnd` (querying past it would
1931            // return a benign "parser has already finished" error);
1932            // propagate any error encountered before that.
1933            //
1934            // A second `DocumentStart` here means the stream carries more
1935            // than one document — `from_str`/`from_str_with_config` only
1936            // support exactly one (`from_str_multi` is the multi-document
1937            // entry point). See #351.
1938            Some(de.end().map(|()| val))
1939        }
1940        Err(ref e) => {
1941            if is_fallback_error(e) {
1942                None
1943            } else {
1944                Some(res)
1945            }
1946        }
1947    }
1948}
1949
1950/// Parse a plain scalar as a float, rejecting the bare special forms.
1951///
1952/// Rust's `f64::from_str` accepts `nan`, `inf` and `infinity` in any
1953/// case, with an optional sign. YAML 1.2 spells those `.nan`, `.inf`
1954/// and `-.inf` — with a leading dot — and the dotted forms are matched
1955/// explicitly by the resolver before this is reached.
1956///
1957/// Accepting the bare spellings here resolved a plain scalar like
1958/// `nAn` to a float, which threw away its original text. Used as a
1959/// mapping key it came back as `nan`, so `nAn: null` did not
1960/// round-trip; the `roundtrip_value` proptest found it. Bare forms are
1961/// left as strings, which is what the spec asks for.
1962///
1963/// An explicit `!!float nan` is unaffected: tagged scalars resolve in
1964/// `parser/loader.rs`, where the tag makes the intent unambiguous.
1965fn parse_plain_float(s: &str) -> Option<f64> {
1966    let unsigned = s.strip_prefix(['+', '-']).unwrap_or(s);
1967    if unsigned.eq_ignore_ascii_case("nan")
1968        || unsigned.eq_ignore_ascii_case("inf")
1969        || unsigned.eq_ignore_ascii_case("infinity")
1970    {
1971        return None;
1972    }
1973    s.parse::<f64>().ok()
1974}
1975
1976/// What `skip_value` tracks for each collection it is inside.
1977#[derive(Debug)]
1978struct SkipLevel {
1979    is_map: bool,
1980    /// Sequence items, or mapping keys, seen so far.
1981    entries: usize,
1982    /// For a mapping: whether the next node is a key.
1983    expecting_key: bool,
1984    /// Scalar keys seen, kept only under `DuplicateKeyPolicy::Error`.
1985    keys: FxHashSet<String>,
1986}
1987
1988impl SkipLevel {
1989    fn new(is_map: bool) -> Self {
1990        Self {
1991            is_map,
1992            entries: 0,
1993            expecting_key: true,
1994            keys: FxHashSet::default(),
1995        }
1996    }
1997}
1998
1999/// The refusal for an input longer than `max_document_length`.
2000fn document_too_long(max: usize) -> Error {
2001    Error::Parse(format!("document exceeds maximum length of {max} bytes"))
2002}
2003
2004/// The expansion cost of a recorded anchor buffer, by the estimator the
2005/// loaders use for an anchored value. Aliases inside the buffer cost
2006/// nothing here: each is charged when it is replayed.
2007fn buffer_cost(events: &[BufferedEvent]) -> AliasCost {
2008    let mut tally = CostTally::default();
2009    for ev in events {
2010        match ev {
2011            BufferedEvent::Scalar { value, .. } => tally.scalar(value.len()),
2012            BufferedEvent::SeqStart | BufferedEvent::MapStart => tally.open(),
2013            BufferedEvent::SeqEnd | BufferedEvent::MapEnd => tally.close(),
2014            BufferedEvent::Alias { .. } => {}
2015        }
2016    }
2017    tally.finish()
2018}
2019
2020fn is_fallback_error(e: &Error) -> bool {
2021    match e {
2022        Error::Custom(msg) => msg == FALLBACK_SENTINEL,
2023        _ => false,
2024    }
2025}
2026
2027/// Is the tag `{handle}{suffix}` registered for strip-through in `registry`?
2028///
2029/// Core YAML 1.2 tags (`!!int`, `!!float`, `!!str`, `!!bool`, `!!null`,
2030/// `!!seq`, `!!map`) are never stripped — their tag carries semantic
2031/// resolution the loader/streamer must see, so registering one is a no-op.
2032/// Shared by the streaming deserializer and the AST loader so both agree on
2033/// exactly what a registry strips (three-loader parity).
2034pub(crate) fn tag_is_registry_stripped(
2035    handle: &str,
2036    suffix: &str,
2037    registry: &crate::TagRegistry,
2038) -> bool {
2039    if matches!(
2040        (handle, suffix),
2041        (
2042            "!!",
2043            "int" | "float" | "str" | "bool" | "null" | "seq" | "map"
2044        )
2045    ) {
2046        return false;
2047    }
2048    registry.contains(&format!("{handle}{suffix}"))
2049}
2050
2051/// Resolve a plain scalar according to YAML 1.2's implicit-typing
2052/// rules, with three `ParserConfig` toggles exposed:
2053///
2054/// - `no_schema` — when `true`, keep every plain scalar as a
2055///   string. Useful for schema-strict pipelines where YAML's
2056///   implicit resolution of `yes` → `true` or `null` → `Null`
2057///   produces unwanted ambiguity.
2058/// - `legacy_octal` — when `true`, accept YAML 1.1-style bare
2059///   `0`-prefix octal literals (e.g. `0644`) in addition to the
2060///   YAML 1.2 `0o644` form. Off by default to honour YAML 1.2's
2061///   stricter integer schema.
2062/// - `legacy_sexagesimal` — when `true`, accept YAML 1.1-style
2063///   colon-separated base-60 numbers (`60:00` → 3 600,
2064///   `1:30:00` → 5 400). Off by default; YAML 1.2 dropped the
2065///   sexagesimal schema.
2066/// - `lossless_u64` — when `true` and the `lossless-u64` feature is
2067///   enabled, resolve unsigned integer scalars up to `u64::MAX`
2068///   before considering a float fallback.
2069pub(crate) fn resolve_plain_ext(
2070    s: &str,
2071    strict: bool,
2072    legacy: bool,
2073    no_schema: bool,
2074    legacy_octal: bool,
2075    legacy_sexagesimal: bool,
2076    lossless_u64: bool,
2077) -> Scalar<'_> {
2078    if no_schema {
2079        // Schema-strict mode: every plain scalar surfaces as a
2080        // string so callers see exactly what the file said.
2081        return Scalar::Str(Cow::Borrowed(s));
2082    }
2083    match s {
2084        "" | "~" | "null" | "Null" | "NULL" => Scalar::Null,
2085        "true" => Scalar::Bool(true),
2086        "false" => Scalar::Bool(false),
2087        "True" | "TRUE" if !strict => Scalar::Bool(true),
2088        "False" | "FALSE" if !strict => Scalar::Bool(false),
2089        ".inf" | ".Inf" | ".INF" | "+.inf" | "+.Inf" | "+.INF" => Scalar::Float(f64::INFINITY),
2090        "-.inf" | "-.Inf" | "-.INF" => Scalar::Float(f64::NEG_INFINITY),
2091        ".nan" | ".NaN" | ".NAN" => Scalar::Float(f64::NAN),
2092        "yes" | "Yes" | "YES" | "y" | "Y" if legacy => Scalar::Bool(true),
2093        "no" | "No" | "NO" | "n" | "N" if legacy => Scalar::Bool(false),
2094        "on" | "On" | "ON" if !strict && legacy => Scalar::Bool(true),
2095        "off" | "Off" | "OFF" if !strict && legacy => Scalar::Bool(false),
2096        _ => {
2097            if let Some(n) = parse_integer(s, legacy_octal, lossless_u64) {
2098                match n {
2099                    ParsedInteger::Signed(n) => Scalar::Int(n),
2100                    #[cfg(feature = "lossless-u64")]
2101                    ParsedInteger::Unsigned(n) => Scalar::Uint(n),
2102                }
2103            } else if lossless_u64 && looks_like_integer_literal(s, legacy_octal) {
2104                Scalar::Str(Cow::Borrowed(s))
2105            } else if legacy_sexagesimal {
2106                if let Some(n) = parse_sexagesimal_int(s) {
2107                    Scalar::Int(n)
2108                } else if let Some(f) = parse_sexagesimal_float(s) {
2109                    Scalar::Float(f)
2110                } else if let Some(f) = parse_plain_float(s) {
2111                    Scalar::Float(f)
2112                } else {
2113                    Scalar::Str(Cow::Borrowed(s))
2114                }
2115            } else if let Some(f) = parse_plain_float(s) {
2116                Scalar::Float(f)
2117            } else {
2118                Scalar::Str(Cow::Borrowed(s))
2119            }
2120        }
2121    }
2122}
2123
2124fn looks_like_integer_literal(s: &str, legacy_octal: bool) -> bool {
2125    let b = s.as_bytes();
2126    if b.is_empty() {
2127        return false;
2128    }
2129    for (prefix, radix) in [("0x", 16), ("0o", 8)] {
2130        if let Some(digits) = s.strip_prefix(prefix) {
2131            return radix_digits(digits, radix);
2132        }
2133    }
2134    if legacy_octal && b.len() >= 2 && b[0] == b'0' {
2135        return b[1..].iter().all(|c| (b'0'..=b'7').contains(c));
2136    }
2137    let start = usize::from(b[0] == b'+' || b[0] == b'-');
2138    start < b.len() && b[start..].iter().all(u8::is_ascii_digit)
2139}
2140
2141enum ParsedInteger {
2142    Signed(i64),
2143    #[cfg(feature = "lossless-u64")]
2144    Unsigned(u64),
2145}
2146
2147/// Parse a YAML 1.1 sexagesimal integer like `60:00` or
2148/// `-1:30:00` — colon-separated base-60 components evaluated
2149/// left-to-right. Returns `None` if the shape doesn't fit:
2150///
2151/// - At least one `:` is required (otherwise this overlaps with
2152///   plain integer parsing).
2153/// - Each component must be one or more decimal digits.
2154/// - Components other than the first must be `00..=59` to avoid
2155///   accepting nonsense like `1:99`.
2156/// - The leading sign (`-` or `+`) applies to the whole value.
2157fn parse_sexagesimal_int(s: &str) -> Option<i64> {
2158    let (sign, rest) = match s.as_bytes().first() {
2159        Some(b'-') => (-1i64, &s[1..]),
2160        Some(b'+') => (1i64, &s[1..]),
2161        _ => (1i64, s),
2162    };
2163    if !rest.contains(':') {
2164        return None;
2165    }
2166    let parts: Vec<&str> = rest.split(':').collect();
2167    if parts.len() < 2 {
2168        return None;
2169    }
2170    let mut total: i64 = 0;
2171    for (idx, part) in parts.iter().enumerate() {
2172        if part.is_empty() || !part.bytes().all(|b| b.is_ascii_digit()) {
2173            return None;
2174        }
2175        let n: i64 = part.parse().ok()?;
2176        if idx > 0 && n >= 60 {
2177            return None;
2178        }
2179        total = total.checked_mul(60)?.checked_add(n)?;
2180    }
2181    sign.checked_mul(total)
2182}
2183
2184/// Parse a YAML 1.1 sexagesimal float like `60:00.5`. Same shape
2185/// as the integer variant but the *last* component may be a
2186/// decimal float (`.5`, `5.5`).
2187fn parse_sexagesimal_float(s: &str) -> Option<f64> {
2188    let (sign, rest) = match s.as_bytes().first() {
2189        Some(b'-') => (-1.0f64, &s[1..]),
2190        Some(b'+') => (1.0f64, &s[1..]),
2191        _ => (1.0f64, s),
2192    };
2193    if !rest.contains(':') {
2194        return None;
2195    }
2196    let parts: Vec<&str> = rest.split(':').collect();
2197    if parts.len() < 2 {
2198        return None;
2199    }
2200    let last_idx = parts.len() - 1;
2201    let mut total: f64 = 0.0;
2202    for (idx, part) in parts.iter().enumerate() {
2203        if part.is_empty() {
2204            return None;
2205        }
2206        let last_with_decimal = idx == last_idx && part.contains('.');
2207        if !last_with_decimal && !part.bytes().all(|b| b.is_ascii_digit()) {
2208            return None;
2209        }
2210        let n: f64 = part.parse().ok()?;
2211        if idx > 0 && n >= 60.0 {
2212            return None;
2213        }
2214        total = f64_mul_add(total, 60.0, n);
2215    }
2216    Some(sign * total)
2217}
2218
2219fn parse_integer(s: &str, legacy_octal: bool, lossless_u64: bool) -> Option<ParsedInteger> {
2220    let b = s.as_bytes();
2221    if b.is_empty() {
2222        return None;
2223    }
2224    // Core schema spellings only: `0x[0-9a-fA-F]+` and `0o[0-7]+`.
2225    // An uppercase prefix or a sign after it stays a string.
2226    for (prefix, radix) in [("0x", 16), ("0o", 8)] {
2227        if let Some(digits) = s.strip_prefix(prefix) {
2228            return radix_digits(digits, radix)
2229                .then(|| parse_radix_integer(digits, radix, lossless_u64))
2230                .flatten();
2231        }
2232    }
2233    // YAML 1.1-style bare `0`-prefix octal — only when explicitly
2234    // opted in. The leading `0` must be followed by an octal digit
2235    // (0-7) so we don't misclassify `08` (decimal eight) as an
2236    // invalid octal.
2237    if legacy_octal
2238        && b.len() >= 2
2239        && b[0] == b'0'
2240        && b[1..].iter().all(|&c| c.is_ascii_digit() && c <= b'7')
2241    {
2242        return parse_radix_integer(&s[1..], 8, lossless_u64);
2243    }
2244    let start = usize::from(b[0] == b'+' || b[0] == b'-');
2245    if start >= b.len() {
2246        return None;
2247    }
2248    if b[start..].iter().all(|&c| c.is_ascii_digit()) {
2249        // SIMD-friendly SWAR decimal parse — bit-for-bit equivalent
2250        // to `s.parse::<i64>()` but processes 8 digits per cycle on
2251        // the inner pipeline, beating the stdlib byte-by-byte loop
2252        // on data-heavy workloads (telemetry, IDs, port numbers).
2253        if let Some(n) = crate::simd::parse_decimal_i64(b) {
2254            return Some(ParsedInteger::Signed(n));
2255        }
2256        #[cfg(feature = "lossless-u64")]
2257        if lossless_u64 && b[0] != b'-' {
2258            let digits = if b[0] == b'+' { &b[1..] } else { b };
2259            return crate::simd::parse_decimal_u64(digits).map(ParsedInteger::Unsigned);
2260        }
2261        None
2262    } else {
2263        None
2264    }
2265}
2266
2267/// Non-empty and every character a digit in `radix`: no sign, no
2268/// separator. `from_str_radix` alone would accept a leading `+`/`-`.
2269fn radix_digits(s: &str, radix: u32) -> bool {
2270    !s.is_empty() && s.chars().all(|c| c.is_digit(radix))
2271}
2272
2273fn parse_radix_integer(s: &str, radix: u32, lossless_u64: bool) -> Option<ParsedInteger> {
2274    #[cfg(not(feature = "lossless-u64"))]
2275    let _ = lossless_u64;
2276    if let Ok(n) = i64::from_str_radix(s, radix) {
2277        return Some(ParsedInteger::Signed(n));
2278    }
2279    #[cfg(feature = "lossless-u64")]
2280    if lossless_u64 {
2281        return u64::from_str_radix(s, radix)
2282            .ok()
2283            .map(ParsedInteger::Unsigned);
2284    }
2285    #[allow(unreachable_code)]
2286    None
2287}
2288
2289fn extract_mapping_body(buf: &[BufferedEvent]) -> Option<&[BufferedEvent]> {
2290    if buf.len() < 2
2291        || !matches!(buf.first(), Some(BufferedEvent::MapStart))
2292        || !matches!(buf.last(), Some(BufferedEvent::MapEnd))
2293    {
2294        None
2295    } else {
2296        Some(&buf[1..buf.len() - 1])
2297    }
2298}
2299
2300fn collect_keys(body: &[BufferedEvent], seen: &mut FxHashSet<String>) -> Option<()> {
2301    let mut i = 0;
2302    while i < body.len() {
2303        if let BufferedEvent::Scalar { value, .. } = &body[i] {
2304            let _ = seen.insert(value.clone());
2305        } else {
2306            return None;
2307        }
2308        i += 1;
2309        if i < body.len() {
2310            let len = skip_buffered_value(&body[i..]);
2311            if len == 0 {
2312                return None;
2313            }
2314            i += len;
2315        } else {
2316            return None;
2317        }
2318    }
2319    Some(())
2320}
2321
2322fn skip_buffered_value(slice: &[BufferedEvent]) -> usize {
2323    if slice.is_empty() {
2324        return 0;
2325    }
2326    match &slice[0] {
2327        BufferedEvent::Scalar { .. } | BufferedEvent::Alias { .. } => 1,
2328        BufferedEvent::SeqStart => {
2329            let mut d = 1;
2330            let mut i = 1;
2331            while i < slice.len() && d > 0 {
2332                match &slice[i] {
2333                    BufferedEvent::SeqStart => d += 1,
2334                    BufferedEvent::SeqEnd => d -= 1,
2335                    _ => {}
2336                }
2337                i += 1;
2338            }
2339            i
2340        }
2341        BufferedEvent::MapStart => {
2342            let mut d = 1;
2343            let mut i = 1;
2344            while i < slice.len() && d > 0 {
2345                match &slice[i] {
2346                    BufferedEvent::MapStart => d += 1,
2347                    BufferedEvent::MapEnd => d -= 1,
2348                    _ => {}
2349                }
2350                i += 1;
2351            }
2352            i
2353        }
2354        _ => 1,
2355    }
2356}
2357
2358fn extract_local_keys(buf: &[BufferedEvent]) -> FxHashSet<String> {
2359    let mut keys = FxHashSet::default();
2360    let mut d: usize = 0;
2361    let mut key = true;
2362    for ev in buf {
2363        match ev {
2364            BufferedEvent::Scalar { value, .. } => {
2365                if d == 0 {
2366                    if key {
2367                        let _ = keys.insert(value.clone());
2368                    }
2369                    key = !key;
2370                }
2371            }
2372            BufferedEvent::Alias { .. } => {
2373                if d == 0 {
2374                    key = !key;
2375                }
2376            }
2377            BufferedEvent::MapStart | BufferedEvent::SeqStart => d += 1,
2378            BufferedEvent::MapEnd | BufferedEvent::SeqEnd => {
2379                if d == 1 {
2380                    key = true;
2381                }
2382                d = d.saturating_sub(1);
2383            }
2384        }
2385    }
2386    keys
2387}
2388
2389fn filter_merge_entries(
2390    inner: &[BufferedEvent],
2391    local: &FxHashSet<String>,
2392) -> Option<SmallVec<[BufferedEvent; SMALL_VEC_SIZE]>> {
2393    let mut out = SmallVec::with_capacity(inner.len());
2394    let mut i = 0;
2395    while i < inner.len() {
2396        let key = if let BufferedEvent::Scalar { value, .. } = &inner[i] {
2397            value.clone()
2398        } else {
2399            return None;
2400        };
2401        let start = i;
2402        i += 1;
2403        if i >= inner.len() {
2404            return None;
2405        }
2406        let len = skip_buffered_value(&inner[i..]);
2407        if len == 0 {
2408            return None;
2409        }
2410        let end = i + len;
2411        if !local.contains(&key) {
2412            for ev in &inner[start..end] {
2413                out.push(ev.clone());
2414            }
2415        }
2416        i = end;
2417    }
2418    Some(out)
2419}
2420
2421#[cfg(test)]
2422mod lookahead_tests {
2423    //! Unit coverage for the lookahead slot's two states.
2424    //!
2425    //! The integration tests in `tests/streaming_alias_lookahead.rs` drive
2426    //! this through real documents, which is the behaviour that matters.
2427    //! These pin the type itself, because the whole point of [`Lookahead`]
2428    //! is that a consumer can no longer confuse a raw parser event with a
2429    //! processed one — and a silent change to the accessors would put that
2430    //! confusion straight back.
2431
2432    use super::{Event, Lookahead, ScalarStyle};
2433    use crate::parser::scanner_span_default;
2434
2435    fn scalar(v: &'static str) -> Event<'static> {
2436        Event::Scalar {
2437            value: std::borrow::Cow::Borrowed(v),
2438            style: ScalarStyle::Plain,
2439            anchor: None,
2440            tag: None,
2441            span: scanner_span_default(),
2442        }
2443    }
2444
2445    fn alias(name: &str) -> Event<'static> {
2446        Event::Alias {
2447            anchor: name.to_string(),
2448            span: scanner_span_default(),
2449        }
2450    }
2451
2452    #[test]
2453    fn event_reads_through_both_states() {
2454        let raw = Lookahead::Raw(scalar("a"));
2455        let done = Lookahead::Processed(scalar("a"));
2456        assert!(matches!(raw.event(), Event::Scalar { .. }));
2457        assert!(matches!(done.event(), Event::Scalar { .. }));
2458    }
2459
2460    #[test]
2461    fn event_mut_reads_through_both_states() {
2462        let mut raw = Lookahead::Raw(scalar("a"));
2463        let mut done = Lookahead::Processed(scalar("a"));
2464        assert!(matches!(raw.event_mut(), Event::Scalar { .. }));
2465        assert!(matches!(done.event_mut(), Event::Scalar { .. }));
2466    }
2467
2468    #[test]
2469    fn event_mut_actually_mutates_in_place() {
2470        // `take_tag_from_current` reaches through this to steal a tag, so a
2471        // copy rather than a borrow would silently lose the edit.
2472        let mut slot = Lookahead::Processed(Event::Scalar {
2473            value: std::borrow::Cow::Borrowed("a"),
2474            style: ScalarStyle::Plain,
2475            anchor: None,
2476            tag: Some(("!".into(), "custom".into())),
2477            span: scanner_span_default(),
2478        });
2479        if let Event::Scalar { tag, .. } = slot.event_mut() {
2480            let stolen = tag.take();
2481            assert!(stolen.is_some(), "tag was there to take");
2482        }
2483        match slot.event() {
2484            Event::Scalar { tag, .. } => assert!(tag.is_none(), "the take must stick"),
2485            other => panic!("unexpected event: {other:?}"),
2486        }
2487    }
2488
2489    #[test]
2490    fn the_two_states_are_distinguishable() {
2491        // The entire reason the type exists. Holding the same event both
2492        // ways must still be tellable apart, or the consumers are back to
2493        // guessing and #301 comes with them.
2494        let raw = Lookahead::Raw(alias("n"));
2495        let done = Lookahead::Processed(alias("n"));
2496        assert!(matches!(raw, Lookahead::Raw(_)));
2497        assert!(matches!(done, Lookahead::Processed(_)));
2498        assert!(!matches!(raw, Lookahead::Processed(_)));
2499        assert!(!matches!(done, Lookahead::Raw(_)));
2500    }
2501
2502    #[test]
2503    fn an_alias_can_sit_in_either_state() {
2504        // A Raw alias is what the merge path parks deliberately; a
2505        // Processed one must already have been resolved, so an
2506        // `Event::Alias` wearing the Processed label is the bug #301 was.
2507        // The type cannot enforce that, which is why the resolution now
2508        // lives in one `process_event` rather than at each call site.
2509        assert!(matches!(
2510            Lookahead::Raw(alias("n")).event(),
2511            Event::Alias { .. }
2512        ));
2513        assert!(matches!(
2514            Lookahead::Processed(alias("n")).event(),
2515            Event::Alias { .. }
2516        ));
2517    }
2518
2519    #[test]
2520    fn into_inner_yields_the_event_from_either_state() {
2521        assert!(matches!(
2522            Lookahead::Raw(alias("n")).into_inner(),
2523            Event::Alias { .. }
2524        ));
2525        assert!(matches!(
2526            Lookahead::Processed(scalar("a")).into_inner(),
2527            Event::Scalar { .. }
2528        ));
2529    }
2530
2531    #[test]
2532    fn debug_is_available_for_tracing_the_slot() {
2533        // The #301 diagnosis came from printing this slot; losing `Debug`
2534        // would remove the only way to see a mislabelled event.
2535        let s = format!("{:?}", Lookahead::Raw(alias("n")));
2536        assert!(s.contains("Raw"), "state must be visible: {s}");
2537        assert!(s.contains("Alias"), "event must be visible: {s}");
2538    }
2539}