Skip to main content

docspec_json/
backend.rs

1//! Backend trait for JSON token emission.
2
3use docspec_core::{Error, Result};
4
5/// Low-level JSON token emitter. Implementations write JSON tokens to an
6/// underlying sink. State validation is performed by [`crate::JsonEmitter`],
7/// not by the backend.
8pub trait JsonBackend {
9    /// Type returned by [`JsonBackend::finish`].
10    type Output;
11
12    /// Begin a JSON array (writes `[`).
13    ///
14    /// # Errors
15    ///
16    /// Returns any error produced by the underlying backend.
17    fn begin_array(&mut self) -> Result<()>;
18    /// Begin a JSON object (writes `{`).
19    ///
20    /// # Errors
21    ///
22    /// Returns any error produced by the underlying backend.
23    fn begin_object(&mut self) -> Result<()>;
24    /// End the current JSON array (writes `]`).
25    ///
26    /// # Errors
27    ///
28    /// Returns any error produced by the underlying backend.
29    fn end_array(&mut self) -> Result<()>;
30    /// End the current JSON object (writes `}`).
31    ///
32    /// # Errors
33    ///
34    /// Returns any error produced by the underlying backend.
35    fn end_object(&mut self) -> Result<()>;
36    /// Finish writing and return the backend's output.
37    ///
38    /// # Errors
39    ///
40    /// Returns any error produced by the underlying backend.
41    fn finish(self) -> Result<Self::Output>;
42    /// Write a boolean value.
43    ///
44    /// # Errors
45    ///
46    /// Returns any error produced by the underlying backend.
47    fn write_bool(&mut self, b: bool) -> Result<()>;
48    /// Write an object key.
49    ///
50    /// # Errors
51    ///
52    /// Returns any error produced by the underlying backend.
53    fn write_name(&mut self, name: &str) -> Result<()>;
54    /// Write a `null` value.
55    ///
56    /// # Errors
57    ///
58    /// Returns any error produced by the underlying backend.
59    fn write_null(&mut self) -> Result<()>;
60    /// Write a `u32` numeric value.
61    ///
62    /// # Errors
63    ///
64    /// Returns any error produced by the underlying backend.
65    fn write_number(&mut self, n: u32) -> Result<()>;
66    /// Write a string value.
67    ///
68    /// # Errors
69    ///
70    /// Returns any error produced by the underlying backend.
71    fn write_string(&mut self, s: &str) -> Result<()>;
72    /// Write a string value by streaming bytes through a closure.
73    ///
74    /// The default implementation buffers the bytes into a `Vec<u8>`, converts
75    /// to a `&str`, and delegates to [`write_string`](Self::write_string).
76    ///
77    /// # Errors
78    ///
79    /// Returns an error if the closure returns an I/O error, or if the buffered
80    /// bytes are not valid UTF-8.
81    #[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/// A token captured by [`CapturingBackend`].
96#[derive(Debug, Clone, PartialEq, Eq)]
97#[non_exhaustive]
98pub enum Token {
99    /// Represents `[`.
100    BeginArray,
101    /// Represents `{`.
102    BeginObject,
103    /// A boolean value.
104    BoolValue(bool),
105    /// Represents `]`.
106    EndArray,
107    /// Represents `}`.
108    EndObject,
109    /// An object key.
110    Name(String),
111    /// A `null` value.
112    NullValue,
113    /// A `u32` numeric value.
114    NumberValue(u32),
115    /// A string value.
116    StringValue(String),
117}
118
119/// Backend that records every operation as a [`Token`] without writing anywhere.
120///
121/// Useful for verifying [`crate::JsonEmitter`] behavior without depending on a
122/// real JSON serializer.
123#[derive(Debug, Default)]
124pub struct CapturingBackend {
125    tokens: Vec<Token>,
126}
127
128impl CapturingBackend {
129    /// Create a new empty `CapturingBackend`.
130    #[inline]
131    #[must_use]
132    pub fn new() -> Self {
133        Self::default()
134    }
135
136    /// Borrow the recorded tokens without consuming the backend.
137    #[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    /// Mock backend that records `write_string` calls for testing the default impl.
226    #[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}