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}