1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
use std::fmt;

/// Adds nuance to errors.
///
/// Goals:
/// - Easily add context for any `Kind` at each level of the call stack.
/// - Programmatic access to the context.
/// - User-friendly without losing helpful debug information.
pub trait Context: Default + Clone + fmt::Display + fmt::Debug + Send + Sync + 'static {
    /// Replace fields in `self` with those populated in `replacements`.
    fn update(self, replacements: Self) -> Self;

    /// Returns `true` is the `Context` has no content.
    fn is_empty(&self) -> bool;
}

/// No context needed.
#[derive(Default, Copy, Clone, Debug)]
pub struct NoContext;

impl fmt::Display for NoContext {
    fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
        Ok(())
    }
}

impl Context for NoContext {
    fn update(self, _replacements: Self) -> Self {
        self
    }

    fn is_empty(&self) -> bool {
        true
    }
}

/// Adhoc [`Context`].
///
/// Unlike most [`Context`]s, this is meant to be opaque and not programmatically specify the status.
/// It is only good for displaying the data to the user when prototyping before one transitions to more formal [`Context`]s.
///
/// Note: This is the default [`Context`] for [`Status`].
#[derive(Default, Clone, Debug)]
pub struct AdhocContext {
    data: indexmap::IndexMap<&'static str, Box<dyn AdhocValue>>,
}

impl AdhocContext {
    /// Create an empty [`Context`].
    pub fn new() -> Self {
        Default::default()
    }

    /// Add `Display`-only context for a [`Status`]
    ///
    /// If an equivalent key already exists: the key remains and retains in its place in
    /// the order, its corresponding value is updated with value.
    ///
    /// If no equivalent key existed: the new key-value pair is inserted, last in order.
    ///
    /// # Example
    ///
    /// ```rust
    /// let c = status::AdhocContext::new().insert("Expected value", 10);
    /// println!("{}", c);
    /// ```
    pub fn insert<V>(mut self, key: &'static str, value: V) -> Self
    where
        V: AdhocValue + Clone,
    {
        self.data.insert(key, Box::new(value));
        self
    }
}

impl fmt::Display for AdhocContext {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for (k, v) in self.data.iter() {
            writeln!(f, "{}: {}", k, v)?;
        }
        Ok(())
    }
}

impl Context for AdhocContext {
    fn update(mut self, replacements: Self) -> Self {
        self.data.extend(replacements.data);
        self
    }

    fn is_empty(&self) -> bool {
        self.data.is_empty()
    }
}

/// Trait alias for values in a [`AdhocContext`]
pub trait AdhocValue: fmt::Display + fmt::Debug + Send + Sync + 'static {
    /// Clone the value
    fn clone_box(&self) -> Box<dyn AdhocValue>;
}

impl<V> AdhocValue for V
where
    V: Clone + fmt::Display + fmt::Debug + Send + Sync + 'static,
{
    fn clone_box(&self) -> Box<dyn AdhocValue> {
        Box::new(self.clone())
    }
}

impl Clone for Box<dyn AdhocValue> {
    fn clone(&self) -> Box<dyn AdhocValue> {
        self.clone_box()
    }
}

#[cfg(test)]
mod test {
    use super::*;

    use static_assertions::*;

    #[test]
    fn no_context() {
        assert_impl_all!(
            NoContext: Default,
            Copy,
            Clone,
            fmt::Debug,
            fmt::Display,
            Context
        );
    }

    #[test]
    fn adhoc_context() {
        assert_impl_all!(NoContext: Default, Clone, fmt::Debug, fmt::Display, Context);
    }
}