docspec_json/
struson_backend.rs1use std::io::Write;
4
5use struson::writer::{JsonStreamWriter, JsonWriter as _};
6
7use crate::JsonBackend;
8use docspec_core::{Error, Result};
9
10pub struct StrusonBackend<W: Write> {
15 writer: JsonStreamWriter<W>,
16}
17
18impl<W: Write> StrusonBackend<W> {
19 #[inline]
21 pub fn new(writer: W) -> Self {
22 Self {
23 writer: JsonStreamWriter::new(writer),
24 }
25 }
26}
27
28impl<W: Write> JsonBackend for StrusonBackend<W> {
29 type Output = W;
30
31 #[inline]
32 fn begin_array(&mut self) -> Result<()> {
33 self.writer.begin_array().map_err(Error::from)
34 }
35
36 #[inline]
37 fn begin_object(&mut self) -> Result<()> {
38 self.writer.begin_object().map_err(Error::from)
39 }
40
41 #[inline]
42 fn end_array(&mut self) -> Result<()> {
43 self.writer.end_array().map_err(Error::from)
44 }
45
46 #[inline]
47 fn end_object(&mut self) -> Result<()> {
48 self.writer.end_object().map_err(Error::from)
49 }
50
51 #[inline]
52 fn finish(self) -> Result<W> {
53 self.writer.finish_document().map_err(Error::from)
54 }
55
56 #[inline]
57 fn write_bool(&mut self, b: bool) -> Result<()> {
58 self.writer.bool_value(b).map_err(Error::from)
59 }
60
61 #[inline]
62 fn write_name(&mut self, name: &str) -> Result<()> {
63 self.writer.name(name).map_err(Error::from)
64 }
65
66 #[inline]
67 fn write_null(&mut self) -> Result<()> {
68 self.writer.null_value().map_err(Error::from)
69 }
70
71 #[inline]
72 fn write_number(&mut self, n: u32) -> Result<()> {
73 self.writer.number_value(n).map_err(Error::from)
74 }
75
76 #[inline]
77 fn write_string(&mut self, s: &str) -> Result<()> {
78 self.writer.string_value(s).map_err(Error::from)
79 }
80
81 #[inline]
82 fn write_string_streaming<F>(&mut self, f: F) -> Result<()>
83 where
84 F: FnOnce(&mut dyn Write) -> std::io::Result<()>,
85 {
86 use struson::writer::StringValueWriter as _;
87
88 let mut svw = self.writer.string_value_writer().map_err(Error::from)?;
89 let inner_result = f(&mut svw);
90 let finish_result = svw.finish_value().map_err(Error::from);
91 inner_result.map_err(Error::from)?;
92 finish_result
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use core::cell::RefCell;
100 use std::rc::Rc;
101
102 struct ErrorWriter;
103
104 impl Write for ErrorWriter {
105 fn flush(&mut self) -> std::io::Result<()> {
106 Ok(())
107 }
108
109 fn write(&mut self, _: &[u8]) -> std::io::Result<usize> {
110 Err(std::io::Error::new(std::io::ErrorKind::BrokenPipe, "test"))
111 }
112 }
113
114 #[test]
115 fn struson_backend_error_propagates_as_io_err() {
116 let mut b = StrusonBackend::new(ErrorWriter);
117 let result = b.begin_object();
118 assert!(matches!(result, Err(Error::Io { .. })));
119 }
120
121 #[test]
122 fn struson_backend_error_writer_flush_succeeds() {
123 let mut w = ErrorWriter;
124 assert!(w.flush().is_ok());
125 }
126
127 #[test]
128 fn struson_backend_finish_returns_underlying_writer() {
129 let mut b = StrusonBackend::new(Vec::new());
130 assert!(b.begin_array().is_ok());
131 assert!(b.end_array().is_ok());
132 let result = b.finish();
133 assert!(result.is_ok());
134 let bytes = result.unwrap_or_default();
135 assert!(bytes == b"[]");
136 }
137
138 #[test]
139 fn struson_backend_writes_array_of_values() {
140 let mut b = StrusonBackend::new(Vec::new());
141 assert!(b.begin_array().is_ok());
142 assert!(b.write_number(1).is_ok());
143 assert!(b.write_bool(true).is_ok());
144 assert!(b.write_null().is_ok());
145 assert!(b.write_string("x").is_ok());
146 assert!(b.end_array().is_ok());
147 let result = b.finish();
148 assert!(result.is_ok());
149 let bytes = result.unwrap_or_default();
150 assert!(bytes == br#"[1,true,null,"x"]"#);
151 }
152
153 #[test]
154 fn struson_backend_writes_empty_object() {
155 let mut b = StrusonBackend::new(Vec::new());
156 assert!(b.begin_object().is_ok());
157 assert!(b.end_object().is_ok());
158 let result = b.finish();
159 assert!(result.is_ok());
160 let bytes = result.unwrap_or_default();
161 assert!(bytes == b"{}");
162 }
163
164 #[test]
165 fn struson_backend_writes_nested_structure() {
166 let mut b = StrusonBackend::new(Vec::new());
167 assert!(b.begin_object().is_ok());
168 assert!(b.write_name("a").is_ok());
169 assert!(b.begin_array().is_ok());
170 assert!(b.write_number(1).is_ok());
171 assert!(b.begin_object().is_ok());
172 assert!(b.write_name("b").is_ok());
173 assert!(b.write_bool(true).is_ok());
174 assert!(b.end_object().is_ok());
175 assert!(b.end_array().is_ok());
176 assert!(b.end_object().is_ok());
177 let result = b.finish();
178 assert!(result.is_ok());
179 let bytes = result.unwrap_or_default();
180 assert!(bytes == br#"{"a":[1,{"b":true}]}"#);
181 }
182
183 #[test]
184 fn struson_backend_writes_simple_key_value() {
185 let mut b = StrusonBackend::new(Vec::new());
186 assert!(b.begin_object().is_ok());
187 assert!(b.write_name("k").is_ok());
188 assert!(b.write_string("v").is_ok());
189 assert!(b.end_object().is_ok());
190 let result = b.finish();
191 assert!(result.is_ok());
192 let bytes = result.unwrap_or_default();
193 assert!(bytes == br#"{"k":"v"}"#);
194 }
195
196 #[test]
197 fn streaming_writes_simple_string() {
198 let mut b = StrusonBackend::new(Vec::new());
199 assert!(b.write_string_streaming(|w| w.write_all(b"hello")).is_ok());
200 let result = b.finish();
201 assert!(result.is_ok());
202 let bytes = result.unwrap_or_default();
203 assert!(bytes == br#""hello""#);
204 }
205
206 #[test]
207 fn streaming_writes_base64_safe_chars() {
208 let mut b = StrusonBackend::new(Vec::new());
209 assert!(b
210 .write_string_streaming(|w| w.write_all(b"data:image/png;base64,iVBORw=="))
211 .is_ok());
212 let result = b.finish();
213 assert!(result.is_ok());
214 let bytes = result.unwrap_or_default();
215 assert!(bytes == br#""data:image/png;base64,iVBORw==""#);
216 }
217
218 #[test]
219 fn streaming_writes_json_special_chars_are_escaped() {
220 let mut b = StrusonBackend::new(Vec::new());
221 assert!(b
222 .write_string_streaming(|w| w.write_all(br#""quoted""#))
223 .is_ok());
224 let result = b.finish();
225 assert!(result.is_ok());
226 let bytes = result.unwrap_or_default();
227 assert!(bytes == br#""\"quoted\"""#);
228 }
229
230 #[test]
231 fn streaming_writes_multi_chunk() {
232 let mut b = StrusonBackend::new(Vec::new());
233 assert!(b
234 .write_string_streaming(|w| {
235 w.write_all(b"one")?;
236 w.write_all(b"-")?;
237 w.write_all(b"two")?;
238 w.write_all(b"-")?;
239 w.write_all(b"three")
240 })
241 .is_ok());
242 let result = b.finish();
243 assert!(result.is_ok());
244 let bytes = result.unwrap_or_default();
245 assert!(bytes == br#""one-two-three""#);
246 }
247
248 #[test]
249 fn streaming_writes_error_in_closure_still_finishes_value() {
250 let mut b = StrusonBackend::new(Vec::new());
251 assert!(b.begin_object().is_ok());
252 assert!(b.write_name("failed").is_ok());
253
254 let result = b.write_string_streaming(|_| Err(std::io::Error::other("closure failed")));
255 assert!(result.is_err());
256
257 let after_key =
258 std::panic::catch_unwind(core::panic::AssertUnwindSafe(|| b.write_name("after")));
259 #[allow(clippy::expect_used)]
260 let write_result = after_key.expect("asserted catch_unwind returned Ok");
261 assert!(write_result.is_ok());
262
263 assert!(b.write_string("ok").is_ok());
264 assert!(b.end_object().is_ok());
265 let finish_result = b.finish();
266 assert!(finish_result.is_ok());
267 let bytes = finish_result.unwrap_or_default();
268 assert!(bytes == br#"{"failed":"","after":"ok"}"#);
269 }
270
271 struct CallCountingWriter {
273 write_calls: Rc<RefCell<Vec<usize>>>,
274 buffer: Vec<u8>,
275 }
276
277 impl CallCountingWriter {
278 fn new() -> Self {
279 Self {
280 write_calls: Rc::new(RefCell::new(Vec::new())),
281 buffer: Vec::new(),
282 }
283 }
284
285 fn write_calls(&self) -> Vec<usize> {
286 self.write_calls.borrow().clone()
287 }
288
289 fn total_bytes(&self) -> usize {
290 self.write_calls.borrow().iter().sum()
291 }
292 }
293
294 impl Write for CallCountingWriter {
295 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
296 self.write_calls.borrow_mut().push(buf.len());
297 self.buffer.extend_from_slice(buf);
298 Ok(buf.len())
299 }
300
301 fn flush(&mut self) -> std::io::Result<()> {
302 Ok(())
303 }
304 }
305
306 #[test]
307 fn override_chunks_through_inner_writer() {
308 let mut counter = CallCountingWriter::new();
314 let mut b = StrusonBackend::new(&mut counter);
315
316 let chunk = vec![b'x'; 256];
320 assert!(b
321 .write_string_streaming(|w| {
322 w.write_all(&chunk)?;
323 w.write_all(&chunk)?;
324 w.write_all(&chunk)?;
325 w.write_all(&chunk)
326 })
327 .is_ok());
328
329 let write_calls = counter.write_calls();
330 let total_bytes = counter.total_bytes();
331
332 assert!(
335 total_bytes >= 1024,
336 "Expected at least 1024 bytes written, got {total_bytes}"
337 );
338
339 let max_write = write_calls.iter().max().copied().unwrap_or(0);
343 assert!(
344 max_write < total_bytes,
345 "Expected multiple writes (streaming), but got one large write: max={max_write}, total={total_bytes}"
346 );
347
348 assert!(
350 write_calls.len() > 1,
351 "Expected multiple write calls (streaming), got {}",
352 write_calls.len()
353 );
354 }
355}