/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 | } |