1use crate::{error::YsonError, lexer::YsonIterator, node::Token, ser::YsonFormat};
15
16const MAX_DEPTH: usize = 128;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum Scan {
25 Complete {
27 len: usize,
29 },
30 Incomplete,
35}
36
37pub fn scan_value(input: &[u8], format: YsonFormat) -> Result<Scan, YsonError> {
64 let mut lexer = YsonIterator::new(input, matches!(format, YsonFormat::Binary));
65
66 match scan_tree(&mut lexer, 0) {
67 Ok(()) => Ok(Scan::Complete { len: lexer.pos() }),
68 Err(YsonError::Eof | YsonError::UnexpectedEof(_)) => Ok(Scan::Incomplete),
72 Err(e) => Err(e),
73 }
74}
75
76fn scan_tree(lexer: &mut YsonIterator<'_>, depth: usize) -> Result<(), YsonError> {
78 if depth > MAX_DEPTH {
79 return Err(YsonError::Custom("Recursion limit exceeded".into()));
80 }
81
82 let mut token = lexer.next_token()?;
83
84 if matches!(token, Token::BeginAttributes) {
85 scan_fragment(lexer, depth + 1, Token::EndAttributes)?;
86 token = lexer.next_token()?;
87 }
88
89 scan_object(lexer, depth, token)
90}
91
92fn scan_object(
93 lexer: &mut YsonIterator<'_>,
94 depth: usize,
95 token: Token<'_>,
96) -> Result<(), YsonError> {
97 match token {
98 Token::String(_)
99 | Token::Int64(_)
100 | Token::Uint64(_)
101 | Token::Double(_)
102 | Token::Boolean(_)
103 | Token::Entity => Ok(()),
104
105 Token::BeginList => scan_list(lexer, depth + 1),
106 Token::BeginMap => scan_fragment(lexer, depth + 1, Token::EndMap),
107
108 other => Err(YsonError::UnexpectedToken {
109 expected: "a YSON value",
110 found: format!("{other:?}"),
111 pos: lexer.pos(),
112 }),
113 }
114}
115
116fn scan_list(lexer: &mut YsonIterator<'_>, depth: usize) -> Result<(), YsonError> {
118 if depth > MAX_DEPTH {
119 return Err(YsonError::Custom("Recursion limit exceeded".into()));
120 }
121
122 loop {
123 match lexer.peek_byte()? {
124 b']' => {
125 lexer.next_token()?;
126 return Ok(());
127 }
128 b';' => {
129 lexer.next_token()?;
130 }
131 _ => scan_tree(lexer, depth)?,
132 }
133 }
134}
135
136fn scan_fragment(
139 lexer: &mut YsonIterator<'_>,
140 depth: usize,
141 end: Token<'static>,
142) -> Result<(), YsonError> {
143 if depth > MAX_DEPTH {
144 return Err(YsonError::Custom("Recursion limit exceeded".into()));
145 }
146
147 let end_byte = match end {
148 Token::EndMap => b'}',
149 Token::EndAttributes => b'>',
150 _ => unreachable!("scan_fragment is only called for maps and attributes"),
151 };
152
153 loop {
154 let peeked = lexer.peek_byte()?;
155 if peeked == end_byte {
156 lexer.next_token()?;
157 return Ok(());
158 }
159 if peeked == b';' {
160 lexer.next_token()?;
161 continue;
162 }
163
164 match lexer.next_token()? {
166 Token::String(_) => {}
167 other => {
168 return Err(YsonError::UnexpectedToken {
169 expected: "a map key",
170 found: format!("{other:?}"),
171 pos: lexer.pos(),
172 });
173 }
174 }
175 match lexer.next_token()? {
176 Token::KeyValueSeparator => {}
177 other => {
178 return Err(YsonError::UnexpectedToken {
179 expected: "'='",
180 found: format!("{other:?}"),
181 pos: lexer.pos(),
182 });
183 }
184 }
185 scan_tree(lexer, depth)?;
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 fn complete(input: &[u8], format: YsonFormat) -> usize {
194 match scan_value(input, format).expect("scan must not fail") {
195 Scan::Complete { len } => len,
196 Scan::Incomplete => panic!("expected a complete value in {input:?}"),
197 }
198 }
199
200 #[test]
201 fn scans_text_scalars() {
202 assert_eq!(complete(b"42", YsonFormat::Text), 2);
203 assert_eq!(complete(b"42;43", YsonFormat::Text), 2);
204 assert_eq!(complete(b"#", YsonFormat::Text), 1);
205 assert_eq!(complete(b"%true", YsonFormat::Text), 5);
206 assert_eq!(complete(br#""a;b""#, YsonFormat::Text), 5);
207 }
208
209 #[test]
210 fn scans_text_composites() {
211 assert_eq!(complete(b"{a=1}", YsonFormat::Text), 5);
212 assert_eq!(complete(b"{a=1};{b=2}", YsonFormat::Text), 5);
213 assert_eq!(complete(b"[1;2;3]", YsonFormat::Text), 7);
214 assert_eq!(complete(b"{a={b=[1;2]}}", YsonFormat::Text), 13);
215 assert_eq!(complete(b"{a=1;}", YsonFormat::Text), 6);
216 }
217
218 #[test]
219 fn scans_attributed_values() {
220 assert_eq!(complete(b"<a=1>#", YsonFormat::Text), 6);
221 assert_eq!(complete(b"<a=1>#;{b=2}", YsonFormat::Text), 6);
222 assert_eq!(complete(b"<a=1;b=2>[1]", YsonFormat::Text), 12);
223 assert_eq!(complete(b"<a=<b=1>#>#", YsonFormat::Text), 11);
224 }
225
226 #[test]
227 fn reports_truncation_as_incomplete() {
228 for input in [
229 b"{a=1".as_slice(),
230 b"{a=",
231 b"{",
232 b"[1;2",
233 b"<a=1>",
234 b"<a=1",
235 b"",
236 b"\"unterminated",
237 ] {
238 assert_eq!(
239 scan_value(input, YsonFormat::Text).expect("no error"),
240 Scan::Incomplete,
241 "input {:?}",
242 String::from_utf8_lossy(input)
243 );
244 }
245 }
246
247 #[test]
248 fn scans_binary_values() {
249 assert_eq!(complete(&[0x02, 0x02], YsonFormat::Binary), 2);
251 assert_eq!(complete(b"\x01\x06abc", YsonFormat::Binary), 5);
253 assert_eq!(complete(b"\x01\x06};]", YsonFormat::Binary), 5);
255 assert_eq!(
257 complete(&[0x03, 0, 0, 0, 0, 0, 0, 0, 0], YsonFormat::Binary),
258 9
259 );
260 }
261
262 #[test]
263 fn scans_a_binary_map_and_stops_at_the_boundary() {
264 let one = b"{\x01\x02a=\x02\x02}";
266 let mut two = one.to_vec();
267 two.push(b';');
268 two.extend_from_slice(one);
269
270 assert_eq!(complete(one, YsonFormat::Binary), one.len());
271 assert_eq!(complete(&two, YsonFormat::Binary), one.len());
272 }
273
274 #[test]
275 fn binary_truncation_is_incomplete() {
276 let full = b"{\x01\x02a=\x02\x02}";
277 for cut in 0..full.len() {
278 assert_eq!(
279 scan_value(&full[..cut], YsonFormat::Binary).expect("no error"),
280 Scan::Incomplete,
281 "cut at {cut}"
282 );
283 }
284 assert_eq!(
286 scan_value(b"\x01\x14abc", YsonFormat::Binary).expect("no error"),
287 Scan::Incomplete
288 );
289 }
290
291 #[test]
292 fn rejects_malformed_input() {
293 assert!(scan_value(&[0x07], YsonFormat::Binary).is_err());
295 assert!(scan_value(b"{a 1}", YsonFormat::Text).is_err());
297 assert!(scan_value(b"]", YsonFormat::Text).is_err());
299 }
300
301 #[test]
302 fn rejects_deep_nesting() {
303 let deep = vec![b'['; MAX_DEPTH + 10];
304 assert!(scan_value(&deep, YsonFormat::Text).is_err());
305 }
306
307 #[test]
308 fn text_comments_count_toward_the_value() {
309 assert_eq!(complete(b"/* c */42", YsonFormat::Text), 9);
311 assert_eq!(complete(b" 42", YsonFormat::Text), 4);
312 }
313}