use std::fmt;
use anyhow::Error as AnyError;
pub type DogResult<T> = std::result::Result<T, AnyError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ErrorKind {
BadRequest, NotAuthenticated, Forbidden, NotFound, MethodNotAllowed, NotAcceptable, Timeout, Conflict, Gone, LengthRequired, Unprocessable, TooManyRequests, GeneralError, NotImplemented, BadGateway, Unavailable, }
impl ErrorKind {
pub fn status_code(&self) -> u16 {
match self {
ErrorKind::BadRequest => 400,
ErrorKind::NotAuthenticated => 401,
ErrorKind::Forbidden => 403,
ErrorKind::NotFound => 404,
ErrorKind::MethodNotAllowed => 405,
ErrorKind::NotAcceptable => 406,
ErrorKind::Timeout => 408,
ErrorKind::Conflict => 409,
ErrorKind::Gone => 410,
ErrorKind::LengthRequired => 411,
ErrorKind::Unprocessable => 422,
ErrorKind::TooManyRequests => 429,
ErrorKind::GeneralError => 500,
ErrorKind::NotImplemented => 501,
ErrorKind::BadGateway => 502,
ErrorKind::Unavailable => 503,
}
}
pub fn name(&self) -> &'static str {
match self {
ErrorKind::BadRequest => "BadRequest",
ErrorKind::NotAuthenticated => "NotAuthenticated",
ErrorKind::Forbidden => "Forbidden",
ErrorKind::NotFound => "NotFound",
ErrorKind::MethodNotAllowed => "MethodNotAllowed",
ErrorKind::NotAcceptable => "NotAcceptable",
ErrorKind::Timeout => "Timeout",
ErrorKind::Conflict => "Conflict",
ErrorKind::Gone => "Gone",
ErrorKind::LengthRequired => "LengthRequired",
ErrorKind::Unprocessable => "Unprocessable",
ErrorKind::TooManyRequests => "TooManyRequests",
ErrorKind::GeneralError => "GeneralError",
ErrorKind::NotImplemented => "NotImplemented",
ErrorKind::BadGateway => "BadGateway",
ErrorKind::Unavailable => "Unavailable",
}
}
pub fn class_name(&self) -> &'static str {
match self {
ErrorKind::BadRequest => "bad-request",
ErrorKind::NotAuthenticated => "not-authenticated",
ErrorKind::Forbidden => "forbidden",
ErrorKind::NotFound => "not-found",
ErrorKind::MethodNotAllowed => "method-not-allowed",
ErrorKind::NotAcceptable => "not-acceptable",
ErrorKind::Timeout => "timeout",
ErrorKind::Conflict => "conflict",
ErrorKind::Gone => "gone",
ErrorKind::LengthRequired => "length-required",
ErrorKind::Unprocessable => "unprocessable",
ErrorKind::TooManyRequests => "too-many-requests",
ErrorKind::GeneralError => "general-error",
ErrorKind::NotImplemented => "not-implemented",
ErrorKind::BadGateway => "bad-gateway",
ErrorKind::Unavailable => "unavailable",
}
}
}
#[cfg(feature = "serde")]
pub type ErrorValue = serde_json::Value;
#[cfg(not(feature = "serde"))]
pub type ErrorValue = std::sync::Arc<dyn std::any::Any + Send + Sync>;
#[derive(Debug)]
pub struct DogError {
pub kind: ErrorKind,
pub message: String,
pub data: Option<ErrorValue>,
pub errors: Option<ErrorValue>,
pub source: Option<AnyError>,
}
impl DogError {
pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
Self {
kind,
message: message.into(),
data: None,
errors: None,
source: None,
}
}
pub fn with_data(mut self, data: ErrorValue) -> Self {
self.data = Some(data);
self
}
pub fn with_errors(mut self, errors: ErrorValue) -> Self {
self.errors = Some(errors);
self
}
pub fn with_source(mut self, source: AnyError) -> Self {
self.source = Some(source);
self
}
pub fn code(&self) -> u16 {
self.kind.status_code()
}
pub fn name(&self) -> &'static str {
self.kind.name()
}
pub fn class_name(&self) -> &'static str {
self.kind.class_name()
}
pub fn into_anyhow(self) -> AnyError {
AnyError::new(self)
}
pub fn from_anyhow(err: &AnyError) -> Option<&DogError> {
err.downcast_ref::<DogError>()
}
pub fn normalize(err: AnyError) -> DogError {
match err.downcast::<DogError>() {
Ok(dog) => dog,
Err(other) => DogError::new(ErrorKind::GeneralError, other.to_string()).with_source(other),
}
}
pub fn sanitize_for_client(&self) -> DogError {
DogError {
kind: self.kind,
message: self.message.clone(),
data: self.data.clone(),
errors: self.errors.clone(),
source: None,
}
}
pub fn bad_request(msg: impl Into<String>) -> Self {
Self::new(ErrorKind::BadRequest, msg)
}
pub fn not_authenticated(msg: impl Into<String>) -> Self {
Self::new(ErrorKind::NotAuthenticated, msg)
}
pub fn forbidden(msg: impl Into<String>) -> Self {
Self::new(ErrorKind::Forbidden, msg)
}
pub fn not_found(msg: impl Into<String>) -> Self {
Self::new(ErrorKind::NotFound, msg)
}
pub fn method_not_allowed(msg: impl Into<String>) -> Self {
Self::new(ErrorKind::MethodNotAllowed, msg)
}
pub fn not_acceptable(msg: impl Into<String>) -> Self {
Self::new(ErrorKind::NotAcceptable, msg)
}
pub fn timeout(msg: impl Into<String>) -> Self {
Self::new(ErrorKind::Timeout, msg)
}
pub fn conflict(msg: impl Into<String>) -> Self {
Self::new(ErrorKind::Conflict, msg)
}
pub fn gone(msg: impl Into<String>) -> Self {
Self::new(ErrorKind::Gone, msg)
}
pub fn length_required(msg: impl Into<String>) -> Self {
Self::new(ErrorKind::LengthRequired, msg)
}
pub fn unprocessable(msg: impl Into<String>) -> Self {
Self::new(ErrorKind::Unprocessable, msg)
}
pub fn too_many_requests(msg: impl Into<String>) -> Self {
Self::new(ErrorKind::TooManyRequests, msg)
}
pub fn general_error(msg: impl Into<String>) -> Self {
Self::new(ErrorKind::GeneralError, msg)
}
pub fn not_implemented(msg: impl Into<String>) -> Self {
Self::new(ErrorKind::NotImplemented, msg)
}
pub fn bad_gateway(msg: impl Into<String>) -> Self {
Self::new(ErrorKind::BadGateway, msg)
}
pub fn unavailable(msg: impl Into<String>) -> Self {
Self::new(ErrorKind::Unavailable, msg)
}
}
impl fmt::Display for DogError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} ({}): {}", self.name(), self.code(), self.message)
}
}
impl std::error::Error for DogError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source
.as_ref()
.map(|e| e.as_ref() as &(dyn std::error::Error + 'static))
}
}
#[cfg(feature = "serde")]
impl DogError {
pub fn to_json(&self) -> serde_json::Value {
use serde_json::json;
let mut base = json!({
"name": self.name(),
"message": self.message,
"code": self.code(),
"className": self.class_name(),
});
if let Some(d) = &self.data {
base["data"] = d.clone();
}
if let Some(e) = &self.errors {
base["errors"] = e.clone();
}
base
}
}
pub trait IntoAnyhowDogError {
fn into_anyhow(self) -> AnyError;
}
impl IntoAnyhowDogError for DogError {
fn into_anyhow(self) -> AnyError {
self.into_anyhow()
}
}
#[macro_export]
macro_rules! bail_dog {
($ctor:ident, $msg:expr) => {
return Err($crate::errors::DogError::$ctor($msg).into_anyhow());
};
($ctor:ident, $fmt:expr, $($arg:tt)*) => {
return Err($crate::errors::DogError::$ctor(format!($fmt, $($arg)*)).into_anyhow());
};
}