mod code;
use exn::Exn;
use tracing_error::SpanTrace;
pub use self::code::{ErrorCode, OwnedErrorCode};
#[derive(Debug)]
pub struct Disconnected;
impl std::fmt::Display for Disconnected {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Repo is disconnected")
}
}
impl std::error::Error for Disconnected {}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Status {
Size,
Server,
Client,
RangeNotSatisfiable,
Forbidden,
Unauthorized,
NotFound,
}
pub struct ApplicationError<E>
where
E: std::error::Error + Send + Sync + 'static,
{
status: Option<Status>,
code: Option<ErrorCode>,
spantrace: SpanTrace,
exn: Exn<E>,
}
impl<E> std::fmt::Debug for ApplicationError<E>
where
E: std::error::Error + Send + Sync + 'static,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Error: ")?;
std::fmt::Display::fmt(&self.root_cause(), f)?;
f.write_str("\nCode: ")?;
std::fmt::Display::fmt(&self.get_error_code(), f)?;
f.write_str("\n\n")?;
std::fmt::Debug::fmt(&self.exn, f)?;
f.write_str("\n\n")?;
std::fmt::Display::fmt(&color_spantrace::colorize(&self.spantrace), f)?;
f.write_str("\n")
}
}
pub trait ApplicationResultExt<T, E>
where
E: std::error::Error + Send + Sync + 'static,
{
fn or_raise<F, G>(self, err: F) -> Result<T, ApplicationError<G>>
where
F: FnOnce() -> G,
G: std::error::Error + Send + Sync + 'static;
fn or_code(self, code: ErrorCode) -> Result<T, ApplicationError<E>>;
fn or_status(self, status: Status) -> Result<T, ApplicationError<E>>;
}
impl<T, E> ApplicationResultExt<T, E> for Result<T, ApplicationError<E>>
where
E: std::error::Error + Send + Sync + 'static,
{
#[track_caller]
fn or_raise<F, G>(self, err: F) -> Result<T, ApplicationError<G>>
where
F: FnOnce() -> G,
G: std::error::Error + Send + Sync + 'static,
{
match self {
Ok(t) => Ok(t),
Err(e) => Err(e.raise(err())),
}
}
fn or_code(self, code: ErrorCode) -> Result<T, ApplicationError<E>> {
self.map_err(|e| e.error_code(code))
}
fn or_status(self, status: Status) -> Result<T, ApplicationError<E>> {
self.map_err(|e| e.status(status))
}
}
impl<T, E> ApplicationResultExt<T, E> for Result<T, E>
where
E: std::error::Error + Send + Sync + 'static,
{
#[track_caller]
fn or_raise<F, G>(self, err: F) -> Result<T, ApplicationError<G>>
where
F: FnOnce() -> G,
G: std::error::Error + Send + Sync + 'static,
{
match self {
Ok(t) => Ok(t),
Err(e) => Err(ApplicationError::new(e).raise(err())),
}
}
#[track_caller]
fn or_code(self, code: ErrorCode) -> Result<T, ApplicationError<E>> {
match self {
Ok(t) => Ok(t),
Err(e) => Err(ApplicationError::new(e).error_code(code)),
}
}
#[track_caller]
fn or_status(self, status: Status) -> Result<T, ApplicationError<E>> {
match self {
Ok(t) => Ok(t),
Err(e) => Err(ApplicationError::new(e).status(status)),
}
}
}
impl<T, E> ApplicationResultExt<T, E> for Result<T, Exn<E>>
where
E: std::error::Error + Send + Sync + 'static,
{
#[track_caller]
fn or_raise<F, G>(self, err: F) -> Result<T, ApplicationError<G>>
where
F: FnOnce() -> G,
G: std::error::Error + Send + Sync + 'static,
{
match self {
Ok(t) => Ok(t),
Err(e) => Err(ApplicationError::wrap(e).raise(err())),
}
}
fn or_code(self, code: ErrorCode) -> Result<T, ApplicationError<E>> {
match self {
Ok(t) => Ok(t),
Err(e) => Err(ApplicationError::wrap(e).error_code(code)),
}
}
fn or_status(self, status: Status) -> Result<T, ApplicationError<E>> {
match self {
Ok(t) => Ok(t),
Err(e) => Err(ApplicationError::wrap(e).status(status)),
}
}
}
impl<E> std::fmt::Display for ApplicationError<E>
where
E: std::error::Error + Send + Sync + 'static,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&*self.exn, f)
}
}
#[derive(Debug)]
pub struct RunError;
impl std::fmt::Display for RunError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Failed to run pict-rs")
}
}
impl std::error::Error for RunError {}
#[derive(Debug)]
pub struct PictRsError {
error: ApplicationError<RunError>,
}
impl std::fmt::Display for PictRsError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Error in pict-rs")
}
}
impl std::error::Error for PictRsError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(self.error.error())
}
}
impl PictRsError {
pub fn frame(&self) -> &exn::Frame {
self.error.frame()
}
}
impl From<ApplicationError<RunError>> for PictRsError {
fn from(value: ApplicationError<RunError>) -> Self {
Self { error: value }
}
}
impl From<std::io::Error> for PictRsError {
fn from(value: std::io::Error) -> Self {
ApplicationError::new(value).raise(RunError).into()
}
}
#[derive(Debug)]
struct IoError;
impl std::fmt::Display for IoError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("IO Error")
}
}
impl std::error::Error for IoError {}
impl std::error::Error for ApplicationError<IoError> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(self.error() as _)
}
}
impl<E> ApplicationError<E>
where
E: std::error::Error + Send + Sync + 'static,
{
#[track_caller]
pub fn new(error: E) -> Self {
Self {
status: None,
code: None,
spantrace: SpanTrace::capture(),
exn: Exn::new(error),
}
}
#[track_caller]
pub fn wrap(exn: Exn<E>) -> Self {
Self {
status: None,
code: None,
spantrace: SpanTrace::capture(),
exn,
}
}
pub fn status(self, status: Status) -> Self {
Self {
status: Some(status),
..self
}
}
pub fn get_status(&self) -> Status {
if let Some(status) = self.status {
status
} else if let Some(io_error) = self.find_io_error() {
io_error.get_status()
} else {
Status::Server
}
}
pub fn get_error_code(&self) -> ErrorCode {
if let Some(code) = self.code {
code
} else if let Some(io_error) = self.find_io_error() {
io_error.get_error_code()
} else {
ErrorCode::UNKNOWN_ERROR
}
}
pub fn error_code(self, code: ErrorCode) -> Self {
Self {
code: Some(code),
..self
}
}
pub fn io_error(self) -> std::io::Error {
fn walk_chain(mut error: &(dyn std::error::Error + 'static)) -> Option<std::io::ErrorKind> {
loop {
if let Some(e) = error.downcast_ref::<std::io::Error>() {
return Some(e.kind());
}
if let Some(src) = error.source() {
error = src
} else {
return None;
}
}
}
fn walk_tree(frame: &exn::Frame) -> Option<std::io::ErrorKind> {
if let Some(kind) = walk_chain(frame.error()) {
Some(kind)
} else {
frame.children().iter().find_map(walk_tree)
}
}
let kind = walk_tree(self.exn.frame()).unwrap_or(std::io::ErrorKind::Other);
std::io::Error::new(kind, self.raise(IoError))
}
#[track_caller]
pub fn raise<G>(self, error: G) -> ApplicationError<G>
where
G: std::error::Error + Send + Sync + 'static,
{
let ApplicationError {
status,
code,
spantrace,
exn,
} = self;
ApplicationError {
status,
code,
spantrace,
exn: exn.raise(error),
}
}
pub fn root_cause(&self) -> &(dyn std::error::Error + 'static) {
fn get_first_root(mut frame: &exn::Frame) -> &(dyn std::error::Error + 'static) {
loop {
if frame.children().is_empty() {
if let Some(e) = frame.error().downcast_ref::<ApplicationError<IoError>>() {
return get_first_root(e.frame());
} else {
return frame.error();
}
} else {
if let Some(e) = frame.error().downcast_ref::<ApplicationError<IoError>>() {
return get_first_root(e.frame());
}
frame = &frame.children()[0]
}
}
}
if let Some(io_error) = self.find_io_error() {
io_error.root_cause()
} else {
get_first_root(self.exn.frame())
}
}
fn find_io_error(&self) -> Option<&ApplicationError<IoError>> {
fn walk_chain<'a>(
mut error: &'a (dyn std::error::Error + 'static),
) -> Option<&'a ApplicationError<IoError>> {
loop {
if let Some(std_io_error) = error.downcast_ref::<std::io::Error>()
&& let Some(io_error) = std_io_error
.get_ref()
.and_then(|e| e.downcast_ref::<ApplicationError<IoError>>())
{
return Some(io_error);
}
if let Some(src) = error.source() {
error = src
} else {
return None;
}
}
}
fn walk_tree(frame: &exn::Frame) -> Option<&ApplicationError<IoError>> {
if let Some(e) = walk_chain(frame.error()) {
Some(e)
} else {
frame.children().iter().find_map(walk_tree)
}
}
walk_chain(&*self.exn).or_else(|| walk_tree(self.exn.frame()))
}
pub fn is_not_found(&self) -> bool {
matches!(self.get_status(), Status::NotFound)
}
pub fn error(&self) -> &E {
&self.exn
}
pub fn frame(&self) -> &exn::Frame {
self.exn.frame()
}
pub fn is_disconnected(&self) -> bool {
fn walk_chain(mut error: &(dyn std::error::Error + 'static)) -> bool {
loop {
if error.is::<Disconnected>() {
return true;
}
if let Some(e) = error.downcast_ref::<ApplicationError<IoError>>()
&& walk_tree(e.frame())
{
return true;
}
if let Some(src) = error.source() {
error = src
} else {
return false;
}
}
}
fn walk_tree(frame: &exn::Frame) -> bool {
if walk_chain(frame.error()) {
true
} else {
frame.children().iter().any(walk_tree)
}
}
walk_tree(self.exn.frame())
}
}
#[test]
fn finds_io_error() {
#[derive(Debug)]
struct RootCause;
impl std::fmt::Display for RootCause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Root Cause")
}
}
impl std::error::Error for RootCause {}
let root_error = ApplicationError::new(RootCause);
let io_error = root_error.io_error();
let outer_error = ApplicationError::new(io_error);
outer_error.find_io_error().expect("IO Error Found");
}
#[test]
fn retrieves_nested_root_cause() {
#[derive(Debug)]
struct RootCause;
impl std::fmt::Display for RootCause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Root Cause")
}
}
impl std::error::Error for RootCause {}
let root_error = ApplicationError::new(RootCause);
let io_error = root_error.io_error();
let outer_error = ApplicationError::new(io_error);
assert_eq!(outer_error.root_cause().to_string(), RootCause.to_string());
}
#[test]
fn retrieves_nested_code() {
#[derive(Debug)]
struct RootCause;
impl std::fmt::Display for RootCause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Root Cause")
}
}
impl std::error::Error for RootCause {}
let root_error = ApplicationError::new(RootCause).error_code(ErrorCode::PANIC);
let io_error = root_error.io_error();
let outer_error = ApplicationError::new(io_error);
assert_eq!(outer_error.get_error_code(), ErrorCode::PANIC);
}
#[test]
fn prefers_outer_code() {
#[derive(Debug)]
struct RootCause;
impl std::fmt::Display for RootCause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Root Cause")
}
}
impl std::error::Error for RootCause {}
let root_error = ApplicationError::new(RootCause).error_code(ErrorCode::PANIC);
let io_error = root_error.io_error();
let outer_error = ApplicationError::new(io_error).error_code(ErrorCode::PUSH_JOB);
assert_eq!(outer_error.get_error_code(), ErrorCode::PUSH_JOB);
}
#[test]
fn retrieves_nested_status() {
#[derive(Debug)]
struct RootCause;
impl std::fmt::Display for RootCause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Root Cause")
}
}
impl std::error::Error for RootCause {}
let root_error = ApplicationError::new(RootCause).status(Status::Client);
let io_error = root_error.io_error();
let outer_error = ApplicationError::new(io_error);
assert_eq!(outer_error.get_status(), Status::Client);
}
#[test]
fn prefers_outer_status() {
#[derive(Debug)]
struct RootCause;
impl std::fmt::Display for RootCause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Root Cause")
}
}
impl std::error::Error for RootCause {}
let root_error = ApplicationError::new(RootCause).status(Status::Client);
let io_error = root_error.io_error();
let outer_error = ApplicationError::new(io_error).status(Status::Size);
assert_eq!(outer_error.get_status(), Status::Size);
}