use std::fmt::{self, Display};
use std::io;
use std::num;
use actix_web::{
dev::ServiceResponse, http::StatusCode, middleware::ErrorHandlerResponse, HttpResponse,
HttpResponseBuilder, ResponseError,
};
use backtrace::Backtrace;
use serde::ser::{Serialize, SerializeMap, Serializer};
use thiserror::Error;
pub type Result<T> = std::result::Result<T, ApcError>;
pub fn render_404<B>(
res: ServiceResponse<B>,
) -> std::result::Result<ErrorHandlerResponse<B>, actix_web::Error> {
let resp = HttpResponseBuilder::new(StatusCode::NOT_FOUND).finish();
Ok(ErrorHandlerResponse::Response(
res.into_response(resp).map_into_right_body(),
))
}
#[derive(Debug)]
pub struct ApcError {
pub kind: ApcErrorKind,
pub backtrace: Box<Backtrace>,
}
impl Display for ApcError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.kind.fmt(f)
}
}
impl std::error::Error for ApcError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.kind.source()
}
}
impl<T> From<T> for ApcError
where
ApcErrorKind: From<T>,
{
fn from(item: T) -> Self {
ApcError {
kind: ApcErrorKind::from(item),
backtrace: Box::new(Backtrace::new()), }
}
}
impl ResponseError for ApcError {
fn status_code(&self) -> StatusCode {
self.kind.status()
}
fn error_response(&self) -> HttpResponse {
let mut builder = HttpResponse::build(self.status_code());
builder.json(self)
}
}
impl Serialize for ApcError {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
let status = self.kind.status();
let mut map = serializer.serialize_map(Some(5))?;
map.serialize_entry("code", &status.as_u16())?;
map.serialize_entry("error", &status.canonical_reason())?;
map.serialize_entry("message", &self.kind.to_string())?;
map.end()
}
}
#[derive(Error, Debug)]
pub enum ApcErrorKind {
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
UuidError(#[from] uuid::Error),
#[error(transparent)]
ParseIntError(#[from] num::ParseIntError),
#[error(transparent)]
ParseUrlError(#[from] url::ParseError),
#[error(transparent)]
ConfigError(#[from] config::ConfigError),
#[error("Broadcast Error: {0}")]
BroadcastError(String),
#[error("Payload Error: {0}")]
PayloadError(String),
#[error("General Error: {0}")]
GeneralError(String),
}
impl ApcErrorKind {
pub fn status(&self) -> StatusCode {
match self {
Self::ParseIntError(_) | Self::ParseUrlError(_) => StatusCode::BAD_REQUEST,
_ => StatusCode::INTERNAL_SERVER_ERROR,
}
}
pub fn is_sentry_event(&self) -> bool {
match self {
Self::PayloadError(_) => false,
_ => true,
}
}
pub fn metric_label(&self) -> Option<&'static str> {
match self {
Self::PayloadError(_) => Some("payload"),
_ => None,
}
}
}
pub trait ReportableError: std::error::Error {
fn reportable_source(&self) -> Option<&(dyn ReportableError + 'static)> {
None
}
fn backtrace(&self) -> Option<&Backtrace> {
None
}
fn is_sentry_event(&self) -> bool {
true
}
fn metric_label(&self) -> Option<&'static str> {
None
}
fn tags(&self) -> Vec<(&str, String)> {
vec![]
}
fn extras(&self) -> Vec<(&str, String)> {
vec![]
}
}
impl ReportableError for ApcError {
fn backtrace(&self) -> Option<&Backtrace> {
Some(&self.backtrace)
}
fn is_sentry_event(&self) -> bool {
self.kind.is_sentry_event()
}
fn metric_label(&self) -> Option<&'static str> {
self.kind.metric_label()
}
}