Skip to main content

serde_saphyr/de/
api.rs

1use std::io::Read;
2
3use serde_core::de::DeserializeOwned;
4
5use super::with_deserializer::deserialize_document_unless_null;
6use super::{Error, Events, Options, ring_reader};
7use crate::budget::EnforcingPolicy;
8use crate::live_events::LiveEvents;
9
10#[cfg(all(feature = "deserialize", feature = "include"))]
11pub(crate) fn resolver_from_options<'a>(
12    options: &Options,
13) -> Option<Box<crate::input_source::IncludeResolver<'a>>> {
14    options.include_resolver.clone().map(|rc_refcell| {
15        Box::new(move |req: crate::input_source::IncludeRequest<'_>| rc_refcell.borrow_mut()(req))
16            as Box<crate::input_source::IncludeResolver<'a>>
17    })
18}
19
20/// Deserialize any `T: serde::de::Deserialize<'de>` directly from a YAML string.
21///
22/// This is the simplest entry point; it parses a single YAML document. If the
23/// input contains multiple documents, this returns an error advising to use
24/// [`from_str_multiple`] or [`from_str_multiple_with_options`].
25///
26/// This function supports both owned types (like `String`) and borrowed types
27/// (like `&str`). For borrowed types, the deserialized value's lifetime is tied
28/// to the input string's lifetime.
29///
30/// **Note**: Borrowing only works for simple plain scalars that don't require
31/// any transformation (no multi-line folding, no escape processing). For
32/// transformed strings, deserialization to `&str` will fail with a helpful
33/// error message suggesting to use `String` or `Cow<str>` instead.
34///
35/// Example: read a small `Config` structure from a YAML string.
36///
37/// ```rust
38/// use serde::Deserialize;
39///
40/// #[derive(Debug, Deserialize, PartialEq)]
41/// struct Config {
42///     name: String,
43///     enabled: bool,
44///     retries: i32,
45/// }
46///
47/// let yaml = r#"
48///     name: My Application
49///     enabled: true
50///     retries: 5
51/// "#;
52///
53/// let cfg: Config = serde_saphyr::from_str(yaml).unwrap();
54/// assert!(cfg.enabled);
55/// ```
56///
57/// Example: read a structure with borrowed string fields.
58///
59/// Borrowed strings are supported when deserializing from an in-memory input (`from_str` / `from_slice`),
60/// and only when the scalar exists verbatim in the input (i.e., no escape processing, folding, or other
61/// normalization is required). If the YAML scalar requires transformation, deserializing into `&str`
62/// fails with an error suggesting `String` or `Cow<str>`.
63///
64/// Note: reader-based entry points like [`from_reader`] require `DeserializeOwned` and therefore cannot
65/// return values that borrow from the input.
66///
67/// ```rust
68/// use serde::Deserialize;
69///
70/// #[derive(Debug, Deserialize, PartialEq)]
71/// struct Data<'a> {
72///     name: &'a str,
73///     value: i32,
74/// }
75///
76/// let yaml = "name: hello\nvalue: 42\n";
77///
78/// let data: Data = serde_saphyr::from_str(yaml).unwrap();
79/// assert_eq!(data.name, "hello");
80/// assert_eq!(data.value, 42);
81/// ```
82#[cfg(feature = "deserialize")]
83pub fn from_str<'de, T>(input: &'de str) -> Result<T, Error>
84where
85    T: serde_core::de::Deserialize<'de>,
86{
87    from_str_with_options(input, Options::default())
88}
89
90#[cfg(feature = "deserialize")]
91fn from_str_with_options_impl<'de, T>(input: &'de str, options: Options) -> Result<T, Error>
92where
93    T: serde_core::de::Deserialize<'de>,
94{
95    super::with_deserializer::with_deserializer_from_str_with_options(input, options, |de| {
96        T::deserialize(de)
97    })
98}
99
100/// Deserialize a single YAML document with configurable [`Options`].
101///
102/// This function supports both owned types (like `String`) and borrowed types
103/// (like `&str`). For borrowed types, the deserialized value's lifetime is tied
104/// to the input string's lifetime.
105///
106/// Example: read a small `Config` with a custom budget and default duplicate-key policy.
107///
108/// ```rust
109/// use serde::Deserialize;
110/// use serde_saphyr::DuplicateKeyPolicy;
111///
112/// #[derive(Debug, Deserialize, PartialEq)]
113/// struct Config {
114///     name: String,
115///     enabled: bool,
116///     retries: i32,
117/// }
118///
119/// let yaml = r#"
120///      name: My Application
121///      enabled: true
122///      retries: 5
123/// "#;
124///
125/// let options = serde_saphyr::options! {
126///     budget: serde_saphyr::budget! {
127///         max_anchors: 200,
128///     },
129///     duplicate_keys: DuplicateKeyPolicy::FirstWins,
130/// };
131/// let cfg: Config = serde_saphyr::from_str_with_options(yaml, options).unwrap();
132/// assert_eq!(cfg.retries, 5);
133/// ```
134#[cfg(feature = "deserialize")]
135pub fn from_str_with_options<'de, T>(input: &'de str, options: Options) -> Result<T, Error>
136where
137    T: serde_core::de::Deserialize<'de>,
138{
139    from_str_with_options_impl(input, options)
140}
141
142#[cfg(feature = "deserialize")]
143pub(crate) fn maybe_with_snippet(
144    err: Error,
145    input: &str,
146    with_snippet: bool,
147    crop_radius: usize,
148) -> Error {
149    if !(with_snippet && crop_radius > 0 && err.location().is_some()) {
150        return err;
151    }
152
153    err.with_snippet(input, crop_radius)
154}
155
156#[cfg(feature = "deserialize")]
157pub(crate) struct RootFragment<'a> {
158    pub text: &'a str,
159    pub start_line: usize,
160    pub source_name: &'a str,
161}
162
163#[cfg(feature = "deserialize")]
164pub(crate) struct StrSnippetContext<'a> {
165    input: &'a str,
166    with_snippet: bool,
167    crop_radius: usize,
168}
169
170#[cfg(feature = "deserialize")]
171impl<'a> StrSnippetContext<'a> {
172    pub(crate) fn new(input: &'a str, with_snippet: bool, crop_radius: usize) -> Self {
173        Self {
174            input,
175            with_snippet,
176            crop_radius,
177        }
178    }
179
180    pub(crate) fn attach_snippet(&self, err: Error, src: &LiveEvents<'_>) -> Error {
181        maybe_with_snippet_from_events(err, self.input, src, self.with_snippet, self.crop_radius)
182    }
183}
184
185#[cfg(feature = "deserialize")]
186pub(crate) struct ReaderSnippetContext<R> {
187    shared_ring: ring_reader::SharedRingReader<R>,
188    with_snippet: bool,
189    crop_radius: usize,
190}
191
192#[cfg(feature = "deserialize")]
193impl<R: Read> ReaderSnippetContext<R> {
194    pub(crate) fn new(
195        reader: R,
196        with_snippet: bool,
197        crop_radius: usize,
198    ) -> (Self, ring_reader::SharedRingReaderHandle<R>) {
199        let shared_ring = ring_reader::SharedRingReader::new(reader);
200        let ring_handle = ring_reader::SharedRingReaderHandle::new(&shared_ring);
201        (
202            Self {
203                shared_ring,
204                with_snippet,
205                crop_radius,
206            },
207            ring_handle,
208        )
209    }
210
211    pub(crate) fn attach_snippet(&self, err: Error, src: &LiveEvents<'_>) -> Error {
212        if !self.with_snippet || self.crop_radius == 0 {
213            return err;
214        }
215
216        match self.shared_ring.get_recent() {
217            Ok(snapshot) => {
218                let text = String::from_utf8_lossy(&snapshot.bytes);
219                let root = RootFragment {
220                    text: text.as_ref(),
221                    start_line: snapshot.start_line,
222                    source_name: "input",
223                };
224                maybe_with_snippet_from_events_and_root_fragment(
225                    err,
226                    Some(&root),
227                    text.as_ref(),
228                    src,
229                    self.with_snippet,
230                    self.crop_radius,
231                )
232            }
233            Err(_) => err,
234        }
235    }
236}
237
238#[cfg(all(feature = "deserialize", feature = "include"))]
239fn with_root_additional_snippet(
240    err: Error,
241    root: Option<&RootFragment<'_>>,
242    input: &str,
243    location: &crate::Location,
244    crop_radius: usize,
245) -> Error {
246    match root {
247        Some(root) => err.with_additional_snippet_offset_named(
248            root.text,
249            root.start_line,
250            root.source_name,
251            location,
252            crop_radius,
253        ),
254        None => err.with_additional_snippet_named(input, "input", location, crop_radius),
255    }
256}
257
258#[cfg(all(feature = "deserialize", feature = "include"))]
259fn with_recorded_source_snippets(
260    err: Error,
261    root: Option<&RootFragment<'_>>,
262    input: &str,
263    chain: &[&crate::include_stack::RecordedSource],
264    crop_radius: usize,
265) -> Error {
266    let Some(current) = chain.first() else {
267        return with_root_or_input_snippet(err, root, input, crop_radius);
268    };
269    let Some(source_text) = current.text.as_deref() else {
270        return with_root_or_input_snippet(err, root, input, crop_radius);
271    };
272    let err_with_snippet = err.with_snippet_named(source_text, current.name.as_str(), crop_radius);
273    with_include_site_snippets(err_with_snippet, root, input, chain, crop_radius)
274}
275
276/// Add an "included from here" window for each include site in `chain` that has source text.
277#[cfg(all(feature = "deserialize", feature = "include"))]
278fn with_include_site_snippets(
279    mut err: Error,
280    root: Option<&RootFragment<'_>>,
281    input: &str,
282    chain: &[&crate::include_stack::RecordedSource],
283    crop_radius: usize,
284) -> Error {
285    for window in chain.windows(2) {
286        let (child, parent) = (window[0], window[1]);
287        if child.include_location == crate::Location::UNKNOWN {
288            continue;
289        }
290        match parent.text.as_deref() {
291            Some(parent_text) => {
292                err = err.with_additional_snippet_named(
293                    parent_text,
294                    parent.name.as_str(),
295                    &child.include_location,
296                    crop_radius,
297                );
298            }
299            None if parent.parent_source_id.is_none() => {
300                err = with_root_additional_snippet(
301                    err,
302                    root,
303                    input,
304                    &child.include_location,
305                    crop_radius,
306                );
307            }
308            None => {}
309        }
310    }
311    err
312}
313
314#[cfg(all(feature = "deserialize", feature = "include"))]
315fn with_root_or_input_snippet(
316    err: Error,
317    root: Option<&RootFragment<'_>>,
318    input: &str,
319    crop_radius: usize,
320) -> Error {
321    match root {
322        Some(root) => {
323            err.with_snippet_offset_named(root.text, root.start_line, root.source_name, crop_radius)
324        }
325        None => maybe_with_snippet(err, input, true, crop_radius),
326    }
327}
328
329#[cfg(feature = "deserialize")]
330pub(crate) fn maybe_with_snippet_from_events_and_root_fragment(
331    err: Error,
332    root: Option<&RootFragment<'_>>,
333    input: &str,
334    #[allow(unused_variables)] events: &crate::live_events::LiveEvents<'_>,
335    with_snippet: bool,
336    crop_radius: usize,
337) -> Error {
338    if !(with_snippet && crop_radius > 0 && err.location().is_some()) {
339        return err;
340    }
341
342    #[cfg(feature = "include")]
343    if let Some(loc) = err.location() {
344        let chain = events.recorded_source_chain(loc.source_id());
345        if let Some(current) = chain.first() {
346            if current.text.is_some() {
347                return with_recorded_source_snippets(err, root, input, &chain, crop_radius);
348            }
349            // An error inside an include whose text was not retained (a reader-backed include)
350            // has no source text to show. Its line/column belong to the included file, so
351            // rendering them against the root text would point at unrelated root lines.
352            // Show where it was included.
353            if current.parent_source_id.is_some() {
354                let err = err.with_no_snippet_regions(crop_radius);
355                return with_include_site_snippets(err, root, input, &chain, crop_radius);
356            }
357        }
358    }
359
360    match root {
361        Some(root) => {
362            err.with_snippet_offset_named(root.text, root.start_line, root.source_name, crop_radius)
363        }
364        None => maybe_with_snippet(err, input, with_snippet, crop_radius),
365    }
366}
367
368#[cfg(feature = "deserialize")]
369pub(crate) fn maybe_with_snippet_from_events(
370    err: Error,
371    input: &str,
372    #[allow(unused_variables)] events: &crate::live_events::LiveEvents<'_>,
373    with_snippet: bool,
374    crop_radius: usize,
375) -> Error {
376    maybe_with_snippet_from_events_and_root_fragment(
377        err,
378        None,
379        input,
380        events,
381        with_snippet,
382        crop_radius,
383    )
384}
385
386/// Deserialize multiple YAML documents from a single string into a vector of `T`.
387/// Completely empty documents, untagged plain null-like documents, and explicit `!!null`
388/// documents are skipped. Application-tagged roots are deserialized, even with null payloads.
389///
390/// Supports both owned values and values borrowing from the input string. Borrowed values
391/// cannot outlive the input, and strings requiring transformation cannot be borrowed as `&str`;
392/// see [`from_str_multiple`] for details and for collecting into other collection types.
393///
394/// Since 2.0, function pointers or callbacks accepting any input lifetime may need a
395/// forwarding closure such as `|input| from_multiple::<String>(input)`.
396///
397/// Example: read two `Config` documents separated by `---`.
398///
399/// ```rust
400/// use serde::Deserialize;
401///
402/// #[derive(Debug, Deserialize, PartialEq)]
403/// struct Config {
404///     name: String,
405///     enabled: bool,
406///     retries: i32,
407/// }
408///
409/// let yaml = r#"
410/// name: First
411/// enabled: true
412/// retries: 1
413/// ---
414/// name: Second
415/// enabled: false
416/// retries: 2
417/// "#;
418///
419/// let cfgs: Vec<Config> = serde_saphyr::from_multiple(yaml).unwrap();
420/// assert_eq!(cfgs.len(), 2);
421/// assert_eq!(cfgs[0].name, "First");
422/// ```
423#[cfg(feature = "deserialize")]
424pub fn from_multiple<'de, T>(input: &'de str) -> Result<Vec<T>, Error>
425where
426    T: serde_core::Deserialize<'de>,
427{
428    from_str_multiple::<T, Vec<T>>(input)
429}
430
431/// Deserialize multiple YAML documents into a vector with configurable [`Options`].
432///
433/// Like [`from_multiple`], this supports owned and borrowed values and skips empty,
434/// untagged null-like, and explicit `!!null` documents. With `DockerComposeWithNulls`,
435/// interpolated nulls are also skipped. Application-tagged roots are deserialized.
436/// Use [`from_str_multiple_with_options`] to collect into other collection types.
437///
438/// Since 2.0, function pointers or callbacks accepting any input lifetime may need a
439/// forwarding closure such as `|input, options| from_multiple_with_options::<String>(input, options)`.
440///
441/// Example: two `Config` documents with a custom budget.
442///
443/// ```rust
444/// use serde::Deserialize;
445/// use serde_saphyr::DuplicateKeyPolicy;
446///
447/// #[derive(Debug, Deserialize, PartialEq)]
448/// struct Config {
449///     name: String,
450///     enabled: bool,
451///     retries: i32,
452/// }
453///
454/// let yaml = r#"
455/// name: First
456/// enabled: true
457/// retries: 1
458/// ---
459/// name: Second
460/// enabled: false
461/// retries: 2
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 cfgs: Vec<Config> = serde_saphyr::from_multiple_with_options(yaml, options).unwrap();
471/// assert_eq!(cfgs.len(), 2);
472/// assert!(!cfgs[1].enabled);
473/// ```
474#[cfg(feature = "deserialize")]
475pub fn from_multiple_with_options<'de, T>(
476    input: &'de str,
477    options: Options,
478) -> Result<Vec<T>, Error>
479where
480    T: serde_core::Deserialize<'de>,
481{
482    from_str_multiple_with_options::<T, Vec<T>>(input, options)
483}
484
485/// Deserialize multiple YAML documents from a string into a collection, supporting borrowed values.
486/// Empty, untagged null-like, and explicit `!!null` documents are skipped, as with
487/// [`from_multiple`]. Application-tagged roots are deserialized, even with null payloads.
488///
489/// The collection is initialized with [`Default`] and extended with each document in input
490/// order. Any `C: Default + Extend<T>` is supported, including `Vec<T>`, `VecDeque<T>`,
491/// and sets. The collection determines how ordering and duplicate values are handled.
492/// Specify both types when using turbofish syntax, for example `::<String, Vec<_>>`.
493/// If the collection implements `Extend` for multiple item types, specify `T` explicitly
494/// to resolve ambiguity, for example `::<i32, Vec<_>>`.
495/// Use [`from_multiple`] to return a `Vec<T>` with a single type argument.
496///
497/// This function supports both owned types (like `String`) and borrowed types (like `&str`).
498/// Borrowed values refer to the input string and cannot outlive it.
499///
500/// Strings can be borrowed when their parsed value exists verbatim in the input, including
501/// plain scalars and simple quoted strings. Strings requiring escape processing, folding,
502/// or other transformations cannot be deserialized into `&str`; use `String` or a
503/// `#[serde(borrow)]`-annotated `Cow<'a, str>` field to handle those values.
504///
505/// ```rust
506/// use serde::Deserialize;
507///
508/// #[derive(Debug, Deserialize)]
509/// struct Person<'a> {
510///     name: &'a str,
511/// }
512///
513/// let yaml = String::from("name: Alice\n---\nname: Bob\n");
514/// let people: Vec<Person<'_>> = serde_saphyr::from_str_multiple(&yaml)?;
515/// assert_eq!(people[0].name, "Alice");
516/// assert_eq!(people[1].name, "Bob");
517/// # Ok::<(), serde_saphyr::Error>(())
518/// ```
519#[cfg(feature = "deserialize")]
520pub fn from_str_multiple<'de, T, C>(input: &'de str) -> Result<C, Error>
521where
522    T: serde_core::Deserialize<'de>,
523    C: Default + Extend<T>,
524{
525    from_str_multiple_with_options::<T, C>(input, Options::default())
526}
527
528/// Deserialize multiple YAML documents into a collection with configurable [`Options`], supporting borrowed values.
529/// Null documents are skipped as described in [`from_multiple_with_options`].
530/// Application-tagged roots are deserialized, even with null payloads.
531///
532/// Like [`from_str_multiple`], this function supports both owned values and values borrowing
533/// from the input string, collected into any `C: Default + Extend<T>`.
534/// Borrowed values cannot outlive the input, and strings requiring
535/// transformation cannot be borrowed as `&str`.
536/// Use [`from_multiple_with_options`] to return a `Vec<T>` with a single type argument.
537///
538/// ```rust
539/// use std::collections::BTreeSet;
540///
541/// let yaml = String::from("first\n---\nsecond\n---\nfirst\n");
542/// let options = serde_saphyr::options! { with_snippet: false };
543/// let values = serde_saphyr::from_str_multiple_with_options::<&str, BTreeSet<_>>(&yaml, options)?;
544/// assert_eq!(values, BTreeSet::from(["first", "second"]));
545/// # Ok::<(), serde_saphyr::Error>(())
546/// ```
547#[cfg(feature = "deserialize")]
548pub fn from_str_multiple_with_options<'de, T, C>(
549    input: &'de str,
550    options: Options,
551) -> Result<C, Error>
552where
553    T: serde_core::Deserialize<'de>,
554    C: Default + Extend<T>,
555{
556    let snippet_ctx = StrSnippetContext::new(input, options.with_snippet, options.crop_radius);
557    let cfg = crate::de::Cfg::from_options(&options);
558    let mut src = LiveEvents::from_str(input, options);
559    let mut values = C::default();
560    let wrap_err = |e, src: &LiveEvents<'_>| snippet_ctx.attach_snippet(e, src);
561
562    loop {
563        match src.peek() {
564            Ok(Some(_)) => {
565                let value = deserialize_document_unless_null(
566                    &mut src,
567                    cfg,
568                    |de| T::deserialize(de),
569                    wrap_err,
570                )?;
571                values.extend(value);
572            }
573            Ok(None) => break,
574            Err(e) => {
575                return Err(wrap_err(e, &src));
576            }
577        }
578    }
579
580    if let Err(e) = src.finish() {
581        return Err(wrap_err(e, &src));
582    }
583    Ok(values)
584}
585
586/// Deserialize a single YAML document from a UTF-8 byte slice.
587///
588/// UTF-8 only (due borrowing). For UTF-16 input, use [`from_reader`] instead:
589/// `let reader = std::io::Cursor::new(bytes);`
590/// `let cfg: Config = serde_saphyr::from_reader(reader)?;`
591///
592/// This is equivalent to [`from_str`], but accepts `&[u8]` and validates it is
593/// valid UTF-8 before parsing.
594///
595/// This function supports both owned types (like `String`) and borrowed types
596/// (like `&str`). For borrowed types, the deserialized value's lifetime is tied
597/// to the input byte slice's lifetime.
598///
599/// Example: read a small `Config` structure from bytes.
600///
601/// ```rust
602/// use serde::Deserialize;
603///
604/// #[derive(Debug, Deserialize, PartialEq)]
605/// struct Config {
606///     name: String,
607///     enabled: bool,
608///     retries: i32,
609/// }
610///
611/// let yaml = r#"
612/// name: My Application
613/// enabled: true
614/// retries: 5
615/// "#;
616/// let bytes = yaml.as_bytes();
617/// let cfg: Config = serde_saphyr::from_slice(bytes).unwrap();
618/// assert!(cfg.enabled);
619/// ```
620///
621#[cfg(feature = "deserialize")]
622pub fn from_slice<'de, T>(bytes: &'de [u8]) -> Result<T, Error>
623where
624    T: serde_core::Deserialize<'de>,
625{
626    from_slice_with_options(bytes, Options::default())
627}
628
629/// Deserialize a single YAML document from a UTF-8 byte slice with configurable [`Options`].
630///
631/// Example: read a small `Config` with a custom budget from bytes.
632///
633/// ```rust
634/// use serde::Deserialize;
635/// use serde_saphyr::DuplicateKeyPolicy;
636///
637/// #[derive(Debug, Deserialize, PartialEq)]
638/// struct Config {
639///     name: String,
640///     enabled: bool,
641///     retries: i32,
642/// }
643///
644/// let yaml = r#"
645///      name: My Application
646///      enabled: true
647///      retries: 5
648/// "#;
649/// let bytes = yaml.as_bytes();
650/// let options = serde_saphyr::options! {
651///     budget: serde_saphyr::budget! {
652///         max_anchors: 200,
653///     },
654///     duplicate_keys: DuplicateKeyPolicy::FirstWins,
655/// };
656/// let cfg: Config = serde_saphyr::from_slice_with_options(bytes, options).unwrap();
657/// assert_eq!(cfg.retries, 5);
658/// ```
659#[cfg(feature = "deserialize")]
660pub fn from_slice_with_options<'de, T>(bytes: &'de [u8], options: Options) -> Result<T, Error>
661where
662    T: serde_core::Deserialize<'de>,
663{
664    let s = std::str::from_utf8(bytes).map_err(|_| Error::InvalidUtf8Input)?;
665    from_str_with_options(s, options)
666}
667
668/// Deserialize multiple YAML documents from a UTF-8 byte slice into a vector of `T`.
669/// Empty, untagged null-like, and explicit `!!null` documents are skipped, as with
670/// [`from_multiple`]. Application-tagged roots are deserialized, even with null payloads.
671///
672/// Supports both owned values and values borrowing from the input byte slice. Borrowed
673/// values cannot outlive the input, and strings requiring transformation cannot be borrowed
674/// as `&str`; see [`from_str_multiple`] for details. Invalid UTF-8 returns [`Error::InvalidUtf8Input`].
675/// Use [`from_bytes_multiple`] to collect into other collection types.
676///
677/// Since 2.0, function pointers or callbacks accepting any input lifetime may need a
678/// forwarding closure such as `|bytes| from_slice_multiple::<String>(bytes)`.
679///
680/// Example: read two `Config` documents separated by `---` from bytes.
681///
682/// ```rust
683/// use serde::Deserialize;
684///
685/// #[derive(Debug, Deserialize, PartialEq)]
686/// struct Config {
687///     name: String,
688///     enabled: bool,
689///     retries: i32,
690/// }
691///
692/// let yaml = r#"
693/// name: First
694/// enabled: true
695/// retries: 1
696/// ---
697/// name: Second
698/// enabled: false
699/// retries: 2
700/// "#;
701/// let bytes = yaml.as_bytes();
702/// let cfgs: Vec<Config> = serde_saphyr::from_slice_multiple(bytes).unwrap();
703/// assert_eq!(cfgs.len(), 2);
704/// assert_eq!(cfgs[0].name, "First");
705/// ```
706#[cfg(feature = "deserialize")]
707pub fn from_slice_multiple<'de, T>(bytes: &'de [u8]) -> Result<Vec<T>, Error>
708where
709    T: serde_core::Deserialize<'de>,
710{
711    from_bytes_multiple::<T, Vec<T>>(bytes)
712}
713
714/// Deserialize multiple YAML documents from a UTF-8 byte slice into a vector with configurable [`Options`].
715///
716/// Like [`from_multiple_with_options`], this supports owned and borrowed values and skips
717/// null documents unless application-tagged. Invalid UTF-8 returns [`Error::InvalidUtf8Input`].
718/// Use [`from_bytes_multiple_with_options`] to collect into other collection types.
719///
720/// Since 2.0, function pointers or callbacks accepting any input lifetime may need a
721/// forwarding closure such as `|bytes, options| from_slice_multiple_with_options::<String>(bytes, options)`.
722///
723/// Example: two `Config` documents with a custom budget from bytes.
724///
725/// ```rust
726/// use serde::Deserialize;
727/// use serde_saphyr::DuplicateKeyPolicy;
728///
729/// #[derive(Debug, Deserialize, PartialEq)]
730/// struct Config {
731///     name: String,
732///     enabled: bool,
733///     retries: i32,
734/// }
735///
736/// let yaml = r#"
737/// name: First
738/// enabled: true
739/// retries: 1
740/// ---
741/// name: Second
742/// enabled: false
743/// retries: 2
744/// "#;
745/// let bytes = yaml.as_bytes();
746/// let options = serde_saphyr::options! {
747///     budget: serde_saphyr::budget! {
748///         max_anchors: 200,
749///     },
750///     duplicate_keys: DuplicateKeyPolicy::FirstWins,
751/// };
752/// let cfgs: Vec<Config> = serde_saphyr::from_slice_multiple_with_options(bytes, options).unwrap();
753/// assert_eq!(cfgs.len(), 2);
754/// assert!(!cfgs[1].enabled);
755/// ```
756#[cfg(feature = "deserialize")]
757pub fn from_slice_multiple_with_options<'de, T>(
758    bytes: &'de [u8],
759    options: Options,
760) -> Result<Vec<T>, Error>
761where
762    T: serde_core::Deserialize<'de>,
763{
764    from_bytes_multiple_with_options::<T, Vec<T>>(bytes, options)
765}
766
767/// Deserialize multiple YAML documents from a UTF-8 byte slice into a collection, supporting borrowed values.
768/// Empty, untagged null-like, and explicit `!!null` documents are skipped, as with
769/// [`from_str_multiple`]. Application-tagged roots are deserialized, even with null payloads.
770///
771/// This is equivalent to [`from_str_multiple`], but validates that the bytes are UTF-8
772/// before parsing. Invalid UTF-8 returns [`Error::InvalidUtf8Input`]. For UTF-16 streams,
773/// use [`read`] or [`read_with_options`] instead.
774///
775/// Any `C: Default + Extend<T>` is supported. Documents extend the collection in input
776/// order; the collection determines how ordering and duplicate values are handled.
777/// Specify both types when using turbofish syntax, for example `::<String, Vec<_>>`.
778/// Use [`from_slice_multiple`] to return a `Vec<T>` with a single type argument.
779///
780/// This function supports both owned types (like `String`) and borrowed types (like `&str`).
781/// Borrowed values refer to the input byte slice and cannot outlive it.
782/// Strings requiring escape processing, folding, or other transformations cannot be
783/// borrowed as `&str`; see [`from_str_multiple`] for details.
784///
785/// ```rust
786/// use serde::Deserialize;
787///
788/// #[derive(Debug, Deserialize)]
789/// struct Person<'a> {
790///     name: &'a str,
791/// }
792///
793/// let bytes = b"name: Alice\n---\nname: Bob\n".to_vec();
794/// let people: Vec<Person<'_>> = serde_saphyr::from_bytes_multiple(&bytes)?;
795/// assert_eq!(people[0].name, "Alice");
796/// assert_eq!(people[1].name, "Bob");
797/// # Ok::<(), serde_saphyr::Error>(())
798/// ```
799#[cfg(feature = "deserialize")]
800pub fn from_bytes_multiple<'de, T, C>(bytes: &'de [u8]) -> Result<C, Error>
801where
802    T: serde_core::Deserialize<'de>,
803    C: Default + Extend<T>,
804{
805    from_bytes_multiple_with_options::<T, C>(bytes, Options::default())
806}
807
808/// Deserialize multiple YAML documents from UTF-8 bytes into a collection with configurable [`Options`],
809/// supporting borrowed values.
810/// Null documents are skipped as described in [`from_str_multiple_with_options`].
811/// Application-tagged roots are deserialized, even with null payloads.
812///
813/// Like [`from_bytes_multiple`], this function supports owned values and values borrowing
814/// from the input byte slice, collected into any `C: Default + Extend<T>`.
815/// Borrowed values cannot outlive the input, and strings requiring
816/// transformation cannot be borrowed as `&str`. Invalid UTF-8 returns [`Error::InvalidUtf8Input`].
817/// Use [`from_slice_multiple_with_options`] to return a `Vec<T>` with a single type argument.
818///
819/// ```rust
820/// use std::collections::VecDeque;
821///
822/// let bytes = b"first\n---\nsecond\n".to_vec();
823/// let options = serde_saphyr::options! { with_snippet: false };
824/// let values = serde_saphyr::from_bytes_multiple_with_options::<&str, VecDeque<_>>(&bytes, options)?;
825/// assert_eq!(values, VecDeque::from(["first", "second"]));
826/// # Ok::<(), serde_saphyr::Error>(())
827/// ```
828#[cfg(feature = "deserialize")]
829pub fn from_bytes_multiple_with_options<'de, T, C>(
830    bytes: &'de [u8],
831    options: Options,
832) -> Result<C, Error>
833where
834    T: serde_core::Deserialize<'de>,
835    C: Default + Extend<T>,
836{
837    let s = std::str::from_utf8(bytes).map_err(|_| Error::InvalidUtf8Input)?;
838    from_str_multiple_with_options::<T, C>(s, options)
839}
840
841/// Deserialize a single YAML document from any `std::io::Read`.
842///
843/// Reader-based entry points accept BOM-marked UTF-8, UTF-16LE, and UTF-16BE. If no
844/// recognized BOM is present, the input bytes are treated as UTF-8.
845///
846/// This method parses as it reads, without loading the entire input into memory first. Hence,
847/// budget limits protect against large (potentially malicious) input.
848///
849/// Example
850///
851/// ```rust
852/// use serde::{Deserialize, Serialize};
853/// use std::collections::HashMap;
854/// use serde_json::Value;
855///
856/// #[derive(Debug, PartialEq, Serialize, Deserialize)]
857/// struct Point {
858///     x: i32,
859///     y: i32,
860/// }
861///
862/// let yaml = "x: 3\ny: 4\n";
863/// let reader = std::io::Cursor::new(yaml.as_bytes());
864/// let p: Point = serde_saphyr::from_reader(reader).unwrap();
865/// assert_eq!(p, Point { x: 3, y: 4 });
866///
867/// // It also works for dynamic values like serde_json::Value
868/// let mut big = String::new();
869/// let mut i = 0usize;
870/// while big.len() < 64 * 1024 { big.push_str(&format!("k{0}: v{0}\n", i)); i += 1; }
871/// let reader = std::io::Cursor::new(big.as_bytes().to_owned());
872/// let _value: Value = serde_saphyr::from_reader(reader).unwrap();
873/// ```
874#[cfg(feature = "deserialize")]
875pub fn from_reader<'a, R: std::io::Read + 'a, T: DeserializeOwned>(reader: R) -> Result<T, Error> {
876    from_reader_with_options(reader, Options::default())
877}
878
879/// Deserialize a single YAML document from any `std::io::Read` with configurable `Options`.
880///
881/// This is the reader-based counterpart to [`from_str_with_options`]. It consumes a
882/// byte-oriented reader and streams events into the deserializer. BOM-marked
883/// UTF-8, UTF-16LE, and UTF-16BE inputs are transcoded to UTF-8 internally
884/// before parsing. If no recognized BOM is present, the input bytes are
885/// treated as UTF-8.
886///
887/// This method parses as it reads, without loading the entire input into memory first. Hence,
888/// budget limits protect against large (potentially malicious) input.
889///
890/// Notes on limits and large inputs
891/// - Parsing limits: Use [`Options::budget`] to constrain YAML complexity (events, nodes,
892///   nesting depth, total scalar bytes, total comment bytes, number of documents, anchors,
893///   aliases, etc.). These
894///   limits are enforced during parsing and are enabled by default via `Options::default()`.
895/// - Byte-level input cap: `Budget::max_reader_input_bytes` is enforced while reading.
896///   The default budget sets this to 256 MiB. You can override it by customizing `Options::budget`.
897///   When the cap is exceeded, deserialization fails early with a budget error.
898///
899/// Example: limit raw input bytes and customize options
900/// ```rust
901/// use std::io::{Read, Cursor};
902/// use serde::Deserialize;
903/// use serde_saphyr::{Budget, Options};
904///
905/// #[derive(Debug, Deserialize, PartialEq)]
906/// struct Point { x: i32, y: i32 }
907///
908/// let yaml = "x: 3\ny: 4\n";
909/// let reader = Cursor::new(yaml.as_bytes());
910///
911/// let opts = serde_saphyr::options! {
912///     budget: serde_saphyr::budget! {
913///         max_events: 10_000,
914///         max_reader_input_bytes: Some(1024),
915///     },
916/// };
917///
918/// let p: Point = serde_saphyr::from_reader_with_options(reader, opts).unwrap();
919/// assert_eq!(p, Point { x: 3, y: 4 });
920/// ```
921///
922/// Error behavior
923/// - If an empty document is provided (no content), a type-mismatch (eof) error is returned when
924///   attempting to deserialize into non-null-like targets.
925/// - If the reader contains multiple documents, an error is returned suggesting the
926///   `read`/`read_with_options` iterator APIs.
927/// - If `Options::budget` is set and a limit is exceeded, an error is returned early.
928#[cfg(feature = "deserialize")]
929pub fn from_reader_with_options<'a, R: std::io::Read + 'a, T: DeserializeOwned>(
930    reader: R,
931    options: Options,
932) -> Result<T, Error> {
933    super::with_deserializer::with_deserializer_from_reader_with_options(reader, options, |de| {
934        T::deserialize(de)
935    })
936}
937
938/// Create an iterator over YAML documents from any `std::io::Read` using default options.
939///
940/// This is a convenience wrapper around [`read_with_options`] that uses the
941/// same defaults as [`Options::default`] **except** it disables the
942/// `max_reader_input_bytes` budget to better support long-lived streams.
943///
944/// - It streams the reader without loading the whole input into memory.
945/// - Each item produced by the returned iterator is one deserialized YAML document of type `T`.
946/// - Empty, untagged null-like, and explicit `!!null` documents are skipped.
947///   Application-tagged roots are deserialized, even with null payloads.
948///
949/// Generic parameters
950/// - `R`: the concrete reader type implementing [`std::io::Read`]. You almost never need to
951///   write this explicitly; the compiler will infer it from the `reader` you pass. When using
952///   turbofish, write `_` to let the compiler infer `R`.
953/// - `T`: the type to deserialize each YAML document into. Must implement [`serde::de::DeserializeOwned`].
954///
955/// Lifetimes
956/// - `'a`: the lifetime of the returned iterator, tied to the lifetime of the provided `reader`.
957///   The iterator cannot outlive the reader it was created from.
958///
959/// Limits and budget
960/// - Uses the same limits as `Options::default()` (events, nodes, nesting depth, total scalar
961///   bytes, total comment bytes) and the default alias-replay caps. The only change is that
962///   `Budget::max_reader_input_bytes` is set to `None` so the streaming iterator can handle
963///   arbitrarily long inputs. To customize these limits, call [`read_with_options`] and set
964///   `Options::budget.max_reader_input_bytes` in the provided `Options`.
965/// - Alias replay limits are also enforced with their default values to mitigate alias bombs.
966///
967/// ```rust
968/// use serde::Deserialize;
969///
970/// #[derive(Debug, Deserialize, PartialEq)]
971/// struct Simple { id: usize }
972///
973/// let yaml = b"id: 1\n---\nid: 2\n";
974/// let mut reader = std::io::Cursor::new(&yaml[..]);
975///
976/// // Type `T` is inferred from the collection target (Vec<Simple>).
977/// let values: Vec<Simple> = serde_saphyr::read(&mut reader)
978///     .map(|r| r.unwrap())
979///     .collect();
980/// assert_eq!(values.len(), 2);
981/// assert_eq!(values[0].id, 1);
982/// ```
983///
984/// Specifying only `T` with turbofish and letting `R` be inferred using `_`:
985/// ```rust
986/// use serde::Deserialize;
987///
988/// #[derive(Debug, Deserialize, PartialEq)]
989/// struct Simple { id: usize }
990///
991/// let yaml = b"id: 10\n---\nid: 20\n";
992/// let mut reader = std::io::Cursor::new(&yaml[..]);
993///
994/// // First turbofish parameter is R (reader type), `_` lets the compiler infer it.
995/// let iter = serde_saphyr::read::<_, Simple>(&mut reader);
996/// let ids: Vec<usize> = iter.map(|res| res.unwrap().id).collect();
997/// assert_eq!(ids, vec![10, 20]);
998/// ```
999///
1000/// - Each `next()` yields either `Ok(T)` for a successfully deserialized document or `Err(Error)`
1001///   if parsing or deserialization fails.
1002/// - After a **deserialization error** (e.g., type mismatch, missing field), the iterator
1003///   skips to the next document boundary (`---`) and continues.
1004/// - After a **syntax error** or **budget/alias limit exceeded**, the iterator ends.
1005/// - Null documents without an application tag are skipped and produce no items.
1006///
1007/// *Note* Some content of the next document is read before the current parsed document is emitted.
1008/// Hence, while streaming is good for safely parsing large files with multiple documents without
1009/// loading it into RAM in advance, it does not emit each document exactly
1010/// after `---`  is encountered.
1011#[cfg(feature = "deserialize")]
1012pub fn read<'a, R, T>(reader: &'a mut R) -> impl Iterator<Item = Result<T, Error>> + 'a
1013where
1014    R: Read + 'a,
1015    T: DeserializeOwned + 'a,
1016{
1017    read_with_options(
1018        reader,
1019        crate::options! {
1020            budget: crate::budget! {
1021                max_reader_input_bytes: None,
1022            },
1023        },
1024    )
1025}
1026
1027/// Create an iterator over YAML documents from any `std::io::Read`, with configurable options.
1028///
1029/// This is the multi-document counterpart to [`from_reader_with_options`]. It does not load
1030/// the entire input into memory. Instead, it streams the reader, deserializing one document
1031/// at a time into values of type `T`, yielding them through the returned iterator. Documents
1032/// that are empty, untagged null-like, or explicitly tagged `!!null` are skipped.
1033/// With `DockerComposeWithNulls`, interpolated nulls are also skipped.
1034/// Application-tagged roots are deserialized, even with null payloads.
1035/// Like [`from_reader_with_options`], BOM-marked UTF-8, UTF-16LE, and UTF-16BE
1036/// inputs are transcoded to UTF-8 internally before parsing. If no recognized
1037/// BOM is present, the input bytes are treated as UTF-8.
1038///
1039/// Generic parameters
1040/// - `R`: the concrete reader type that implements [`std::io::Read`]. You rarely need to spell
1041///   this out; it is almost always inferred from the `reader` value you pass in. When using
1042///   turbofish, you can write `_` for this parameter to let the compiler infer it.
1043/// - `T`: the type to deserialize each YAML document into. This must implement [`serde::de::DeserializeOwned`].
1044///
1045/// Lifetimes
1046/// - `'a`: the lifetime of the returned iterator. It is tied to the lifetime of the provided
1047///   `reader` value because the iterator borrows internal state that references the reader.
1048///   In practice, this means the iterator cannot outlive the reader it was created from.
1049///
1050/// Limits and budget
1051/// - All parsing limits configured via [`Options::budget`] (such as maximum events, nodes,
1052///   nesting depth, total scalar bytes, total comment bytes) are enforced while streaming. The
1053///   reader input-byte cap is also enforced via `Budget::max_reader_input_bytes` (256 MiB by
1054///   default). Set this to `None` if the stream may legitimately run without a fixed byte cap.
1055/// - Alias replay limits from [`Options::alias_limits`] are also enforced to mitigate alias bombs.
1056///
1057/// ```rust
1058/// use serde::Deserialize;
1059///
1060/// #[derive(Debug, Deserialize, PartialEq)]
1061/// struct Simple { id: usize }
1062///
1063/// let yaml = b"id: 1\n---\nid: 2\n";
1064/// let mut reader = std::io::Cursor::new(&yaml[..]);
1065///
1066/// // Type `T` is inferred from the collection target (Vec<Simple>).
1067/// let values: Vec<Simple> = serde_saphyr::read(&mut reader)
1068///     .map(|r| r.unwrap())
1069///     .collect();
1070/// assert_eq!(values.len(), 2);
1071/// assert_eq!(values[0].id, 1);
1072/// ```
1073///
1074/// Specifying only `T` with turbofish and letting `R` be inferred using `_`:
1075/// ```rust
1076/// use serde::Deserialize;
1077///
1078/// #[derive(Debug, Deserialize, PartialEq)]
1079/// struct Simple { id: usize }
1080///
1081/// let yaml = b"id: 10\n---\nid: 20\n";
1082/// let mut reader = std::io::Cursor::new(&yaml[..]);
1083///
1084/// // First turbofish parameter is R (reader type) which we let the compiler infer via `_`.
1085/// let iter = serde_saphyr::read_with_options::<_, Simple>(&mut reader, serde_saphyr::Options::default());
1086/// let ids: Vec<usize> = iter.map(|res| res.unwrap().id).collect();
1087/// assert_eq!(ids, vec![10, 20]);
1088/// ```
1089///
1090/// - Each `next()` yields either `Ok(T)` for a successfully deserialized document or `Err(Error)`
1091///   if parsing or deserialization fails.
1092/// - After a **deserialization error** (e.g., type mismatch, missing field), the iterator
1093///   automatically recovers by skipping to the next document boundary (`---`) and continues
1094///   iteration. This allows processing subsequent valid documents even when some fail.
1095/// - After a **syntax error** or **budget/alias limit exceeded**, the iterator ends because
1096///   the parser state may be unrecoverable.
1097/// - Null documents without an application tag are skipped and produce no items.
1098#[cfg(feature = "deserialize")]
1099pub fn read_with_options<'a, R, T>(
1100    reader: &'a mut R, // iterator must not outlive this borrow
1101    options: Options,
1102) -> impl Iterator<Item = Result<T, Error>> + 'a
1103where
1104    R: Read + 'a,
1105    T: DeserializeOwned + 'a,
1106{
1107    struct ReadIter<'a, T> {
1108        src: LiveEvents<'a>, // borrows from `reader`
1109        cfg: crate::de::Cfg,
1110        finished: bool,
1111        _marker: std::marker::PhantomData<T>,
1112    }
1113
1114    impl<'a, T> Iterator for ReadIter<'a, T>
1115    where
1116        T: DeserializeOwned + 'a,
1117    {
1118        type Item = Result<T, Error>;
1119
1120        fn next(&mut self) -> Option<Self::Item> {
1121            if self.finished {
1122                return None;
1123            }
1124            loop {
1125                match self.src.peek() {
1126                    Ok(Some(_)) => {
1127                        let res = match deserialize_document_unless_null(
1128                            &mut self.src,
1129                            self.cfg,
1130                            |de| T::deserialize(de),
1131                            |e, _| e,
1132                        ) {
1133                            Ok(None) => continue,
1134                            Ok(Some(value)) => Ok(value),
1135                            Err(e) if e.is_property_interpolation_budget_breach() => {
1136                                // Property interpolation records breaches across documents;
1137                                // ordinary per-document budgets keep their recovery path.
1138                                self.finished = true;
1139                                let _ = self.src.finish();
1140                                return Some(Err(e));
1141                            }
1142                            // Interpolation errors recover at the next document just like
1143                            // errors encountered while deserializing the document's value.
1144                            Err(e) => Err(e),
1145                        };
1146                        if res.is_err() {
1147                            // After a deserialization error, skip remaining events in the
1148                            // current document and try to recover at the next document boundary.
1149                            // If no next document is found, mark as finished.
1150                            if !self.src.skip_to_next_document() {
1151                                self.finished = true;
1152                                let _ = self.src.finish();
1153                            }
1154                        }
1155                        return Some(res);
1156                    }
1157                    Ok(None) => {
1158                        self.finished = true;
1159                        if let Err(e) = self.src.finish() {
1160                            return Some(Err(e));
1161                        }
1162                        return None;
1163                    }
1164                    Err(e) => {
1165                        self.finished = true;
1166                        let _ = self.src.finish();
1167                        return Some(Err(e));
1168                    }
1169                }
1170            }
1171        }
1172    }
1173
1174    let cfg = crate::de::Cfg::from_options(&options);
1175    let src = LiveEvents::from_reader(reader, options, EnforcingPolicy::PerDocument);
1176
1177    ReadIter::<T> {
1178        src,
1179        cfg,
1180        finished: false,
1181        _marker: std::marker::PhantomData,
1182    }
1183}