1use core::{
4 error::Error,
5 fmt::{self, Write},
6 panic::Location,
7};
8use std::{env::current_exe, ffi::OsStr, path::PathBuf};
9
10use crate::style::{BOLD, RED, RESET};
11
12pub trait ErrorLogger {
14 #[track_caller]
16 fn log_error(self) -> Self;
17}
18
19impl<T, E: fmt::Display> ErrorLogger for Result<T, E> {
20 #[track_caller]
21 fn log_error(self) -> Self {
22 if let Err(error) = self.as_ref() {
23 let location = Location::caller();
24 #[cfg(feature = "log")]
25 log::error!("[{location}] {error}");
26 #[cfg(not(feature = "log"))]
27 eprintln!("{BOLD}{RED}error{RESET}{BOLD}:{RESET} [{location}] {error}");
28 }
29 self
30 }
31}
32impl<T> ErrorLogger for Option<T> {
33 #[track_caller]
34 fn log_error(self) -> Self {
35 if self.is_none() {
36 let location = Location::caller();
37 #[cfg(feature = "log")]
38 log::error!("[{location}] value was None");
39 #[cfg(not(feature = "log"))]
40 eprintln!("{BOLD}{RED}error{RESET}{BOLD}:{RESET} [{location}] value was None");
41 }
42 self
43 }
44}
45
46pub type ReportProgramExit = Result<(), ProgramReport>;
48
49pub struct ProgramReport(Box<dyn Error + 'static>);
51impl<E: Error + 'static> From<E> for ProgramReport {
52 fn from(value: E) -> Self {
53 Self(Box::new(value))
54 }
55}
56impl fmt::Debug for ProgramReport {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 write!(f, "{self}")
59 }
60}
61impl fmt::Display for ProgramReport {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 let report = Report::new(self.0.as_ref(), ErrorStackStyle::Stacked { indent: 2 });
64
65 let exe_path = current_exe().unwrap_or_else(|_| PathBuf::from("program"));
66 let exe = exe_path
67 .file_name()
68 .unwrap_or_else(|| OsStr::new("program"))
69 .to_string_lossy();
70
71 writeln!(f, "`{exe}` exited unsuccessfully")?;
72 write!(f, "{report}")
73 }
74}
75
76pub trait IntoErrorReport<'a, T>: Sized {
78 fn into_report(self) -> Result<T, Report<'a>>;
80}
81
82impl<'a, T, E: Error + 'a> IntoErrorReport<'a, T> for Result<T, E> {
83 fn into_report(self) -> Result<T, Report<'a>> {
84 self.map_err(|source| Report::new(source, ErrorStackStyle::default()))
85 }
86}
87
88impl<'a, T> IntoErrorReport<'a, T> for Option<T> {
89 fn into_report(self) -> Result<T, Report<'a>> {
90 #[derive(Debug)]
91 struct NoneError;
92 impl fmt::Display for NoneError {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 write!(f, "value was none")
95 }
96 }
97 impl Error for NoneError {}
98
99 self.ok_or_else(|| Report::new(NoneError, ErrorStackStyle::default()))
100 }
101}
102
103pub struct Report<'a> {
105 pub source: Box<dyn Error + 'a>,
107 pub style: ErrorStackStyle<'a>,
109}
110impl<'a> Report<'a> {
111 pub fn new<E: Error + 'a>(source: E, style: ErrorStackStyle<'a>) -> Self {
113 Self {
114 source: Box::new(source),
115 style,
116 }
117 }
118
119 #[track_caller]
120 pub fn log_error(&self) {
122 let location = Location::caller();
123 #[cfg(feature = "log")]
124 log::error!("[{location}] {self}");
125 #[cfg(not(feature = "log"))]
126 eprintln!("{BOLD}{RED}error{RESET}{BOLD}:{RESET} [{location}] {self}");
127 }
128}
129impl Error for Report<'static> {
130 fn source(&self) -> Option<&(dyn Error + 'static)> {
131 Some(self.source.as_ref())
132 }
133}
134impl fmt::Debug for Report<'_> {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 write!(f, "{self}")
137 }
138}
139impl fmt::Display for Report<'_> {
140 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141 let output = self.style.display(self.source.as_ref())?;
142 writeln!(f, "{output}")
143 }
144}
145
146pub type FmtErrorClosure<'a> = Box<dyn Fn(&mut String, usize, &dyn Error) -> fmt::Result + 'a>;
148
149pub enum ErrorStackStyle<'a> {
151 Inline,
153 Stacked {
155 indent: usize,
157 },
158 Custom(FmtErrorClosure<'a>),
160}
161impl Default for ErrorStackStyle<'_> {
162 fn default() -> Self {
163 Self::Stacked { indent: 2 }
164 }
165}
166
167impl ErrorStackStyle<'_> {
168 pub fn display(&self, source: &dyn Error) -> Result<String, fmt::Error> {
170 let mut output = String::new();
171
172 let fmt_fn = self.fmt_fn();
173
174 let mut current_error = Some(source);
175 let mut index = 1;
176 while let Some(error) = current_error {
177 fmt_fn(&mut output, index, error)?;
178 current_error = error.source();
179 index += 1;
180 }
181
182 Ok(output)
183 }
184
185 fn fmt_fn(&self) -> FmtErrorClosure<'_> {
186 match &self {
187 Self::Inline => Box::new(|f, i, e| write!(f, " ----- {i}. {e}")),
188
189 Self::Stacked { indent } => Box::new(|f, i, e| {
190 writeln!(
191 f,
192 "{}{BOLD}{RED}{i}{RESET}{BOLD}.{RESET} {e}",
193 " ".repeat(*indent)
194 )
195 }),
196
197 Self::Custom(f) => Box::new(f),
198 }
199 }
200}