1use std::{
2 env,
3 panic::{self, UnwindSafe},
4 path::PathBuf,
5 process,
6};
7
8use color_eyre::{Report, Result, Section, config::HookBuilder, owo_colors::style};
9use futures_util::FutureExt;
10use tokio::sync::mpsc;
11
12pub async fn init<F>(log_path: Option<PathBuf>, fut: F) -> Result<()>
14where
15 F: Future<Output = Result<()>> + UnwindSafe,
16{
17 tracing::trace!("Initializing error handlers");
18 let panic_section = if let Some(log_path) = log_path {
20 format!(
21 "This is a bug. Consider reporting it at {}\nLogs can be found at {}",
22 env!("CARGO_PKG_REPOSITORY"),
23 log_path.display()
24 )
25 } else {
26 format!(
27 "This is a bug. Consider reporting it at {}\nLogs were not generated, consider enabling them on the \
28 config or running with INTELLI_LOG=debug.",
29 env!("CARGO_PKG_REPOSITORY")
30 )
31 };
32 let (panic_hook, eyre_hook) = HookBuilder::default()
33 .panic_section(panic_section.clone())
34 .display_env_section(false)
35 .display_location_section(true)
36 .capture_span_trace_by_default(true)
37 .into_hooks();
38
39 let (panic_tx, mut panic_rx) = mpsc::channel(1);
41
42 eyre_hook.install()?;
44 panic::set_hook(Box::new(move |panic_info| {
45 let panic_report = panic_hook.panic_report(panic_info).to_string();
48 tracing::error!("Error: {}", strip_ansi_escapes::strip_str(&panic_report));
49 if panic_tx.try_send(panic_report).is_err() {
50 tracing::error!("Error sending panic report",);
51 process::exit(2);
52 }
53 }));
54
55 tokio::select! {
56 biased;
57 panic_report = panic_rx.recv().fuse() => {
59 if let Some(report) = panic_report {
60 eprintln!("{report}");
61 } else {
62 eprintln!(
63 "{}\n\n{panic_section}",
64 style().bright_red().style("A panic occurred, but the detailed report could not be captured.")
65 );
66 tracing::error!("A panic occurred, but the detailed report could not be captured.");
67 }
68 process::exit(1);
70 }
71 res = Box::pin(fut).catch_unwind() => {
73 match res {
74 Ok(r) => r
75 .with_section(move || panic_section)
76 .inspect_err(|err| tracing::error!("Error: {}", strip_ansi_escapes::strip_str(format!("{err:?}")))),
77 Err(err) => {
78 if let Ok(report) = panic_rx.try_recv() {
79 eprintln!("{report}");
80 } else if let Some(err) = err.downcast_ref::<&str>() {
81 print_panic_msg(err, panic_section);
82 } else if let Some(err) = err.downcast_ref::<String>() {
83 print_panic_msg(err, panic_section);
84 } else {
85 eprintln!(
86 "{}\n\n{panic_section}",
87 style().bright_red().style("An unexpected panic happened")
88 );
89 tracing::error!("An unexpected panic happened");
90 }
91 process::exit(1);
93 }
94 }
95 }
96 }
97}
98
99fn print_panic_msg(err: impl AsRef<str>, panic_section: String) {
100 let err = err.as_ref();
101 eprintln!(
102 "{}\nMessage: {}\n\n{panic_section}",
103 style().bright_red().style("The application panicked (crashed)."),
104 style().blue().style(err)
105 );
106 tracing::error!("Panic: {err}");
107}
108
109#[derive(Debug)]
111pub enum SearchError {
112 InvalidRegex(regex::Error),
114 InvalidFuzzy,
116 Unexpected(Report),
118}
119
120#[derive(Debug)]
122pub enum InsertError {
123 Invalid(&'static str),
125 AlreadyExists,
127 Unexpected(Report),
129}
130
131#[derive(Debug)]
133pub enum UpdateError {
134 Invalid(&'static str),
136 AlreadyExists,
138 Unexpected(Report),
140}
141
142#[derive(Debug)]
144pub enum ImportExportError {
145 NotAFile,
147 FileNotFound,
149 FileNotAccessible,
151 FileBrokenPipe,
153 HttpInvalidUrl,
155 HttpRequestFailed(String),
157 GistMissingId,
159 GistInvalidLocation,
161 GistLocationHasSha,
163 GistFileNotFound,
165 GistMissingToken,
167 GistRequestFailed(String),
169 Unexpected(Report),
171}
172
173impl UpdateError {
174 pub fn into_report(self) -> Report {
175 match self {
176 UpdateError::Invalid(msg) => Report::msg(msg),
177 UpdateError::AlreadyExists => Report::msg("Entity already exists"),
178 UpdateError::Unexpected(report) => report,
179 }
180 }
181}
182
183macro_rules! impl_from_report {
184 ($err:ty) => {
185 impl<T> From<T> for $err
186 where
187 T: Into<Report>,
188 {
189 fn from(err: T) -> Self {
190 Self::Unexpected(err.into())
191 }
192 }
193 };
194}
195impl_from_report!(SearchError);
196impl_from_report!(InsertError);
197impl_from_report!(UpdateError);
198impl_from_report!(ImportExportError);
199
200#[macro_export]
206macro_rules! trace_dbg {
207 (target: $target:expr, level: $level:expr, $ex:expr) => {
208 {
209 match $ex {
210 value => {
211 tracing::event!(target: $target, $level, ?value, stringify!($ex));
212 value
213 }
214 }
215 }
216 };
217 (level: $level:expr, $ex:expr) => {
218 trace_dbg!(target: module_path!(), level: $level, $ex)
219 };
220 (target: $target:expr, $ex:expr) => {
221 trace_dbg!(target: $target, level: tracing::Level::DEBUG, $ex)
222 };
223 ($ex:expr) => {
224 trace_dbg!(level: tracing::Level::DEBUG, $ex)
225 };
226}