status/
context.rs

1use std::fmt;
2
3/// Adds nuance to errors.
4///
5/// Goals:
6/// - Easily add context for any `Kind` at each level of the call stack.
7/// - Programmatic access to the context.
8/// - User-friendly without losing helpful debug information.
9pub trait Context: Default + Clone + fmt::Display + fmt::Debug + Send + Sync + 'static {
10    /// Replace fields in `self` with those populated in `replacements`.
11    fn update(self, replacements: Self) -> Self;
12
13    /// Returns `true` is the `Context` has no content.
14    fn is_empty(&self) -> bool;
15}
16
17/// No context needed.
18#[derive(Default, Copy, Clone, Debug)]
19pub struct NoContext;
20
21impl fmt::Display for NoContext {
22    fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        Ok(())
24    }
25}
26
27impl Context for NoContext {
28    fn update(self, _replacements: Self) -> Self {
29        self
30    }
31
32    fn is_empty(&self) -> bool {
33        true
34    }
35}
36
37/// Adhoc [`Context`].
38///
39/// Unlike most [`Context`]s, this is meant to be opaque and not programmatically specify the status.
40/// It is only good for displaying the data to the user when prototyping before one transitions to more formal [`Context`]s.
41///
42/// Note: This is the default [`Context`] for [`Status`].
43#[derive(Default, Clone, Debug)]
44pub struct AdhocContext {
45    data: indexmap::IndexMap<&'static str, Box<dyn AdhocValue>>,
46}
47
48impl AdhocContext {
49    /// Create an empty [`Context`].
50    pub fn new() -> Self {
51        Default::default()
52    }
53
54    /// Add `Display`-only context for a [`Status`]
55    ///
56    /// If an equivalent key already exists: the key remains and retains in its place in
57    /// the order, its corresponding value is updated with value.
58    ///
59    /// If no equivalent key existed: the new key-value pair is inserted, last in order.
60    ///
61    /// # Example
62    ///
63    /// ```rust
64    /// let c = status::AdhocContext::new().insert("Expected value", 10);
65    /// println!("{}", c);
66    /// ```
67    pub fn insert<V>(mut self, key: &'static str, value: V) -> Self
68    where
69        V: AdhocValue + Clone,
70    {
71        self.data.insert(key, Box::new(value));
72        self
73    }
74}
75
76impl fmt::Display for AdhocContext {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        for (k, v) in self.data.iter() {
79            writeln!(f, "{}: {}", k, v)?;
80        }
81        Ok(())
82    }
83}
84
85impl Context for AdhocContext {
86    fn update(mut self, replacements: Self) -> Self {
87        self.data.extend(replacements.data);
88        self
89    }
90
91    fn is_empty(&self) -> bool {
92        self.data.is_empty()
93    }
94}
95
96/// Trait alias for values in a [`AdhocContext`]
97pub trait AdhocValue: fmt::Display + fmt::Debug + Send + Sync + 'static {
98    /// Clone the value
99    fn clone_box(&self) -> Box<dyn AdhocValue>;
100}
101
102impl<V> AdhocValue for V
103where
104    V: Clone + fmt::Display + fmt::Debug + Send + Sync + 'static,
105{
106    fn clone_box(&self) -> Box<dyn AdhocValue> {
107        Box::new(self.clone())
108    }
109}
110
111impl Clone for Box<dyn AdhocValue> {
112    fn clone(&self) -> Box<dyn AdhocValue> {
113        self.clone_box()
114    }
115}
116
117#[cfg(test)]
118mod test {
119    use super::*;
120
121    use static_assertions::*;
122
123    #[test]
124    fn no_context() {
125        assert_impl_all!(
126            NoContext: Default,
127            Copy,
128            Clone,
129            fmt::Debug,
130            fmt::Display,
131            Context
132        );
133    }
134
135    #[test]
136    fn adhoc_context() {
137        assert_impl_all!(NoContext: Default, Clone, fmt::Debug, fmt::Display, Context);
138    }
139}