1#![deny(clippy::all)]
60#![warn(clippy::nursery)]
61#![warn(clippy::pedantic)]
62#![allow(clippy::use_self)] #![allow(clippy::missing_errors_doc)] #![warn(unknown_lints)]
65
66use std::{
67 error,
68 fmt::{self, Display, Formatter},
69 panic::Location,
70 string::ToString,
71};
72
73mod macros;
74mod terminator;
75pub use terminator::Terminator;
76
77pub type Result<T> = std::result::Result<T, Error>;
78
79#[derive(Debug)]
81pub struct Error
82{
83 pub ctx: String,
85
86 pub location: &'static Location<'static>,
88
89 pub cause: Option<Box<dyn error::Error + Send + 'static>>,
91}
92
93impl Error
94{
95 #[allow(clippy::needless_pass_by_value)] #[track_caller]
98 pub fn new<S, E>(ctx: S, cause: E) -> Error
99 where
100 S: ToString,
101 E: error::Error + Send + 'static,
102 {
103 let ctx = ctx.to_string();
104 let location = Location::caller();
105 let cause: Option<Box<dyn error::Error + Send + 'static>> = Some(Box::new(cause));
106
107 Error { ctx, location, cause }
108 }
109}
110
111impl Display for Error
112{
113 fn fmt(&self, f: &mut Formatter) -> fmt::Result
114 {
115 write!(f, "{} ({})", self.ctx, self.location)
116 }
117}
118
119impl error::Error for Error
120{
121 fn description(&self) -> &str { &self.ctx }
122
123 fn source(&self) -> Option<&(dyn error::Error + 'static)>
124 {
125 self.cause.as_ref().map(|c| &**c as _)
126 }
127}
128
129pub trait ResultExt<T>
131{
132 #[track_caller]
134 fn context<S: ToString>(self, ctx: S) -> Result<T>;
135
136 #[track_caller]
139 fn with_context<S: ToString, F: FnOnce() -> S>(self, ctx_fn: F) -> Result<T>;
140}
141
142impl<T, E> ResultExt<T> for std::result::Result<T, E>
143where
144 E: error::Error + Send + 'static,
145{
146 fn context<S: ToString>(self, ctx: S) -> Result<T>
147 {
148 let location = Location::caller();
149 self.map_err(|e| Error { ctx: ctx.to_string(), location, cause: Some(Box::new(e)) })
150 }
151
152 fn with_context<S: ToString, F: FnOnce() -> S>(self, ctx_fn: F) -> Result<T>
153 {
154 let location = Location::caller();
155 self.map_err(|e| Error { ctx: ctx_fn().to_string(), location, cause: Some(Box::new(e)) })
156 }
157}
158
159pub trait ErrorExt: error::Error
161{
162 fn iter_chain(&self) -> Causes;
163
164 fn iter_causes(&self) -> Causes { Causes { cause: self.iter_chain().nth(1) } }
165
166 fn find_root_cause(&self) -> &(dyn error::Error + 'static)
167 {
168 self.iter_chain().last().expect("source chain should at least contain original error")
169 }
170}
171
172impl<E: error::Error + 'static> ErrorExt for E
173{
174 fn iter_chain(&self) -> Causes { Causes { cause: Some(self) } }
175}
176
177impl ErrorExt for dyn error::Error
178{
179 fn iter_chain(&self) -> Causes { Causes { cause: Some(self) } }
180}
181
182#[must_use = "iterators are lazy and do nothing unless consumed"]
186pub struct Causes<'a>
187{
188 cause: Option<&'a (dyn error::Error + 'static)>,
190}
191
192impl<'a> Iterator for Causes<'a>
193{
194 type Item = &'a (dyn error::Error + 'static);
195
196 fn next(&mut self) -> Option<Self::Item>
197 {
198 let cause = self.cause.take();
199 self.cause = cause.and_then(error::Error::source);
200
201 cause
202 }
203}
204
205#[inline]
207#[allow(clippy::needless_pass_by_value)] #[track_caller]
209pub fn err_msg<S: ToString>(ctx: S) -> Error
210{
211 Error { ctx: ctx.to_string(), location: Location::caller(), cause: None }
212}