use std::borrow::Cow;
use std::cell::RefCell;
use std::io::Read;
use std::rc::Rc;
use oneyaml_core::{Diagnostic, Settings, code};
use oneyaml_syntax::Encoding;
use serde::de::{self, DeserializeOwned, Visitor};
use super::Core;
use crate::construct::expand::Expander;
use crate::construct::feed::Feed;
use crate::construct::settings::read_diagnostics;
use crate::construct::{Ev, Node, Pull, Source, Tag};
use crate::diag_cap::Capped;
use crate::error::Error;
use crate::resolve::Resolver;
type Stream<'de> = Core<'de, Expander<'de, Feed<'de>>>;
pub(crate) type SourceCore<'de, S> = Core<'de, Expander<'de, S>>;
fn open<'de>(src: &'de str, settings: &Settings) -> Result<(Stream<'de>, Vec<Diagnostic>), Error> {
let feed = Feed::new(oneyaml_syntax::events_with(src, settings), src.len());
open_source(feed, settings, settings.get_limits().get_max_diagnostics())
}
const DISCARDED_WARNINGS: usize = 16;
fn discarding_limit(settings: &Settings) -> usize {
DISCARDED_WARNINGS.min(settings.get_limits().get_max_diagnostics())
}
fn open_source<'de, S: Source<'de>>(
mut source: S,
settings: &Settings,
limit: usize,
) -> Result<(SourceCore<'de, S>, Vec<Diagnostic>), Error> {
let setting_warnings = read_diagnostics(settings)?;
let collector = || Capped::with_keep(limit.saturating_add(1));
source.collect_warnings(collector());
let expander = Expander::new(source, settings.get_limits());
let shared = expander.shared();
let mut core = Core::new(expander, settings, shared);
core.warnings = collector();
Ok((core, setting_warnings))
}
fn warnings<'de, S: Source<'de>>(
settings: &mut Vec<Diagnostic>,
core: &mut SourceCore<'de, S>,
limit: usize,
) -> (Vec<Diagnostic>, usize) {
let mut all = std::mem::take(settings);
let (mut rest, a) = core.src.p.source_mut().take_warnings();
let (mut more, b) = core.warnings.take();
rest.append(&mut more);
rest.sort_by_key(|d| d.primary.span.start);
all.append(&mut rest);
let extra = a.and(b);
let found = all.len().saturating_add(extra.count);
crate::diag_cap::cap(&mut all, extra, limit);
(all, found)
}
fn single<'de, S: Source<'de>, R>(
core: &mut SourceCore<'de, S>,
settings: &Settings,
f: impl FnOnce(&mut SourceCore<'de, S>) -> Result<R, Error>,
) -> Result<R, Error> {
let ev = core.src.next()?;
match ev.node {
Node::StreamEnd => {
let empty = Node::Scalar {
value: Cow::Borrowed(""),
plain: true,
tag: Tag::Absent,
};
core.src.push_front(Ev::new(empty, ev.span));
f(core)
}
Node::DocStart { yaml11 } => {
core.resolver = Resolver::for_document(settings, yaml11);
let value = f(core)?;
end_document(core)?;
let next = core.src.next()?;
match next.node {
Node::StreamEnd => Ok(value),
_ => Err(Error::new(
code::MULTIPLE_DOCUMENTS,
"the input has more than one document",
Some(next.span),
)
.with_help("use oneyaml::Deserializer to read each document")),
}
}
_ => Err(Error::internal("document expected", Some(ev.span))),
}
}
fn end_document<'de, S: Source<'de>>(core: &mut SourceCore<'de, S>) -> Result<(), Error> {
core.check_unpoisoned()?;
let ev = core.src.next()?;
match ev.node {
Node::DocEnd => Ok(()),
_ => Err(Error::internal("document end expected", Some(ev.span))),
}
}
pub fn from_str<'de, T: de::Deserialize<'de>>(s: &'de str) -> Result<T, Error> {
from_str_with(s, &Settings::new())
}
pub fn from_str_with<'de, T: de::Deserialize<'de>>(
s: &'de str,
settings: &Settings,
) -> Result<T, Error> {
from_str_discarding(s, settings)
}
pub fn from_str_with_diagnostics<'de, T: de::Deserialize<'de>>(
s: &'de str,
settings: &Settings,
) -> Result<(T, Vec<Diagnostic>), Error> {
let feed = Feed::new(oneyaml_syntax::events_with(s, settings), s.len());
from_source(feed, s, settings, None)
}
fn from_str_discarding<'de, T: de::Deserialize<'de>>(
s: &'de str,
settings: &Settings,
) -> Result<T, Error> {
let feed = || Feed::new(oneyaml_syntax::events_with(s, settings), s.len());
read_discarding(feed, || Cow::Borrowed(s), settings, None)
}
pub(crate) fn from_source<'de, S: Source<'de>, T: de::Deserialize<'de>>(
source: S,
text: &'de str,
settings: &Settings,
lines: Option<&std::sync::OnceLock<oneyaml_core::LineIndex>>,
) -> Result<(T, Vec<Diagnostic>), Error> {
from_source_lazy(source, || std::borrow::Cow::Borrowed(text), settings, lines)
}
pub(crate) fn from_source_lazy<'de, 't, S: Source<'de>, T: de::Deserialize<'de>>(
source: S,
text: impl FnOnce() -> std::borrow::Cow<'t, str>,
settings: &Settings,
lines: Option<&std::sync::OnceLock<oneyaml_core::LineIndex>>,
) -> Result<(T, Vec<Diagnostic>), Error> {
let limit = settings.get_limits().get_max_diagnostics();
read_one(source, settings, limit, true).map_err(|(e, w)| fail(e, w, text, lines))
}
fn fail<'t>(
e: Error,
w: Vec<Diagnostic>,
text: impl FnOnce() -> std::borrow::Cow<'t, str>,
lines: Option<&std::sync::OnceLock<oneyaml_core::LineIndex>>,
) -> Error {
let text = text();
let e = match lines {
Some(l) => e.locate(l.get_or_init(|| oneyaml_core::LineIndex::new(&text))),
None => e,
};
e.finish(Some(&text), w)
}
type Failed = (Error, Vec<Diagnostic>);
fn read_one<'de, S: Source<'de>, T: de::Deserialize<'de>>(
source: S,
settings: &Settings,
limit: usize,
ok_warnings: bool,
) -> Result<(T, Vec<Diagnostic>), Failed> {
let (mut core, mut sw) = match open_source(source, settings, limit) {
Ok(x) => x,
Err(e) => return Err((e, Vec::new())),
};
match single(&mut core, settings, |c| T::deserialize(c)) {
Ok(v) if ok_warnings => Ok((v, warnings(&mut sw, &mut core, limit).0)),
Ok(v) => Ok((v, Vec::new())),
Err(e) => Err((e, warnings(&mut sw, &mut core, limit).0)),
}
}
pub(crate) fn read_discarding<'de, 't, S: Source<'de>, T: de::Deserialize<'de>>(
source: impl FnOnce() -> S,
text: impl FnOnce() -> std::borrow::Cow<'t, str>,
settings: &Settings,
lines: Option<&std::sync::OnceLock<oneyaml_core::LineIndex>>,
) -> Result<T, Error> {
read_one(source(), settings, discarding_limit(settings), false)
.map(|(v, _)| v)
.map_err(|(e, w)| fail(e, w, text, lines))
}
pub fn from_slice<T: DeserializeOwned>(bytes: &[u8]) -> Result<T, Error> {
from_slice_with(bytes, &Settings::new())
}
pub fn from_slice_with<T: DeserializeOwned>(bytes: &[u8], settings: &Settings) -> Result<T, Error> {
match oneyaml_syntax::decode_strict(bytes) {
Ok(decoded) => from_str_with(decoded.text(), settings),
Err(failed) => {
let (d, text) = *failed;
Err(Error::from_diagnostic(d).finish(Some(&text), Vec::new()))
}
}
}
pub fn from_reader<R: Read, T: DeserializeOwned>(reader: R) -> Result<T, Error> {
from_reader_with(reader, &Settings::new())
}
fn encoded_cap(encoding: Encoding, limit: usize) -> u64 {
let (per_byte, bom) = match encoding {
Encoding::Utf8 => (1u64, 3u64),
Encoding::Utf16Le | Encoding::Utf16Be => (2, 2),
Encoding::Utf32Le | Encoding::Utf32Be => (4, 4),
_ => (4, 4),
};
u64::try_from(limit)
.unwrap_or(u64::MAX)
.saturating_mul(per_byte)
.saturating_add(bom)
}
pub fn from_reader_with<R: Read, T: DeserializeOwned>(
mut reader: R,
settings: &Settings,
) -> Result<T, Error> {
let limit = settings.get_limits().get_max_document_size();
let io = |e: std::io::Error| Error::io(e).finish(None, Vec::new());
let mut buf = Vec::new();
(&mut reader).take(4).read_to_end(&mut buf).map_err(io)?;
let cap = encoded_cap(oneyaml_syntax::detect_encoding(&buf), limit);
let have = u64::try_from(buf.len()).unwrap_or(u64::MAX);
let rest = cap.saturating_add(1).saturating_sub(have);
reader.take(rest).read_to_end(&mut buf).map_err(io)?;
if u64::try_from(buf.len()).unwrap_or(u64::MAX) > cap {
return Err(Error::new(
code::DOCUMENT_TOO_LARGE,
format!("input larger than {limit} bytes"),
None,
)
.with_help(format!(
"raise Limits::max_document_size (currently {limit})"
))
.finish(None, Vec::new()));
}
from_slice_with(&buf, settings)
}
pub struct Deserializer<'de> {
shared: Rc<RefCell<Shared<'de>>>,
role: Role,
}
enum Role {
Stream,
Document(usize),
}
struct Shared<'de> {
src: &'de str,
settings: Settings,
core: Option<Stream<'de>>,
setting_warnings: Vec<Diagnostic>,
yielded: usize,
ready: Option<usize>,
in_doc: bool,
error: Option<(usize, Error)>,
done: bool,
}
fn unavailable() -> Error {
Error::new(
code::DOCUMENT_UNAVAILABLE,
"this document is no longer available: the stream has moved past it, or it was already read",
None,
)
}
impl<'de> Deserializer<'de> {
#[allow(clippy::should_implement_trait)]
#[must_use]
pub fn from_str(s: &'de str) -> Self {
Self::from_str_with(s, &Settings::new())
}
#[must_use]
pub fn from_str_with(s: &'de str, settings: &Settings) -> Self {
let (core, setting_warnings, error) = match open(s, settings) {
Ok((c, w)) => (Some(c), w, None),
Err(e) => (None, Vec::new(), Some((0, e))),
};
let shared = Shared {
src: s,
settings: settings.clone(),
core,
setting_warnings,
yielded: 0,
ready: None,
in_doc: false,
error,
done: false,
};
Self {
shared: Rc::new(RefCell::new(shared)),
role: Role::Stream,
}
}
fn run<R>(self, f: impl FnOnce(&mut Stream<'de>) -> Result<R, Error>) -> Result<R, Error> {
let Ok(mut guard) = self.shared.try_borrow_mut() else {
return Err(unavailable());
};
let sh = &mut *guard;
let src = sh.src;
let doc = match self.role {
Role::Stream if sh.yielded == 0 => None,
Role::Stream => return Err(unavailable().finish(Some(src), Vec::new())),
Role::Document(i) => Some(i),
};
if let Some((at, _)) = &sh.error {
if doc.is_none_or(|i| i == *at) {
if let Some((_, e)) = sh.error.take() {
sh.done = true;
return Err(e.finish(Some(src), std::mem::take(&mut sh.setting_warnings)));
}
}
}
let Some(core) = sh.core.as_mut() else {
return Err(unavailable().finish(Some(src), Vec::new()));
};
let result = match doc {
None => {
sh.yielded = 1;
sh.done = true;
single(core, &sh.settings, f)
}
Some(i) if sh.ready == Some(i) => {
sh.ready = None;
f(core).and_then(|r| {
end_document(core)?;
sh.in_doc = false;
Ok(r)
})
}
Some(_) => return Err(unavailable().finish(Some(src), Vec::new())),
};
result.map_err(|e| {
sh.done = true;
let limit = core.max_diagnostics();
e.finish(Some(src), warnings(&mut sh.setting_warnings, core, limit).0)
})
}
fn advance(sh: &mut Shared<'de>) -> Result<bool, Error> {
let Some(core) = sh.core.as_mut() else {
return Ok(false);
};
if sh.in_doc {
sh.ready = None;
loop {
let ev = core.src.next()?;
match ev.node {
Node::DocEnd | Node::StreamEnd => break,
Node::SeqEnd | Node::MapEnd => {}
_ if ev.starts_node() => {
core.src.push_front(ev);
core.skip_node()?;
}
_ => {}
}
}
sh.in_doc = false;
}
let ev = core.src.next()?;
match ev.node {
Node::DocStart { yaml11 } => {
core.resolver = Resolver::for_document(&sh.settings, yaml11);
sh.in_doc = true;
Ok(true)
}
_ => Ok(false),
}
}
}
impl<'de> Iterator for Deserializer<'de> {
type Item = Deserializer<'de>;
fn next(&mut self) -> Option<Self::Item> {
if !matches!(self.role, Role::Stream) {
return None;
}
let mut guard = self.shared.try_borrow_mut().ok()?;
let sh = &mut *guard;
if sh.done {
return None;
}
let index = sh.yielded;
if sh.error.is_none() {
match Self::advance(sh) {
Ok(true) => sh.ready = Some(index),
Ok(false) => {
sh.done = true;
return None;
}
Err(e) => sh.error = Some((index, e)),
}
} else if let Some((at, _)) = &mut sh.error {
*at = index;
}
if sh.error.is_some() {
sh.done = true;
}
sh.yielded = index.saturating_add(1);
Some(Deserializer {
shared: Rc::clone(&self.shared),
role: Role::Document(index),
})
}
}
macro_rules! forward {
($($method:ident),* $(,)?) => {$(
fn $method<V: Visitor<'de>>(self, v: V) -> Result<V::Value, Error> {
self.run(|core| core.$method(v))
}
)*};
}
impl<'de> de::Deserializer<'de> for Deserializer<'de> {
type Error = Error;
forward! {
deserialize_any, deserialize_bool, deserialize_i8, deserialize_i16, deserialize_i32,
deserialize_i64, deserialize_i128, deserialize_u8, deserialize_u16, deserialize_u32,
deserialize_u64, deserialize_u128, deserialize_f32, deserialize_f64, deserialize_char,
deserialize_str, deserialize_string, deserialize_bytes, deserialize_byte_buf,
deserialize_option, deserialize_unit, deserialize_seq, deserialize_map,
deserialize_identifier, deserialize_ignored_any,
}
fn deserialize_unit_struct<V: Visitor<'de>>(
self,
name: &'static str,
v: V,
) -> Result<V::Value, Error> {
self.run(|core| core.deserialize_unit_struct(name, v))
}
fn deserialize_newtype_struct<V: Visitor<'de>>(
self,
name: &'static str,
v: V,
) -> Result<V::Value, Error> {
self.run(|core| core.deserialize_newtype_struct(name, v))
}
fn deserialize_tuple<V: Visitor<'de>>(self, len: usize, v: V) -> Result<V::Value, Error> {
self.run(|core| core.deserialize_tuple(len, v))
}
fn deserialize_tuple_struct<V: Visitor<'de>>(
self,
name: &'static str,
len: usize,
v: V,
) -> Result<V::Value, Error> {
self.run(|core| core.deserialize_tuple_struct(name, len, v))
}
fn deserialize_struct<V: Visitor<'de>>(
self,
name: &'static str,
fields: &'static [&'static str],
v: V,
) -> Result<V::Value, Error> {
self.run(|core| core.deserialize_struct(name, fields, v))
}
fn deserialize_enum<V: Visitor<'de>>(
self,
name: &'static str,
variants: &'static [&'static str],
v: V,
) -> Result<V::Value, Error> {
self.run(|core| core.deserialize_enum(name, variants, v))
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)]
use super::*;
#[test]
fn reader_cap_follows_the_encoding() {
assert_eq!(encoded_cap(Encoding::Utf8, 10), 13);
assert_eq!(encoded_cap(Encoding::Utf16Le, 10), 22);
assert_eq!(encoded_cap(Encoding::Utf16Be, 10), 22);
assert_eq!(encoded_cap(Encoding::Utf32Le, 10), 44);
assert_eq!(encoded_cap(Encoding::Utf32Be, 10), 44);
assert_eq!(encoded_cap(Encoding::Utf8, usize::MAX), u64::MAX);
}
}