use std::error::Error as StdError;
use std::fmt;
use std::sync::Arc;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ErrorKind {
NotFound,
Timeout,
InvalidInput,
PermissionDenied,
Io,
Api,
Parse,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ErrorStatus {
Permanent,
Temporary,
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct Error {
pub kind: ErrorKind,
pub status: ErrorStatus,
pub message: String,
pub context: Vec<(&'static str, String)>,
pub source: Option<Arc<dyn StdError + Send + Sync>>,
}
impl Error {
pub fn with(mut self, key: &'static str, value: impl Into<String>) -> Self {
self.context.push((key, value.into()));
self
}
#[must_use]
pub fn display_for_user(&self) -> String {
if self.context.is_empty() {
return self.message.clone();
}
let ctx: String = self
.context
.iter()
.map(|(k, v)| format!("{k}: {v}"))
.collect::<Vec<_>>()
.join(", ");
format!("{} ({})", self.message, ctx)
}
#[must_use]
pub fn is_retryable(&self) -> bool {
self.status == ErrorStatus::Temporary
}
pub fn not_found(message: impl Into<String>) -> Self {
Self {
kind: ErrorKind::NotFound,
status: ErrorStatus::Permanent,
message: message.into(),
context: Vec::new(),
source: None,
}
}
pub fn timeout(message: impl Into<String>) -> Self {
Self {
kind: ErrorKind::Timeout,
status: ErrorStatus::Temporary,
message: message.into(),
context: Vec::new(),
source: None,
}
}
pub fn invalid_input(message: impl Into<String>) -> Self {
Self {
kind: ErrorKind::InvalidInput,
status: ErrorStatus::Permanent,
message: message.into(),
context: Vec::new(),
source: None,
}
}
pub fn permission_denied(message: impl Into<String>) -> Self {
Self {
kind: ErrorKind::PermissionDenied,
status: ErrorStatus::Permanent,
message: message.into(),
context: Vec::new(),
source: None,
}
}
pub fn io(message: impl Into<String>, source: impl StdError + Send + Sync + 'static) -> Self {
Self {
kind: ErrorKind::Io,
status: ErrorStatus::Permanent,
message: message.into(),
context: Vec::new(),
source: Some(Arc::new(source)),
}
}
pub fn api(message: impl Into<String>) -> Self {
Self {
kind: ErrorKind::Api,
status: ErrorStatus::Temporary,
message: message.into(),
context: Vec::new(),
source: None,
}
}
pub fn api_permanent(message: impl Into<String>) -> Self {
Self {
kind: ErrorKind::Api,
status: ErrorStatus::Permanent,
message: message.into(),
context: Vec::new(),
source: None,
}
}
pub fn parse(
message: impl Into<String>,
source: impl StdError + Send + Sync + 'static,
) -> Self {
Self {
kind: ErrorKind::Parse,
status: ErrorStatus::Permanent,
message: message.into(),
context: Vec::new(),
source: Some(Arc::new(source)),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.display_for_user())
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
None
}
}
impl From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Self {
Error::parse("parse error", e)
}
}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Error::io("IO error", e)
}
}