Skip to main content

standout_dispatch/
results.rs

1//! The typed results channel a handler produces values through.
2//!
3//! A command produces either one batch value, which the handler returns, or a
4//! sequence of typed events it emits through [`Results`] before returning the
5//! summary. Both are results: the values the command exists to produce, as
6//! opposed to operational messages about the run.
7//!
8//! [`Results`] carries what retains a value and what writes it behind an `Rc`,
9//! so it needs no lifetime parameter and `Handler::handle` gains none.
10//! [`RunRecorder`] retains each value as data whatever representation the run
11//! selected, so a test asserts on the values and on the rendered bytes
12//! separately. [`EventSink`] is the representation-specific destination the
13//! consuming framework implements, because the human representation of an event
14//! is a template render and this crate does not render.
15
16use 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/// The event type of a command that emits none: uninhabited, so `emit` has no
24/// argument that can be constructed.
25#[derive(Debug, Serialize)]
26pub enum NoEvents {}
27
28/// Whether a command whose `Handler::Event` is `E` produces its result while
29/// it runs: false for [`NoEvents`] and true for every other event type.
30pub 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    /// The destination could not turn the value into bytes: a render failure
39    /// carrying the message the run reports.
40    #[error("{0}")]
41    Render(String),
42    #[error("event could not be written: {0}")]
43    Write(#[from] std::io::Error),
44}
45
46/// Where the run's rendered bytes went. `Pager` carries the shell word list the
47/// environment named, decided without starting the pager.
48#[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
65/// The representation's destination for one emitted event.
66///
67/// `deliver` returns once the value has been rendered or framed and written,
68/// so the handler's next statement runs after the consumer could read it. It
69/// returns `Err` for every reason the event did not reach the destination, so
70/// the handler's `?` stops at the emit that failed.
71pub trait EventSink {
72    fn deliver(&self, event: &serde_json::Value) -> Result<(), EmitError>;
73
74    /// False once the destination has gone away; nothing further is written.
75    fn is_open(&self) -> bool {
76        true
77    }
78
79    /// Remembers a failure the channel raised before reaching `deliver`, so a
80    /// value that never became an event fails the run the way one the
81    /// destination refused does, whether or not the handler propagates it.
82    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/// Retains the run's result values and its delivery decision.
93#[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    /// Retains every value the run produces, events included.
104    pub fn new() -> Self {
105        Self::with_event_retention(true)
106    }
107
108    /// Retains the summary and the delivery decision and drops each event, so a
109    /// run whose events nobody reads back costs memory for one value.
110    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
143/// The handler's channel for the values a command produces while it runs.
144///
145/// Not `Clone`: `Handler::handle` receives it as a `&mut` borrow the framework
146/// owns, and a clone would let a handler keep emitting past its own run.
147pub 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    /// The channel a run installs: every value is written, and retained too
180    /// when the entry point has a recorder that keeps events.
181    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    /// Returns once the value has been written and retained; fails when it does
190    /// not serialize, does not render, or cannot be written. The write comes
191    /// first, so a value the destination refused is never retained. Every
192    /// failure reaches the sink, the serialization one through
193    /// [`EventSink::record_failure`] because it happens before the write.
194    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}