bigerror/
attachment.rs

1#[cfg(not(feature = "std"))]
2use core::{any, fmt, ops, time::Duration};
3#[cfg(feature = "std")]
4use std::{any, fmt, ops, time::Duration};
5
6#[cfg(not(feature = "std"))]
7use alloc::{
8    format,
9    string::{String, ToString},
10};
11
12use derive_more as dm;
13pub use error_stack::{self, Context, Report, ResultExt};
14
15/// Trait alias for types that can be displayed and used in error attachments.
16///
17/// This trait combines `Display`, `Debug`, `Send`, `Sync`, and `'static` bounds
18/// for types that can be attached to error reports.
19pub trait Display: fmt::Display + fmt::Debug + Send + Sync + 'static {}
20
21impl<A> Display for A where A: fmt::Display + fmt::Debug + Send + Sync + 'static {}
22
23/// Trait alias for types that can be debug-formatted and used in error attachments.
24///
25/// This trait combines `Debug`, `Send`, `Sync`, and `'static` bounds for types
26/// that can be debug-formatted in error reports.
27pub trait Debug: fmt::Debug + Send + Sync + 'static {}
28
29impl<A> Debug for A where A: fmt::Debug + Send + Sync + 'static {}
30
31/// Wrapper for types that only implement `Debug` to make them displayable.
32///
33/// This wrapper allows debug-only types to be used where `Display` is required
34/// by formatting them using their `Debug` implementation.
35#[derive(Debug)]
36pub struct Dbg<A: Debug>(pub A);
37
38impl<A: Debug> core::error::Error for Dbg<A> {}
39
40impl Dbg<String> {
41    /// Create a `Dbg<String>` by debug-formatting any type.
42    ///
43    /// This is a convenience method for wrapping debug-formatted values in a string.
44    pub fn format(attachment: impl fmt::Debug) -> Self {
45        Self(format!("{attachment:?}"))
46    }
47}
48
49impl<A: Debug> fmt::Display for Dbg<A> {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        write!(f, "{:?}", self.0)
52    }
53}
54
55/// A simple key-value pair attachment for error reports.
56///
57/// This type represents a key-value pair that can be attached to error reports
58/// for additional context information.
59#[derive(Debug, PartialEq, Eq)]
60pub struct KeyValue<K, V>(pub K, pub V);
61
62impl<K: fmt::Display, V: fmt::Display> fmt::Display for KeyValue<K, V> {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        write!(f, "{}: {}", self.0, self.1)
65    }
66}
67
68impl<C: Context> core::error::Error for KeyValue<Type, C> {}
69impl<C: Context> core::error::Error for KeyValue<&'static str, C> {}
70
71impl<K: Display, V: Debug> KeyValue<K, Dbg<V>> {
72    /// Create a key-value pair where the value is debug-formatted.
73    ///
74    /// This is a convenience method for creating key-value pairs where
75    /// the value only implements `Debug` but not `Display`.
76    pub const fn dbg(key: K, value: V) -> Self {
77        Self(key, Dbg(value))
78    }
79}
80
81/// Creates a [`KeyValue`] pair for error attachments with flexible key-value syntax.
82///
83/// # Examples
84///
85/// ## Type-based key-value pairs
86///
87/// ```
88/// use bigerror::{kv, KeyValue, attachment::Type};
89///
90/// let number = 42;
91/// let kv_pair = kv!(ty: number);
92/// assert_eq!(format!("{kv_pair}"), "<i32>: 42");
93/// ```
94///
95/// ## Field/variable extraction
96///
97/// ```
98/// use bigerror::{kv, KeyValue};
99///
100/// let username = "alice";
101/// let kv_var = kv!(username);
102/// assert_eq!(kv_var, KeyValue("username", "alice"));
103///
104/// #[derive(Clone)]
105/// struct User { name: String }
106/// let user = User { name: "bob".to_string() };
107///
108///
109/// let kv_field = kv!(user.name.clone());
110/// assert_eq!(format!("{kv_field}"), "user.name: bob");
111///
112/// // adding a `%` will use just the field name
113/// let kv_field = kv!(user.%name.clone());
114/// assert_eq!(format!("{kv_field}"), "name: bob");
115///
116/// // `%` works on methods too!
117/// impl User {
118///     fn name(&self) -> &str {
119///         self.name.as_str()
120///     }
121/// }
122/// let kv_field = kv!(user.%name());
123/// assert_eq!(format!("{kv_field}"), "name: bob");
124/// ```
125#[macro_export]
126macro_rules! kv {
127    (ty: $value: expr) => {
128        $crate::KeyValue($crate::attachment::Type::of_val(&$value), $value)
129    };
130    (type: $value: expr) => {
131        $crate::KeyValue($crate::Type::any_val(&$value), $value)
132    };
133    ($($body:tt)+) => {
134        {
135            let (__key, __value)= $crate::__field!($($body)+);
136            $crate::KeyValue(__key, __value)
137        }
138    };
139}
140
141/// Represents a field or property with its associated status.
142///
143/// Field differs from [`KeyValue`] in that the id/key points to a preexisting
144/// field, index, or property of a data structure. This is useful for indicating
145/// the status of specific fields in validation or processing contexts.
146#[derive(Debug)]
147pub struct Field<Id, S> {
148    /// The identifiable property of a data structure
149    /// such as `hash_map["key"]` or a `struct.property`
150    id: Id,
151    /// The status or state of the field
152    status: S,
153}
154
155impl<Id: Display, S: Display> fmt::Display for Field<Id, S> {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        write!(f, "{}: {}", self.id, self.status)
158    }
159}
160
161impl<Id: Display, S: Display> Field<Id, S> {
162    /// Create a new field with the given identifier and status.
163    pub const fn new(key: Id, status: S) -> Self {
164        Self { id: key, status }
165    }
166}
167/// Wrapper attachment that refers to the type of an object rather than its value.
168///
169/// This type is used to attach type information to error reports, which is useful
170/// for debugging type-related issues or showing what types were involved in an operation.
171#[derive(PartialEq, Eq, derive_more::Deref)]
172pub struct Type(&'static str);
173
174impl Type {
175    /// Create a type attachment for the given type.
176    ///
177    /// This will be a const fn when `type_name` becomes const fn in stable Rust.
178    #[must_use]
179    pub fn of<T>() -> Self {
180        Self(simple_type_name::<T>())
181    }
182
183    #[must_use]
184    pub fn any<T>() -> Self {
185        Self(any::type_name::<T>())
186    }
187
188    /// Create a type attachment for the type of the given value.
189    pub fn of_val<T: ?Sized>(_val: &T) -> Self {
190        Self(simple_type_name::<T>())
191    }
192    /// Create a type attachment with a fully qualified URI
193    pub fn any_val<T: ?Sized>(_val: &T) -> Self {
194        Self(any::type_name::<T>())
195    }
196}
197
198impl fmt::Display for Type {
199    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200        write!(f, "<{}>", self.0)
201    }
202}
203
204impl fmt::Debug for Type {
205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206        f.debug_tuple("Type").field(&self.0).finish()
207    }
208}
209
210/// Creates a [`Type`] attachment for the specified type.
211///
212/// This macro provides a convenient way to create type attachments for error reports.
213/// The type attachment shows the type name in error messages, which is useful for
214/// debugging type-related issues or showing what types were involved in an operation.
215///
216/// # Example
217///
218/// ```
219/// use std::path::PathBuf;
220/// use bigerror::{ty, Type};
221///
222/// // Create type attachments for built-in types
223/// let num_type = ty!(PathBuf);
224/// assert_eq!(*num_type, "PathBuf");
225///
226/// let num_type = ty!(full: PathBuf);
227/// assert_eq!(*num_type, "std::path::PathBuf");
228/// ```
229#[macro_export]
230macro_rules! ty {
231    ($type:ty) => {
232        $crate::Type::of::<$type>()
233    };
234    (full: $type:ty) => {
235        $crate::Type::any::<$type>()
236    };
237}
238
239/// Status indicator for something that is already present.
240///
241/// This is commonly used in field status attachments to indicate
242/// that a field or value already exists when it shouldn't.
243#[derive(Debug, dm::Display)]
244#[display("already present")]
245pub struct AlreadyPresent;
246
247/// Status indicator for something that is missing.
248///
249/// This is commonly used in field status attachments to indicate
250/// that a required field or value is missing.
251#[derive(Debug, dm::Display)]
252#[display("missing")]
253pub struct Missing;
254
255/// Status indicator for something that is unsupported.
256///
257/// This is commonly used to indicate that a feature, operation,
258/// or value is not supported in the current context.
259#[derive(Debug, dm::Display)]
260#[display("unsupported")]
261pub struct Unsupported;
262
263/// Status indicator for something that is invalid.
264///
265/// This is commonly used in field status attachments to indicate
266/// that a field or value is invalid or malformed.
267#[derive(Debug, dm::Display)]
268#[display("invalid")]
269pub struct Invalid;
270
271/// Attachment that shows expected vs actual values.
272///
273/// This is useful for validation errors and mismatches where you want
274/// to clearly show what was expected versus what was actually received.
275#[derive(Debug)]
276pub struct Expectation<E, A> {
277    /// The expected value
278    pub expected: E,
279    /// The actual value that was received
280    pub actual: A,
281}
282
283/// Attachment that shows a conversion from one type to another.
284///
285/// This is useful for conversion errors to show the source and target types.
286#[derive(Debug)]
287pub struct FromTo<F, T>(pub F, pub T);
288
289#[allow(dead_code)]
290enum Symbol {
291    Vertical,
292    VerticalRight,
293    Horizontal,
294    HorizontalLeft,
295    HorizontalDown,
296    ArrowRight,
297    CurveRight,
298    Space,
299}
300
301impl fmt::Display for Symbol {
302    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303        let utf8 = match self {
304            Self::Vertical => "\u{2502}",       // │
305            Self::VerticalRight => "\u{251c}",  // ├
306            Self::Horizontal => "\u{2500}",     // ─
307            Self::HorizontalLeft => "\u{2574}", // ╴
308            Self::HorizontalDown => "\u{252c}", // ┬
309            Self::ArrowRight => "\u{25b6}",     // ▶
310            Self::CurveRight => "\u{2570}",     // ╰
311            Self::Space => " ",
312        };
313        write!(f, "{utf8}")
314    }
315}
316
317impl<E: Display, A: Display> fmt::Display for Expectation<E, A> {
318    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319        let curve_right = Symbol::CurveRight;
320        let horizontal_left = Symbol::HorizontalLeft;
321        let expected = KeyValue("expected", &self.expected);
322        let actual = KeyValue("actual", &self.actual);
323        // "expected": expected
324        // ╰╴"actual": actual
325        write!(f, "{expected}\n{curve_right}{horizontal_left}{actual}")
326    }
327}
328impl<F: Display, T: Display> fmt::Display for FromTo<F, T> {
329    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330        let curve_right = Symbol::CurveRight;
331        let horizontal_left = Symbol::HorizontalLeft;
332        let from = KeyValue("from", &self.0);
333        let to = KeyValue("to", &self.1);
334        // "from": from
335        // ╰╴"to": to
336        write!(f, "{from}\n{curve_right}{horizontal_left}{to}")
337    }
338}
339
340/// Wrapper for `Duration` that provides human-readable display formatting.
341///
342/// This wrapper converts duration values into a readable format like "1H30m45s"
343/// instead of the default debug representation.
344#[derive(Debug)]
345pub struct DisplayDuration(pub Duration);
346impl fmt::Display for DisplayDuration {
347    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348        write!(f, "{}", hms_string(self.0))
349    }
350}
351
352impl From<Duration> for DisplayDuration {
353    fn from(duration: Duration) -> Self {
354        Self(duration)
355    }
356}
357
358impl ops::Deref for DisplayDuration {
359    type Target = Duration;
360
361    fn deref(&self) -> &Self::Target {
362        &self.0
363    }
364}
365
366/// Convert a [`Duration`] into a human-readable "0H00m00s" format string.
367///
368/// This function formats durations in a compact, readable format:
369/// - Milliseconds: "123ms"
370/// - Seconds only: "00s"
371/// - Minutes and seconds: "05m30s"
372/// - Hours, minutes, and seconds: "02H15m30s"
373/// - Zero duration: "ZERO"
374#[must_use]
375pub fn hms_string(duration: Duration) -> String {
376    if duration.is_zero() {
377        return "ZERO".to_string();
378    }
379    let s = duration.as_secs();
380    let ms = duration.subsec_millis();
381    // if only milliseconds available
382    if s == 0 {
383        return format!("{ms}ms");
384    }
385    // Grab total hours from seconds
386    let (h, s) = (s / 3600, s % 3600);
387    let (m, s) = (s / 60, s % 60);
388
389    let mut hms = String::new();
390    if h != 0 {
391        hms += &format!("{h:02}H");
392    }
393    if m != 0 {
394        hms += &format!("{m:02}m");
395    }
396    hms += &format!("{s:02}s");
397
398    hms
399}
400
401/// Extract the simple name of a type, removing module paths.
402///
403/// This function returns just the type name without the full module path.
404/// For generic types like `Option<T>` or `Vec<T>`, it preserves the full
405/// generic syntax.
406///
407/// # Example
408///
409/// ```
410/// # use bigerror::attachment::simple_type_name;
411/// assert_eq!(simple_type_name::<String>(), "String");
412/// assert_eq!(simple_type_name::<Option<i32>>(), "core::option::Option<i32>");
413/// ```
414#[must_use]
415pub fn simple_type_name<T: ?Sized>() -> &'static str {
416    let full_type = any::type_name::<T>();
417    // Option<T>, [T], Vec<T>
418    if full_type.contains(['<', '[']) {
419        return full_type;
420    }
421    full_type.rsplit_once("::").map_or(full_type, |t| t.1)
422}
423
424/// Wrapper that explicitly indicates a value is being used as an index key.
425///
426/// This wrapper is used to indicate that the underlying value is being used
427/// as an index key for getter methods in collections, such as `HashMap` keys
428/// and `Vec` indices. It helps distinguish between regular values and index keys
429/// in error messages.
430#[derive(Debug, dm::Display)]
431#[display("idx [{0}: {}]", simple_type_name::<I>())]
432pub struct Index<I: fmt::Display>(pub I);
433
434#[cfg(test)]
435mod test {
436
437    use super::*;
438    use crate::MyStruct;
439
440    #[test]
441    fn kv_macro() {
442        let foo = "Foo";
443
444        // foo: "Foo"
445        assert_eq!(kv!(foo), KeyValue("foo", "Foo"));
446        // <&str>: "Foo"
447        assert_eq!(kv!(ty: foo), KeyValue(Type::of_val(&foo), "Foo"));
448
449        let foo = 13;
450
451        // <i32>: 13
452        assert_eq!(kv!(ty: foo), KeyValue(Type::of_val(&foo), 13));
453        // ensure literal values are handled correctly
454        assert_eq!(kv!(ty: 13), KeyValue(Type::of_val(&13), 13));
455    }
456
457    #[test]
458    fn kv_macro_var() {
459        let foo = "Foo";
460        let key_value = kv!(foo.to_owned());
461
462        assert_eq!(key_value, KeyValue("foo", String::from(foo)));
463    }
464
465    #[test]
466    fn kv_macro_struct() {
467        let my_struct = MyStruct {
468            my_field: None,
469            _string: String::from("Value"),
470        };
471
472        let key_value = kv!(my_struct.%my_field());
473        assert_eq!(key_value, KeyValue("my_field", None));
474
475        let key_value = kv!(my_struct.%_string);
476        assert_eq!(key_value, KeyValue("_string", String::from("Value")));
477
478        let key_value = kv!(my_struct.my_field);
479        assert_eq!(key_value, KeyValue("my_struct.my_field", None));
480    }
481}