standout_dispatch/
stream.rs1use serde::Serialize;
19use std::cell::RefCell;
20use std::fmt;
21use std::io::Write;
22use std::rc::Rc;
23
24#[derive(Clone)]
25pub struct StreamSink(Rc<RefCell<Box<dyn Write>>>);
26
27impl StreamSink {
28 pub fn new(writer: impl Write + 'static) -> Self {
29 Self(Rc::new(RefCell::new(Box::new(writer))))
30 }
31
32 pub fn process_stdout() -> Self {
33 Self::new(std::io::stdout())
34 }
35
36 pub fn redirect(&self, writer: impl Write + 'static) {
38 *self.0.borrow_mut() = Box::new(writer);
39 }
40
41 pub fn with_writer<R>(&self, write: impl FnOnce(&mut dyn Write) -> R) -> R {
43 write(&mut **self.0.borrow_mut())
44 }
45
46 fn write_line(&self, line: &[u8]) -> std::io::Result<()> {
47 self.with_writer(|writer| {
48 writer.write_all(line)?;
49 writer.write_all(b"\n")?;
50 writer.flush()
51 })
52 }
53}
54
55#[derive(Clone, Debug, Default)]
56pub struct StreamCapture(Rc<RefCell<Vec<u8>>>);
57
58impl StreamCapture {
59 pub fn take(&self) -> Vec<u8> {
60 std::mem::take(&mut *self.0.borrow_mut())
61 }
62}
63
64impl Write for StreamCapture {
65 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
66 self.0.borrow_mut().extend_from_slice(buf);
67 Ok(buf.len())
68 }
69
70 fn flush(&mut self) -> std::io::Result<()> {
71 Ok(())
72 }
73}
74
75impl fmt::Debug for StreamSink {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 f.write_str("StreamSink")
78 }
79}
80
81#[derive(Clone, Debug, Default)]
82pub struct EntryStream {
83 sink: Option<StreamSink>,
84}
85
86impl EntryStream {
87 pub fn discarding() -> Self {
88 Self { sink: None }
89 }
90
91 pub fn writing_to(sink: StreamSink) -> Self {
92 Self { sink: Some(sink) }
93 }
94
95 pub fn is_live(&self) -> bool {
97 self.sink.is_some()
98 }
99
100 pub fn emit<T: Serialize + ?Sized>(&self, entry: &T) -> Result<(), StreamError> {
102 let Some(sink) = &self.sink else {
103 return Ok(());
104 };
105 let line = serde_json::to_vec(entry)?;
106 sink.write_line(&line)?;
107 Ok(())
108 }
109}
110
111#[derive(Debug, thiserror::Error)]
112pub enum StreamError {
113 #[error("stream entry does not serialize: {0}")]
114 Serialize(#[from] serde_json::Error),
115 #[error("stream entry could not be written: {0}")]
116 Write(#[from] std::io::Error),
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[derive(Serialize)]
124 struct Entry<'a> {
125 #[serde(rename = "type")]
126 entry_type: &'a str,
127 resource: &'a str,
128 }
129
130 #[test]
131 fn a_live_stream_writes_one_compact_line_per_entry() {
132 let captured = StreamCapture::default();
133 let stream = EntryStream::writing_to(StreamSink::new(captured.clone()));
134 assert!(stream.is_live());
135 stream
136 .emit(&Entry {
137 entry_type: "apply_start",
138 resource: "web",
139 })
140 .unwrap();
141 stream
142 .emit(&Entry {
143 entry_type: "note",
144 resource: "line\nbreak",
145 })
146 .unwrap();
147 assert_eq!(
148 String::from_utf8(captured.take()).unwrap(),
149 "{\"type\":\"apply_start\",\"resource\":\"web\"}\n{\"type\":\"note\",\"resource\":\"line\\nbreak\"}\n"
150 );
151 }
152
153 #[test]
154 fn a_redirected_sink_moves_every_clone_to_the_new_destination() {
155 let first = StreamCapture::default();
156 let second = StreamCapture::default();
157 let sink = StreamSink::new(first.clone());
158 let stream = EntryStream::writing_to(sink.clone());
159 stream.emit(&serde_json::json!({"n": 1})).unwrap();
160 sink.redirect(second.clone());
161 stream.emit(&serde_json::json!({"n": 2})).unwrap();
162 sink.with_writer(|w| w.write_all(b"tail\n")).unwrap();
163 assert_eq!(first.take(), b"{\"n\":1}\n");
164 assert_eq!(second.take(), b"{\"n\":2}\ntail\n");
165 }
166
167 #[test]
168 fn a_discarding_stream_neither_serializes_nor_writes() {
169 struct Unserializable;
170 impl Serialize for Unserializable {
171 fn serialize<S: serde::Serializer>(&self, _: S) -> Result<S::Ok, S::Error> {
172 Err(serde::ser::Error::custom("never asked"))
173 }
174 }
175 let stream = EntryStream::discarding();
176 assert!(!stream.is_live());
177 stream.emit(&Unserializable).unwrap();
178 assert!(EntryStream::default().emit(&Unserializable).is_ok());
179 }
180
181 #[test]
182 fn serialization_and_write_failures_are_distinct_errors() {
183 struct Closed;
184 impl Write for Closed {
185 fn write(&mut self, _: &[u8]) -> std::io::Result<usize> {
186 Err(std::io::Error::new(
187 std::io::ErrorKind::BrokenPipe,
188 "closed",
189 ))
190 }
191 fn flush(&mut self) -> std::io::Result<()> {
192 Ok(())
193 }
194 }
195 let stream = EntryStream::writing_to(StreamSink::new(Closed));
196 let write = stream.emit(&serde_json::json!({})).unwrap_err();
197 assert!(matches!(write, StreamError::Write(_)), "{write}");
198
199 let mut map = std::collections::HashMap::new();
200 map.insert((1u8, 2u8), 3u8);
201 let stream = EntryStream::writing_to(StreamSink::new(Vec::new()));
202 let serialize = stream.emit(&map).unwrap_err();
203 assert!(
204 matches!(serialize, StreamError::Serialize(_)),
205 "{serialize}"
206 );
207 }
208}