Skip to main content

serde_saphyr/
lib.rs

1#![forbid(unsafe_code)]
2#![allow(deprecated)]
3
4#[cfg(not(any(feature = "serialize", feature = "deserialize")))]
5compile_error!(
6    "Invalid feature configuration: enable at least one of \
7     \"serialize\" or \"deserialize\"."
8);
9
10#[cfg(feature = "serialize")]
11pub use self::ser::{
12    error as ser_error,
13    options::{CommentPosition, SerializerOptions},
14};
15#[cfg(feature = "deserialize")]
16pub use self::{
17    de::{Budget, DuplicateKeyPolicy, Error, Options, budget, localizer, options},
18    de_error::{
19        CroppedRegion, MessageFormatter, RenderOptions, SnippetMode, TransformReason,
20        UserMessageFormatter,
21    },
22    indentation::RequireIndent,
23    input_source::{
24        IncludeRequest, IncludeResolveError, IncludeResolver, InputSource, ResolveProblem,
25        ResolvedInclude,
26    },
27    localizer::{
28        DEFAULT_ENGLISH_LOCALIZER, DefaultEnglishLocalizer, ExternalMessage, ExternalMessageSource,
29        Localizer,
30    },
31    message_formatters::{DefaultMessageFormatter, DeveloperMessageFormatter},
32};
33pub use anchors::{
34    ArcAnchor, ArcRecursion, ArcRecursive, ArcWeakAnchor, RcAnchor, RcRecursion, RcRecursive,
35    RcWeakAnchor,
36};
37#[cfg(feature = "figment")]
38pub use de::figment;
39#[cfg(feature = "miette")]
40pub use de::miette;
41#[cfg(any(feature = "garde", feature = "validator"))]
42pub use de::path_map;
43#[cfg(feature = "properties")]
44pub use de::properties;
45#[cfg(feature = "robotics")]
46pub use de::robotics;
47#[cfg(all(feature = "deserialize", feature = "include_fs"))]
48pub use de::safe_resolver::{SafeFileReadMode, SafeFileResolver, SymlinkPolicy};
49pub use location::{Location, Locations, Span};
50pub use long_strings::{FoldStr, FoldString, LitStr, LitString};
51pub use spanned::Spanned;
52#[cfg(any(feature = "serialize", feature = "deserialize"))]
53pub use wrappers::{Commented, FlowMap, FlowSeq, SpaceAfter};
54
55#[cfg(all(feature = "deserialize", feature = "include"))]
56pub(crate) fn resolver_from_options<'a>(
57    options: Options,
58) -> Option<Box<crate::input_source::IncludeResolver<'a>>> {
59    options.include_resolver.clone().map(|rc_refcell| {
60        Box::new(move |req: crate::input_source::IncludeRequest<'_>| rc_refcell.borrow_mut()(req))
61            as Box<crate::input_source::IncludeResolver<'a>>
62    })
63}
64
65#[cfg(feature = "deserialize")]
66use crate::budget::EnforcingPolicy;
67#[cfg(feature = "deserialize")]
68use crate::de::{Ev, Events};
69#[cfg(feature = "deserialize")]
70use crate::live_events::LiveEvents;
71#[cfg(feature = "deserialize")]
72use crate::parse_scalars::scalar_is_nullish;
73#[cfg(feature = "deserialize")]
74use crate::properties_redaction::with_interp_redaction_scope;
75#[cfg(feature = "deserialize")]
76use serde::de::DeserializeOwned;
77#[cfg(feature = "deserialize")]
78use std::io::Read;
79
80#[cfg(feature = "deserialize")]
81#[path = "de/anchor_store.rs"]
82mod anchor_store;
83mod anchors;
84#[cfg(feature = "deserialize")]
85#[path = "de/mod.rs"]
86mod de;
87mod long_strings;
88#[path = "de/parse_scalars.rs"]
89mod parse_scalars;
90#[cfg(feature = "serialize")]
91#[path = "ser/mod.rs"]
92pub mod ser;
93mod spanned;
94#[cfg(any(feature = "serialize", feature = "deserialize"))]
95mod wrappers;
96
97#[cfg(all(feature = "deserialize", feature = "include"))]
98pub(crate) use de::include_stack;
99#[cfg(any(feature = "garde", feature = "validator"))]
100use de::lib_validate;
101#[cfg(feature = "deserialize")]
102pub(crate) use de::{
103    buffered_input, error as de_error, include, indentation, input_source, live_events,
104    message_formatters, properties_redaction, ring_reader, snippet as de_snippet, tags,
105};
106
107#[cfg(feature = "deserialize")]
108pub use de::YamlDeserializer as Deserializer;
109#[cfg(any(feature = "garde", feature = "validator"))]
110pub use lib_validate::*;
111#[cfg(feature = "serialize")]
112pub use ser::YamlSerializer as Serializer;
113
114#[cfg(feature = "deserialize")]
115pub use de::{
116    with_deserializer_from_reader, with_deserializer_from_reader_with_options,
117    with_deserializer_from_slice, with_deserializer_from_slice_with_options,
118    with_deserializer_from_str, with_deserializer_from_str_with_options,
119};
120
121#[path = "de/location.rs"]
122mod location;
123mod macros;
124// ---------------- Serialization (public API) ----------------
125
126/// Serialize a value to a YAML `String`.
127///
128/// This is the easiest entry point when you just want a YAML string.
129///
130/// Example
131///
132/// ```rust
133/// use serde::Serialize;
134///
135/// #[derive(Serialize)]
136/// struct Foo { a: i32, b: bool }
137///
138/// let s = serde_saphyr::to_string(&Foo { a: 1, b: true }).unwrap();
139/// assert!(s.contains("a: 1"));
140/// ```
141#[cfg(feature = "serialize")]
142pub fn to_string<T: serde::Serialize>(value: &T) -> std::result::Result<String, crate::ser::Error> {
143    let mut out = String::new();
144    to_fmt_writer(&mut out, value)?;
145    Ok(out)
146}
147
148/// Serialize a value to a YAML `String`, with [`SerializerOptions`].
149///
150/// This is like [`to_string`], but lets you control formatting and serialization
151/// behavior through the provided `options`.
152///
153/// Example
154///
155/// ```rust
156/// use serde::Serialize;
157/// use serde_saphyr::SerializerOptions;
158///
159/// #[derive(Serialize)]
160/// struct Foo { a: i32, b: bool }
161///
162/// let options = SerializerOptions::default();
163/// let s = serde_saphyr::to_string_with_options(&Foo { a: 1, b: true }, options).unwrap();
164/// assert!(s.contains("a: 1"));
165/// ```
166#[cfg(feature = "serialize")]
167pub fn to_string_with_options<T: serde::Serialize>(
168    value: &T,
169    options: SerializerOptions,
170) -> std::result::Result<String, crate::ser::Error> {
171    let mut out = String::new();
172    to_fmt_writer_with_options(&mut out, value, options)?;
173    Ok(out)
174}
175
176/// Deprecated: use `to_fmt_writer` or `to_io_writer`
177/// Kept for a transition release to avoid instant breakage.
178#[deprecated(
179    since = "0.0.7",
180    note = "Use `to_fmt_writer` for `fmt::Write` (String, fmt::Formatter) or `to_io_writer` for files/sockets."
181)]
182#[cfg(feature = "serialize")]
183pub fn to_writer<W: std::fmt::Write, T: serde::Serialize>(
184    output: &mut W,
185    value: &T,
186) -> std::result::Result<(), crate::ser::Error> {
187    let mut ser = crate::ser::YamlSerializer::new(output);
188    value.serialize(&mut ser)
189}
190
191/// Serialize a value as YAML into any [`fmt::Write`] target.
192#[cfg(feature = "serialize")]
193pub fn to_fmt_writer<W: std::fmt::Write, T: serde::Serialize>(
194    output: &mut W,
195    value: &T,
196) -> std::result::Result<(), crate::ser::Error> {
197    to_fmt_writer_with_options(output, value, SerializerOptions::default())
198}
199
200/// Serialize a value as YAML into any [`io::Write`] target.
201#[cfg(feature = "serialize")]
202pub fn to_io_writer<W: std::io::Write, T: serde::Serialize>(
203    output: &mut W,
204    value: &T,
205) -> std::result::Result<(), crate::ser::Error> {
206    to_io_writer_with_options(output, value, SerializerOptions::default())
207}
208
209/// Serialize a value as YAML into any [`fmt::Write`] target, with options.
210/// Options are consumed because anchor generator may be taken from them.
211#[cfg(feature = "serialize")]
212pub fn to_fmt_writer_with_options<W: std::fmt::Write, T: serde::Serialize>(
213    output: &mut W,
214    value: &T,
215    mut options: SerializerOptions,
216) -> std::result::Result<(), crate::ser::Error> {
217    options.consistent()?;
218    let mut ser = crate::ser::YamlSerializer::with_options(output, &mut options);
219    value.serialize(&mut ser)
220}
221
222/// Serialize a value as YAML into any [`io::Write`] target, with options.
223/// Options are consumed because anchor generator may be taken from them.
224#[cfg(feature = "serialize")]
225pub fn to_io_writer_with_options<W: std::io::Write, T: serde::Serialize>(
226    output: &mut W,
227    value: &T,
228    mut options: SerializerOptions,
229) -> std::result::Result<(), crate::ser::Error> {
230    options.consistent()?;
231    struct Adapter<'a, W: std::io::Write> {
232        output: &'a mut W,
233        last_err: Option<std::io::Error>,
234    }
235    impl<'a, W: std::io::Write> std::fmt::Write for Adapter<'a, W> {
236        fn write_str(&mut self, s: &str) -> std::fmt::Result {
237            if let Err(e) = self.output.write_all(s.as_bytes()) {
238                self.last_err = Some(e);
239                return Err(std::fmt::Error);
240            }
241            Ok(())
242        }
243        fn write_char(&mut self, c: char) -> std::fmt::Result {
244            let mut buf = [0u8; 4];
245            let s = c.encode_utf8(&mut buf);
246            self.write_str(s)
247        }
248    }
249    let mut adapter = Adapter {
250        output,
251        last_err: None,
252    };
253    let mut ser = crate::ser::YamlSerializer::with_options(&mut adapter, &mut options);
254    match value.serialize(&mut ser) {
255        Ok(()) => Ok(()),
256        Err(e) => {
257            if let Some(io_error) = adapter.last_err.take() {
258                return Err(crate::ser::Error::from(io_error));
259            }
260            Err(e)
261        }
262    }
263}
264
265/// Deprecated: use `to_fmt_writer_with_options` for `fmt::Write` or `to_io_writer_with_options` for `io::Write`.
266#[deprecated(
267    since = "0.0.7",
268    note = "Use `to_fmt_writer_with_options` for fmt::Write or `to_io_writer_with_options` for io::Write."
269)]
270#[cfg(feature = "serialize")]
271pub fn to_writer_with_options<W: std::fmt::Write, T: serde::Serialize>(
272    output: &mut W,
273    value: &T,
274    options: SerializerOptions,
275) -> std::result::Result<(), crate::ser::Error> {
276    to_fmt_writer_with_options(output, value, options)
277}
278
279/// Deserialize any `T: serde::de::Deserialize<'de>` directly from a YAML string.
280///
281/// This is the simplest entry point; it parses a single YAML document. If the
282/// input contains multiple documents, this returns an error advising to use
283/// [`from_multiple`] or [`from_multiple_with_options`].
284///
285/// This function supports both owned types (like `String`) and borrowed types
286/// (like `&str`). For borrowed types, the deserialized value's lifetime is tied
287/// to the input string's lifetime.
288///
289/// **Note**: Borrowing only works for simple plain scalars that don't require
290/// any transformation (no multi-line folding, no escape processing). For
291/// transformed strings, deserialization to `&str` will fail with a helpful
292/// error message suggesting to use `String` or `Cow<str>` instead.
293///
294/// Example: read a small `Config` structure from a YAML string.
295///
296/// ```rust
297/// use serde::Deserialize;
298///
299/// #[derive(Debug, Deserialize, PartialEq)]
300/// struct Config {
301///     name: String,
302///     enabled: bool,
303///     retries: i32,
304/// }
305///
306/// let yaml = r#"
307///     name: My Application
308///     enabled: true
309///     retries: 5
310/// "#;
311///
312/// let cfg: Config = serde_saphyr::from_str(yaml).unwrap();
313/// assert!(cfg.enabled);
314/// ```
315///
316/// Example: read a structure with borrowed string fields.
317///
318/// Borrowed strings are supported when deserializing from an in-memory input (`from_str` / `from_slice`),
319/// and only when the scalar exists verbatim in the input (i.e., no escape processing, folding, or other
320/// normalization is required). If the YAML scalar requires transformation, deserializing into `&str`
321/// fails with an error suggesting `String` or `Cow<str>`.
322///
323/// Note: reader-based entry points like [`from_reader`] require `DeserializeOwned` and therefore cannot
324/// return values that borrow from the input.
325///
326/// ```rust
327/// use serde::Deserialize;
328///
329/// #[derive(Debug, Deserialize, PartialEq)]
330/// struct Data<'a> {
331///     name: &'a str,
332///     value: i32,
333/// }
334///
335/// let yaml = "name: hello\nvalue: 42\n";
336///
337/// let data: Data = serde_saphyr::from_str(yaml).unwrap();
338/// assert_eq!(data.name, "hello");
339/// assert_eq!(data.value, 42);
340/// ```
341#[cfg(feature = "deserialize")]
342pub fn from_str<'de, T>(input: &'de str) -> Result<T, Error>
343where
344    T: serde::de::Deserialize<'de>,
345{
346    from_str_with_options(input, Options::default())
347}
348
349#[allow(deprecated)]
350#[cfg(feature = "deserialize")]
351fn from_str_with_options_impl<'de, T>(input: &'de str, options: Options) -> Result<T, Error>
352where
353    T: serde::de::Deserialize<'de>,
354{
355    // Normalize: ignore a single leading UTF-8 BOM if present.
356    let input = if let Some(rest) = input.strip_prefix('\u{FEFF}') {
357        rest
358    } else {
359        input
360    };
361
362    let with_snippet = options.with_snippet;
363    let crop_radius = options.crop_radius;
364
365    let cfg = crate::de::Cfg::from_options(&options);
366    // Do not stop at DocumentEnd; we'll probe for trailing content/errors explicitly.
367    let mut src = LiveEvents::from_str(input, options, false);
368    let value_res = crate::anchor_store::with_document_scope(|| {
369        with_interp_redaction_scope(|| {
370            crate::de::with_root_redaction(crate::de::YamlDeserializer::new(&mut src, cfg), |de| {
371                T::deserialize(de)
372            })
373        })
374    });
375    let value = match value_res {
376        Ok(v) => v,
377        Err(e) => {
378            if src.synthesized_null_emitted() {
379                let err = Error::eof().with_location(src.last_location());
380                return Err(maybe_with_snippet_from_events(
381                    err,
382                    input,
383                    &src,
384                    with_snippet,
385                    crop_radius,
386                ));
387            } else {
388                return Err(maybe_with_snippet_from_events(
389                    e,
390                    input,
391                    &src,
392                    with_snippet,
393                    crop_radius,
394                ));
395            }
396        }
397    };
398
399    match src.peek() {
400        Ok(Some(_)) => {
401            let err = Error::multiple_documents("use from_multiple or from_multiple_with_options")
402                .with_location(src.last_location());
403            return Err(maybe_with_snippet_from_events(
404                err,
405                input,
406                &src,
407                with_snippet,
408                crop_radius,
409            ));
410        }
411        Ok(None) => {}
412        Err(e) => {
413            if src.seen_doc_end() {
414                // Trailing garbage after a proper document end marker is ignored.
415            } else {
416                return Err(maybe_with_snippet_from_events(
417                    e,
418                    input,
419                    &src,
420                    with_snippet,
421                    crop_radius,
422                ));
423            }
424        }
425    }
426
427    if let Err(e) = src.finish() {
428        return Err(maybe_with_snippet_from_events(
429            e,
430            input,
431            &src,
432            with_snippet,
433            crop_radius,
434        ));
435    }
436    Ok(value)
437}
438
439/// Deserialize a single YAML document with configurable [`Options`].
440///
441/// This function supports both owned types (like `String`) and borrowed types
442/// (like `&str`). For borrowed types, the deserialized value's lifetime is tied
443/// to the input string's lifetime.
444///
445/// Example: read a small `Config` with a custom budget and default duplicate-key policy.
446///
447/// ```rust
448/// use serde::Deserialize;
449/// use serde_saphyr::DuplicateKeyPolicy;
450///
451/// #[derive(Debug, Deserialize, PartialEq)]
452/// struct Config {
453///     name: String,
454///     enabled: bool,
455///     retries: i32,
456/// }
457///
458/// let yaml = r#"
459///      name: My Application
460///      enabled: true
461///      retries: 5
462/// "#;
463///
464/// let options = serde_saphyr::options! {
465///     budget: serde_saphyr::budget! {
466///         max_anchors: 200,
467///     },
468///     duplicate_keys: DuplicateKeyPolicy::FirstWins,
469/// };
470/// let cfg: Config = serde_saphyr::from_str_with_options(yaml, options).unwrap();
471/// assert_eq!(cfg.retries, 5);
472/// ```
473#[allow(deprecated)]
474#[cfg(feature = "deserialize")]
475pub fn from_str_with_options<'de, T>(input: &'de str, options: Options) -> Result<T, Error>
476where
477    T: serde::de::Deserialize<'de>,
478{
479    from_str_with_options_impl(input, options)
480}
481
482#[cfg(feature = "deserialize")]
483pub(crate) fn maybe_with_snippet(
484    err: Error,
485    input: &str,
486    with_snippet: bool,
487    crop_radius: usize,
488) -> Error {
489    if !(with_snippet && crop_radius > 0 && err.location().is_some()) {
490        return err;
491    }
492
493    err.with_snippet(input, crop_radius)
494}
495
496#[cfg(feature = "deserialize")]
497pub(crate) struct RootFragment<'a> {
498    pub text: &'a str,
499    pub start_line: usize,
500    pub source_name: &'a str,
501}
502
503#[cfg(feature = "deserialize")]
504struct ReaderSnippetContext<R> {
505    shared_ring: ring_reader::SharedRingReader<R>,
506    with_snippet: bool,
507    crop_radius: usize,
508}
509
510#[cfg(feature = "deserialize")]
511impl<R: Read> ReaderSnippetContext<R> {
512    fn new(
513        reader: R,
514        with_snippet: bool,
515        crop_radius: usize,
516    ) -> (Self, ring_reader::SharedRingReaderHandle<R>) {
517        let shared_ring = ring_reader::SharedRingReader::new(reader);
518        let ring_handle = ring_reader::SharedRingReaderHandle::new(&shared_ring);
519        (
520            Self {
521                shared_ring,
522                with_snippet,
523                crop_radius,
524            },
525            ring_handle,
526        )
527    }
528
529    fn attach_snippet(&self, err: Error, src: &LiveEvents<'_>) -> Error {
530        if !self.with_snippet || self.crop_radius == 0 {
531            return err;
532        }
533
534        match self.shared_ring.get_recent() {
535            Ok(snapshot) => {
536                let text = String::from_utf8_lossy(&snapshot.bytes);
537                let root = RootFragment {
538                    text: text.as_ref(),
539                    start_line: snapshot.start_line,
540                    source_name: "input",
541                };
542                maybe_with_snippet_from_events_and_root_fragment(
543                    err,
544                    Some(&root),
545                    text.as_ref(),
546                    src,
547                    self.with_snippet,
548                    self.crop_radius,
549                )
550            }
551            Err(_) => err,
552        }
553    }
554}
555
556#[cfg(all(feature = "deserialize", feature = "include"))]
557fn with_root_additional_snippet(
558    err: Error,
559    root: Option<&RootFragment<'_>>,
560    input: &str,
561    location: &crate::Location,
562    crop_radius: usize,
563) -> Error {
564    match root {
565        Some(root) => err.with_additional_snippet_offset_named(
566            root.text,
567            root.start_line,
568            root.source_name,
569            location,
570            crop_radius,
571        ),
572        None => err.with_additional_snippet_named(input, "input", location, crop_radius),
573    }
574}
575
576#[cfg(all(feature = "deserialize", feature = "include"))]
577fn recorded_source_snippet_chain<'a>(
578    events: &'a crate::live_events::LiveEvents<'_>,
579    location: &crate::Location,
580) -> Option<Vec<&'a crate::include_stack::RecordedSource>> {
581    let chain = events.recorded_source_chain(location.source_id());
582    chain.first()?.text.as_deref()?;
583    Some(chain)
584}
585
586#[cfg(all(feature = "deserialize", feature = "include"))]
587fn with_recorded_source_snippets(
588    err: Error,
589    root: Option<&RootFragment<'_>>,
590    input: &str,
591    chain: &[&crate::include_stack::RecordedSource],
592    crop_radius: usize,
593) -> Error {
594    let current = chain[0];
595    let source_text = current
596        .text
597        .as_deref()
598        .expect("recorded source snippet chain must start with text-backed source");
599    let mut err_with_snippet =
600        err.with_snippet_named(source_text, current.name.as_str(), crop_radius);
601
602    for window in chain.windows(2) {
603        let child = window[0];
604        let parent = window[1];
605        if child.include_location == crate::Location::UNKNOWN {
606            continue;
607        }
608
609        match parent.text.as_deref() {
610            Some(parent_text) => {
611                err_with_snippet = err_with_snippet.with_additional_snippet_named(
612                    parent_text,
613                    parent.name.as_str(),
614                    &child.include_location,
615                    crop_radius,
616                );
617            }
618            None if parent.parent_source_id.is_none() => {
619                err_with_snippet = with_root_additional_snippet(
620                    err_with_snippet,
621                    root,
622                    input,
623                    &child.include_location,
624                    crop_radius,
625                );
626            }
627            None => {}
628        }
629    }
630    err_with_snippet
631}
632
633#[cfg(feature = "deserialize")]
634pub(crate) fn maybe_with_snippet_from_events_and_root_fragment(
635    err: Error,
636    root: Option<&RootFragment<'_>>,
637    input: &str,
638    #[allow(unused_variables)] events: &crate::live_events::LiveEvents<'_>,
639    with_snippet: bool,
640    crop_radius: usize,
641) -> Error {
642    if !(with_snippet && crop_radius > 0 && err.location().is_some()) {
643        return err;
644    }
645
646    #[cfg(feature = "include")]
647    if let Some(loc) = err.location()
648        && let Some(chain) = recorded_source_snippet_chain(events, &loc)
649    {
650        return with_recorded_source_snippets(err, root, input, &chain, crop_radius);
651    }
652
653    match root {
654        Some(root) => {
655            err.with_snippet_offset_named(root.text, root.start_line, root.source_name, crop_radius)
656        }
657        None => maybe_with_snippet(err, input, with_snippet, crop_radius),
658    }
659}
660
661#[cfg(feature = "deserialize")]
662pub(crate) fn maybe_with_snippet_from_events(
663    err: Error,
664    input: &str,
665    #[allow(unused_variables)] events: &crate::live_events::LiveEvents<'_>,
666    with_snippet: bool,
667    crop_radius: usize,
668) -> Error {
669    maybe_with_snippet_from_events_and_root_fragment(
670        err,
671        None,
672        input,
673        events,
674        with_snippet,
675        crop_radius,
676    )
677}
678
679/// Deserialize multiple YAML documents from a single string into a vector of `T`.
680/// Completely empty documents are ignored and not included into returned vector.
681///
682/// Example: read two `Config` documents separated by `---`.
683///
684/// ```rust
685/// use serde::Deserialize;
686///
687/// #[derive(Debug, Deserialize, PartialEq)]
688/// struct Config {
689///     name: String,
690///     enabled: bool,
691///     retries: i32,
692/// }
693///
694/// let yaml = r#"
695/// name: First
696/// enabled: true
697/// retries: 1
698/// ---
699/// name: Second
700/// enabled: false
701/// retries: 2
702/// "#;
703///
704/// let cfgs: Vec<Config> = serde_saphyr::from_multiple(yaml).unwrap();
705/// assert_eq!(cfgs.len(), 2);
706/// assert_eq!(cfgs[0].name, "First");
707/// ```
708#[cfg(feature = "deserialize")]
709pub fn from_multiple<T: DeserializeOwned>(input: &str) -> Result<Vec<T>, Error> {
710    from_multiple_with_options(input, Options::default())
711}
712
713/// Deserialize multiple YAML documents into a vector with configurable [`Options`].
714///
715/// Example: two `Config` documents with a custom budget.
716///
717/// ```rust
718/// use serde::Deserialize;
719/// use serde_saphyr::DuplicateKeyPolicy;
720///
721/// #[derive(Debug, Deserialize, PartialEq)]
722/// struct Config {
723///     name: String,
724///     enabled: bool,
725///     retries: i32,
726/// }
727///
728/// let yaml = r#"
729/// name: First
730/// enabled: true
731/// retries: 1
732/// ---
733/// name: Second
734/// enabled: false
735/// retries: 2
736/// "#;
737///
738/// let options = serde_saphyr::options! {
739///     budget: serde_saphyr::budget! {
740///         max_anchors: 200,
741///     },
742///     duplicate_keys: DuplicateKeyPolicy::FirstWins,
743/// };
744/// let cfgs: Vec<Config> = serde_saphyr::from_multiple_with_options(yaml, options).unwrap();
745/// assert_eq!(cfgs.len(), 2);
746/// assert!(!cfgs[1].enabled);
747/// ```
748#[allow(deprecated)]
749#[cfg(feature = "deserialize")]
750pub fn from_multiple_with_options<T: DeserializeOwned>(
751    input: &str,
752    options: Options,
753) -> Result<Vec<T>, Error> {
754    // Normalize: ignore a single leading UTF-8 BOM if present.
755    let input = if let Some(rest) = input.strip_prefix('\u{FEFF}') {
756        rest
757    } else {
758        input
759    };
760    let with_snippet = options.with_snippet;
761    let crop_radius = options.crop_radius;
762
763    let cfg = crate::de::Cfg::from_options(&options);
764    let mut src = LiveEvents::from_str(input, options, false);
765    let mut values = Vec::new();
766
767    loop {
768        match src.peek()? {
769            // Skip documents that are explicit null-like scalars ("", "~", or "null").
770            Some(Ev::Scalar {
771                value: s,
772                style,
773                tag,
774                ..
775            }) if *tag == crate::tags::SfTag::Null
776                || (*tag != crate::tags::SfTag::String && scalar_is_nullish(s, style)) =>
777            {
778                let _ = src.next()?; // consume the null scalar document
779                // Do not push anything for this document; move to the next one.
780                continue;
781            }
782            Some(_) => {
783                let value_res = crate::anchor_store::with_document_scope(|| {
784                    with_interp_redaction_scope(|| {
785                        crate::de::with_root_redaction(
786                            crate::de::YamlDeserializer::new(&mut src, cfg),
787                            |de| T::deserialize(de),
788                        )
789                    })
790                });
791                let value = match value_res {
792                    Ok(v) => v,
793                    Err(e) => {
794                        return Err(maybe_with_snippet_from_events(
795                            e,
796                            input,
797                            &src,
798                            with_snippet,
799                            crop_radius,
800                        ));
801                    }
802                };
803                values.push(value);
804            }
805            None => break,
806        }
807    }
808
809    if let Err(e) = src.finish() {
810        return Err(maybe_with_snippet_from_events(
811            e,
812            input,
813            &src,
814            with_snippet,
815            crop_radius,
816        ));
817    }
818    Ok(values)
819}
820
821/// Deserialize a single YAML document from a UTF-8 byte slice.
822///
823/// This is equivalent to [`from_str`], but accepts `&[u8]` and validates it is
824/// valid UTF-8 before parsing.
825///
826/// Example: read a small `Config` structure from bytes.
827///
828/// ```rust
829/// use serde::Deserialize;
830///
831/// #[derive(Debug, Deserialize, PartialEq)]
832/// struct Config {
833///     name: String,
834///     enabled: bool,
835///     retries: i32,
836/// }
837///
838/// let yaml = r#"
839/// name: My Application
840/// enabled: true
841/// retries: 5
842/// "#;
843/// let bytes = yaml.as_bytes();
844/// let cfg: Config = serde_saphyr::from_slice(bytes).unwrap();
845/// assert!(cfg.enabled);
846/// ```
847///
848#[cfg(feature = "deserialize")]
849pub fn from_slice<T: DeserializeOwned>(bytes: &[u8]) -> Result<T, Error> {
850    from_slice_with_options(bytes, Options::default())
851}
852
853/// Deserialize a single YAML document from a UTF-8 byte slice with configurable [`Options`].
854///
855/// Example: read a small `Config` with a custom budget from bytes.
856///
857/// ```rust
858/// use serde::Deserialize;
859/// use serde_saphyr::DuplicateKeyPolicy;
860///
861/// #[derive(Debug, Deserialize, PartialEq)]
862/// struct Config {
863///     name: String,
864///     enabled: bool,
865///     retries: i32,
866/// }
867///
868/// let yaml = r#"
869///      name: My Application
870///      enabled: true
871///      retries: 5
872/// "#;
873/// let bytes = yaml.as_bytes();
874/// let options = serde_saphyr::options! {
875///     budget: serde_saphyr::budget! {
876///         max_anchors: 200,
877///     },
878///     duplicate_keys: DuplicateKeyPolicy::FirstWins,
879/// };
880/// let cfg: Config = serde_saphyr::from_slice_with_options(bytes, options).unwrap();
881/// assert_eq!(cfg.retries, 5);
882/// ```
883#[cfg(feature = "deserialize")]
884pub fn from_slice_with_options<'de, T>(bytes: &'de [u8], options: Options) -> Result<T, Error>
885where
886    T: serde::Deserialize<'de>,
887{
888    let s = std::str::from_utf8(bytes).map_err(|_| Error::InvalidUtf8Input)?;
889    from_str_with_options(s, options)
890}
891
892/// Deserialize multiple YAML documents from a UTF-8 byte slice into a vector of `T`.
893///
894/// Example: read two `Config` documents separated by `---` from bytes.
895///
896/// ```rust
897/// use serde::Deserialize;
898///
899/// #[derive(Debug, Deserialize, PartialEq)]
900/// struct Config {
901///     name: String,
902///     enabled: bool,
903///     retries: i32,
904/// }
905///
906/// let yaml = r#"
907/// name: First
908/// enabled: true
909/// retries: 1
910/// ---
911/// name: Second
912/// enabled: false
913/// retries: 2
914/// "#;
915/// let bytes = yaml.as_bytes();
916/// let cfgs: Vec<Config> = serde_saphyr::from_slice_multiple(bytes).unwrap();
917/// assert_eq!(cfgs.len(), 2);
918/// assert_eq!(cfgs[0].name, "First");
919/// ```
920#[cfg(feature = "deserialize")]
921pub fn from_slice_multiple<T: DeserializeOwned>(bytes: &[u8]) -> Result<Vec<T>, Error> {
922    from_slice_multiple_with_options(bytes, Options::default())
923}
924
925/// Deserialize multiple YAML documents from bytes with configurable [`Options`].
926/// Completely empty documents are ignored and not included into returned vector.
927///
928/// Example: two `Config` documents with a custom budget from bytes.
929///
930/// ```rust
931/// use serde::Deserialize;
932/// use serde_saphyr::DuplicateKeyPolicy;
933///
934/// #[derive(Debug, Deserialize, PartialEq)]
935/// struct Config {
936///     name: String,
937///     enabled: bool,
938///     retries: i32,
939/// }
940///
941/// let yaml = r#"
942/// name: First
943/// enabled: true
944/// retries: 1
945/// ---
946/// name: Second
947/// enabled: false
948/// retries: 2
949/// "#;
950/// let bytes = yaml.as_bytes();
951/// let options = serde_saphyr::options! {
952///     budget: serde_saphyr::budget! {
953///         max_anchors: 200,
954///     },
955///     duplicate_keys: DuplicateKeyPolicy::FirstWins,
956/// };
957/// let cfgs: Vec<Config> = serde_saphyr::from_slice_multiple_with_options(bytes, options).unwrap();
958/// assert_eq!(cfgs.len(), 2);
959/// assert!(!cfgs[1].enabled);
960/// ```
961#[cfg(feature = "deserialize")]
962pub fn from_slice_multiple_with_options<T: DeserializeOwned>(
963    bytes: &[u8],
964    options: Options,
965) -> Result<Vec<T>, Error> {
966    let s = std::str::from_utf8(bytes).map_err(|_| Error::InvalidUtf8Input)?;
967    from_multiple_with_options(s, options)
968}
969
970/// Serialize multiple documents into a YAML string.
971///
972/// Serializes each value in the provided slice as an individual YAML document.
973/// Documents are separated by a standard YAML document start marker ("---\n").
974/// No marker is emitted before the first document.
975///
976/// Example
977///
978/// ```rust
979/// use serde::Serialize;
980///
981/// #[derive(Serialize)]
982/// struct Point { x: i32 }
983///
984/// let docs = vec![Point { x: 1 }, Point { x: 2 }];
985/// let out = serde_saphyr::to_string_multiple(&docs).unwrap();
986/// assert_eq!(out, "x: 1\n---\nx: 2\n");
987/// ```
988#[cfg(feature = "serialize")]
989pub fn to_string_multiple<T: serde::Serialize>(
990    values: &[T],
991) -> std::result::Result<String, crate::ser::Error> {
992    to_string_multiple_with_options(values, SerializerOptions::default())
993}
994
995/// Serialize multiple documents into a YAML string with configurable `Options`.
996///
997/// Serializes each value in the provided slice as an individual YAML document.
998/// Documents are separated by a standard YAML document start marker ("---\n").
999/// No marker is emitted before the first document.
1000///
1001/// Example
1002///
1003/// ```rust
1004/// use serde::Serialize;
1005///
1006/// #[derive(Serialize)]
1007/// struct Point { coords: Vec<i32> }
1008///
1009/// let docs = vec![Point { coords: vec![0,1] }, Point { coords: vec![3,2] }];
1010/// let options = serde_saphyr::ser_options! {
1011///     indent_step: 2,
1012///     compact_list_indent: true
1013/// };
1014/// let out = serde_saphyr::to_string_multiple_with_options(&docs, options).unwrap();
1015/// assert_eq!(out, "coords:\n- 0\n- 1\n---\ncoords:\n- 3\n- 2\n");
1016/// ```
1017#[cfg(feature = "serialize")]
1018pub fn to_string_multiple_with_options<T: serde::Serialize>(
1019    values: &[T],
1020    options: SerializerOptions,
1021) -> std::result::Result<String, crate::ser::Error> {
1022    let mut out = String::new();
1023    let mut first = true;
1024    for v in values {
1025        if !first {
1026            out.push_str("---\n");
1027        }
1028        first = false;
1029        to_fmt_writer_with_options(&mut out, v, options)?;
1030    }
1031    Ok(out)
1032}
1033
1034/// Deserialize a single YAML document from any `std::io::Read`.
1035///
1036///
1037/// This method parsers as it reads, without loading the entire input into memory first. Hence,
1038/// budget limits protect against large (potentially malicious) input.
1039///
1040/// Example
1041///
1042/// ```rust
1043/// use serde::{Deserialize, Serialize};
1044/// use std::collections::HashMap;
1045/// use serde_json::Value;
1046///
1047/// #[derive(Debug, PartialEq, Serialize, Deserialize)]
1048/// struct Point {
1049///     x: i32,
1050///     y: i32,
1051/// }
1052///
1053/// let yaml = "x: 3\ny: 4\n";
1054/// let reader = std::io::Cursor::new(yaml.as_bytes());
1055/// let p: Point = serde_saphyr::from_reader(reader).unwrap();
1056/// assert_eq!(p, Point { x: 3, y: 4 });
1057///
1058/// // It also works for dynamic values like serde_json::Value
1059/// let mut big = String::new();
1060/// let mut i = 0usize;
1061/// while big.len() < 64 * 1024 { big.push_str(&format!("k{0}: v{0}\n", i)); i += 1; }
1062/// let reader = std::io::Cursor::new(big.as_bytes().to_owned());
1063/// let _value: Value = serde_saphyr::from_reader(reader).unwrap();
1064/// ```
1065#[cfg(feature = "deserialize")]
1066pub fn from_reader<'a, R: std::io::Read + 'a, T: DeserializeOwned>(reader: R) -> Result<T, Error> {
1067    from_reader_with_options(reader, Options::default())
1068}
1069
1070/// Deserialize a single YAML document from any `std::io::Read` with configurable `Options`.
1071///
1072/// This is the reader-based counterpart to [`from_str_with_options`]. It consumes a
1073/// byte-oriented reader, decodes it to UTF-8, and streams events into the deserializer.
1074///
1075/// This method parsers as it reads, without loading the entire input into memory first. Hence,
1076/// budget limits protect against large (potentially malicious) input.
1077///
1078/// Notes on limits and large inputs
1079/// - Parsing limits: Use [`Options::budget`] to constrain YAML complexity (events, nodes,
1080///   nesting depth, total scalar bytes, number of documents, anchors, aliases, etc.). These
1081///   limits are enforced during parsing and are enabled by default via `Options::default()`.
1082/// - Byte-level input cap: from_slice_multiple hard cap on input bytes is enforced via `Options::budget.max_reader_input_bytes`.
1083///   The default budget sets this to 256 MiB. You can override it by customizing `Options::budget`.
1084///   When the cap is exceeded, deserialization fails early with a budget error.
1085///
1086/// Example: limit raw input bytes and customize options
1087/// ```rust
1088/// use std::io::{Read, Cursor};
1089/// use serde::Deserialize;
1090/// use serde_saphyr::{Budget, Options};
1091///
1092/// #[derive(Debug, Deserialize, PartialEq)]
1093/// struct Point { x: i32, y: i32 }
1094///
1095/// let yaml = "x: 3\ny: 4\n";
1096/// let reader = Cursor::new(yaml.as_bytes());
1097///
1098/// let opts = serde_saphyr::options! {
1099///     budget: serde_saphyr::budget! {
1100///         max_events: 10_000,
1101///         max_reader_input_bytes: Some(1024),
1102///     },
1103/// };
1104///
1105/// let p: Point = serde_saphyr::from_reader_with_options(reader, opts).unwrap();
1106/// assert_eq!(p, Point { x: 3, y: 4 });
1107/// ```
1108///
1109/// Error behavior
1110/// - If an empty document is provided (no content), a type-mismatch (eof) error is returned when
1111///   attempting to deserialize into non-null-like targets.
1112/// - If the reader contains multiple documents, an error is returned suggesting the
1113///   `read`/`read_with_options` iterator APIs.
1114/// - If `Options::budget` is set and a limit is exceeded, an error is returned early.
1115#[allow(deprecated)]
1116#[cfg(feature = "deserialize")]
1117pub fn from_reader_with_options<'a, R: std::io::Read + 'a, T: DeserializeOwned>(
1118    reader: R,
1119    options: Options,
1120) -> Result<T, Error> {
1121    let cfg = crate::de::Cfg::from_options(&options);
1122    let (snippet_ctx, ring_handle) =
1123        ReaderSnippetContext::new(reader, options.with_snippet, options.crop_radius);
1124
1125    let mut src = LiveEvents::from_reader(ring_handle, options, false, EnforcingPolicy::AllContent);
1126
1127    let value_res = crate::anchor_store::with_document_scope(|| {
1128        with_interp_redaction_scope(|| {
1129            crate::de::with_root_redaction(crate::de::YamlDeserializer::new(&mut src, cfg), |de| {
1130                T::deserialize(de)
1131            })
1132        })
1133    });
1134    let value = match value_res {
1135        Ok(v) => v,
1136        Err(e) => {
1137            if src.synthesized_null_emitted() {
1138                // If the only thing in the input was an empty document (synthetic null),
1139                // surface this as an EOF error to preserve expected error semantics
1140                // for incompatible target types (e.g., bool).
1141                return Err(snippet_ctx
1142                    .attach_snippet(Error::eof().with_location(src.last_location()), &src));
1143            } else {
1144                return Err(snippet_ctx.attach_snippet(e, &src));
1145            }
1146        }
1147    };
1148
1149    // After finishing first document, peek ahead to detect either another document/content
1150    // or trailing garbage. If a scan error occurs but we have seen a DocumentEnd ("..."),
1151    // ignore the trailing garbage. Otherwise, surface the error.
1152    match src.peek() {
1153        Ok(Some(_)) => {
1154            return Err(snippet_ctx.attach_snippet(
1155                Error::multiple_documents("use read or read_with_options to obtain the iterator")
1156                    .with_location(src.last_location()),
1157                &src,
1158            ));
1159        }
1160        Ok(None) => {}
1161        Err(e) => {
1162            if src.seen_doc_end() {
1163                // Trailing garbage after a proper document end marker is ignored.
1164            } else {
1165                return Err(snippet_ctx.attach_snippet(e, &src));
1166            }
1167        }
1168    }
1169
1170    if let Err(e) = src.finish() {
1171        return Err(snippet_ctx.attach_snippet(e, &src));
1172    }
1173    Ok(value)
1174}
1175
1176/// Create an iterator over YAML documents from any `std::io::Read` using default options.
1177///
1178/// This is a convenience wrapper around [`read_with_options`] that uses the
1179/// same defaults as [`Options::default`] **except** it disables the
1180/// `max_reader_input_bytes` budget to better support long-lived streams.
1181///
1182/// - It streams the reader without loading the whole input into memory.
1183/// - Each item produced by the returned iterator is one deserialized YAML document of type `T`.
1184/// - Documents that are completely empty or null-like (e.g., `"", ~, null`) are skipped.
1185///
1186/// Generic parameters
1187/// - `R`: the concrete reader type implementing [`std::io::Read`]. You almost never need to
1188///   write this explicitly; the compiler will infer it from the `reader` you pass. When using
1189///   turbofish, write `_` to let the compiler infer `R`.
1190/// - `T`: the type to deserialize each YAML document into. Must implement [`serde::de::DeserializeOwned`].
1191///
1192/// Lifetimes
1193/// - `'a`: the lifetime of the returned iterator, tied to the lifetime of the provided `reader`.
1194///   The iterator cannot outlive the reader it was created from.
1195///
1196/// Limits and budget
1197/// - Uses the same limits as `Options::default()` (events, nodes, nesting depth, total scalar
1198///   bytes) and the default alias-replay caps. The only change is that
1199///   `Budget::max_reader_input_bytes` is set to `None` so the streaming iterator can handle
1200///   arbitrarily long inputs. To customize these limits, call [`read_with_options`] and set
1201///   `Options::budget.max_reader_input_bytes` in the provided `Options`.
1202/// - Alias replay limits are also enforced with their default values to mitigate alias bombs.
1203///
1204/// ```rust
1205/// use serde::Deserialize;
1206///
1207/// #[derive(Debug, Deserialize, PartialEq)]
1208/// struct Simple { id: usize }
1209///
1210/// let yaml = b"id: 1\n---\nid: 2\n";
1211/// let mut reader = std::io::Cursor::new(&yaml[..]);
1212///
1213/// // Type `T` is inferred from the collection target (Vec<Simple>).
1214/// let values: Vec<Simple> = serde_saphyr::read(&mut reader)
1215///     .map(|r| r.unwrap())
1216///     .collect();
1217/// assert_eq!(values.len(), 2);
1218/// assert_eq!(values[0].id, 1);
1219/// ```
1220///
1221/// Specifying only `T` with turbofish and letting `R` be inferred using `_`:
1222/// ```rust
1223/// use serde::Deserialize;
1224///
1225/// #[derive(Debug, Deserialize, PartialEq)]
1226/// struct Simple { id: usize }
1227///
1228/// let yaml = b"id: 10\n---\nid: 20\n";
1229/// let mut reader = std::io::Cursor::new(&yaml[..]);
1230///
1231/// // First turbofish parameter is R (reader type), `_` lets the compiler infer it.
1232/// let iter = serde_saphyr::read::<_, Simple>(&mut reader);
1233/// let ids: Vec<usize> = iter.map(|res| res.unwrap().id).collect();
1234/// assert_eq!(ids, vec![10, 20]);
1235/// ```
1236///
1237/// - Each `next()` yields either `Ok(T)` for a successfully deserialized document or `Err(Error)`
1238///   if parsing fails or a limit is exceeded. After an error, the iterator ends.
1239/// - Empty/null-like documents are skipped and produce no items.
1240///
1241/// *Note* Some content of the next document is read before the current parsed document is emitted.
1242/// Hence, while streaming is good for safely parsing large files with multiple documents without
1243/// loading it into RAM in advance, it does not emit each document exactly
1244/// after `---`  is encountered.
1245#[cfg(feature = "deserialize")]
1246pub fn read<'a, R, T>(reader: &'a mut R) -> Box<dyn Iterator<Item = Result<T, Error>> + 'a>
1247where
1248    R: Read + 'a,
1249    T: DeserializeOwned + 'a,
1250{
1251    Box::new(read_with_options(
1252        reader,
1253        crate::options! {
1254            budget: crate::budget! {
1255                max_reader_input_bytes: None,
1256            },
1257        },
1258    ))
1259}
1260
1261/// Create an iterator over YAML documents from any `std::io::Read`, with configurable options.
1262///
1263/// This is the multi-document counterpart to [`from_reader_with_options`]. It does not load
1264/// the entire input into memory. Instead, it streams the reader, deserializing one document
1265/// at a time into values of type `T`, yielding them through the returned iterator. Documents
1266/// that are completely empty or null-like (e.g., `""`, `~`, or `null`) are skipped.
1267///
1268/// Generic parameters
1269/// - `R`: the concrete reader type that implements [`std::io::Read`]. You rarely need to spell
1270///   this out; it is almost always inferred from the `reader` value you pass in. When using
1271///   turbofish, you can write `_` for this parameter to let the compiler infer it.
1272/// - `T`: the type to deserialize each YAML document into. This must implement [`serde::de::DeserializeOwned`].
1273///
1274/// Lifetimes
1275/// - `'a`: the lifetime of the returned iterator. It is tied to the lifetime of the provided
1276///   `reader` value because the iterator borrows internal state that references the reader.
1277///   In practice, this means the iterator cannot outlive the reader it was created from.
1278///
1279/// Limits and budget
1280/// - All parsing limits configured via [`Options::budget`] (such as maximum events, nodes,
1281///   nesting depth, total scalar bytes) are enforced while streaming. from_slice_multiple hard input-byte cap
1282///   is also enforced via `Budget::max_reader_input_bytes` (256 MiB by default), set this
1283///   to None if you need a streamer to exist for arbitrary long time.
1284/// - Alias replay limits from [`Options::alias_limits`] are also enforced to mitigate alias bombs.
1285///
1286/// ```rust
1287/// use serde::Deserialize;
1288///
1289/// #[derive(Debug, Deserialize, PartialEq)]
1290/// struct Simple { id: usize }
1291///
1292/// let yaml = b"id: 1\n---\nid: 2\n";
1293/// let mut reader = std::io::Cursor::new(&yaml[..]);
1294///
1295/// // Type `T` is inferred from the collection target (Vec<Simple>).
1296/// let values: Vec<Simple> = serde_saphyr::read(&mut reader)
1297///     .map(|r| r.unwrap())
1298///     .collect();
1299/// assert_eq!(values.len(), 2);
1300/// assert_eq!(values[0].id, 1);
1301/// ```
1302///
1303/// Specifying only `T` with turbofish and letting `R` be inferred using `_`:
1304/// ```rust
1305/// use serde::Deserialize;
1306///
1307/// #[derive(Debug, Deserialize, PartialEq)]
1308/// struct Simple { id: usize }
1309///
1310/// let yaml = b"id: 10\n---\nid: 20\n";
1311/// let mut reader = std::io::Cursor::new(&yaml[..]);
1312///
1313/// // First turbofish parameter is R (reader type) which we let the compiler infer via `_`.
1314/// let iter = serde_saphyr::read_with_options::<_, Simple>(&mut reader, serde_saphyr::Options::default());
1315/// let ids: Vec<usize> = iter.map(|res| res.unwrap().id).collect();
1316/// assert_eq!(ids, vec![10, 20]);
1317/// ```
1318///
1319/// - Each `next()` yields either `Ok(T)` for a successfully deserialized document or `Err(Error)`
1320///   if parsing or deserialization fails.
1321/// - After a **deserialization error** (e.g., type mismatch, missing field), the iterator
1322///   automatically recovers by skipping to the next document boundary (`---`) and continues
1323///   iteration. This allows processing subsequent valid documents even when some fail.
1324/// - After a **syntax error** or **budget/alias limit exceeded**, the iterator ends because
1325///   the parser state may be unrecoverable.
1326/// - Empty/null-like documents are skipped and produce no items.
1327#[allow(deprecated)]
1328#[cfg(feature = "deserialize")]
1329pub fn read_with_options<'a, R, T>(
1330    reader: &'a mut R, // iterator must not outlive this borrow
1331    options: Options,
1332) -> impl Iterator<Item = Result<T, Error>> + 'a
1333where
1334    R: Read + 'a,
1335    T: DeserializeOwned + 'a,
1336{
1337    struct ReadIter<'a, T> {
1338        src: LiveEvents<'a>, // borrows from `reader`
1339        cfg: crate::de::Cfg,
1340        finished: bool,
1341        _marker: std::marker::PhantomData<T>,
1342    }
1343
1344    impl<'a, T> Iterator for ReadIter<'a, T>
1345    where
1346        T: DeserializeOwned + 'a,
1347    {
1348        type Item = Result<T, Error>;
1349
1350        fn next(&mut self) -> Option<Self::Item> {
1351            if self.finished {
1352                return None;
1353            }
1354            loop {
1355                match self.src.peek() {
1356                    Ok(Some(Ev::Scalar { value, style, .. }))
1357                        if scalar_is_nullish(value, style) =>
1358                    {
1359                        let _ = self.src.next();
1360                        continue;
1361                    }
1362                    Ok(Some(_)) => {
1363                        let res = crate::anchor_store::with_document_scope(|| {
1364                            with_interp_redaction_scope(|| {
1365                                crate::de::with_root_redaction(
1366                                    crate::de::YamlDeserializer::new(&mut self.src, self.cfg),
1367                                    |de| T::deserialize(de),
1368                                )
1369                            })
1370                        });
1371                        if res.is_err() {
1372                            // After a deserialization error, skip remaining events in the
1373                            // current document and try to recover at the next document boundary.
1374                            // If no next document is found, mark as finished.
1375                            if !self.src.skip_to_next_document() {
1376                                self.finished = true;
1377                            }
1378                        }
1379                        return Some(res);
1380                    }
1381                    Ok(None) => {
1382                        self.finished = true;
1383                        if let Err(e) = self.src.finish() {
1384                            return Some(Err(e));
1385                        }
1386                        return None;
1387                    }
1388                    Err(e) => {
1389                        self.finished = true;
1390                        let _ = self.src.finish();
1391                        return Some(Err(e));
1392                    }
1393                }
1394            }
1395        }
1396    }
1397
1398    let cfg = crate::de::Cfg::from_options(&options);
1399    let src = LiveEvents::from_reader(reader, options, false, EnforcingPolicy::PerDocument);
1400
1401    ReadIter::<T> {
1402        src,
1403        cfg,
1404        finished: false,
1405        _marker: std::marker::PhantomData,
1406    }
1407}