bigerror/
attachment.rs

1use std::{fmt, time::Duration};
2
3use derive_more as dm;
4pub use error_stack::{self, Context, Report, ResultExt};
5
6pub trait Display: fmt::Display + fmt::Debug + Send + Sync + 'static {}
7
8impl<A> Display for A where A: fmt::Display + fmt::Debug + Send + Sync + 'static {}
9
10pub trait Debug: fmt::Debug + Send + Sync + 'static {}
11
12impl<A> Debug for A where A: fmt::Debug + Send + Sync + 'static {}
13
14// used to wrap types that only implement `std::fmt::Debug`
15#[derive(Debug)]
16pub struct Dbg<A: Debug>(pub A);
17
18impl Dbg<String> {
19    pub fn format(attachment: impl fmt::Debug) -> Self {
20        Self(format!("{attachment:?}"))
21    }
22}
23
24impl<A: Debug> fmt::Display for Dbg<A> {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        write!(f, "{:?}", self.0)
27    }
28}
29
30// simple key-value pair attachment
31#[derive(Debug, PartialEq, Eq)]
32pub struct KeyValue<K, V>(pub K, pub V);
33
34impl<K: fmt::Display, V: fmt::Display> fmt::Display for KeyValue<K, V> {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        write!(f, "{}: {}", self.0, self.1)
37    }
38}
39
40impl<K: Display, V: Debug> KeyValue<K, Dbg<V>> {
41    pub const fn dbg(key: K, value: V) -> Self {
42        Self(key, Dbg(value))
43    }
44}
45
46/// Allows one to quickly specify a [`KeyValue`] pair, optionally using a
47/// `ty:` prefix using the `$value` [`Type`] as the key
48#[macro_export]
49macro_rules! kv {
50    (ty: $value: expr) => {
51        $crate::KeyValue($crate::Type::of_val(&$value), $value)
52    };
53    ($value: expr) => {
54        $crate::KeyValue(stringify!($value), $value)
55    };
56}
57
58#[derive(Debug)]
59// Field differs from [`KeyValue`] in that the id/key points to a preexisting [`Index`] or
60// [`Property`]
61pub struct Field<Id, S> {
62    // the identifiable property of a data structure
63    // such has  `hash_map["key"]` or a `struct.property`
64    id: Id,
65    status: S,
66}
67
68impl<Id: Display, S: Display> fmt::Display for Field<Id, S> {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        write!(f, "{}: {}", self.id, self.status)
71    }
72}
73
74impl<Id: Display, S: Display> Field<Id, S> {
75    pub const fn new(key: Id, status: S) -> Self {
76        Self { id: key, status }
77    }
78}
79/// wrapper attachment that is used to refer to the type of an object
80/// rather than the value
81#[derive(PartialEq, Eq)]
82pub struct Type(&'static str);
83
84impl Type {
85    // const fn when type_name is const fn in stable
86    #[must_use]
87    pub fn of<T>() -> Self {
88        Self(simple_type_name::<T>())
89    }
90
91    pub fn of_val<T: ?Sized>(_val: &T) -> Self {
92        Self(simple_type_name::<T>())
93    }
94}
95
96impl fmt::Display for Type {
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        write!(f, "<{}>", self.0)
99    }
100}
101
102impl fmt::Debug for Type {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        f.debug_tuple("Type").field(&self.0).finish()
105    }
106}
107
108#[macro_export]
109macro_rules! ty {
110    ($type:ty) => {
111        $crate::attachment::Type::of::<$type>()
112    };
113}
114
115#[derive(Debug, dm::Display)]
116#[display("already present")]
117pub struct AlreadyPresent;
118
119#[derive(Debug, dm::Display)]
120#[display("missing")]
121pub struct Missing;
122
123#[derive(Debug, dm::Display)]
124#[display("unsupported")]
125pub struct Unsupported;
126
127#[derive(Debug, dm::Display)]
128#[display("invalid")]
129pub struct Invalid;
130
131#[derive(Debug)]
132pub struct Expectation<E, A> {
133    pub expected: E,
134    pub actual: A,
135}
136
137#[derive(Debug)]
138pub struct FromTo<F, T>(pub F, pub T);
139
140#[allow(dead_code)]
141enum Symbol {
142    Vertical,
143    VerticalRight,
144    Horizontal,
145    HorizontalLeft,
146    HorizontalDown,
147    ArrowRight,
148    CurveRight,
149    Space,
150}
151
152impl fmt::Display for Symbol {
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        let utf8 = match self {
155            Self::Vertical => "\u{2502}",       // │
156            Self::VerticalRight => "\u{251c}",  // ├
157            Self::Horizontal => "\u{2500}",     // ─
158            Self::HorizontalLeft => "\u{2574}", // ╴
159            Self::HorizontalDown => "\u{252c}", // ┬
160            Self::ArrowRight => "\u{25b6}",     // ▶
161            Self::CurveRight => "\u{2570}",     // ╰
162            Self::Space => " ",
163        };
164        write!(f, "{utf8}")
165    }
166}
167
168impl<E: Display, A: Display> fmt::Display for Expectation<E, A> {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        let curve_right = Symbol::CurveRight;
171        let horizontal_left = Symbol::HorizontalLeft;
172        let expected = KeyValue("expected", &self.expected);
173        let actual = KeyValue("actual", &self.actual);
174        // "expected": expected
175        // ╰╴"actual": actual
176        write!(f, "{expected}\n{curve_right}{horizontal_left}{actual}")
177    }
178}
179impl<F: Display, T: Display> fmt::Display for FromTo<F, T> {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        let curve_right = Symbol::CurveRight;
182        let horizontal_left = Symbol::HorizontalLeft;
183        let from = KeyValue("from", &self.0);
184        let to = KeyValue("to", &self.1);
185        // "from": from
186        // ╰╴"to": to
187        write!(f, "{from}\n{curve_right}{horizontal_left}{to}")
188    }
189}
190
191#[derive(Debug)]
192pub struct DisplayDuration(pub Duration);
193impl fmt::Display for DisplayDuration {
194    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195        write!(f, "{}", hms_string(self.0))
196    }
197}
198
199impl From<Duration> for DisplayDuration {
200    fn from(duration: Duration) -> Self {
201        Self(duration)
202    }
203}
204
205impl std::ops::Deref for DisplayDuration {
206    type Target = Duration;
207
208    fn deref(&self) -> &Self::Target {
209        &self.0
210    }
211}
212
213/// convert a [`Duration`] into a "0H00m00s" string
214#[must_use]
215pub fn hms_string(duration: Duration) -> String {
216    if duration.is_zero() {
217        return "ZERO".to_string();
218    }
219    let s = duration.as_secs();
220    let ms = duration.subsec_millis();
221    // if only milliseconds available
222    if s == 0 {
223        return format!("{ms}ms");
224    }
225    // Grab total hours from seconds
226    let (h, s) = (s / 3600, s % 3600);
227    let (m, s) = (s / 60, s % 60);
228
229    let mut hms = String::new();
230    if h != 0 {
231        hms += &format!("{h:02}H");
232    }
233    if m != 0 {
234        hms += &format!("{m:02}m");
235    }
236    hms += &format!("{s:02}s");
237
238    hms
239}
240
241#[must_use]
242pub fn simple_type_name<T: ?Sized>() -> &'static str {
243    let full_type = std::any::type_name::<T>();
244    // Option<T>, [T], Vec<T>
245    if full_type.contains(['<', '[']) {
246        return full_type;
247    }
248    full_type.rsplit_once("::").map_or(full_type, |t| t.1)
249}
250
251// this is meant to explicitly indicate
252// that the underlying `A` is being
253// used as an index key for getter methods in a collection
254// such as `HashMap` keys and `Vec` indices
255#[derive(Debug, dm::Display)]
256#[display("idx [{0}: {}]", simple_type_name::<I>())]
257pub struct Index<I: fmt::Display>(pub I);
258
259#[cfg(test)]
260mod test {
261    use super::*;
262
263    #[test]
264    fn kv_macro() {
265        let foo = "Foo";
266
267        // foo: "Foo"
268        assert_eq!(kv!(foo), KeyValue("foo", "Foo"));
269        // <&str>: "Foo"
270        assert_eq!(kv!(ty: foo), KeyValue(Type::of_val(&foo), "Foo"));
271
272        let foo = 13;
273
274        // <i32>: 13
275        assert_eq!(kv!(ty: foo), KeyValue(Type::of_val(&foo), 13));
276        // ensure literal values are handled correctly
277        assert_eq!(kv!(ty: 13), KeyValue(Type::of_val(&13), 13));
278    }
279}