1use serde::Serialize;
17use std::any::TypeId;
18use std::cell::RefCell;
19use std::marker::PhantomData;
20use std::path::{Path, PathBuf};
21use std::rc::Rc;
22
23#[derive(Debug, Serialize)]
26pub enum NoEvents {}
27
28pub fn emits_events<E: 'static>() -> bool {
31 TypeId::of::<E>() != TypeId::of::<NoEvents>()
32}
33
34#[derive(Debug, thiserror::Error)]
35pub enum EmitError {
36 #[error("event does not serialize: {0}")]
37 Serialize(#[from] serde_json::Error),
38 #[error("{0}")]
41 Render(String),
42 #[error("event could not be written: {0}")]
43 Write(#[from] std::io::Error),
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Default)]
49pub enum Delivery {
50 #[default]
51 Stdout,
52 File(PathBuf),
53 Pager(String),
54}
55
56impl Delivery {
57 pub fn path(&self) -> Option<&Path> {
58 match self {
59 Delivery::Stdout | Delivery::Pager(_) => None,
60 Delivery::File(path) => Some(path),
61 }
62 }
63}
64
65pub trait EventSink {
72 fn deliver(&self, event: &serde_json::Value) -> Result<(), EmitError>;
73
74 fn is_open(&self) -> bool {
76 true
77 }
78
79 fn record_failure(&self, _error: &EmitError) {}
83}
84
85#[derive(Debug)]
86struct RunRecord {
87 records: Vec<serde_json::Value>,
88 delivery: Delivery,
89 retain_events: bool,
90}
91
92#[derive(Debug, Clone)]
94pub struct RunRecorder(Rc<RefCell<RunRecord>>);
95
96impl Default for RunRecorder {
97 fn default() -> Self {
98 Self::new()
99 }
100}
101
102impl RunRecorder {
103 pub fn new() -> Self {
105 Self::with_event_retention(true)
106 }
107
108 pub fn summary_only() -> Self {
111 Self::with_event_retention(false)
112 }
113
114 fn with_event_retention(retain_events: bool) -> Self {
115 Self(Rc::new(RefCell::new(RunRecord {
116 records: Vec::new(),
117 delivery: Delivery::default(),
118 retain_events,
119 })))
120 }
121
122 pub fn record(&self, value: serde_json::Value) {
123 self.0.borrow_mut().records.push(value);
124 }
125
126 pub fn retains_events(&self) -> bool {
127 self.0.borrow().retain_events
128 }
129
130 pub fn set_delivery(&self, delivery: Delivery) {
131 self.0.borrow_mut().delivery = delivery;
132 }
133
134 pub fn records(&self) -> Vec<serde_json::Value> {
135 self.0.borrow().records.clone()
136 }
137
138 pub fn delivery(&self) -> Delivery {
139 self.0.borrow().delivery.clone()
140 }
141}
142
143pub struct Results<E: Serialize> {
148 recorder: Option<RunRecorder>,
149 sink: Option<Rc<dyn EventSink>>,
150 _event: PhantomData<fn(E)>,
151}
152
153impl<E: Serialize> std::fmt::Debug for Results<E> {
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 f.debug_struct("Results")
156 .field("recording", &self.recorder.is_some())
157 .field("writing", &self.sink.is_some())
158 .finish()
159 }
160}
161
162impl<E: Serialize> Results<E> {
163 pub fn discarding() -> Self {
164 Self {
165 recorder: None,
166 sink: None,
167 _event: PhantomData,
168 }
169 }
170
171 pub fn recording(recorder: RunRecorder) -> Self {
172 Self {
173 recorder: Some(recorder),
174 sink: None,
175 _event: PhantomData,
176 }
177 }
178
179 pub fn for_run(recorder: Option<RunRecorder>, sink: Rc<dyn EventSink>) -> Self {
182 Self {
183 recorder,
184 sink: Some(sink),
185 _event: PhantomData,
186 }
187 }
188
189 pub fn emit(&mut self, event: E) -> Result<(), EmitError> {
195 let retaining = self
196 .recorder
197 .as_ref()
198 .filter(|recorder| recorder.retains_events());
199 let open = self.sink.as_ref().filter(|sink| sink.is_open());
200 if retaining.is_none() && open.is_none() {
201 return Ok(());
202 }
203 let value = match serde_json::to_value(&event) {
204 Ok(value) => value,
205 Err(error) => {
206 let error = EmitError::from(error);
207 if let Some(sink) = self.sink.as_ref() {
208 sink.record_failure(&error);
209 }
210 return Err(error);
211 }
212 };
213 if let Some(sink) = open {
214 sink.deliver(&value)?;
215 }
216 if let Some(recorder) = retaining {
217 recorder.record(value);
218 }
219 Ok(())
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[derive(Default)]
228 struct Delivered {
229 values: RefCell<Vec<serde_json::Value>>,
230 open: bool,
231 }
232
233 impl EventSink for Delivered {
234 fn deliver(&self, event: &serde_json::Value) -> Result<(), EmitError> {
235 self.values.borrow_mut().push(event.clone());
236 Ok(())
237 }
238 fn is_open(&self) -> bool {
239 self.open
240 }
241 }
242
243 fn open() -> Rc<Delivered> {
244 Rc::new(Delivered {
245 values: RefCell::new(Vec::new()),
246 open: true,
247 })
248 }
249
250 #[test]
251 fn a_recorder_retains_values_in_order() {
252 let recorder = RunRecorder::new();
253 recorder.record(serde_json::json!({"n": 1}));
254 recorder.record(serde_json::json!({"n": 2}));
255 assert_eq!(
256 recorder.records(),
257 vec![serde_json::json!({"n": 1}), serde_json::json!({"n": 2})]
258 );
259 }
260
261 #[test]
262 fn a_recorder_carries_the_delivery_decision() {
263 let recorder = RunRecorder::new();
264 assert_eq!(recorder.delivery(), Delivery::Stdout);
265 assert_eq!(recorder.delivery().path(), None);
266 recorder.set_delivery(Delivery::File(PathBuf::from("out.txt")));
267 assert_eq!(recorder.delivery().path(), Some(Path::new("out.txt")));
268 }
269
270 #[test]
271 fn an_emitted_event_is_retained_and_written_in_the_same_call() {
272 let recorder = RunRecorder::new();
273 let sink = open();
274 let mut results = Results::for_run(Some(recorder.clone()), sink.clone());
275 results
276 .emit(serde_json::json!({"type": "apply_start"}))
277 .unwrap();
278 assert_eq!(recorder.records().len(), 1);
279 assert_eq!(sink.values.borrow().len(), 1);
280 }
281
282 struct Unserializable;
283
284 impl Serialize for Unserializable {
285 fn serialize<S: serde::Serializer>(&self, _: S) -> Result<S::Ok, S::Error> {
286 Err(serde::ser::Error::custom("never asked"))
287 }
288 }
289
290 #[test]
291 fn a_discarding_channel_keeps_nothing_and_never_serializes() {
292 Results::discarding().emit(Unserializable).unwrap();
293 }
294
295 #[test]
296 fn a_summary_only_recorder_writes_every_event_and_retains_none() {
297 let recorder = RunRecorder::summary_only();
298 let sink = open();
299 let mut results = Results::for_run(Some(recorder.clone()), sink.clone());
300 for n in 0..3 {
301 results.emit(serde_json::json!({ "n": n })).unwrap();
302 }
303 recorder.record(serde_json::json!({"total": 3}));
304 assert_eq!(sink.values.borrow().len(), 3);
305 assert_eq!(recorder.records(), vec![serde_json::json!({"total": 3})]);
306 }
307
308 #[test]
309 fn a_summary_only_recorder_over_a_closed_sink_never_serializes_an_event() {
310 let closed = Rc::new(Delivered {
311 values: RefCell::new(Vec::new()),
312 open: false,
313 });
314 let mut results = Results::for_run(Some(RunRecorder::summary_only()), closed);
315 results.emit(Unserializable).unwrap();
316 }
317
318 #[test]
319 fn a_closed_sink_still_retains_the_value_and_writes_nothing() {
320 let recorder = RunRecorder::new();
321 let closed = Rc::new(Delivered {
322 values: RefCell::new(Vec::new()),
323 open: false,
324 });
325 let mut results = Results::for_run(Some(recorder.clone()), closed.clone());
326 results.emit(serde_json::json!({"n": 1})).unwrap();
327 assert_eq!(recorder.records().len(), 1);
328 assert!(closed.values.borrow().is_empty());
329 }
330
331 #[test]
332 fn an_unserializable_event_is_an_emit_error() {
333 let mut results = Results::recording(RunRecorder::new());
334 let mut map = std::collections::HashMap::new();
335 map.insert((1u8, 2u8), 3u8);
336 let error = results.emit(map).unwrap_err();
337 assert!(matches!(error, EmitError::Serialize(_)), "{error}");
338 }
339
340 #[test]
341 fn an_unserializable_event_reaches_the_sink_as_a_recorded_failure() {
342 #[derive(Default)]
343 struct Remembers(RefCell<Vec<String>>);
344 impl EventSink for Remembers {
345 fn deliver(&self, _: &serde_json::Value) -> Result<(), EmitError> {
346 Ok(())
347 }
348 fn record_failure(&self, error: &EmitError) {
349 self.0.borrow_mut().push(error.to_string());
350 }
351 }
352 let sink = Rc::new(Remembers::default());
353 let mut results = Results::for_run(None, sink.clone());
354 let mut map = std::collections::HashMap::new();
355 map.insert((1u8, 2u8), 3u8);
356 results.emit(map).unwrap_err();
357 assert_eq!(sink.0.borrow().len(), 1, "{:?}", sink.0.borrow());
358 }
359
360 #[test]
361 fn a_destination_that_refuses_the_bytes_is_an_emit_error() {
362 struct Refuses;
363 impl EventSink for Refuses {
364 fn deliver(&self, _: &serde_json::Value) -> Result<(), EmitError> {
365 Err(EmitError::Write(std::io::Error::other("no room")))
366 }
367 }
368 let mut results = Results::for_run(None, Rc::new(Refuses));
369 let error = results.emit(serde_json::json!({"n": 1})).unwrap_err();
370 assert!(matches!(error, EmitError::Write(_)), "{error}");
371 }
372
373 #[test]
374 fn an_event_the_destination_refused_is_not_retained() {
375 struct Refuses;
376 impl EventSink for Refuses {
377 fn deliver(&self, _: &serde_json::Value) -> Result<(), EmitError> {
378 Err(EmitError::Write(std::io::Error::other("no room")))
379 }
380 }
381 let recorder = RunRecorder::new();
382 let mut results = Results::for_run(Some(recorder.clone()), Rc::new(Refuses));
383 results.emit(serde_json::json!({"n": 1})).unwrap_err();
384 assert!(recorder.records().is_empty());
385 }
386}