#[cfg(feature = "io")]
use std::io::Read;
use deser_core::Error;
#[cfg(feature = "io")]
use deser_core::de::DeserializeOwned;
use deser_core::de::{self, DeserializeDriver, Frame};
#[cfg(feature = "io")]
use deser_core::{Atom, ErrorKind};
use crate::de::{Deserializer, DeserializerConfig};
#[derive(PartialEq, Eq)]
enum Line {
DocumentStart,
DocumentEnd,
Other,
Content,
}
fn classify(line: &[u8]) -> Line {
let line = line.strip_prefix(b"\xef\xbb\xbf").unwrap_or(line);
let is_marker = |marker: &[u8]| {
line.starts_with(marker) && matches!(line.get(3), None | Some(b' ' | b'\t' | b'\r' | b'\n'))
};
if is_marker(b"---") {
return Line::DocumentStart;
}
if is_marker(b"...") {
return Line::DocumentEnd;
}
match line
.iter()
.find(|&&b| !matches!(b, b' ' | b'\t' | b'\r' | b'\n'))
{
None | Some(b'#') => Line::Other,
Some(b'%') if line[0] == b'%' => Line::Other,
Some(_) => Line::Content,
}
}
#[derive(Debug, Default)]
struct StreamState {
pos: usize,
has_document: bool,
}
impl StreamState {
fn document(&mut self, end: usize) -> Frame {
self.pos = 0;
self.has_document = false;
Frame::Value {
start: 0,
end,
consumed: end,
}
}
}
#[derive(Debug)]
pub struct StreamDeserializer {
config: DeserializerConfig,
state: StreamState,
}
impl Default for StreamDeserializer {
fn default() -> StreamDeserializer {
StreamDeserializer::new()
}
}
impl StreamDeserializer {
pub fn new() -> StreamDeserializer {
StreamDeserializer::with_config(DeserializerConfig::new())
}
pub fn with_config(config: DeserializerConfig) -> StreamDeserializer {
StreamDeserializer {
config,
state: StreamState::default(),
}
}
pub fn config(&self) -> &DeserializerConfig {
&self.config
}
}
impl de::StreamDeserializer for StreamDeserializer {
fn context(&self) -> deser_core::Context {
self.config.context().clone()
}
fn frame(&mut self, input: &[u8], eof: bool) -> Result<Frame, Error> {
let state = &mut self.state;
loop {
let line_end = match input[state.pos..]
.iter()
.position(|&b| b == b'\n' || b == b'\r')
{
Some(index) => state.pos + index + 1,
None if eof => input.len(),
None => return Ok(Frame::Incomplete { consumed: 0 }),
};
if line_end == state.pos {
if state.has_document {
return Ok(state.document(input.len()));
}
if !input.is_empty() {
Deserializer::from_slice_with_config(input, self.config.clone()).end()?;
}
return Ok(Frame::End);
}
match classify(&input[state.pos..line_end]) {
Line::DocumentStart if state.has_document => return Ok(state.document(state.pos)),
Line::DocumentStart | Line::Content => state.has_document = true,
Line::DocumentEnd if state.has_document => return Ok(state.document(line_end)),
Line::DocumentEnd | Line::Other => {}
}
state.pos = line_end;
}
}
fn drive_frame<'de>(
&mut self,
frame: &'de [u8],
driver: &mut DeserializeDriver<'_, 'de>,
) -> Result<(), Error> {
let mut de = Deserializer::from_slice_with_config(frame, self.config.without_context());
de.drive(driver)?;
de.end()
}
fn is_text(&self) -> bool {
true
}
}
#[cfg(feature = "io")]
impl DeserializerConfig {
pub fn reader<R: Read>(&self, reader: R) -> deser_core::io::Reader<R, StreamDeserializer> {
deser_core::io::Reader::new(reader, StreamDeserializer::with_config(self.clone()))
}
pub fn from_reader<T: DeserializeOwned, R: Read>(&self, reader: R) -> Result<T, Error> {
let mut reader = self.reader(reader);
let value = match reader.read()? {
Some(value) => value,
None => {
let mut out = None;
{
let mut driver = DeserializeDriver::new(&mut out);
driver.emit(Atom::Null)?;
}
out.ok_or_else(|| Error::new(ErrorKind::EndOfFile, "empty document"))?
}
};
reader.end()?;
Ok(value)
}
}
#[cfg(feature = "io")]
pub fn from_reader<T: DeserializeOwned, R: Read>(reader: R) -> Result<T, Error> {
DeserializerConfig::new().from_reader(reader)
}