charon_error/report/
error_report.rs1use crate::{
8 ERGlobalSettings, ErrorAttachment, ErrorFmt, ErrorFmtLoD, ErrorFmtSettings, ErrorFormatObj,
9 ErrorFrame, ErrorSensitivityLabel, IndentationStyle, ResultER, ResultExt, StringError, map,
10};
11use colored::*;
12use std::error::Error;
13use std::fmt::{Debug, Display};
14use tracing::instrument;
15
16#[derive(Debug)]
54#[must_use = "error reports must be used or returned"]
55pub struct ErrorReport {
56 pub frames: Vec<ErrorFrame>,
59 unique_id: String,
61}
62
63impl ErrorFmt for ErrorReport {
64 #[instrument]
65 fn create_format_obj(&self, settings: ErrorFmtSettings) -> ResultER<ErrorFormatObj> {
66 let object = ErrorFormatObj::Object(map! {
67 "last_error" => ErrorFormatObj::ColorString(self.get_last_error_title().bright_red().bold()),
68 "unique_id" => ErrorFormatObj::String(self.unique_id.clone()),
69 "frames" => ErrorFormatObj::Array(self.frames.iter().map(|f| f.create_format_obj(settings)).collect::<ResultER<Vec<_>>>()?),
70 });
71 Ok(match settings.level_of_detail {
72 ErrorFmtLoD::Compact => object.filter_object_fields(vec!["last_error", "frames"])?,
73 ErrorFmtLoD::Medium | ErrorFmtLoD::SubmitReport => {
74 object.filter_object_fields(vec!["last_error", "frames", "unique_id"])?
75 }
76 ErrorFmtLoD::Full | ErrorFmtLoD::Debug => object,
77 })
78 }
79}
80
81impl ErrorReport {
82 #[track_caller]
87 #[must_use = "the newly created report must be used"]
88 pub fn from_error<E: Error + Send + Sync + 'static>(error: E) -> Self {
89 Self {
90 frames: vec![ErrorFrame::new(error)],
91 unique_id: Self::create_new_unique_id(),
92 }
93 }
94
95 #[track_caller]
97 #[must_use = "the newly created report must be used"]
98 pub fn from_dyn_error(error: Box<dyn Error + Send + Sync + 'static>) -> Self {
99 Self {
100 frames: vec![ErrorFrame::from_dyn_error(error)],
101 unique_id: Self::create_new_unique_id(),
102 }
103 }
104
105 #[track_caller]
109 #[must_use = "the newly created report must be used"]
110 pub fn from_anyhow_error_ref(error: &anyhow::Error) -> Self {
111 let errors: Vec<_> = error.chain().collect();
113 let mut frames: Vec<ErrorFrame> = vec![];
114 for cause in errors {
115 frames.push(ErrorFrame::new(StringError::from_error(cause)));
116 }
117 if frames.is_empty() {
118 std::panic::panic_any(StringError::new(
119 "Calling `ErrorReport::from_anyhow_error_ref()` on anyhow::Error with no causes. \
120 This should not be possible, please report.",
121 ));
122 }
123 Self {
124 frames,
125 unique_id: "".to_owned(),
126 }
127 }
128
129 #[track_caller]
133 #[must_use = "the modified report with the new error must be used"]
134 pub fn push_error<R: Error + Send + Sync + 'static>(mut self, new_error: R) -> Self {
135 self.frames.push(ErrorFrame::new(new_error));
136 self
137 }
138
139 #[track_caller]
149 #[must_use = "the modified report with the attachment must be used"]
150 #[instrument(skip(key_name, attachment))]
151 pub fn attach<S: Into<String>>(
152 mut self,
153 key_name: S,
154 attachment: ErrorSensitivityLabel<ErrorAttachment>,
155 ) -> Self {
156 if self.frames.is_empty() {
157 tracing::error!(
158 "Calling `ErrorReport::attach()` on ErrorReport with no frames. {:?}",
159 self
160 );
161 std::panic::panic_any(self.push_error(StringError::new(
162 "Calling `ErrorReport::attach()` on ErrorReport with no frames.",
163 )));
164 }
165 let last_frame = self.frames.last_mut().unwrap_error();
166 last_frame
167 .attachments
168 .insert(key_name.into(), Box::new(attachment));
169 self
170 }
171
172 #[allow(clippy::borrowed_box)]
180 #[must_use]
181 #[instrument]
182 pub fn get_last_error(&self) -> &Box<dyn Error + Send + Sync + 'static> {
183 if self.frames.is_empty() {
184 tracing::error!(
185 "Calling `ErrorReport::get_last_error()` on ErrorReport with no frames. {:?}",
186 self
187 );
188 panic!("Calling `ErrorReport::get_last_error()` on ErrorReport with no frames.");
190 }
191 &self.frames.last().unwrap_error().error
192 }
193
194 #[must_use]
196 pub fn get_last_error_title(&self) -> String {
197 self.get_last_error().to_string()
198 }
199
200 fn create_new_unique_id() -> String {
202 let amount_chars: usize = 20;
203 use rand::RngExt;
204 let hash: String = rand::rng()
205 .sample_iter(rand::distr::Alphanumeric)
206 .take(amount_chars)
207 .map(char::from)
208 .collect();
209 hash
210 }
211
212 #[must_use]
214 pub fn get_unique_id(&self) -> String {
215 self.unique_id.clone()
216 }
217
218 #[must_use = "the modified report with the attachment must be used"]
220 pub fn attach_public_string<S: Into<String>, A: Display>(
221 self,
222 key_name: S,
223 attachment: A,
224 ) -> Self {
225 self.attach(
226 key_name,
227 ErrorSensitivityLabel::Public(ErrorAttachment::String(attachment.to_string())),
228 )
229 }
230}
231
232impl Display for ErrorReport {
233 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
234 write!(
235 f,
236 "{}",
237 self.stringify(ErrorFmtSettings {
238 level_of_detail: ErrorFmtLoD::Medium,
239 frame_limit: None,
240 enable_color: true,
241 link_format: ERGlobalSettings::get_or_default_settings()
242 .unwrap_error()
243 .link_format,
244 indentation_style: IndentationStyle::FourSpaces,
245 ..Default::default()
246 })
247 .unwrap_error()
248 )
249 }
250}
251
252impl<E: Error + Send + Sync + 'static> From<E> for ErrorReport {
253 #[track_caller]
254 fn from(error: E) -> Self {
255 Self::from_error(error)
256 }
257}