1pub type Result<T> = std::result::Result<T, EzError>;
6
7#[macro_export]
10macro_rules! bail {
11 ($($args:tt)*) => {
12 Err(EzError::message(&::std::format_args!($($args)*).to_string())).loc(flc!())?
13 };
14}
15
16#[macro_export]
19macro_rules! flc {
20 () => {{
21 #[cfg(not(feature = "no_stacktrace"))]
22 const LOC: ConstLocation = ConstLocation::new(file!(), line!(), column!());
23 #[cfg(feature = "no_stacktrace")]
24 const LOC: ConstLocation = ConstLocation::new("", 0, 0);
25 &LOC
26 }};
27}
28
29pub fn handle<F, R>(func: F) -> Option<R>
32where
33 F: FnOnce() -> Result<R>,
34{
35 func().handle()
36}
37
38#[derive(Debug, PartialEq)]
41pub struct EzError {
42 inner: Box<EzErrorInner>,
43}
44
45#[derive(Debug, PartialEq)]
46struct EzErrorInner {
47 ty: ErrorType,
48 #[cfg(not(feature = "no_stacktrace"))]
49 frames: Vec<&'static ConstLocation>,
50}
51
52impl EzError {
53 pub fn new(ty: ErrorType) -> EzError {
55 #[cfg(not(feature = "no_stacktrace"))]
56 return EzError {
57 inner: Box::new(EzErrorInner {
58 ty,
59 frames: Vec::new(),
60 }),
61 };
62 #[cfg(feature = "no_stacktrace")]
63 return EzError {
64 inner: Box::new(EzErrorInner { ty }),
65 };
66 }
67
68 pub fn message(msg: &str) -> EzError {
71 EzError::new(ErrorType::Message(msg.to_owned()))
72 }
73
74 pub fn custom(code: u32, name: String, message: String) -> EzError {
77 EzError::new(ErrorType::Custom {
78 code,
79 name,
80 message,
81 })
82 }
83
84 pub fn add_frame(&mut self, loc: &'static ConstLocation) {
87 self.inner.frames.push(loc);
88 }
89
90 pub fn with(mut self, other: EzError) -> Self {
92 self.inner.frames.extend_from_slice(&other.inner.frames);
93 self
94 }
95
96 pub fn ty(&self) -> &ErrorType {
98 &self.inner.ty
99 }
100
101 #[cfg(not(feature = "no_stacktrace"))]
103 pub fn frames(&self) -> &[&'static ConstLocation] {
104 &self.inner.frames
105 }
106}
107
108impl<E> From<E> for EzError
109where
110 E: std::fmt::Display,
111{
112 fn from(err: E) -> Self {
113 EzError::new(ErrorType::Internal(format!("{}", err)))
114 }
115}
116
117#[derive(Debug, PartialEq)]
119pub enum ErrorType {
120 Internal(String),
123
124 NoneOption,
126
127 IndexOutOfBounds(usize, usize),
130
131 RangeOutOfBounds(usize, usize, usize),
133
134 InvalidRange,
136
137 Message(String),
139
140 Custom {
142 code: u32,
144 name: String,
146 message: String,
148 },
149}
150
151impl ErrorType {
152 pub fn format(self) -> String {
154 match self {
155 ErrorType::Internal(msg) => msg,
156 ErrorType::NoneOption => format!("Option was none"),
157 ErrorType::IndexOutOfBounds(idx, len) => {
158 format!("Index {} was outside of the range 0..{}", idx, len)
159 }
160 ErrorType::RangeOutOfBounds(start, end, len) => {
161 format!(
162 "Range {}..{} was larger than the array range 0..{}",
163 start, end, len
164 )
165 }
166 ErrorType::InvalidRange => {
167 "The provided range was invalid (end < start or X..=usize::MAX)".into()
168 }
169 ErrorType::Message(msg) => msg,
170 ErrorType::Custom { message, .. } => message,
171 }
172 }
173
174 pub fn name(&self) -> &str {
177 match self {
178 ErrorType::Internal(_) => "WrappedInternal",
179 ErrorType::NoneOption => "NoneOption",
180 ErrorType::IndexOutOfBounds(_, _) => "IndexOutOfBounds",
181 ErrorType::RangeOutOfBounds(_, _, _) => "RangeOutOfBounds",
182 ErrorType::InvalidRange => "InvalidRange",
183 ErrorType::Message(_) => "Message",
184 ErrorType::Custom { name, .. } => &name,
185 }
186 }
187}
188
189#[derive(Debug, PartialEq)]
191pub struct ConstLocation {
192 pub file: &'static str,
194 pub line: u32,
196 pub column: u32,
198}
199
200impl ConstLocation {
201 pub const fn new(file: &'static str, line: u32, column: u32) -> ConstLocation {
203 ConstLocation { file, line, column }
204 }
205}
206
207pub trait LocData<T> {
209 type Result;
212
213 fn loc(self, flc: &'static ConstLocation) -> Self::Result;
216}
217
218pub trait Handle<T> {
220 fn handle(self) -> Option<T>;
223
224 fn handle_or_panic(self) -> T;
227}
228
229impl<T> LocData<T> for Result<T> {
230 type Result = Result<T>;
231
232 #[inline(always)]
233 fn loc(mut self, loc: &'static ConstLocation) -> Self::Result {
234 if let Err(err) = &mut self {
235 err.add_frame(loc);
236 }
237
238 self
239 }
240}
241
242impl<T> Handle<T> for Result<T> {
243 fn handle(self) -> Option<T> {
244 fn inner(e: EzError) {
245 let e = e.inner;
246
247 #[cfg(not(feature = "no_stacktrace"))]
248 let trace = {
249 let mut s = String::with_capacity(1024);
250 s.push_str("Stacktrace:\n");
251 for frame in e.frames {
252 s.push_str(frame.file);
253 s.push(':');
254 s.push_str(&frame.line.to_string());
255 s.push(':');
256 s.push_str(&frame.column.to_string());
257 s.push('\n');
258 }
259 s
260 };
261 #[cfg(feature = "no_stacktrace")]
262 let trace = "";
263
264 let name = e.ty.name().to_owned();
265 let message = e.ty.format();
266
267 #[cfg(feature = "log")]
268 log::error!("Error {}: {}\n\n{}", name, message, trace);
269 #[cfg(not(feature = "log"))]
270 println!("Error {}: {}\n\n{}", name, message, trace);
271 }
272
273 match self {
274 Ok(v) => Some(v),
275 Err(e) => {
276 inner(e);
277 None
278 }
279 }
280 }
281 fn handle_or_panic(self) -> T {
282 match self.handle() {
283 Some(v) => v,
284 None => panic!(),
285 }
286 }
287}
288
289impl<T, E> LocData<T> for std::result::Result<T, E>
290where
291 E: std::fmt::Display,
292{
293 type Result = Result<T>;
294
295 #[inline(always)]
296 fn loc(self, loc: &'static ConstLocation) -> Self::Result {
297 #[cfg(not(feature = "no_stacktrace"))]
298 match self {
299 Ok(v) => Ok(v),
300 Err(e) => Err({
301 let mut err: EzError = e.into();
302 err.add_frame(loc);
303 err
304 }),
305 }
306 #[cfg(feature = "no_stacktrace")]
307 self
308 }
309}
310
311impl<T> LocData<T> for Option<T> {
312 type Result = Result<T>;
313
314 #[inline(always)]
315 fn loc(self, loc: &'static ConstLocation) -> Self::Result {
316 #[cfg(not(feature = "no_stacktrace"))]
317 match self {
318 Some(v) => Ok(v),
319 None => Err({
320 let mut err = EzError::new(ErrorType::NoneOption);
321 err.add_frame(loc);
322 err
323 }),
324 }
325 #[cfg(feature = "no_stacktrace")]
326 self
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333
334 #[test]
335 fn correct_info() {
336 let err: Result<()> = Err(EzError::message("test")).loc(flc!());
337 let (file, line) = (file!(), line!());
338
339 let loc = err.err().unwrap().frames()[0];
340 assert_eq!(loc.file, file);
341 assert_eq!(loc.line, line - 1);
342 assert_eq!(loc.column, 65);
343 }
344
345 #[test]
346 fn correct_bail() {
347 let inner_line = line!() + 2;
348 fn inner() -> Result<()> {
349 bail!("bailed");
350
351 Ok(())
352 }
353
354 let err = inner().err().unwrap();
355 assert_eq!(&ErrorType::Message("bailed".into()), err.ty());
356 assert_eq!(inner_line, err.frames()[0].line);
357 }
358}