1use std::fmt;
14
15use anyhow::Error as AnyError;
16
17pub type DogResult<T> = std::result::Result<T, AnyError>;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub enum ErrorKind {
23 BadRequest, NotAuthenticated, Forbidden, NotFound, MethodNotAllowed, NotAcceptable, Timeout, Conflict, Gone, LengthRequired, Unprocessable, TooManyRequests, GeneralError, NotImplemented, BadGateway, Unavailable, }
40
41impl ErrorKind {
42 pub fn status_code(&self) -> u16 {
43 match self {
44 ErrorKind::BadRequest => 400,
45 ErrorKind::NotAuthenticated => 401,
46 ErrorKind::Forbidden => 403,
47 ErrorKind::NotFound => 404,
48 ErrorKind::MethodNotAllowed => 405,
49 ErrorKind::NotAcceptable => 406,
50 ErrorKind::Timeout => 408,
51 ErrorKind::Conflict => 409,
52 ErrorKind::Gone => 410,
53 ErrorKind::LengthRequired => 411,
54 ErrorKind::Unprocessable => 422,
55 ErrorKind::TooManyRequests => 429,
56 ErrorKind::GeneralError => 500,
57 ErrorKind::NotImplemented => 501,
58 ErrorKind::BadGateway => 502,
59 ErrorKind::Unavailable => 503,
60 }
61 }
62
63 pub fn name(&self) -> &'static str {
65 match self {
66 ErrorKind::BadRequest => "BadRequest",
67 ErrorKind::NotAuthenticated => "NotAuthenticated",
68 ErrorKind::Forbidden => "Forbidden",
69 ErrorKind::NotFound => "NotFound",
70 ErrorKind::MethodNotAllowed => "MethodNotAllowed",
71 ErrorKind::NotAcceptable => "NotAcceptable",
72 ErrorKind::Timeout => "Timeout",
73 ErrorKind::Conflict => "Conflict",
74 ErrorKind::Gone => "Gone",
75 ErrorKind::LengthRequired => "LengthRequired",
76 ErrorKind::Unprocessable => "Unprocessable",
77 ErrorKind::TooManyRequests => "TooManyRequests",
78 ErrorKind::GeneralError => "GeneralError",
79 ErrorKind::NotImplemented => "NotImplemented",
80 ErrorKind::BadGateway => "BadGateway",
81 ErrorKind::Unavailable => "Unavailable",
82 }
83 }
84
85 pub fn class_name(&self) -> &'static str {
87 match self {
88 ErrorKind::BadRequest => "bad-request",
89 ErrorKind::NotAuthenticated => "not-authenticated",
90 ErrorKind::Forbidden => "forbidden",
91 ErrorKind::NotFound => "not-found",
92 ErrorKind::MethodNotAllowed => "method-not-allowed",
93 ErrorKind::NotAcceptable => "not-acceptable",
94 ErrorKind::Timeout => "timeout",
95 ErrorKind::Conflict => "conflict",
96 ErrorKind::Gone => "gone",
97 ErrorKind::LengthRequired => "length-required",
98 ErrorKind::Unprocessable => "unprocessable",
99 ErrorKind::TooManyRequests => "too-many-requests",
100 ErrorKind::GeneralError => "general-error",
101 ErrorKind::NotImplemented => "not-implemented",
102 ErrorKind::BadGateway => "bad-gateway",
103 ErrorKind::Unavailable => "unavailable",
104 }
105 }
106}
107
108#[cfg(feature = "serde")]
109pub type ErrorValue = serde_json::Value;
110
111#[cfg(not(feature = "serde"))]
112pub type ErrorValue = std::sync::Arc<dyn std::any::Any + Send + Sync>;
113
114#[derive(Debug)]
124pub struct DogError {
125 pub kind: ErrorKind,
126 pub message: String,
127 pub data: Option<ErrorValue>,
128 pub errors: Option<ErrorValue>,
129 pub source: Option<AnyError>,
130}
131
132impl DogError {
133 pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
134 Self {
135 kind,
136 message: message.into(),
137 data: None,
138 errors: None,
139 source: None,
140 }
141 }
142
143 pub fn with_data(mut self, data: ErrorValue) -> Self {
144 self.data = Some(data);
145 self
146 }
147
148 pub fn with_errors(mut self, errors: ErrorValue) -> Self {
149 self.errors = Some(errors);
150 self
151 }
152
153 pub fn with_source(mut self, source: AnyError) -> Self {
154 self.source = Some(source);
155 self
156 }
157
158 pub fn code(&self) -> u16 {
159 self.kind.status_code()
160 }
161
162 pub fn name(&self) -> &'static str {
163 self.kind.name()
164 }
165
166 pub fn class_name(&self) -> &'static str {
167 self.kind.class_name()
168 }
169
170 pub fn into_anyhow(self) -> AnyError {
172 AnyError::new(self)
173 }
174
175 pub fn from_anyhow(err: &AnyError) -> Option<&DogError> {
177 err.downcast_ref::<DogError>()
178 }
179
180 pub fn normalize(err: AnyError) -> DogError {
184 match err.downcast::<DogError>() {
185 Ok(dog) => dog,
186 Err(other) => DogError::new(ErrorKind::GeneralError, other.to_string()).with_source(other),
187 }
188 }
189
190 pub fn sanitize_for_client(&self) -> DogError {
194 DogError {
195 kind: self.kind,
196 message: self.message.clone(),
197 data: self.data.clone(),
198 errors: self.errors.clone(),
199 source: None,
200 }
201 }
202
203 pub fn bad_request(msg: impl Into<String>) -> Self {
206 Self::new(ErrorKind::BadRequest, msg)
207 }
208 pub fn not_authenticated(msg: impl Into<String>) -> Self {
209 Self::new(ErrorKind::NotAuthenticated, msg)
210 }
211 pub fn forbidden(msg: impl Into<String>) -> Self {
212 Self::new(ErrorKind::Forbidden, msg)
213 }
214 pub fn not_found(msg: impl Into<String>) -> Self {
215 Self::new(ErrorKind::NotFound, msg)
216 }
217 pub fn method_not_allowed(msg: impl Into<String>) -> Self {
218 Self::new(ErrorKind::MethodNotAllowed, msg)
219 }
220 pub fn not_acceptable(msg: impl Into<String>) -> Self {
221 Self::new(ErrorKind::NotAcceptable, msg)
222 }
223 pub fn timeout(msg: impl Into<String>) -> Self {
224 Self::new(ErrorKind::Timeout, msg)
225 }
226 pub fn conflict(msg: impl Into<String>) -> Self {
227 Self::new(ErrorKind::Conflict, msg)
228 }
229 pub fn gone(msg: impl Into<String>) -> Self {
230 Self::new(ErrorKind::Gone, msg)
231 }
232 pub fn length_required(msg: impl Into<String>) -> Self {
233 Self::new(ErrorKind::LengthRequired, msg)
234 }
235 pub fn unprocessable(msg: impl Into<String>) -> Self {
236 Self::new(ErrorKind::Unprocessable, msg)
237 }
238 pub fn too_many_requests(msg: impl Into<String>) -> Self {
239 Self::new(ErrorKind::TooManyRequests, msg)
240 }
241 pub fn general_error(msg: impl Into<String>) -> Self {
242 Self::new(ErrorKind::GeneralError, msg)
243 }
244 pub fn not_implemented(msg: impl Into<String>) -> Self {
245 Self::new(ErrorKind::NotImplemented, msg)
246 }
247 pub fn bad_gateway(msg: impl Into<String>) -> Self {
248 Self::new(ErrorKind::BadGateway, msg)
249 }
250 pub fn unavailable(msg: impl Into<String>) -> Self {
251 Self::new(ErrorKind::Unavailable, msg)
252 }
253}
254
255impl fmt::Display for DogError {
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257 write!(f, "{} ({}): {}", self.name(), self.code(), self.message)
258 }
259}
260
261impl std::error::Error for DogError {
262 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
263 self.source
264 .as_ref()
265 .map(|e| e.as_ref() as &(dyn std::error::Error + 'static))
266 }
267}
268
269#[cfg(feature = "serde")]
270impl DogError {
271 pub fn to_json(&self) -> serde_json::Value {
273 use serde_json::json;
274
275 let mut base = json!({
276 "name": self.name(),
277 "message": self.message,
278 "code": self.code(),
279 "className": self.class_name(),
280 });
281
282 if let Some(d) = &self.data {
283 base["data"] = d.clone();
284 }
285 if let Some(e) = &self.errors {
286 base["errors"] = e.clone();
287 }
288 base
289 }
290}
291
292pub trait IntoAnyhowDogError {
294 fn into_anyhow(self) -> AnyError;
295}
296
297impl IntoAnyhowDogError for DogError {
298 fn into_anyhow(self) -> AnyError {
299 self.into_anyhow()
300 }
301}
302
303#[macro_export]
305macro_rules! bail_dog {
306 ($ctor:ident, $msg:expr) => {
307 return Err($crate::errors::DogError::$ctor($msg).into_anyhow());
308 };
309 ($ctor:ident, $fmt:expr, $($arg:tt)*) => {
310 return Err($crate::errors::DogError::$ctor(format!($fmt, $($arg)*)).into_anyhow());
311 };
312}