use std::io::Read;
use serde::de::DeserializeOwned;
use super::{Error, Ev, Events, Options, ring_reader};
use crate::budget::EnforcingPolicy;
use crate::live_events::LiveEvents;
use crate::parse_scalars::scalar_is_nullish;
use crate::properties_redaction::with_interp_redaction_scope;
#[cfg(all(feature = "deserialize", feature = "include"))]
pub(crate) fn resolver_from_options<'a>(
options: Options,
) -> Option<Box<crate::input_source::IncludeResolver<'a>>> {
options.include_resolver.clone().map(|rc_refcell| {
Box::new(move |req: crate::input_source::IncludeRequest<'_>| rc_refcell.borrow_mut()(req))
as Box<crate::input_source::IncludeResolver<'a>>
})
}
#[cfg(feature = "deserialize")]
pub fn from_str<'de, T>(input: &'de str) -> Result<T, Error>
where
T: serde::de::Deserialize<'de>,
{
from_str_with_options(input, Options::default())
}
#[allow(deprecated)]
#[cfg(feature = "deserialize")]
fn from_str_with_options_impl<'de, T>(input: &'de str, options: Options) -> Result<T, Error>
where
T: serde::de::Deserialize<'de>,
{
let input = if let Some(rest) = input.strip_prefix('\u{FEFF}') {
rest
} else {
input
};
let with_snippet = options.with_snippet;
let crop_radius = options.crop_radius;
let cfg = crate::de::Cfg::from_options(&options);
let mut src = LiveEvents::from_str(input, options, false);
let value_res = crate::anchor_store::with_document_scope(|| {
with_interp_redaction_scope(|| {
crate::de::with_root_redaction(crate::de::YamlDeserializer::new(&mut src, cfg), |de| {
T::deserialize(de)
})
})
});
let value = match value_res {
Ok(v) => v,
Err(e) => {
if src.synthesized_null_emitted() {
let err = Error::eof().with_location(src.last_location());
return Err(maybe_with_snippet_from_events(
err,
input,
&src,
with_snippet,
crop_radius,
));
}
return Err(maybe_with_snippet_from_events(
e,
input,
&src,
with_snippet,
crop_radius,
));
}
};
match src.peek() {
Ok(Some(_)) => {
let err = Error::multiple_documents("use from_multiple or from_multiple_with_options")
.with_location(src.last_location());
return Err(maybe_with_snippet_from_events(
err,
input,
&src,
with_snippet,
crop_radius,
));
}
Ok(None) => {}
Err(e) => {
if src.seen_doc_end() {
} else {
return Err(maybe_with_snippet_from_events(
e,
input,
&src,
with_snippet,
crop_radius,
));
}
}
}
if let Err(e) = src.finish() {
return Err(maybe_with_snippet_from_events(
e,
input,
&src,
with_snippet,
crop_radius,
));
}
Ok(value)
}
#[allow(deprecated)]
#[cfg(feature = "deserialize")]
pub fn from_str_with_options<'de, T>(input: &'de str, options: Options) -> Result<T, Error>
where
T: serde::de::Deserialize<'de>,
{
from_str_with_options_impl(input, options)
}
#[cfg(feature = "deserialize")]
pub(crate) fn maybe_with_snippet(
err: Error,
input: &str,
with_snippet: bool,
crop_radius: usize,
) -> Error {
if !(with_snippet && crop_radius > 0 && err.location().is_some()) {
return err;
}
err.with_snippet(input, crop_radius)
}
#[cfg(feature = "deserialize")]
pub(crate) struct RootFragment<'a> {
pub text: &'a str,
pub start_line: usize,
pub source_name: &'a str,
}
#[cfg(feature = "deserialize")]
pub(crate) struct ReaderSnippetContext<R> {
shared_ring: ring_reader::SharedRingReader<R>,
with_snippet: bool,
crop_radius: usize,
}
#[cfg(feature = "deserialize")]
impl<R: Read> ReaderSnippetContext<R> {
pub(crate) fn new(
reader: R,
with_snippet: bool,
crop_radius: usize,
) -> (Self, ring_reader::SharedRingReaderHandle<R>) {
let shared_ring = ring_reader::SharedRingReader::new(reader);
let ring_handle = ring_reader::SharedRingReaderHandle::new(&shared_ring);
(
Self {
shared_ring,
with_snippet,
crop_radius,
},
ring_handle,
)
}
pub(crate) fn attach_snippet(&self, err: Error, src: &LiveEvents<'_>) -> Error {
if !self.with_snippet || self.crop_radius == 0 {
return err;
}
match self.shared_ring.get_recent() {
Ok(snapshot) => {
let text = String::from_utf8_lossy(&snapshot.bytes);
let root = RootFragment {
text: text.as_ref(),
start_line: snapshot.start_line,
source_name: "input",
};
maybe_with_snippet_from_events_and_root_fragment(
err,
Some(&root),
text.as_ref(),
src,
self.with_snippet,
self.crop_radius,
)
}
Err(_) => err,
}
}
}
#[cfg(all(feature = "deserialize", feature = "include"))]
fn with_root_additional_snippet(
err: Error,
root: Option<&RootFragment<'_>>,
input: &str,
location: &crate::Location,
crop_radius: usize,
) -> Error {
match root {
Some(root) => err.with_additional_snippet_offset_named(
root.text,
root.start_line,
root.source_name,
location,
crop_radius,
),
None => err.with_additional_snippet_named(input, "input", location, crop_radius),
}
}
#[cfg(all(feature = "deserialize", feature = "include"))]
fn recorded_source_snippet_chain<'a>(
events: &'a crate::live_events::LiveEvents<'_>,
location: &crate::Location,
) -> Option<Vec<&'a crate::include_stack::RecordedSource>> {
let chain = events.recorded_source_chain(location.source_id());
chain.first()?.text.as_deref()?;
Some(chain)
}
#[cfg(all(feature = "deserialize", feature = "include"))]
fn with_recorded_source_snippets(
err: Error,
root: Option<&RootFragment<'_>>,
input: &str,
chain: &[&crate::include_stack::RecordedSource],
crop_radius: usize,
) -> Error {
let Some(current) = chain.first() else {
return with_root_or_input_snippet(err, root, input, crop_radius);
};
let Some(source_text) = current.text.as_deref() else {
return with_root_or_input_snippet(err, root, input, crop_radius);
};
let mut err_with_snippet =
err.with_snippet_named(source_text, current.name.as_str(), crop_radius);
for window in chain.windows(2) {
let child = window[0];
let parent = window[1];
if child.include_location == crate::Location::UNKNOWN {
continue;
}
match parent.text.as_deref() {
Some(parent_text) => {
err_with_snippet = err_with_snippet.with_additional_snippet_named(
parent_text,
parent.name.as_str(),
&child.include_location,
crop_radius,
);
}
None if parent.parent_source_id.is_none() => {
err_with_snippet = with_root_additional_snippet(
err_with_snippet,
root,
input,
&child.include_location,
crop_radius,
);
}
None => {}
}
}
err_with_snippet
}
#[cfg(all(feature = "deserialize", feature = "include"))]
fn with_root_or_input_snippet(
err: Error,
root: Option<&RootFragment<'_>>,
input: &str,
crop_radius: usize,
) -> Error {
match root {
Some(root) => {
err.with_snippet_offset_named(root.text, root.start_line, root.source_name, crop_radius)
}
None => maybe_with_snippet(err, input, true, crop_radius),
}
}
#[cfg(feature = "deserialize")]
pub(crate) fn maybe_with_snippet_from_events_and_root_fragment(
err: Error,
root: Option<&RootFragment<'_>>,
input: &str,
#[allow(unused_variables)] events: &crate::live_events::LiveEvents<'_>,
with_snippet: bool,
crop_radius: usize,
) -> Error {
if !(with_snippet && crop_radius > 0 && err.location().is_some()) {
return err;
}
#[cfg(feature = "include")]
if let Some(loc) = err.location()
&& let Some(chain) = recorded_source_snippet_chain(events, &loc)
{
return with_recorded_source_snippets(err, root, input, &chain, crop_radius);
}
match root {
Some(root) => {
err.with_snippet_offset_named(root.text, root.start_line, root.source_name, crop_radius)
}
None => maybe_with_snippet(err, input, with_snippet, crop_radius),
}
}
#[cfg(feature = "deserialize")]
pub(crate) fn maybe_with_snippet_from_events(
err: Error,
input: &str,
#[allow(unused_variables)] events: &crate::live_events::LiveEvents<'_>,
with_snippet: bool,
crop_radius: usize,
) -> Error {
maybe_with_snippet_from_events_and_root_fragment(
err,
None,
input,
events,
with_snippet,
crop_radius,
)
}
#[cfg(feature = "deserialize")]
pub fn from_multiple<T: DeserializeOwned>(input: &str) -> Result<Vec<T>, Error> {
from_multiple_with_options(input, Options::default())
}
#[allow(deprecated)]
#[cfg(feature = "deserialize")]
pub fn from_multiple_with_options<T: DeserializeOwned>(
input: &str,
options: Options,
) -> Result<Vec<T>, Error> {
let input = if let Some(rest) = input.strip_prefix('\u{FEFF}') {
rest
} else {
input
};
let with_snippet = options.with_snippet;
let crop_radius = options.crop_radius;
let cfg = crate::de::Cfg::from_options(&options);
let mut src = LiveEvents::from_str(input, options, false);
let mut values = Vec::new();
loop {
match src.peek()? {
Some(Ev::Scalar {
value: s,
style,
tag,
..
}) if *tag == crate::tags::SfTag::Null
|| (*tag != crate::tags::SfTag::String && scalar_is_nullish(s, style)) =>
{
let _ = src.next()?; continue;
}
Some(_) => {
let value_res = crate::anchor_store::with_document_scope(|| {
with_interp_redaction_scope(|| {
crate::de::with_root_redaction(
crate::de::YamlDeserializer::new(&mut src, cfg),
|de| T::deserialize(de),
)
})
});
let value = match value_res {
Ok(v) => v,
Err(e) => {
return Err(maybe_with_snippet_from_events(
e,
input,
&src,
with_snippet,
crop_radius,
));
}
};
values.push(value);
}
None => break,
}
}
if let Err(e) = src.finish() {
return Err(maybe_with_snippet_from_events(
e,
input,
&src,
with_snippet,
crop_radius,
));
}
Ok(values)
}
#[cfg(feature = "deserialize")]
pub fn from_slice<'de, T>(bytes: &'de [u8]) -> Result<T, Error>
where
T: serde::Deserialize<'de>,
{
from_slice_with_options(bytes, Options::default())
}
#[cfg(feature = "deserialize")]
pub fn from_slice_with_options<'de, T>(bytes: &'de [u8], options: Options) -> Result<T, Error>
where
T: serde::Deserialize<'de>,
{
let s = std::str::from_utf8(bytes).map_err(|_| Error::InvalidUtf8Input)?;
from_str_with_options(s, options)
}
#[cfg(feature = "deserialize")]
pub fn from_slice_multiple<T: DeserializeOwned>(bytes: &[u8]) -> Result<Vec<T>, Error> {
from_slice_multiple_with_options(bytes, Options::default())
}
#[cfg(feature = "deserialize")]
pub fn from_slice_multiple_with_options<T: DeserializeOwned>(
bytes: &[u8],
options: Options,
) -> Result<Vec<T>, Error> {
let s = std::str::from_utf8(bytes).map_err(|_| Error::InvalidUtf8Input)?;
from_multiple_with_options(s, options)
}
#[cfg(feature = "deserialize")]
pub fn from_reader<'a, R: std::io::Read + 'a, T: DeserializeOwned>(reader: R) -> Result<T, Error> {
from_reader_with_options(reader, Options::default())
}
#[allow(deprecated)]
#[cfg(feature = "deserialize")]
pub fn from_reader_with_options<'a, R: std::io::Read + 'a, T: DeserializeOwned>(
reader: R,
options: Options,
) -> Result<T, Error> {
let cfg = crate::de::Cfg::from_options(&options);
let (snippet_ctx, ring_handle) =
ReaderSnippetContext::new(reader, options.with_snippet, options.crop_radius);
let mut src = LiveEvents::from_reader(ring_handle, options, false, EnforcingPolicy::AllContent);
let value_res = crate::anchor_store::with_document_scope(|| {
with_interp_redaction_scope(|| {
crate::de::with_root_redaction(crate::de::YamlDeserializer::new(&mut src, cfg), |de| {
T::deserialize(de)
})
})
});
let value = match value_res {
Ok(v) => v,
Err(e) => {
if src.synthesized_null_emitted() {
return Err(snippet_ctx
.attach_snippet(Error::eof().with_location(src.last_location()), &src));
}
return Err(snippet_ctx.attach_snippet(e, &src));
}
};
match src.peek() {
Ok(Some(_)) => {
return Err(snippet_ctx.attach_snippet(
Error::multiple_documents("use read or read_with_options to obtain the iterator")
.with_location(src.last_location()),
&src,
));
}
Ok(None) => {}
Err(e) => {
if src.seen_doc_end() {
} else {
return Err(snippet_ctx.attach_snippet(e, &src));
}
}
}
if let Err(e) = src.finish() {
return Err(snippet_ctx.attach_snippet(e, &src));
}
Ok(value)
}
#[cfg(feature = "deserialize")]
pub fn read<'a, R, T>(reader: &'a mut R) -> Box<dyn Iterator<Item = Result<T, Error>> + 'a>
where
R: Read + 'a,
T: DeserializeOwned + 'a,
{
Box::new(read_with_options(
reader,
crate::options! {
budget: crate::budget! {
max_reader_input_bytes: None,
},
},
))
}
#[allow(deprecated)]
#[cfg(feature = "deserialize")]
pub fn read_with_options<'a, R, T>(
reader: &'a mut R, options: Options,
) -> impl Iterator<Item = Result<T, Error>> + 'a
where
R: Read + 'a,
T: DeserializeOwned + 'a,
{
struct ReadIter<'a, T> {
src: LiveEvents<'a>, cfg: crate::de::Cfg,
finished: bool,
_marker: std::marker::PhantomData<T>,
}
impl<'a, T> Iterator for ReadIter<'a, T>
where
T: DeserializeOwned + 'a,
{
type Item = Result<T, Error>;
fn next(&mut self) -> Option<Self::Item> {
if self.finished {
return None;
}
loop {
match self.src.peek() {
Ok(Some(Ev::Scalar { value, style, .. }))
if scalar_is_nullish(value, style) =>
{
let _ = self.src.next();
continue;
}
Ok(Some(_)) => {
let res = crate::anchor_store::with_document_scope(|| {
with_interp_redaction_scope(|| {
crate::de::with_root_redaction(
crate::de::YamlDeserializer::new(&mut self.src, self.cfg),
|de| T::deserialize(de),
)
})
});
if res.is_err() {
if !self.src.skip_to_next_document() {
self.finished = true;
}
}
return Some(res);
}
Ok(None) => {
self.finished = true;
if let Err(e) = self.src.finish() {
return Some(Err(e));
}
return None;
}
Err(e) => {
self.finished = true;
let _ = self.src.finish();
return Some(Err(e));
}
}
}
}
}
let cfg = crate::de::Cfg::from_options(&options);
let src = LiveEvents::from_reader(reader, options, false, EnforcingPolicy::PerDocument);
ReadIter::<T> {
src,
cfg,
finished: false,
_marker: std::marker::PhantomData,
}
}