use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Kind {
InvalidInput,
Unauthorized,
Forbidden,
NotFound,
Conflict,
Canceled,
Unavailable,
Internal,
}
impl Kind {
pub fn as_str(&self) -> &'static str {
match self {
Kind::InvalidInput => "invalid_input",
Kind::Unauthorized => "unauthorized",
Kind::Forbidden => "forbidden",
Kind::NotFound => "not_found",
Kind::Conflict => "conflict",
Kind::Canceled => "canceled",
Kind::Unavailable => "unavailable",
Kind::Internal => "internal",
}
}
}
impl fmt::Display for Kind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug)]
pub struct Error {
kind: Kind,
message: String,
cause: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
rich: Option<Box<Rich>>,
}
#[derive(Debug, Clone, Default)]
pub struct Rich {
pub code: Option<String>,
pub detail: Option<serde_json::Map<String, serde_json::Value>>,
pub status: Option<u16>,
}
impl Error {
fn base(
kind: Kind,
message: String,
cause: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
) -> Self {
Error {
kind,
message,
cause,
rich: None,
}
}
pub fn new(kind: Kind, msg: impl Into<String>) -> Self {
Error::base(kind, msg.into(), None)
}
pub fn errorf(kind: Kind, format: std::fmt::Arguments<'_>) -> Self {
Error::base(kind, fmt::format(format), None)
}
pub fn wrap(kind: Kind, cause: impl std::error::Error + Send + Sync + 'static) -> Self {
Error::base(kind, String::new(), Some(Box::new(cause)))
}
pub fn wrap_msg(
kind: Kind,
cause: impl std::error::Error + Send + Sync + 'static,
msg: impl Into<String>,
) -> Self {
Error::base(kind, msg.into(), Some(Box::new(cause)))
}
pub fn upgrade(cause: impl std::error::Error + Send + Sync + 'static) -> Self {
Error::base(Kind::Internal, String::new(), Some(Box::new(cause)))
}
fn rich_mut(&mut self) -> &mut Rich {
self.rich.get_or_insert_with(|| Box::new(Rich::default()))
}
pub fn with_code(mut self, code: impl Into<String>) -> Self {
self.rich_mut().code = Some(code.into());
self
}
pub fn with_detail(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
self.rich_mut()
.detail
.get_or_insert_with(serde_json::Map::new)
.insert(key.into(), value);
self
}
pub fn with_details(mut self, details: serde_json::Map<String, serde_json::Value>) -> Self {
self.rich_mut()
.detail
.get_or_insert_with(serde_json::Map::new)
.extend(details);
self
}
pub fn with_status(mut self, status: u16) -> Self {
self.rich_mut().status = Some(status);
self
}
pub fn inherit_rich(self, src: &(dyn std::error::Error + 'static)) -> Self {
self.inherit_values(rich_fields(src))
}
pub(crate) fn inherit_values(self, (code, detail, status): RichValues) -> Self {
if code.is_none() && detail.is_none() && status.is_none() {
return self;
}
let mut me = self;
let rich = me.rich_mut();
if rich.code.is_none() {
rich.code = code;
}
if rich.detail.is_none() {
rich.detail = detail;
}
if rich.status.is_none() {
rich.status = status;
}
me
}
pub fn kind(&self) -> Kind {
self.kind
}
pub fn status_override(&self) -> Option<u16> {
self.rich.as_ref().and_then(|r| r.status)
}
pub fn cause(&self) -> Option<&(dyn std::error::Error + Send + Sync)> {
self.cause.as_deref()
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match (&self.message.is_empty(), &self.cause) {
(false, Some(cause)) => write!(f, "{}: {}", self.message, cause),
(false, None) => f.write_str(&self.message),
(true, Some(cause)) => write!(f, "{}", cause),
(true, None) => f.write_str(self.kind.as_str()),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.cause
.as_ref()
.map(|c| c.as_ref() as &(dyn std::error::Error + 'static))
}
}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Error::wrap_msg(Kind::Internal, e, "io")
}
}
pub fn is_broken_pipe(err: &(dyn std::error::Error + 'static)) -> bool {
let mut cur: Option<&(dyn std::error::Error + 'static)> = Some(err);
while let Some(e) = cur {
if let Some(io) = e.downcast_ref::<std::io::Error>()
&& io.kind() == std::io::ErrorKind::BrokenPipe
{
return true;
}
cur = e.source();
}
false
}
pub fn classify(err: &(dyn std::error::Error + 'static)) -> Option<Kind> {
let mut cur: Option<&(dyn std::error::Error + 'static)> = Some(err);
while let Some(e) = cur {
if let Some(ce) = e.downcast_ref::<Error>() {
return Some(ce.kind);
}
cur = e.source();
}
Some(Kind::Internal)
}
pub fn http_status(kind: Kind) -> u16 {
match kind {
Kind::InvalidInput => 400,
Kind::Unauthorized => 401,
Kind::Forbidden => 403,
Kind::NotFound => 404,
Kind::Conflict => 409,
Kind::Unavailable => 503,
Kind::Canceled => 499, Kind::Internal => 500,
}
}
pub fn exit_code(kind: Kind) -> i32 {
if kind == Kind::InvalidInput {
2 } else {
1
}
}
pub fn jsonrpc_code(kind: Kind) -> i64 {
match kind {
Kind::InvalidInput => -32602, Kind::NotFound => -32001,
Kind::Conflict => -32009,
Kind::Unauthorized => -32010,
Kind::Forbidden => -32011,
Kind::Canceled => -32012,
_ => -32603, }
}
pub type Result<T> = std::result::Result<T, Error>;
pub fn from_chain<'a>(err: &'a (dyn std::error::Error + 'static)) -> Option<&'a Error> {
let mut cur: Option<&(dyn std::error::Error + 'static)> = Some(err);
while let Some(e) = cur {
if let Some(coded) = e.downcast_ref::<Error>() {
return Some(coded);
}
cur = e.source();
}
None
}
pub(crate) type RichValues = (
Option<String>,
Option<serde_json::Map<String, serde_json::Value>>,
Option<u16>,
);
pub(crate) fn rich_fields(err: &(dyn std::error::Error + 'static)) -> RichValues {
let mut cur: Option<&(dyn std::error::Error + 'static)> = Some(err);
let (mut code, mut detail, mut status) = (None, None, None);
while let Some(e) = cur {
if let Some(ce) = e.downcast_ref::<Error>()
&& let Some(r) = &ce.rich
{
if code.is_none() {
code = r.code.clone();
}
if detail.is_none() {
detail = r.detail.clone();
}
if status.is_none() {
status = r.status;
}
}
cur = e.source();
}
(code, detail, status)
}
fn deepest_message(err: &(dyn std::error::Error + 'static)) -> String {
let mut cur: &(dyn std::error::Error + 'static) = err;
while let Some(src) = cur.source() {
cur = src;
}
cur.to_string()
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct Body {
pub error: String,
pub kind: Kind,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub detail: Option<serde_json::Map<String, serde_json::Value>>,
}
impl Default for Body {
fn default() -> Self {
Body {
error: String::new(),
kind: Kind::Internal,
code: None,
detail: None,
}
}
}
pub fn error_body(err: &(dyn std::error::Error + 'static)) -> Body {
let mut b = Body {
error: deepest_message(err),
kind: classify(err).unwrap_or(Kind::Internal),
code: None,
detail: None,
};
let (code, detail, _) = rich_fields(err);
b.code = code;
b.detail = detail;
b
}
pub fn error_status(err: &(dyn std::error::Error + 'static)) -> u16 {
let (_, _, status) = rich_fields(err);
if let Some(s) = status {
return s;
}
http_status(classify(err).unwrap_or(Kind::Internal))
}
pub fn new(kind: Kind, msg: impl Into<String>) -> Error {
Error::new(kind, msg)
}
#[macro_export]
macro_rules! errorf {
($kind:expr, $($arg:tt)*) => {
$crate::errors::Error::errorf($kind, format_args!($($arg)*))
};
}
pub use errorf;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mappings() {
assert_eq!(http_status(Kind::InvalidInput), 400);
assert_eq!(http_status(Kind::Unauthorized), 401);
assert_eq!(http_status(Kind::Forbidden), 403);
assert_eq!(http_status(Kind::NotFound), 404);
assert_eq!(http_status(Kind::Conflict), 409);
assert_eq!(http_status(Kind::Unavailable), 503);
assert_eq!(http_status(Kind::Canceled), 499);
assert_eq!(http_status(Kind::Internal), 500);
assert_eq!(exit_code(Kind::InvalidInput), 2);
assert_eq!(exit_code(Kind::NotFound), 1);
assert_eq!(jsonrpc_code(Kind::InvalidInput), -32602);
assert_eq!(jsonrpc_code(Kind::NotFound), -32001);
assert_eq!(jsonrpc_code(Kind::Conflict), -32009);
assert_eq!(jsonrpc_code(Kind::Unauthorized), -32010);
assert_eq!(jsonrpc_code(Kind::Forbidden), -32011);
assert_eq!(jsonrpc_code(Kind::Canceled), -32012);
assert_eq!(jsonrpc_code(Kind::Internal), -32603);
}
#[test]
fn classify_chain() {
let e = Error::wrap_msg(Kind::NotFound, Error::new(Kind::Conflict, "inner"), "outer");
assert_eq!(classify(&e), Some(Kind::NotFound));
let cause = std::error::Error::source(&e).unwrap();
assert_eq!(classify(cause), Some(Kind::Conflict));
}
#[test]
fn error_display_forms() {
assert_eq!(Error::new(Kind::NotFound, "x").to_string(), "x");
let wrapped = Error::wrap(Kind::InvalidInput, std::io::Error::other("ioerr"));
assert_eq!(wrapped.to_string(), "ioerr");
let both = Error::wrap_msg(Kind::Internal, std::io::Error::other("ioerr"), "boom");
assert_eq!(both.to_string(), "boom: ioerr");
}
}