writer/
json_writer.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::{Format, Result, TestBuffers, ToolIO, Writer};
6use serde::Serialize;
7use std::fmt::Display;
8use std::io::Write;
9
10/// Type-safe machine output implementation of [`crate::ToolIO`]
11pub struct JsonWriter<T> {
12    format: Option<Format>,
13    simple_writer: Writer,
14    _p: std::marker::PhantomData<fn(T)>,
15}
16
17impl<T> JsonWriter<T> {
18    /// Create a new writer that doesn't support machine output at all, with the
19    /// given streams underlying it.
20    pub fn new_buffers<'a, O, E>(format: Option<Format>, stdout: O, stderr: E) -> Self
21    where
22        O: Write + 'static,
23        E: Write + 'static,
24    {
25        let simple_writer = Writer::new_buffers(stdout, stderr);
26        let _p = Default::default();
27        Self { format, simple_writer, _p }
28    }
29
30    /// Create a new Writer with the specified format.
31    ///
32    /// Passing None for format implies no output via the machine function.
33    pub fn new(format: Option<Format>) -> Self {
34        let simple_writer = Writer::new();
35        let _p = Default::default();
36        Self { format, simple_writer, _p }
37    }
38
39    /// Returns a writer backed by string buffers that can be extracted after
40    /// the writer is done with
41    pub fn new_test(format: Option<Format>, test_buffers: &TestBuffers) -> Self {
42        Self::new_buffers(format, test_buffers.stdout.clone(), test_buffers.stderr.clone())
43    }
44
45    /// Allow using this writer as a SimpleWriter.
46    /// This is useful for calling other tools that do not use JsonWriter.
47    pub fn simple_writer(self) -> Writer {
48        self.simple_writer
49    }
50
51    pub fn stderr(&mut self) -> &'_ mut Box<dyn Write> {
52        self.simple_writer.stderr()
53    }
54}
55
56impl<T> JsonWriter<T>
57where
58    T: Serialize,
59{
60    /// Write the items from the iterable object to standard output.
61    ///
62    /// This is a no-op if `is_machine` returns false.
63    pub fn machine_many<I: IntoIterator<Item = T>>(&mut self, output: I) -> Result<()> {
64        if self.format.is_some() {
65            for output in output {
66                self.machine(&output)?;
67            }
68        }
69        Ok(())
70    }
71
72    /// Write the item to standard output.
73    ///
74    /// This is a no-op if `is_machine` returns false.
75    pub fn machine(&mut self, output: &T) -> Result<()> {
76        if let Some(format) = self.format {
77            format_output(format, &mut self.simple_writer, output)
78        } else {
79            Ok(())
80        }
81    }
82
83    /// If this object is outputting machine output, print the item's machine
84    /// representation to stdout. Otherwise, print the display item given.
85    pub fn machine_or<D: Display>(&mut self, value: &T, or: D) -> Result<()> {
86        match self.format {
87            Some(format) => format_output(format, &mut self.simple_writer, value)?,
88            None => writeln!(self, "{or}")?,
89        }
90        Ok(())
91    }
92
93    /// If this object is outputting machine output, print the item's machine
94    /// representation to stdout. Otherwise, `write!` the display item given.
95    pub fn machine_or_write<D: Display>(&mut self, value: &T, or: D) -> Result<()> {
96        match self.format {
97            Some(format) => format_output(format, &mut self.simple_writer, value)?,
98            None => write!(self, "{or}")?,
99        }
100        Ok(())
101    }
102
103    /// If this object is outputting machine output, prints the item's machine
104    /// representation to stdout. Otherwise, call the closure with the object
105    /// and print the result.
106    pub fn machine_or_else<F, R>(&mut self, value: &T, f: F) -> Result<()>
107    where
108        F: FnOnce() -> R,
109        R: Display,
110    {
111        match self.format {
112            Some(format) => format_output(format, &mut self.simple_writer, value)?,
113            None => writeln!(self, "{}", f())?,
114        }
115        Ok(())
116    }
117
118    pub fn format(&self) -> Option<Format> {
119        self.format
120    }
121
122    pub fn formatted<J: Serialize>(&mut self, output: &J) -> Result<()> {
123        if let Some(format) = self.format {
124            format_output(format, &mut self.simple_writer, output)
125        } else {
126            Ok(())
127        }
128    }
129}
130
131pub fn format_output<W: Write, T>(format: Format, mut out: W, output: &T) -> Result<()>
132where
133    T: Serialize,
134{
135    match format {
136        Format::Json => {
137            serde_json::to_writer(&mut out, output)?;
138            writeln!(out)?;
139            out.flush()?;
140        }
141        Format::JsonPretty => {
142            serde_json::to_writer_pretty(&mut out, output)?;
143        }
144    }
145    Ok(())
146}
147
148impl<T> ToolIO for JsonWriter<T>
149where
150    T: Serialize,
151{
152    type OutputItem = T;
153
154    fn is_machine_supported() -> bool {
155        true
156    }
157
158    fn is_machine(&self) -> bool {
159        self.format.is_some()
160    }
161
162    fn stderr(&mut self) -> &'_ mut Box<dyn Write> {
163        self.stderr()
164    }
165
166    fn item(&mut self, value: &T) -> Result<()>
167    where
168        T: Display,
169    {
170        if self.is_machine() {
171            self.machine(value)
172        } else {
173            self.line(value)
174        }
175    }
176}
177
178impl<T> Write for JsonWriter<T>
179where
180    T: Serialize,
181{
182    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
183        if self.format.is_some() {
184            Ok(buf.len())
185        } else {
186            self.simple_writer.write(buf)
187        }
188    }
189
190    fn flush(&mut self) -> std::io::Result<()> {
191        if self.format.is_some() {
192            Ok(())
193        } else {
194            self.simple_writer.flush()
195        }
196    }
197}
198#[cfg(test)]
199mod test {
200    use super::*;
201
202    #[test]
203    fn test_not_machine_is_ok() {
204        let test_buffers = TestBuffers::default();
205        let mut writer: JsonWriter<&str> = JsonWriter::new_test(None, &test_buffers);
206        let res = writer.machine(&"ehllo");
207        assert!(res.is_ok());
208    }
209
210    #[test]
211    fn test_machine_valid_json_is_ok() {
212        let test_buffers = TestBuffers::default();
213        let mut writer: JsonWriter<&str> = JsonWriter::new_test(Some(Format::Json), &test_buffers);
214        let res = writer.machine(&"ehllo");
215        assert!(res.is_ok());
216    }
217
218    #[test]
219    fn writer_implements_write() {
220        let test_buffers = TestBuffers::default();
221        let mut writer: JsonWriter<&str> = JsonWriter::new_test(None, &test_buffers);
222        writer.write_all(b"foobar").unwrap();
223
224        let (stdout, stderr) = test_buffers.into_strings();
225        assert_eq!(stdout, "foobar");
226        assert_eq!(stderr, "");
227    }
228
229    #[test]
230    fn writer_implements_write_ignored_on_machine() {
231        let test_buffers = TestBuffers::default();
232        let mut writer: JsonWriter<&str> = JsonWriter::new_test(Some(Format::Json), &test_buffers);
233        writer.write_all(b"foobar").unwrap();
234
235        let (stdout, stderr) = test_buffers.into_strings();
236        assert_eq!(stdout, "");
237        assert_eq!(stderr, "");
238    }
239
240    #[test]
241    fn test_item_for_test_as_machine() {
242        let test_buffers = TestBuffers::default();
243        let mut writer: JsonWriter<&str> = JsonWriter::new_test(Some(Format::Json), &test_buffers);
244        writer.item(&"hello").unwrap();
245        writer.machine_or(&"hello again", "but what if").unwrap();
246        writer.machine_or_else(&"hello forever", || "but what if else").unwrap();
247
248        assert_eq!(
249            test_buffers.into_stdout_str(),
250            "\"hello\"\n\"hello again\"\n\"hello forever\"\n"
251        );
252    }
253
254    #[test]
255    fn test_item_for_test_as_not_machine() {
256        let test_buffers = TestBuffers::default();
257        let mut writer: JsonWriter<&str> = JsonWriter::new_test(None, &test_buffers);
258        writer.item(&"hello").unwrap();
259        writer.machine_or(&"hello again", "but what if").unwrap();
260        writer.machine_or_else(&"hello forever", || "but what if else").unwrap();
261
262        assert_eq!(test_buffers.into_stdout_str(), "hello\nbut what if\nbut what if else\n");
263    }
264
265    #[test]
266    fn test_machine_for_test() {
267        let test_buffers = TestBuffers::default();
268        let mut writer: JsonWriter<&str> = JsonWriter::new_test(Some(Format::Json), &test_buffers);
269        writer.machine(&"hello").unwrap();
270
271        assert_eq!(test_buffers.into_stdout_str(), "\"hello\"\n");
272    }
273
274    #[test]
275    fn test_not_machine_for_test_is_empty() {
276        let test_buffers = TestBuffers::default();
277        let mut writer: JsonWriter<&str> = JsonWriter::new_test(None, &test_buffers);
278        writer.machine(&"hello").unwrap();
279
280        assert!(test_buffers.into_stdout_str().is_empty());
281    }
282
283    #[test]
284    fn test_machine_makes_is_machine_true() {
285        let test_buffers = TestBuffers::default();
286        let writer: JsonWriter<&str> = JsonWriter::new_test(Some(Format::Json), &test_buffers);
287        assert!(writer.is_machine());
288    }
289
290    #[test]
291    fn test_not_machine_makes_is_machine_false() {
292        let test_buffers = TestBuffers::default();
293        let writer: JsonWriter<&str> = JsonWriter::new_test(None, &test_buffers);
294        assert!(!writer.is_machine());
295    }
296
297    #[test]
298    fn line_writer_for_machine_is_ok() {
299        let test_buffers = TestBuffers::default();
300        let mut writer: JsonWriter<&str> = JsonWriter::new_test(Some(Format::Json), &test_buffers);
301        writer.line("hello").unwrap();
302
303        let (stdout, stderr) = test_buffers.into_strings();
304        assert_eq!(stdout, "");
305        assert_eq!(stderr, "");
306    }
307
308    #[test]
309    fn writer_write_for_machine_is_ok() {
310        let test_buffers = TestBuffers::default();
311        let mut writer: JsonWriter<&str> = JsonWriter::new_test(Some(Format::Json), &test_buffers);
312        writer.print("foobar").unwrap();
313
314        let (stdout, stderr) = test_buffers.into_strings();
315        assert_eq!(stdout, "");
316        assert_eq!(stderr, "");
317    }
318
319    #[test]
320    fn writer_print_output_has_no_newline() {
321        let test_buffers = TestBuffers::default();
322        let mut writer: JsonWriter<&str> = JsonWriter::new_test(None, &test_buffers);
323        writer.print("foobar").unwrap();
324
325        let (stdout, stderr) = test_buffers.into_strings();
326        assert_eq!(stdout, "foobar");
327        assert_eq!(stderr, "");
328    }
329
330    #[test]
331    fn writing_errors_goes_to_the_right_stream() {
332        let test_buffers = TestBuffers::default();
333        let mut writer: JsonWriter<&str> = JsonWriter::new_test(None, &test_buffers);
334        writeln!(writer.stderr(), "hello").unwrap();
335
336        let (stdout, stderr) = test_buffers.into_strings();
337        assert_eq!(stdout, "");
338        assert_eq!(stderr, "hello\n");
339    }
340
341    #[test]
342    fn test_machine_writes_pretty_json() {
343        let test_buffers = TestBuffers::default();
344        let mut writer: JsonWriter<serde_json::Value> =
345            JsonWriter::new_test(Some(Format::JsonPretty), &test_buffers);
346        let test_input = serde_json::json!({
347            "object1": {
348                "line1": "hello",
349                "line2": "foobar"
350            }
351        });
352        writer.machine(&test_input).unwrap();
353
354        assert_eq!(
355            test_buffers.into_stdout_str(),
356            r#"{
357  "object1": {
358    "line1": "hello",
359    "line2": "foobar"
360  }
361}"#
362        );
363    }
364}