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}