decrust_core/
backtrace.rs

1/* src/backtrace.rs */
2#![warn(missing_docs)]
3//! **Brief:** Direct backtrace implementation with custom GenerateImplicitData trait.
4// ~=####====A===r===c===M===o===o===n====S===t===u===d===i===o===s====X|0|$>
5//! + [Direct Backtrace System]
6//!  - [Environment-Aware Capture]
7//!  - [Custom GenerateImplicitData Trait]
8//!  - [Crisis-Resistant Implementation]
9//!  - [Zero External Dependencies]
10// ~=####====A===r===c===M===o===o===n====S===t===u===d===i===o===s====X|0|$>
11// **GitHub:** [ArcMoon Studios](https://github.com/arcmoonstudios)
12// **Copyright:** (c) 2025 ArcMoon Studios
13// **Author:** Lord Xyn
14// **License:** Business Source License 1.1 (BSL-1.1)
15// **License File:** /LICENSE
16// **License Terms:** Non-production use only; commercial/production use requires a paid license.
17// **Change Date:** 2029-05-25 | **Change License:** GPL v3
18// **Contact:** LordXyn@proton.me
19
20use std::collections::HashMap;
21use std::env;
22use std::fmt;
23use std::sync::OnceLock;
24
25/// Our own backtrace type that wraps std::backtrace::Backtrace
26/// This provides crisis-resistant backtrace functionality
27#[derive(Debug)]
28pub struct DecrustBacktrace {
29    inner: Option<std::backtrace::Backtrace>,
30    capture_enabled: bool,
31    capture_timestamp: std::time::SystemTime,
32    thread_id: std::thread::ThreadId,
33    thread_name: Option<String>,
34}
35
36/// Status of backtrace capture
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum BacktraceStatus {
39    /// Backtrace was successfully captured
40    Captured,
41    /// Backtrace capture was disabled by environment
42    Disabled,
43    /// Backtrace capture is not supported on this platform
44    Unsupported,
45}
46
47impl DecrustBacktrace {
48    /// Creates a new backtrace, respecting environment variables
49    ///
50    /// Checks RUST_LIB_BACKTRACE first, then RUST_BACKTRACE
51    /// Only captures if set to "1" or "full"
52    pub fn capture() -> Self {
53        let should_capture = Self::should_capture_from_env();
54        let current_thread = std::thread::current();
55
56        if should_capture {
57            Self {
58                inner: Some(std::backtrace::Backtrace::capture()),
59                capture_enabled: true,
60                capture_timestamp: std::time::SystemTime::now(),
61                thread_id: current_thread.id(),
62                thread_name: current_thread.name().map(|s| s.to_string()),
63            }
64        } else {
65            Self {
66                inner: None,
67                capture_enabled: false,
68                capture_timestamp: std::time::SystemTime::now(),
69                thread_id: current_thread.id(),
70                thread_name: current_thread.name().map(|s| s.to_string()),
71            }
72        }
73    }
74
75    /// Forces backtrace capture regardless of environment variables
76    ///
77    /// Use this when you need backtraces for debugging purposes
78    pub fn force_capture() -> Self {
79        let current_thread = std::thread::current();
80        Self {
81            inner: Some(std::backtrace::Backtrace::force_capture()),
82            capture_enabled: true,
83            capture_timestamp: std::time::SystemTime::now(),
84            thread_id: current_thread.id(),
85            thread_name: current_thread.name().map(|s| s.to_string()),
86        }
87    }
88
89    /// Creates a disabled backtrace (no capture)
90    pub fn disabled() -> Self {
91        let current_thread = std::thread::current();
92        Self {
93            inner: None,
94            capture_enabled: false,
95            capture_timestamp: std::time::SystemTime::now(),
96            thread_id: current_thread.id(),
97            thread_name: current_thread.name().map(|s| s.to_string()),
98        }
99    }
100
101    /// Returns the status of this backtrace
102    pub fn status(&self) -> BacktraceStatus {
103        match &self.inner {
104            Some(bt) => {
105                // Handle all possible backtrace status variants more robustly
106                use std::backtrace::BacktraceStatus as StdStatus;
107                match bt.status() {
108                    StdStatus::Captured => BacktraceStatus::Captured,
109                    StdStatus::Disabled => BacktraceStatus::Disabled,
110                    StdStatus::Unsupported => BacktraceStatus::Unsupported,
111                    // Handle any future variants by defaulting to Unsupported
112                    #[allow(unreachable_patterns)]
113                    _ => BacktraceStatus::Unsupported,
114                }
115            }
116            None => BacktraceStatus::Disabled,
117        }
118    }
119
120    /// Get the timestamp when this backtrace was captured
121    pub fn capture_timestamp(&self) -> std::time::SystemTime {
122        self.capture_timestamp
123    }
124
125    /// Get the thread ID where this backtrace was captured
126    pub fn thread_id(&self) -> std::thread::ThreadId {
127        self.thread_id
128    }
129
130    /// Get the thread name where this backtrace was captured, if available
131    pub fn thread_name(&self) -> Option<&str> {
132        self.thread_name.as_deref()
133    }
134
135    /// Check if backtrace should be captured based on environment variables
136    fn should_capture_from_env() -> bool {
137        static SHOULD_CAPTURE: OnceLock<bool> = OnceLock::new();
138
139        *SHOULD_CAPTURE.get_or_init(|| {
140            // Check RUST_LIB_BACKTRACE first (higher priority)
141            if let Ok(val) = env::var("RUST_LIB_BACKTRACE") {
142                return val == "1" || val.to_lowercase() == "full";
143            }
144
145            // Fall back to RUST_BACKTRACE
146            if let Ok(val) = env::var("RUST_BACKTRACE") {
147                return val == "1" || val.to_lowercase() == "full";
148            }
149
150            false
151        })
152    }
153
154    /// Get the inner backtrace if available
155    pub fn as_std_backtrace(&self) -> Option<&std::backtrace::Backtrace> {
156        self.inner.as_ref()
157    }
158
159    /// Extract frame information from the backtrace
160    pub fn extract_frames(&self) -> Vec<BacktraceFrame> {
161        match &self.inner {
162            Some(bt) => {
163                let bt_string = format!("{}", bt);
164                self.parse_backtrace_string(&bt_string)
165            }
166            None => Vec::new(),
167        }
168    }
169
170    /// Parse backtrace string into structured frame information
171    fn parse_backtrace_string(&self, bt_str: &str) -> Vec<BacktraceFrame> {
172        let mut frames = Vec::new();
173
174        for line in bt_str.lines() {
175            if let Some(frame) = self.parse_frame_line(line) {
176                frames.push(frame);
177            }
178        }
179
180        frames
181    }
182
183    /// Parse a single frame line from backtrace output
184    fn parse_frame_line(&self, line: &str) -> Option<BacktraceFrame> {
185        // Parse format like: "   0: symbol_name at /path/to/file.rs:123:45"
186        let trimmed = line.trim();
187
188        if let Some(colon_pos) = trimmed.find(':') {
189            let number_part = &trimmed[..colon_pos].trim();
190            let rest = &trimmed[colon_pos + 1..].trim();
191
192            if number_part.parse::<usize>().is_ok() {
193                // Split on " at " to separate symbol from location
194                if let Some(at_pos) = rest.rfind(" at ") {
195                    let symbol = rest[..at_pos].trim().to_string();
196                    let location = rest[at_pos + 4..].trim();
197
198                    let (file, line, column) = self.parse_location(location);
199
200                    return Some(BacktraceFrame {
201                        symbol,
202                        file,
203                        line,
204                        column,
205                    });
206                } else {
207                    // No location info, just symbol
208                    return Some(BacktraceFrame {
209                        symbol: rest.to_string(),
210                        file: None,
211                        line: None,
212                        column: None,
213                    });
214                }
215            }
216        }
217
218        None
219    }
220
221    /// Parse location string like "/path/to/file.rs:123:45"
222    fn parse_location(&self, location: &str) -> (Option<String>, Option<u32>, Option<u32>) {
223        let parts: Vec<&str> = location.rsplitn(3, ':').collect();
224
225        match parts.len() {
226            3 => {
227                let column = parts[0].parse().ok();
228                let line = parts[1].parse().ok();
229                let file = Some(parts[2].to_string());
230                (file, line, column)
231            }
232            2 => {
233                let line = parts[0].parse().ok();
234                let file = Some(parts[1].to_string());
235                (file, line, None)
236            }
237            1 => (Some(parts[0].to_string()), None, None),
238            _ => (None, None, None),
239        }
240    }
241}
242
243/// Structured information about a backtrace frame
244#[derive(Debug, Clone, PartialEq, Eq)]
245pub struct BacktraceFrame {
246    /// Symbol name or function name
247    pub symbol: String,
248    /// Source file path
249    pub file: Option<String>,
250    /// Line number in the source file
251    pub line: Option<u32>,
252    /// Column number in the source file
253    pub column: Option<u32>,
254}
255
256impl fmt::Display for BacktraceFrame {
257    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258        write!(f, "{}", self.symbol)?;
259        if let Some(ref file) = self.file {
260            write!(f, " at {}", file)?;
261            if let Some(line) = self.line {
262                write!(f, ":{}", line)?;
263                if let Some(column) = self.column {
264                    write!(f, ":{}", column)?;
265                }
266            }
267        }
268        Ok(())
269    }
270}
271
272impl fmt::Display for DecrustBacktrace {
273    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274        match &self.inner {
275            Some(bt) => {
276                writeln!(f, "Backtrace captured at: {:?}", self.capture_timestamp)?;
277                if let Some(ref thread_name) = self.thread_name {
278                    writeln!(f, "Thread: {} ({:?})", thread_name, self.thread_id)?;
279                } else {
280                    writeln!(f, "Thread: {:?}", self.thread_id)?;
281                }
282                write!(f, "{}", bt)
283            }
284            None => write!(f, "<backtrace disabled>"),
285        }
286    }
287}
288
289impl Clone for DecrustBacktrace {
290    fn clone(&self) -> Self {
291        // We can't clone the actual backtrace, so create a new one
292        // with the same capture_enabled setting
293        if self.capture_enabled {
294            // Use force_capture to ensure we get a backtrace regardless of env vars
295            Self::force_capture()
296        } else {
297            Self::disabled()
298        }
299    }
300}
301
302/// Trait for generating implicit data automatically
303///
304/// This replaces snafu's GenerateImplicitData trait with our own implementation
305pub trait GenerateImplicitData {
306    /// Generate the implicit data
307    fn generate() -> Self;
308
309    /// Generate implicit data with access to a source error
310    ///
311    /// Default implementation ignores the source and calls generate()
312    fn generate_with_source(_source: &dyn std::error::Error) -> Self
313    where
314        Self: Sized,
315    {
316        Self::generate()
317    }
318
319    /// Generate implicit data with custom context
320    fn generate_with_context(context: &HashMap<String, String>) -> Self
321    where
322        Self: Sized,
323    {
324        let _ = context; // Suppress unused parameter warning
325        Self::generate()
326    }
327}
328
329/// Implementation for our backtrace type
330impl GenerateImplicitData for DecrustBacktrace {
331    fn generate() -> Self {
332        Self::capture()
333    }
334
335    fn generate_with_source(source: &dyn std::error::Error) -> Self {
336        // Check if the source already has a backtrace we can use
337        // For now, just generate a new one, but this could be enhanced
338        // to delegate to the source if it implements our backtrace trait
339        let _ = source; // Suppress unused parameter warning for now
340        Self::capture()
341    }
342
343    fn generate_with_context(context: &HashMap<String, String>) -> Self {
344        // Use context to determine if we should force capture
345        if context
346            .get("force_backtrace")
347            .map(|s| s == "true")
348            .unwrap_or(false)
349        {
350            Self::force_capture()
351        } else {
352            Self::capture()
353        }
354    }
355}
356
357// Add a static method to DecrustBacktrace for compatibility with code that expects generate()
358impl DecrustBacktrace {
359    /// Generate a new backtrace - compatibility method for code that expects generate()
360    pub fn generate() -> Self {
361        Self::capture()
362    }
363}
364
365// Implement From<std::backtrace::Backtrace> for DecrustBacktrace
366impl From<std::backtrace::Backtrace> for DecrustBacktrace {
367    fn from(backtrace: std::backtrace::Backtrace) -> Self {
368        let current_thread = std::thread::current();
369        Self {
370            inner: Some(backtrace),
371            capture_enabled: true,
372            capture_timestamp: std::time::SystemTime::now(),
373            thread_id: current_thread.id(),
374            thread_name: current_thread.name().map(|s| s.to_string()),
375        }
376    }
377}
378
379/// Trait for types that can provide backtraces
380///
381/// This is our version of snafu's ErrorCompat trait
382pub trait BacktraceCompat: std::error::Error {
383    /// Get the backtrace associated with this error, if any
384    fn backtrace(&self) -> Option<&DecrustBacktrace>;
385}
386
387/// Extension trait for easier backtrace handling
388pub trait BacktraceProvider {
389    /// Get a backtrace from this error or any of its sources
390    fn get_deepest_backtrace(&self) -> Option<&DecrustBacktrace>;
391}
392
393impl<E: std::error::Error + BacktraceCompat> BacktraceProvider for E {
394    fn get_deepest_backtrace(&self) -> Option<&DecrustBacktrace> {
395        // First check if this error has a backtrace
396        if let Some(bt) = self.backtrace() {
397            return Some(bt);
398        }
399        None
400    }
401}
402
403/// Custom timestamp type that implements GenerateImplicitData
404#[derive(Debug, Clone, PartialEq, Eq)]
405pub struct Timestamp {
406    instant: std::time::SystemTime,
407    formatted: String,
408}
409
410impl Timestamp {
411    /// Create a new timestamp with the current time
412    pub fn now() -> Self {
413        let instant = std::time::SystemTime::now();
414        let formatted = Self::format_timestamp(&instant);
415        Self { instant, formatted }
416    }
417
418    /// Create a timestamp from a SystemTime
419    pub fn from_system_time(time: std::time::SystemTime) -> Self {
420        let formatted = Self::format_timestamp(&time);
421        Self {
422            instant: time,
423            formatted,
424        }
425    }
426
427    /// Get the inner SystemTime
428    pub fn as_system_time(&self) -> std::time::SystemTime {
429        self.instant
430    }
431
432    /// Get the formatted timestamp string
433    pub fn formatted(&self) -> &str {
434        &self.formatted
435    }
436
437    /// Format a SystemTime into a readable string
438    fn format_timestamp(time: &std::time::SystemTime) -> String {
439        match time.duration_since(std::time::UNIX_EPOCH) {
440            Ok(duration) => {
441                let secs = duration.as_secs();
442                let millis = duration.subsec_millis();
443
444                // Convert to human-readable format
445                let datetime = std::time::UNIX_EPOCH + std::time::Duration::from_secs(secs);
446                format!(
447                    "{}.{:03} (epoch: {})",
448                    secs,
449                    millis,
450                    datetime
451                        .duration_since(std::time::UNIX_EPOCH)
452                        .map(|d| d.as_secs())
453                        .unwrap_or(0)
454                )
455            }
456            Err(_) => "<invalid timestamp>".to_string(),
457        }
458    }
459}
460
461impl GenerateImplicitData for Timestamp {
462    fn generate() -> Self {
463        Self::now()
464    }
465
466    fn generate_with_context(context: &HashMap<String, String>) -> Self {
467        // Check if a specific timestamp is requested
468        if let Some(timestamp_str) = context.get("timestamp") {
469            if let Ok(secs) = timestamp_str.parse::<u64>() {
470                let time = std::time::UNIX_EPOCH + std::time::Duration::from_secs(secs);
471                return Self::from_system_time(time);
472            }
473        }
474        Self::now()
475    }
476}
477
478impl fmt::Display for Timestamp {
479    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
480        write!(f, "{}", self.formatted)
481    }
482}
483
484/// Thread ID type that implements GenerateImplicitData
485#[derive(Debug, Clone, PartialEq, Eq)]
486pub struct ThreadId {
487    id: std::thread::ThreadId,
488    name: Option<String>,
489    formatted: String,
490}
491
492impl ThreadId {
493    /// Get the current thread's ID and name
494    pub fn current() -> Self {
495        let thread = std::thread::current();
496        let id = thread.id();
497        let name = thread.name().map(|s| s.to_string());
498        let formatted = Self::format_thread_info(id, name.as_deref());
499
500        Self {
501            id,
502            name,
503            formatted,
504        }
505    }
506
507    /// Create a ThreadId from components
508    pub fn from_components(id: std::thread::ThreadId, name: Option<String>) -> Self {
509        let formatted = Self::format_thread_info(id, name.as_deref());
510        Self {
511            id,
512            name,
513            formatted,
514        }
515    }
516
517    /// Get the thread ID
518    pub fn id(&self) -> std::thread::ThreadId {
519        self.id
520    }
521
522    /// Get the thread name if available
523    pub fn name(&self) -> Option<&str> {
524        self.name.as_deref()
525    }
526
527    /// Get the formatted thread information
528    pub fn formatted(&self) -> &str {
529        &self.formatted
530    }
531
532    /// Format thread information into a readable string
533    fn format_thread_info(id: std::thread::ThreadId, name: Option<&str>) -> String {
534        match name {
535            Some(thread_name) => format!("{}({:?})", thread_name, id),
536            None => format!("{:?}", id),
537        }
538    }
539}
540
541impl GenerateImplicitData for ThreadId {
542    fn generate() -> Self {
543        Self::current()
544    }
545
546    fn generate_with_context(context: &HashMap<String, String>) -> Self {
547        // For thread context, we can only return current thread info
548        // Context might be used for additional formatting hints
549        let _ = context;
550        Self::current()
551    }
552}
553
554impl fmt::Display for ThreadId {
555    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
556        write!(f, "{}", self.formatted)
557    }
558}
559
560/// Location information for where an error was created
561#[derive(Debug, Clone, PartialEq, Eq)]
562pub struct Location {
563    file: &'static str,
564    line: u32,
565    column: u32,
566    formatted: String,
567}
568
569impl Location {
570    /// Create a new location
571    pub const fn new(file: &'static str, line: u32, column: u32) -> Self {
572        Self {
573            file,
574            line,
575            column,
576            formatted: String::new(), // Will be filled in post-construction
577        }
578    }
579
580    /// Create a new location with formatting
581    pub fn new_formatted(file: &'static str, line: u32, column: u32) -> Self {
582        let formatted = format!("{}:{}:{}", file, line, column);
583        Self {
584            file,
585            line,
586            column,
587            formatted,
588        }
589    }
590
591    /// Create a new location with context description
592    pub fn with_context(file: &'static str, line: u32, column: u32, context: &str) -> Self {
593        let formatted = format!("{}:{}:{} ({})", file, line, column, context);
594        Self {
595            file,
596            line,
597            column,
598            formatted,
599        }
600    }
601
602    /// Create a new location with function name
603    pub fn with_function(file: &'static str, line: u32, column: u32, function: &str) -> Self {
604        let formatted = format!("{}:{}:{} in {}", file, line, column, function);
605        Self {
606            file,
607            line,
608            column,
609            formatted,
610        }
611    }
612
613    /// Create a new location with both context and function
614    pub fn with_context_and_function(
615        file: &'static str,
616        line: u32,
617        column: u32,
618        context: &str,
619        function: &str,
620    ) -> Self {
621        let formatted = format!("{}:{}:{} in {} ({})", file, line, column, function, context);
622        Self {
623            file,
624            line,
625            column,
626            formatted,
627        }
628    }
629
630    /// Get the file path
631    pub fn file(&self) -> &'static str {
632        self.file
633    }
634
635    /// Get the line number
636    pub fn line(&self) -> u32 {
637        self.line
638    }
639
640    /// Get the column number
641    pub fn column(&self) -> u32 {
642        self.column
643    }
644
645    /// Get the formatted location string
646    pub fn formatted(&self) -> String {
647        if self.formatted.is_empty() {
648            format!("{}:{}:{}", self.file, self.line, self.column)
649        } else {
650            self.formatted.clone()
651        }
652    }
653}
654
655impl fmt::Display for Location {
656    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
657        write!(f, "{}:{}:{}", self.file, self.line, self.column)
658    }
659}
660
661/// Macro to create a location at the current source position
662///
663/// This macro provides several forms:
664/// - `location!()` - Basic location with file, line, column
665/// - `location!(context: "description")` - Location with context description
666/// - `location!(function: "fn_name")` - Location with function name
667/// - `location!(context: "desc", function: "fn_name")` - Location with both
668#[macro_export]
669macro_rules! location {
670    () => {
671        $crate::backtrace::Location::new_formatted(file!(), line!(), column!())
672    };
673
674    (context: $context:expr) => {
675        $crate::backtrace::Location::with_context(file!(), line!(), column!(), $context)
676    };
677
678    (function: $function:expr) => {
679        $crate::backtrace::Location::with_function(file!(), line!(), column!(), $function)
680    };
681
682    (context: $context:expr, function: $function:expr) => {
683        $crate::backtrace::Location::with_context_and_function(
684            file!(),
685            line!(),
686            column!(),
687            $context,
688            $function,
689        )
690    };
691
692    (function: $function:expr, context: $context:expr) => {
693        $crate::backtrace::Location::with_context_and_function(
694            file!(),
695            line!(),
696            column!(),
697            $context,
698            $function,
699        )
700    };
701}
702
703/// Macro to generate implicit data at the call site
704///
705/// This macro provides several forms:
706/// - `implicit_data!(Type)` - Generate with default settings
707/// - `implicit_data!(Type, context: map)` - Generate with context map
708/// - `implicit_data!(Type, source: error)` - Generate with source error
709/// - `implicit_data!(Type, force: true)` - Force generation (for backtraces)
710/// - `implicit_data!(Type, timestamp: secs)` - Generate with specific timestamp
711/// - `implicit_data!(Type, location: true)` - Include location information
712#[macro_export]
713macro_rules! implicit_data {
714    // Basic generation
715    ($type:ty) => {
716        <$type as $crate::backtrace::GenerateImplicitData>::generate()
717    };
718
719    // Generation with context map (supports both &HashMap and HashMap)
720    ($type:ty, context: $context:expr) => {
721        <$type as $crate::backtrace::GenerateImplicitData>::generate_with_context($context)
722    };
723
724    // Direct context map (for backward compatibility)
725    ($type:ty, $context:expr) => {
726        <$type as $crate::backtrace::GenerateImplicitData>::generate_with_context($context)
727    };
728
729    // Generation with source error
730    ($type:ty, source: $source:expr) => {
731        <$type as $crate::backtrace::GenerateImplicitData>::generate_with_source($source)
732    };
733
734    // Force generation (useful for backtraces)
735    ($type:ty, force: true) => {{
736        let mut context = std::collections::HashMap::new();
737        context.insert("force_backtrace".to_string(), "true".to_string());
738        <$type as $crate::backtrace::GenerateImplicitData>::generate_with_context(&context)
739    }};
740
741    // Generate with specific timestamp (for Timestamp type)
742    ($type:ty, timestamp: $secs:expr) => {{
743        let mut context = std::collections::HashMap::new();
744        context.insert("timestamp".to_string(), $secs.to_string());
745        <$type as $crate::backtrace::GenerateImplicitData>::generate_with_context(&context)
746    }};
747
748    // Generate with location information embedded
749    ($type:ty, location: true) => {{
750        let mut context = std::collections::HashMap::new();
751        context.insert("file".to_string(), file!().to_string());
752        context.insert("line".to_string(), line!().to_string());
753        context.insert("column".to_string(), column!().to_string());
754        <$type as $crate::backtrace::GenerateImplicitData>::generate_with_context(&context)
755    }};
756
757    // Multiple options combined
758    ($type:ty, force: true, location: true) => {{
759        let mut context = std::collections::HashMap::new();
760        context.insert("force_backtrace".to_string(), "true".to_string());
761        context.insert("file".to_string(), file!().to_string());
762        context.insert("line".to_string(), line!().to_string());
763        context.insert("column".to_string(), column!().to_string());
764        <$type as $crate::backtrace::GenerateImplicitData>::generate_with_context(&context)
765    }};
766
767    // Custom key-value pairs
768    ($type:ty, $($key:ident: $value:expr),+ $(,)?) => {{
769        let mut context = std::collections::HashMap::new();
770        $(
771            context.insert(stringify!($key).to_string(), $value.to_string());
772        )+
773        <$type as $crate::backtrace::GenerateImplicitData>::generate_with_context(&context)
774    }};
775}
776
777/// Macro to create a comprehensive error context with location and metadata
778///
779/// Usage:
780/// - `error_context!("message")` - Basic error context
781/// - `error_context!("message", severity: High)` - With severity
782/// - `error_context!("message", component: "auth", correlation_id: "123")` - With metadata
783#[macro_export]
784macro_rules! error_context {
785    ($message:expr) => {
786        $crate::types::ErrorContext::new($message)
787            .with_location($crate::location!())
788    };
789
790    ($message:expr, severity: $severity:expr) => {
791        $crate::types::ErrorContext::new($message)
792            .with_location($crate::location!())
793            .with_severity($severity)
794    };
795
796    ($message:expr, $($key:ident: $value:expr),+ $(,)?) => {{
797        let mut context = $crate::types::ErrorContext::new($message)
798            .with_location($crate::location!());
799
800        $(
801            context = match stringify!($key) {
802                "severity" => context.with_severity($value),
803                "component" => context.with_component(format!("{}", $value)),
804                "correlation_id" => context.with_correlation_id(format!("{}", $value)),
805                "recovery_suggestion" => context.with_recovery_suggestion(format!("{}", $value)),
806                _ => {
807                    context.add_metadata(stringify!($key), format!("{:?}", $value));
808                    context
809                }
810            };
811        )+
812
813        context
814    }};
815}
816
817/// Macro to create a DecrustError::Oops with rich context
818///
819/// Usage:
820/// - `oops!("message", source)` - Basic oops error
821/// - `oops!("message", source, context: "additional info")` - With context
822/// - `oops!("message", source, severity: High, component: "auth")` - With metadata
823#[macro_export]
824macro_rules! oops {
825    ($message:expr, $source:expr) => {
826        $crate::DecrustError::Oops {
827            message: $message.to_string(),
828            source: Box::new($source),
829            backtrace: $crate::implicit_data!($crate::backtrace::DecrustBacktrace, location: true),
830        }
831    };
832
833    ($message:expr, $source:expr, $($key:ident: $value:expr),+ $(,)?) => {{
834        let error = $crate::DecrustError::Oops {
835            message: $message.to_string(),
836            source: Box::new($source),
837            backtrace: $crate::implicit_data!($crate::backtrace::DecrustBacktrace, location: true),
838        };
839
840        // Wrap with rich context if metadata is provided
841        let context = $crate::error_context!($message, $($key: $value),+);
842        $crate::DecrustError::WithRichContext {
843            context,
844            source: Box::new(error),
845        }
846    }};
847}
848
849/// Macro to create a quick validation error
850///
851/// Usage:
852/// - `validation_error!("field", "message")` - Basic validation error
853/// - `validation_error!("field", "message", suggestion: "try this")` - With suggestion
854#[macro_export]
855macro_rules! validation_error {
856    ($field:expr, $message:expr) => {
857        $crate::DecrustError::Validation {
858            field: $field.to_string(),
859            message: $message.to_string(),
860            expected: None,
861            actual: None,
862            rule: None,
863            backtrace: $crate::implicit_data!($crate::backtrace::DecrustBacktrace, location: true),
864        }
865    };
866
867    ($field:expr, $message:expr, suggestion: $suggestion:expr) => {{
868        let error = $crate::DecrustError::Validation {
869            field: $field.to_string(),
870            message: $message.to_string(),
871            expected: None,
872            actual: None,
873            rule: None,
874            backtrace: $crate::implicit_data!($crate::backtrace::DecrustBacktrace, location: true),
875        };
876
877        let context = $crate::error_context!($message)
878            .with_recovery_suggestion($suggestion.to_string());
879        $crate::DecrustError::WithRichContext {
880            context,
881            source: Box::new(error),
882        }
883    }};
884}
885
886// ===== Snafu-based Backtrace Compatibility Layer =====
887
888/// Implementation of GenerateImplicitData for std::backtrace::Backtrace
889impl GenerateImplicitData for std::backtrace::Backtrace {
890    fn generate() -> Self {
891        std::backtrace::Backtrace::force_capture()
892    }
893
894    fn generate_with_context(context: &HashMap<String, String>) -> Self {
895        // Check if force capture is requested
896        if context
897            .get("force_backtrace")
898            .map(|s| s == "true")
899            .unwrap_or(false)
900        {
901            std::backtrace::Backtrace::force_capture()
902        } else {
903            std::backtrace::Backtrace::capture()
904        }
905    }
906}
907
908/// Trait for types that can provide a backtrace
909///
910/// This is our version of snafu's AsBacktrace trait
911pub trait AsBacktrace {
912    /// Get the backtrace associated with this error, if any
913    fn as_backtrace(&self) -> Option<&std::backtrace::Backtrace>;
914}
915
916// Implementation for std::backtrace::Backtrace
917impl AsBacktrace for std::backtrace::Backtrace {
918    fn as_backtrace(&self) -> Option<&std::backtrace::Backtrace> {
919        Some(self)
920    }
921}
922
923// Implementation for our DecrustBacktrace
924impl AsBacktrace for DecrustBacktrace {
925    fn as_backtrace(&self) -> Option<&std::backtrace::Backtrace> {
926        self.as_std_backtrace()
927    }
928}