1use docspec_core::{Error, Result};
4
5pub trait JsonBackend {
9 type Output;
11
12 fn begin_array(&mut self) -> Result<()>;
18 fn begin_object(&mut self) -> Result<()>;
24 fn end_array(&mut self) -> Result<()>;
30 fn end_object(&mut self) -> Result<()>;
36 fn finish(self) -> Result<Self::Output>;
42 fn write_bool(&mut self, b: bool) -> Result<()>;
48 fn write_name(&mut self, name: &str) -> Result<()>;
54 fn write_null(&mut self) -> Result<()>;
60 fn write_number(&mut self, n: u32) -> Result<()>;
66 fn write_string(&mut self, s: &str) -> Result<()>;
72 #[allow(clippy::missing_inline_in_public_items)]
82 fn write_string_streaming<F>(&mut self, f: F) -> Result<()>
83 where
84 F: FnOnce(&mut dyn std::io::Write) -> std::io::Result<()>,
85 {
86 let mut buf: Vec<u8> = Vec::new();
87 f(&mut buf).map_err(Error::from)?;
88 let s = core::str::from_utf8(&buf).map_err(|e| Error::Other {
89 message: format!("write_string_streaming produced invalid UTF-8: {e}"),
90 })?;
91 self.write_string(s)
92 }
93}
94
95#[derive(Debug, Clone, PartialEq, Eq)]
97#[non_exhaustive]
98pub enum Token {
99 BeginArray,
101 BeginObject,
103 BoolValue(bool),
105 EndArray,
107 EndObject,
109 Name(String),
111 NullValue,
113 NumberValue(u32),
115 StringValue(String),
117}
118
119#[derive(Debug, Default)]
124pub struct CapturingBackend {
125 tokens: Vec<Token>,
126}
127
128impl CapturingBackend {
129 #[inline]
131 #[must_use]
132 pub fn new() -> Self {
133 Self::default()
134 }
135
136 #[inline]
138 #[must_use]
139 pub fn tokens(&self) -> &[Token] {
140 &self.tokens
141 }
142}
143
144impl JsonBackend for CapturingBackend {
145 type Output = Vec<Token>;
146
147 #[inline]
148 fn begin_array(&mut self) -> Result<()> {
149 self.tokens.push(Token::BeginArray);
150 Ok(())
151 }
152
153 #[inline]
154 fn begin_object(&mut self) -> Result<()> {
155 self.tokens.push(Token::BeginObject);
156 Ok(())
157 }
158
159 #[inline]
160 fn end_array(&mut self) -> Result<()> {
161 self.tokens.push(Token::EndArray);
162 Ok(())
163 }
164
165 #[inline]
166 fn end_object(&mut self) -> Result<()> {
167 self.tokens.push(Token::EndObject);
168 Ok(())
169 }
170
171 #[inline]
172 fn finish(self) -> Result<Vec<Token>> {
173 Ok(self.tokens)
174 }
175
176 #[inline]
177 fn write_bool(&mut self, b: bool) -> Result<()> {
178 self.tokens.push(Token::BoolValue(b));
179 Ok(())
180 }
181
182 #[inline]
183 fn write_name(&mut self, name: &str) -> Result<()> {
184 self.tokens.push(Token::Name(name.to_string()));
185 Ok(())
186 }
187
188 #[inline]
189 fn write_null(&mut self) -> Result<()> {
190 self.tokens.push(Token::NullValue);
191 Ok(())
192 }
193
194 #[inline]
195 fn write_number(&mut self, n: u32) -> Result<()> {
196 self.tokens.push(Token::NumberValue(n));
197 Ok(())
198 }
199
200 #[inline]
201 fn write_string(&mut self, s: &str) -> Result<()> {
202 self.tokens.push(Token::StringValue(s.to_string()));
203 Ok(())
204 }
205
206 #[inline]
207 fn write_string_streaming<F>(&mut self, f: F) -> Result<()>
208 where
209 F: FnOnce(&mut dyn std::io::Write) -> std::io::Result<()>,
210 {
211 let mut buf: Vec<u8> = Vec::new();
212 f(&mut buf).map_err(Error::from)?;
213 let s = core::str::from_utf8(&buf).map_err(|e| Error::Other {
214 message: format!("write_string_streaming produced invalid UTF-8: {e}"),
215 })?;
216 self.tokens.push(Token::StringValue(s.to_string()));
217 Ok(())
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[derive(Debug, Default)]
227 struct MockBackend {
228 write_string_calls: Vec<String>,
229 }
230
231 impl JsonBackend for MockBackend {
232 type Output = ();
233
234 fn begin_array(&mut self) -> Result<()> {
235 Ok(())
236 }
237
238 fn begin_object(&mut self) -> Result<()> {
239 Ok(())
240 }
241
242 fn end_array(&mut self) -> Result<()> {
243 Ok(())
244 }
245
246 fn end_object(&mut self) -> Result<()> {
247 Ok(())
248 }
249
250 fn finish(self) -> Result<()> {
251 Ok(())
252 }
253
254 fn write_bool(&mut self, _b: bool) -> Result<()> {
255 Ok(())
256 }
257
258 fn write_name(&mut self, _name: &str) -> Result<()> {
259 Ok(())
260 }
261
262 fn write_null(&mut self) -> Result<()> {
263 Ok(())
264 }
265
266 fn write_number(&mut self, _n: u32) -> Result<()> {
267 Ok(())
268 }
269
270 fn write_string(&mut self, s: &str) -> Result<()> {
271 self.write_string_calls.push(s.to_string());
272 Ok(())
273 }
274 }
275
276 #[test]
277 fn default_impl_buffers_and_delegates_to_write_string() {
278 let mut mock = MockBackend::default();
279 let result = mock.write_string_streaming(|w| {
280 w.write_all(b"hello world")?;
281 Ok(())
282 });
283 assert!(result.is_ok());
284 assert_eq!(mock.write_string_calls, vec!["hello world"]);
285 }
286
287 #[test]
288 fn default_impl_errors_on_invalid_utf8() {
289 let mut mock = MockBackend::default();
290 let result = mock.write_string_streaming(|w| {
291 w.write_all(&[0xFF, 0xFE, 0x00])?;
292 Ok(())
293 });
294 assert!(result.is_err());
295 }
296
297 #[test]
298 fn capturing_backend_starts_empty() {
299 let b = CapturingBackend::new();
300 assert!(b.tokens().is_empty());
301 }
302
303 #[test]
304 fn capturing_backend_records_begin_object() {
305 let mut b = CapturingBackend::new();
306 assert!(b.begin_object().is_ok());
307 assert_eq!(b.tokens(), &[Token::BeginObject]);
308 }
309
310 #[test]
311 fn capturing_backend_records_all_token_types_in_order() {
312 let mut b = CapturingBackend::new();
313 assert!(b.begin_array().is_ok());
314 assert!(b.begin_object().is_ok());
315 assert!(b.write_name("k").is_ok());
316 assert!(b.write_string("v").is_ok());
317 assert!(b.write_bool(true).is_ok());
318 assert!(b.write_number(42).is_ok());
319 assert!(b.write_null().is_ok());
320 assert!(b.end_object().is_ok());
321 assert!(b.end_array().is_ok());
322 assert_eq!(
323 b.tokens(),
324 &[
325 Token::BeginArray,
326 Token::BeginObject,
327 Token::Name("k".to_string()),
328 Token::StringValue("v".to_string()),
329 Token::BoolValue(true),
330 Token::NumberValue(42),
331 Token::NullValue,
332 Token::EndObject,
333 Token::EndArray,
334 ]
335 );
336 }
337
338 #[test]
339 fn capturing_backend_finish_returns_recorded_tokens() {
340 let mut b = CapturingBackend::new();
341 assert!(b.begin_object().is_ok());
342 assert!(b.end_object().is_ok());
343 let result = b.finish();
344 assert!(result.is_ok());
345 let tokens = result.unwrap_or_default();
346 assert!(tokens == vec![Token::BeginObject, Token::EndObject]);
347 }
348
349 #[test]
350 fn capturing_streaming_pushes_single_token() {
351 let mut b = CapturingBackend::new();
352 let result = b.write_string_streaming(|w| w.write_all(b"hello"));
353 assert!(result.is_ok());
354 assert_eq!(b.tokens(), &[Token::StringValue("hello".to_string())]);
355 }
356
357 #[test]
358 fn capturing_streaming_multi_chunk_concatenates() {
359 let mut b = CapturingBackend::new();
360 let result = b.write_string_streaming(|w| {
361 w.write_all(b"hello")?;
362 w.write_all(b" ")?;
363 w.write_all(b"world")?;
364 Ok(())
365 });
366 assert!(result.is_ok());
367 assert_eq!(b.tokens(), &[Token::StringValue("hello world".to_string())]);
368 }
369
370 #[test]
371 fn capturing_streaming_error_does_not_push_token() {
372 let mut b = CapturingBackend::new();
373 let result = b.write_string_streaming(|_w| Err(std::io::Error::other("test error")));
374 assert!(result.is_err());
375 assert!(b.tokens().is_empty());
376 }
377}