Skip to main content

deser_yaml/
stream.rs

1//! Reading YAML streams.
2#[cfg(feature = "io")]
3use std::io::Read;
4
5use deser_core::Error;
6#[cfg(feature = "io")]
7use deser_core::de::DeserializeOwned;
8use deser_core::de::{self, DeserializeDriver, Frame};
9#[cfg(feature = "io")]
10use deser_core::{Atom, ErrorKind};
11
12use crate::de::{Deserializer, DeserializerConfig};
13
14/// The kind of a line for splitting documents.
15#[derive(PartialEq, Eq)]
16enum Line {
17    /// `---`
18    DocumentStart,
19    /// `...`
20    DocumentEnd,
21    /// Blank lines, comments and directives.
22    Other,
23    /// Anything else.
24    Content,
25}
26
27/// Classifies a line (including its line break).
28fn classify(line: &[u8]) -> Line {
29    // byte order marks can precede every document
30    let line = line.strip_prefix(b"\xef\xbb\xbf").unwrap_or(line);
31    let is_marker = |marker: &[u8]| {
32        line.starts_with(marker) && matches!(line.get(3), None | Some(b' ' | b'\t' | b'\r' | b'\n'))
33    };
34    if is_marker(b"---") {
35        return Line::DocumentStart;
36    }
37    if is_marker(b"...") {
38        return Line::DocumentEnd;
39    }
40    match line
41        .iter()
42        .find(|&&b| !matches!(b, b' ' | b'\t' | b'\r' | b'\n'))
43    {
44        None | Some(b'#') => Line::Other,
45        Some(b'%') if line[0] == b'%' => Line::Other,
46        Some(_) => Line::Content,
47    }
48}
49
50/// The state of a YAML stream that is read.
51#[derive(Debug, Default)]
52struct StreamState {
53    // the start of the next line to scan
54    pos: usize,
55    // the lines scanned so far contain a document
56    has_document: bool,
57}
58
59impl StreamState {
60    fn document(&mut self, end: usize) -> Frame {
61        self.pos = 0;
62        self.has_document = false;
63        Frame::Value {
64            start: 0,
65            end,
66            consumed: end,
67        }
68    }
69}
70
71/// Reads a stream of YAML documents (see [`deser::stream`](deser_core::stream)).
72///
73/// A document ends where the next one starts (at a `---` line) or at a
74/// document end marker (`...`).  When reading a stream that stays open
75/// (for instance a socket), the writer should end every document with `...`
76/// (see [`SerializerConfig::set_end_documents`](crate::SerializerConfig::set_end_documents)), otherwise a document is only
77/// complete once the next one starts.  Comments and directives before a
78/// document belong to it.
79///
80/// ```
81/// # #[cfg(feature = "io")] {
82/// use deser_yaml::DeserializerConfig;
83///
84/// let mut reader =
85///     DeserializerConfig::new().reader(&b"--- a\n--- b\n"[..]);
86/// assert_eq!(reader.read::<String>().unwrap().as_deref(), Some("a"));
87/// assert_eq!(reader.read::<String>().unwrap().as_deref(), Some("b"));
88/// assert_eq!(reader.read::<String>().unwrap(), None);
89/// # }
90/// ```
91///
92/// Documents are parsed like with a [`Deserializer`], so they can borrow
93/// from the stream's buffer (see
94/// [`InputBuffer::deserialize`](deser_core::stream::InputBuffer::deserialize)).
95/// Errors (including syntax errors) only discard their document, reading
96/// continues with the next one.
97#[derive(Debug)]
98pub struct StreamDeserializer {
99    config: DeserializerConfig,
100    state: StreamState,
101}
102
103impl Default for StreamDeserializer {
104    fn default() -> StreamDeserializer {
105        StreamDeserializer::new()
106    }
107}
108
109impl StreamDeserializer {
110    /// Creates a stream deserializer.
111    pub fn new() -> StreamDeserializer {
112        StreamDeserializer::with_config(DeserializerConfig::new())
113    }
114
115    /// Creates a stream deserializer with the given configuration.
116    pub fn with_config(config: DeserializerConfig) -> StreamDeserializer {
117        StreamDeserializer {
118            config,
119            state: StreamState::default(),
120        }
121    }
122
123    /// Returns the configuration.
124    pub fn config(&self) -> &DeserializerConfig {
125        &self.config
126    }
127}
128
129impl de::StreamDeserializer for StreamDeserializer {
130    fn context(&self) -> deser_core::Context {
131        self.config.context().clone()
132    }
133
134    fn frame(&mut self, input: &[u8], eof: bool) -> Result<Frame, Error> {
135        let state = &mut self.state;
136        loop {
137            // a carriage return alone is a line break too (`\r\n` is a
138            // line followed by an empty one)
139            let line_end = match input[state.pos..]
140                .iter()
141                .position(|&b| b == b'\n' || b == b'\r')
142            {
143                Some(index) => state.pos + index + 1,
144                None if eof => input.len(),
145                // wait for the whole line
146                None => return Ok(Frame::Incomplete { consumed: 0 }),
147            };
148            if line_end == state.pos {
149                // the end of the stream
150                if state.has_document {
151                    return Ok(state.document(input.len()));
152                }
153                // what follows the last document has to be valid too (like
154                // directives without a document or comments which are not
155                // UTF-8)
156                if !input.is_empty() {
157                    Deserializer::from_slice_with_config(input, self.config.clone()).end()?;
158                }
159                return Ok(Frame::End);
160            }
161            match classify(&input[state.pos..line_end]) {
162                Line::DocumentStart if state.has_document => return Ok(state.document(state.pos)),
163                Line::DocumentStart | Line::Content => state.has_document = true,
164                Line::DocumentEnd if state.has_document => return Ok(state.document(line_end)),
165                Line::DocumentEnd | Line::Other => {}
166            }
167            state.pos = line_end;
168        }
169    }
170
171    fn drive_frame<'de>(
172        &mut self,
173        frame: &'de [u8],
174        driver: &mut DeserializeDriver<'_, 'de>,
175    ) -> Result<(), Error> {
176        let mut de = Deserializer::from_slice_with_config(frame, self.config.without_context());
177        de.drive(driver)?;
178        de.end()
179    }
180
181    fn is_text(&self) -> bool {
182        true
183    }
184}
185
186#[cfg(feature = "io")]
187impl DeserializerConfig {
188    /// Creates a reader of a stream of YAML documents (see
189    /// [`deser::io::Reader`](deser_core::io::Reader)).
190    ///
191    /// See [`StreamDeserializer`] for how the stream is split into
192    /// documents.
193    pub fn reader<R: Read>(&self, reader: R) -> deser_core::io::Reader<R, StreamDeserializer> {
194        deser_core::io::Reader::new(reader, StreamDeserializer::with_config(self.clone()))
195    }
196
197    /// Deserializes a value from a reader.
198    ///
199    /// See [`from_reader`].
200    pub fn from_reader<T: DeserializeOwned, R: Read>(&self, reader: R) -> Result<T, Error> {
201        let mut reader = self.reader(reader);
202        let value = match reader.read()? {
203            Some(value) => value,
204            None => {
205                // an empty stream is null
206                let mut out = None;
207                {
208                    let mut driver = DeserializeDriver::new(&mut out);
209                    driver.emit(Atom::Null)?;
210                }
211                out.ok_or_else(|| Error::new(ErrorKind::EndOfFile, "empty document"))?
212            }
213        };
214        reader.end()?;
215        Ok(value)
216    }
217}
218
219/// Deserializes a value from a reader.
220///
221/// This works like [`from_str`](crate::from_str): the stream must contain
222/// at most one document, an empty stream is null.  The reader is read to
223/// the end, it does not need to be buffered.  To read more than one
224/// document use [`DeserializerConfig::reader`].
225///
226/// ```
227/// let value: Vec<u32> =
228///     deser_yaml::from_reader(&b"- 1\n- 2\n"[..]).unwrap();
229/// assert_eq!(value, [1, 2]);
230/// ```
231#[cfg(feature = "io")]
232pub fn from_reader<T: DeserializeOwned, R: Read>(reader: R) -> Result<T, Error> {
233    DeserializerConfig::new().from_reader(reader)
234}