Coverage Report

Created: 2026-10-28 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/deser-yaml-0.10.1/src/stream.rs
Line
Count
Source
1
//! Reading YAML streams.
2
#[cfg(feature = "io")]
3
use std::io::Read;
4
5
use deser_core::Error;
6
#[cfg(feature = "io")]
7
use deser_core::de::DeserializeOwned;
8
use deser_core::de::{self, DeserializeDriver, Frame};
9
#[cfg(feature = "io")]
10
use deser_core::{Atom, ErrorKind};
11
12
use crate::de::{Deserializer, DeserializerConfig};
13
14
/// The kind of a line for splitting documents.
15
#[derive(PartialEq, Eq)]
16
enum 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).
28
0
fn classify(line: &[u8]) -> Line {
29
    // byte order marks can precede every document
30
0
    let line = line.strip_prefix(b"\xef\xbb\xbf").unwrap_or(line);
31
0
    let is_marker = |marker: &[u8]| {
32
0
        line.starts_with(marker) && matches!(line.get(3), None | Some(b' ' | b'\t' | b'\r' | b'\n'))
33
0
    };
34
0
    if is_marker(b"---") {
35
0
        return Line::DocumentStart;
36
0
    }
37
0
    if is_marker(b"...") {
38
0
        return Line::DocumentEnd;
39
0
    }
40
0
    match line
41
0
        .iter()
42
0
        .find(|&&b| !matches!(b, b' ' | b'\t' | b'\r' | b'\n'))
43
    {
44
0
        None | Some(b'#') => Line::Other,
45
0
        Some(b'%') if line[0] == b'%' => Line::Other,
46
0
        Some(_) => Line::Content,
47
    }
48
0
}
49
50
/// The state of a YAML stream that is read.
51
#[derive(Debug, Default)]
52
struct 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
59
impl StreamState {
60
0
    fn document(&mut self, end: usize) -> Frame {
61
0
        self.pos = 0;
62
0
        self.has_document = false;
63
0
        Frame::Value {
64
0
            start: 0,
65
0
            end,
66
0
            consumed: end,
67
0
        }
68
0
    }
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)]
98
pub struct StreamDeserializer {
99
    config: DeserializerConfig,
100
    state: StreamState,
101
}
102
103
impl Default for StreamDeserializer {
104
0
    fn default() -> StreamDeserializer {
105
0
        StreamDeserializer::new()
106
0
    }
107
}
108
109
impl StreamDeserializer {
110
    /// Creates a stream deserializer.
111
0
    pub fn new() -> StreamDeserializer {
112
0
        StreamDeserializer::with_config(DeserializerConfig::new())
113
0
    }
114
115
    /// Creates a stream deserializer with the given configuration.
116
0
    pub fn with_config(config: DeserializerConfig) -> StreamDeserializer {
117
0
        StreamDeserializer {
118
0
            config,
119
0
            state: StreamState::default(),
120
0
        }
121
0
    }
122
123
    /// Returns the configuration.
124
0
    pub fn config(&self) -> &DeserializerConfig {
125
0
        &self.config
126
0
    }
127
}
128
129
impl de::StreamDeserializer for StreamDeserializer {
130
0
    fn context(&self) -> deser_core::Context {
131
0
        self.config.context().clone()
132
0
    }
133
134
0
    fn frame(&mut self, input: &[u8], eof: bool) -> Result<Frame, Error> {
135
0
        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
0
            let line_end = match input[state.pos..]
140
0
                .iter()
141
0
                .position(|&b| b == b'\n' || b == b'\r')
142
            {
143
0
                Some(index) => state.pos + index + 1,
144
0
                None if eof => input.len(),
145
                // wait for the whole line
146
0
                None => return Ok(Frame::Incomplete { consumed: 0 }),
147
            };
148
0
            if line_end == state.pos {
149
                // the end of the stream
150
0
                if state.has_document {
151
0
                    return Ok(state.document(input.len()));
152
0
                }
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
0
                if !input.is_empty() {
157
0
                    Deserializer::from_slice_with_config(input, self.config.clone()).end()?;
158
0
                }
159
0
                return Ok(Frame::End);
160
0
            }
161
0
            match classify(&input[state.pos..line_end]) {
162
0
                Line::DocumentStart if state.has_document => return Ok(state.document(state.pos)),
163
0
                Line::DocumentStart | Line::Content => state.has_document = true,
164
0
                Line::DocumentEnd if state.has_document => return Ok(state.document(line_end)),
165
0
                Line::DocumentEnd | Line::Other => {}
166
            }
167
0
            state.pos = line_end;
168
        }
169
0
    }
170
171
0
    fn drive_frame<'de>(
172
0
        &mut self,
173
0
        frame: &'de [u8],
174
0
        driver: &mut DeserializeDriver<'_, 'de>,
175
0
    ) -> Result<(), Error> {
176
0
        let mut de = Deserializer::from_slice_with_config(frame, self.config.without_context());
177
0
        de.drive(driver)?;
178
0
        de.end()
179
0
    }
180
181
0
    fn is_text(&self) -> bool {
182
0
        true
183
0
    }
184
}
185
186
#[cfg(feature = "io")]
187
impl 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
0
    pub fn reader<R: Read>(&self, reader: R) -> deser_core::io::Reader<R, StreamDeserializer> {
194
0
        deser_core::io::Reader::new(reader, StreamDeserializer::with_config(self.clone()))
195
0
    }
196
197
    /// Deserializes a value from a reader.
198
    ///
199
    /// See [`from_reader`].
200
0
    pub fn from_reader<T: DeserializeOwned, R: Read>(&self, reader: R) -> Result<T, Error> {
201
0
        let mut reader = self.reader(reader);
202
0
        let value = match reader.read()? {
203
0
            Some(value) => value,
204
            None => {
205
                // an empty stream is null
206
0
                let mut out = None;
207
                {
208
0
                    let mut driver = DeserializeDriver::new(&mut out);
209
0
                    driver.emit(Atom::Null)?;
210
                }
211
0
                out.ok_or_else(|| Error::new(ErrorKind::EndOfFile, "empty document"))?
212
            }
213
        };
214
0
        reader.end()?;
215
0
        Ok(value)
216
0
    }
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")]
232
0
pub fn from_reader<T: DeserializeOwned, R: Read>(reader: R) -> Result<T, Error> {
233
0
    DeserializerConfig::new().from_reader(reader)
234
0
}