Skip to main content

serde_saphyr/de/
with_deserializer.rs

1use crate::budget::EnforcingPolicy;
2use crate::live_events::LiveEvents;
3
4use super::api::{ReaderSnippetContext, StrSnippetContext};
5use super::{Cfg, Error, Events, Options, YamlDeserializer};
6
7pub(crate) fn run_with_document_scope<'de, R, F, W, P>(
8    src: &mut LiveEvents<'de>,
9    f: F,
10    wrap_err: W,
11    synthesized_null_should_be_eof: P,
12) -> Result<R, Error>
13where
14    F: FnOnce(&mut LiveEvents<'de>) -> Result<R, Error>,
15    W: Fn(Error, &LiveEvents<'de>) -> Error,
16    P: Fn(&Error) -> bool,
17{
18    let value_res = crate::anchor_store::with_document_scope(|| {
19        crate::properties_redaction::with_interp_redaction_scope(|| f(src))
20    });
21    match value_res {
22        Ok(v) => {
23            src.finalize_document_budget()
24                .map_err(|e| wrap_err(e, src))?;
25            Ok(v)
26        }
27        Err(e) => {
28            if src.synthesized_null_emitted() && synthesized_null_should_be_eof(&e) {
29                // If the only thing in the input was an empty document (synthetic null),
30                // surface this as an EOF error to preserve expected error semantics
31                // for incompatible target types (e.g., bool).
32                Err(wrap_err(
33                    Error::eof().with_location(src.last_location()),
34                    src,
35                ))
36            } else {
37                Err(wrap_err(e, src))
38            }
39        }
40    }
41}
42
43pub(crate) fn deserialize_with_scope_and_null_policy<'de, R, F, W, P>(
44    src: &mut LiveEvents<'de>,
45    cfg: Cfg,
46    f: F,
47    wrap_err: W,
48    synthesized_null_should_be_eof: P,
49) -> Result<R, Error>
50where
51    for<'e> F: FnOnce(crate::Deserializer<'de, 'e>) -> Result<R, Error>,
52    W: Fn(Error, &LiveEvents<'de>) -> Error,
53    P: Fn(&Error) -> bool,
54{
55    run_with_document_scope(
56        src,
57        |src| super::with_root_redaction(YamlDeserializer::new(src, cfg), f),
58        wrap_err,
59        synthesized_null_should_be_eof,
60    )
61}
62
63pub(crate) fn deserialize_with_scope<'de, R, F, W>(
64    src: &mut LiveEvents<'de>,
65    cfg: Cfg,
66    f: F,
67    wrap_err: W,
68) -> Result<R, Error>
69where
70    for<'e> F: FnOnce(crate::Deserializer<'de, 'e>) -> Result<R, Error>,
71    W: Fn(Error, &LiveEvents<'de>) -> Error,
72{
73    deserialize_with_scope_and_null_policy(src, cfg, f, wrap_err, |_| true)
74}
75
76/// Deserialize one stream document, returning `None` when its root is null.
77/// Keeps deserialization, error wrapping, and budget finalization within the document scope.
78pub(crate) fn deserialize_document_unless_null<'de, R, F, W>(
79    src: &mut LiveEvents<'de>, // Event source positioned at the document's root.
80    cfg: Cfg,                  // Deserialization policies.
81    f: F,                      // Callback for non-null documents.
82    wrap_err: W,               // Attaches source context to errors.
83) -> Result<Option<R>, Error>
84where
85    for<'e> F: FnOnce(crate::Deserializer<'de, 'e>) -> Result<R, Error>,
86    W: Fn(Error, &LiveEvents<'de>) -> Error,
87{
88    run_with_document_scope(
89        src,
90        |src| {
91            super::deserializer::with_root_redaction_unless_null(YamlDeserializer::new(src, cfg), f)
92        },
93        wrap_err,
94        |_| false,
95    )
96}
97
98pub(crate) fn enforce_single_document_and_finish<'de, W>(
99    src: &mut LiveEvents<'de>,
100    multiple_docs_hint: &'static str,
101    wrap_err: W,
102) -> Result<(), Error>
103where
104    W: Fn(Error, &LiveEvents<'de>) -> Error,
105{
106    // After finishing first document, peek ahead to detect either another document/content
107    // or trailing garbage. A document end, explicit "..." or implicit, does not end the
108    // stream: what follows is parsed as usual, and a parser error there is reported.
109    match src.peek() {
110        Ok(Some(_)) => {
111            return Err(wrap_err(
112                Error::multiple_documents(multiple_docs_hint).with_location(src.last_location()),
113                src,
114            ));
115        }
116        Ok(None) => {}
117        Err(e) => return Err(wrap_err(e, src)),
118    }
119
120    src.finish().map_err(|e| wrap_err(e, src))
121}
122
123/// Create a streaming [`crate::Deserializer`] for a YAML string and run a closure against it.
124///
125/// This is useful for tooling that needs access to the underlying Serde deserializer,
126/// such as wrappers that report unknown/ignored fields (e.g. the `serde_ignored` crate)
127/// or wrappers that augment error paths.
128///
129/// The deserializer borrows internal parsing state, so it cannot be returned directly.
130/// Instead, you provide a closure `f` that performs the desired deserialization.
131pub fn with_deserializer_from_str_with_options<'de, R, F>(
132    input: &'de str,
133    options: Options,
134    f: F,
135) -> Result<R, Error>
136where
137    for<'e> F: FnOnce(crate::Deserializer<'de, 'e>) -> Result<R, Error>,
138{
139    // A leading BOM is kept: granit-parser skips it, and spans then stay relative to `input`.
140    let with_snippet = options.with_snippet;
141    let crop_radius = options.crop_radius;
142
143    let cfg = Cfg::from_options(&options);
144    // Do not stop at DocumentEnd; we'll probe for trailing content/errors explicitly.
145    let mut src = LiveEvents::from_str(input, options);
146    let snippet_ctx = StrSnippetContext::new(input, with_snippet, crop_radius);
147
148    let wrap_err = |e, src: &LiveEvents<'de>| snippet_ctx.attach_snippet(e, src);
149
150    let value = deserialize_with_scope(&mut src, cfg, f, wrap_err)?;
151    enforce_single_document_and_finish(
152        &mut src,
153        "use from_str_multiple or from_str_multiple_with_options",
154        wrap_err,
155    )?;
156    Ok(value)
157}
158
159/// Convenience wrapper around [`with_deserializer_from_str_with_options`] using
160/// [`Options::default`].
161pub fn with_deserializer_from_str<'de, R, F>(input: &'de str, f: F) -> Result<R, Error>
162where
163    for<'e> F: FnOnce(crate::Deserializer<'de, 'e>) -> Result<R, Error>,
164{
165    with_deserializer_from_str_with_options(input, Options::default(), f)
166}
167
168/// Create a streaming [`crate::Deserializer`] for a UTF-8 byte slice and run a closure against it.
169///
170/// This is equivalent to [`with_deserializer_from_str`], but validates the input is UTF-8.
171pub fn with_deserializer_from_slice<'de, R, F>(bytes: &'de [u8], f: F) -> Result<R, Error>
172where
173    for<'e> F: FnOnce(crate::Deserializer<'de, 'e>) -> Result<R, Error>,
174{
175    with_deserializer_from_slice_with_options(bytes, Options::default(), f)
176}
177
178/// Create a streaming [`crate::Deserializer`] for a UTF-8 byte slice with configurable [`Options`]
179/// and run a closure against it.
180pub fn with_deserializer_from_slice_with_options<'de, R, F>(
181    bytes: &'de [u8],
182    options: Options,
183    f: F,
184) -> Result<R, Error>
185where
186    for<'e> F: FnOnce(crate::Deserializer<'de, 'e>) -> Result<R, Error>,
187{
188    let s = std::str::from_utf8(bytes).map_err(|_| Error::InvalidUtf8Input)?;
189    with_deserializer_from_str_with_options(s, options, f)
190}
191
192/// Create a streaming [`crate::Deserializer`] for any [`std::io::Read`] and run a closure against it.
193///
194/// This is the reader-based counterpart to [`with_deserializer_from_str`]. It consumes a
195/// byte-oriented reader and streams events into the deserializer. The reader accepts BOM-marked
196/// UTF-8, UTF-16LE, and UTF-16BE. If no recognized BOM is present, the input bytes are
197/// treated as UTF-8.
198pub fn with_deserializer_from_reader<R, Out, F>(reader: R, f: F) -> Result<Out, Error>
199where
200    for<'de, 'e> F: FnOnce(crate::Deserializer<'de, 'e>) -> Result<Out, Error>,
201    R: std::io::Read,
202{
203    with_deserializer_from_reader_with_options(reader, Options::default(), f)
204}
205
206/// Create a streaming [`crate::Deserializer`] for any [`std::io::Read`] with configurable [`Options`]
207/// and run a closure against it.
208pub fn with_deserializer_from_reader_with_options<R, Out, F>(
209    reader: R,
210    options: Options,
211    f: F,
212) -> Result<Out, Error>
213where
214    for<'de, 'e> F: FnOnce(crate::Deserializer<'de, 'e>) -> Result<Out, Error>,
215    R: std::io::Read,
216{
217    let with_snippet = options.with_snippet;
218    let crop_radius = options.crop_radius;
219    let cfg = Cfg::from_options(&options);
220    let (snippet_ctx, ring_handle) = ReaderSnippetContext::new(reader, with_snippet, crop_radius);
221    let mut src = LiveEvents::from_reader(ring_handle, options, EnforcingPolicy::AllContent);
222
223    let wrap_err = |e, src: &LiveEvents<'_>| snippet_ctx.attach_snippet(e, src);
224
225    let value = deserialize_with_scope(&mut src, cfg, f, wrap_err)?;
226    enforce_single_document_and_finish(
227        &mut src,
228        "use read or read_with_options to obtain the iterator",
229        wrap_err,
230    )?;
231    Ok(value)
232}