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