Skip to main content

charon_error/submit/
simple_error_report.rs

1use crate::{
2    ErrorFmt, ErrorFmtLoD, ErrorFmtSettings, ErrorReport, IndentationStyle, LinkDebugIde,
3    ResultExt, StringError, SubmitErrorReport,
4};
5use colored::Colorize;
6use std::sync::{OnceLock, RwLock, RwLockReadGuard};
7use url::Url;
8
9/// Simple implementation of [`SubmitErrorReport`].
10///
11/// Generates a structured, markdown-formatted error report suitable for both
12/// humans and AI/LLMs. Does not generate URLs — the report is the output.
13///
14/// Respects the `NO_COLOR` environment variable: when set, terminal hyperlinks
15/// are disabled, producing clean plain text (markdown is unaffected).
16///
17/// # Example
18///
19/// ```rust,no_run
20/// use charon_error::prelude::*;
21/// use charon_error::prelude::simple_er::*;
22/// use charon_error::LinkDebugIde;
23///
24/// SimpleErrorReport::setup_global_config(SimpleERGlobalSettings {
25///     link_format: LinkDebugIde::Vscode,
26/// }).unwrap_error();
27/// ```
28#[derive(Debug, Clone)]
29pub struct SimpleErrorReport<'a> {
30    /// Reference to the error report being formatted.
31    pub error_report: &'a ErrorReport,
32}
33
34impl<'a> SubmitErrorReport<'a> for SimpleErrorReport<'a> {
35    fn new(error: &'a ErrorReport) -> Self {
36        SimpleErrorReport {
37            error_report: error,
38        }
39    }
40
41    fn get_error_report(&self) -> &ErrorReport {
42        self.error_report
43    }
44
45    fn get_title(&self) -> String {
46        self.error_report.get_last_error_title()
47    }
48
49    fn create_message(&self) -> Result<String, ErrorReport> {
50        self.create_bug_report()
51    }
52
53    fn create_bug_report(&self) -> Result<String, ErrorReport> {
54        let link_format = Self::get_global_config()
55            .map(|c| c.link_format)
56            .unwrap_or(LinkDebugIde::Vscode);
57
58        let last_error = self.get_title();
59
60        let mut report = String::new();
61
62        // Title
63        report.push_str(&format!("{}\n\n", "# Error Report".bright_cyan().bold()));
64
65        // Error
66        report.push_str(&format!("{}\n\n", "## Error".bright_cyan().bold()));
67        report.push_str(&format!("{}\n\n", last_error.bright_red().bold()));
68
69        // Error report (structured output from ErrorFmt)
70        let summary = self.error_report.stringify(ErrorFmtSettings {
71            level_of_detail: ErrorFmtLoD::SubmitReport,
72            frame_limit: None,
73            enable_color: true,
74            link_format,
75            indentation_style: IndentationStyle::TwoSpaces,
76            ..Default::default()
77        })?;
78        report.push_str(&format!("{}\n\n", "## Error report".bright_cyan().bold()));
79        report.push_str("```\n");
80        report.push_str(&summary);
81        report.push_str("\n```\n\n");
82
83        // Backtrace
84        if let Some(last_frame) = self.error_report.frames.last() {
85            let backtrace_string = last_frame.create_backtrace_string();
86            if !backtrace_string.trim().is_empty() {
87                report.push_str(&format!("{}\n\n", "## Backtrace".bright_cyan().bold()));
88                report.push_str("```\n");
89                report.push_str(&backtrace_string);
90                report.push_str("```\n\n");
91            }
92        }
93
94        // System info
95        report.push_str(&format!("{}\n\n", "## System".bright_cyan().bold()));
96        report.push_str(&format!(
97            "* **OS:** {} {}\n",
98            std::env::consts::OS,
99            std::env::consts::ARCH
100        ));
101        report.push_str(&format!("* **Version:** {}\n", env!("CARGO_PKG_VERSION")));
102
103        Ok(report)
104    }
105
106    fn create_submit_url(&self) -> Result<Url, ErrorReport> {
107        Ok(Url::parse("data:text/plain,N/A")?)
108    }
109
110    fn create_submit_url_limited(&self, _max_length: usize) -> Result<Url, ErrorReport> {
111        Ok(Url::parse("data:text/plain,N/A")?)
112    }
113
114    fn check_existing_reports(&self) -> Result<Url, ErrorReport> {
115        Ok(Url::parse("data:text/plain,N/A")?)
116    }
117}
118
119impl<'a> SimpleErrorReport<'a> {
120    /// Initialize the global simple report configuration. Must be called once at startup.
121    ///
122    /// Returns an error if called more than once.
123    pub fn setup_global_config(config: SimpleERGlobalSettings) -> Result<(), ErrorReport> {
124        SIMPLE_REPORT_GLOBAL_CONFIG
125            .set(RwLock::new(config))
126            .map_err(|_| StringError::new("`SIMPLE_REPORT_GLOBAL_CONFIG` was already set"))
127            .change_context(SimpleGlobalSettingsError::AlreadySet)
128    }
129
130    /// Read the global simple report configuration. Returns an error if not yet set.
131    pub fn get_global_config()
132    -> Result<RwLockReadGuard<'static, SimpleERGlobalSettings>, ErrorReport> {
133        let setting_reader = SIMPLE_REPORT_GLOBAL_CONFIG
134            .get()
135            .ok_or(SimpleGlobalSettingsError::SettingNotYetSet)?;
136        setting_reader
137            .read()
138            .map_err(StringError::from_error)
139            .change_context(SimpleGlobalSettingsError::AcquireReadLockFailed)
140    }
141}
142
143static SIMPLE_REPORT_GLOBAL_CONFIG: OnceLock<RwLock<SimpleERGlobalSettings>> = OnceLock::new();
144
145/// Global configuration for the simple error report handler.
146///
147/// Set once at application startup via
148/// [`SimpleErrorReport::setup_global_config`].
149#[derive(Debug, Clone)]
150pub struct SimpleERGlobalSettings {
151    /// IDE link format for source locations (default: Vscode).
152    pub link_format: LinkDebugIde,
153}
154
155impl Default for SimpleERGlobalSettings {
156    fn default() -> Self {
157        Self {
158            link_format: LinkDebugIde::Vscode,
159        }
160    }
161}
162
163#[derive(thiserror::Error, Debug, Clone)]
164enum SimpleGlobalSettingsError {
165    #[error("Simple Global Settings where already set, this can only be set once.")]
166    AlreadySet,
167    #[error("Simple Global Settings where not set.")]
168    SettingNotYetSet,
169    #[error("Could not acquire read lock to read Simple Global Settings.")]
170    AcquireReadLockFailed,
171}