use std::backtrace::Backtrace;
use std::backtrace::BacktraceStatus;
use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Formatter;
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ErrorKind {
Unexpected,
Unsupported,
ConfigInvalid,
NotFound,
Unauthorized,
Forbidden,
ServerError,
LoginFailed,
RateLimited,
ConditionNotMatch,
}
impl ErrorKind {
pub fn into_static(self) -> &'static str {
self.into()
}
fn disable_backtrace(&self) -> bool {
matches!(self, ErrorKind::NotFound)
}
}
impl Display for ErrorKind {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.into_static())
}
}
impl From<ErrorKind> for &'static str {
fn from(v: ErrorKind) -> &'static str {
match v {
ErrorKind::Unexpected => "Unexpected",
ErrorKind::Unsupported => "Unsupported",
ErrorKind::ConfigInvalid => "ConfigInvalid",
ErrorKind::NotFound => "NotFound",
ErrorKind::Unauthorized => "Unauthorized",
ErrorKind::Forbidden => "Forbidden",
ErrorKind::ServerError => "ServerError",
ErrorKind::LoginFailed => "LoginFailed",
ErrorKind::RateLimited => "RateLimited",
ErrorKind::ConditionNotMatch => "ConditionNotMatch",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ErrorStatus {
Permanent,
Temporary,
Persistent,
}
impl Display for ErrorStatus {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
ErrorStatus::Permanent => write!(f, "permanent"),
ErrorStatus::Temporary => write!(f, "temporary"),
ErrorStatus::Persistent => write!(f, "persistent"),
}
}
}
pub struct Error {
kind: ErrorKind,
message: String,
status: ErrorStatus,
operation: &'static str,
context: Vec<(&'static str, String)>,
source: Option<anyhow::Error>,
backtrace: Backtrace,
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{} ({}) at {}", self.kind, self.status, self.operation)?;
if !self.context.is_empty() {
write!(f, ", context: {{ ")?;
write!(
f,
"{}",
self.context
.iter()
.map(|(k, v)| format!("{k}: {v}"))
.collect::<Vec<_>>()
.join(", ")
)?;
write!(f, " }}")?;
}
if !self.message.is_empty() {
write!(f, " => {}", self.message)?;
}
if let Some(source) = &self.source {
write!(f, ", source: {source}")?;
}
Ok(())
}
}
impl Debug for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if f.alternate() {
let mut de = f.debug_struct("Error");
de.field("kind", &self.kind);
de.field("message", &self.message);
de.field("status", &self.status);
de.field("operation", &self.operation);
de.field("context", &self.context);
de.field("source", &self.source);
return de.finish();
}
write!(f, "{} ({}) at {}", self.kind, self.status, self.operation)?;
if !self.message.is_empty() {
write!(f, " => {}", self.message)?;
}
writeln!(f)?;
if !self.context.is_empty() {
writeln!(f)?;
writeln!(f, "Context:")?;
for (k, v) in self.context.iter() {
writeln!(f, " {k}: {v}")?;
}
}
if let Some(source) = &self.source {
writeln!(f)?;
writeln!(f, "Source:")?;
writeln!(f, " {source:#}")?;
}
if self.backtrace.status() == BacktraceStatus::Captured {
writeln!(f)?;
writeln!(f, "Backtrace:")?;
writeln!(f, "{}", self.backtrace)?;
}
Ok(())
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source.as_ref().map(|v| v.as_ref())
}
}
impl Error {
pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
Self {
kind,
message: message.into(),
status: ErrorStatus::Permanent,
operation: "",
context: Vec::default(),
source: None,
backtrace: if kind.disable_backtrace() {
Backtrace::disabled()
} else {
Backtrace::capture()
},
}
}
pub fn with_operation(mut self, operation: impl Into<&'static str>) -> Self {
if !self.operation.is_empty() {
self.context.push(("called", self.operation.to_string()));
}
self.operation = operation.into();
self
}
pub fn with_context(mut self, key: &'static str, value: impl ToString) -> Self {
self.context.push((key, value.to_string()));
self
}
pub fn set_source(mut self, src: impl Into<anyhow::Error>) -> Self {
debug_assert!(self.source.is_none(), "the source error has been set");
self.source = Some(src.into());
self
}
pub fn map<F>(self, f: F) -> Self
where
F: FnOnce(Self) -> Self,
{
f(self)
}
pub fn set_permanent(mut self) -> Self {
self.status = ErrorStatus::Permanent;
self
}
pub fn set_temporary(mut self) -> Self {
self.status = ErrorStatus::Temporary;
self
}
#[allow(dead_code)]
pub(crate) fn with_temporary(mut self, temporary: bool) -> Self {
if temporary {
self.status = ErrorStatus::Temporary;
}
self
}
pub fn set_persistent(mut self) -> Self {
self.status = ErrorStatus::Persistent;
self
}
pub fn kind(&self) -> ErrorKind {
self.kind
}
pub fn is_temporary(&self) -> bool {
self.status == ErrorStatus::Temporary
}
}