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}