use actix_web::http::StatusCode;
use actix_web::{HttpResponse, ResponseError};
use log::error;
use serde::{Deserialize, Serialize};
#[cfg(feature = "sqlx")]
use sqlx::Error as SqlxError;
use std::collections::HashMap;
use validator::{ValidationError, ValidationErrors};
#[derive(Debug, Serialize)]
pub struct InternalErrorPayload {
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<&'static str>,
pub error: &'static str,
}
impl InternalErrorPayload {
pub fn init(error: &'static str) -> Self {
Self {
code: None,
error,
}
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct ValidationErrorPayload {
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
pub error: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub field_errors: Option<HashMap<String, Vec<ValidationError>>>,
}
impl ValidationErrorPayload {
pub fn new(detail: String) -> Self {
ValidationErrorPayload {
code: None,
error: detail,
field_errors: None,
}
}
pub fn with_code(code_error: String, detail: String) -> Self {
ValidationErrorPayload {
code: Some(code_error),
error: detail,
field_errors: None,
}
}
}
impl From<&ValidationErrors> for ValidationErrorPayload {
fn from(error: &ValidationErrors) -> Self {
let mut errors: HashMap<String, Vec<ValidationError>> = HashMap::new();
errors.extend(
error
.field_errors()
.iter()
.map(|(k, v)| (String::from(k.clone()), (*v).clone())),
);
ValidationErrorPayload {
code: Some("validation_error".to_owned()),
error: if errors.len() > 1 { "Validations error".to_owned() } else { "Validation error".to_owned() },
field_errors: Some(errors),
}
}
}
#[derive(thiserror::Error, Debug)]
pub enum AppError {
#[error("{0}")]
StaticValidation(&'static str),
#[error("{1}")]
Validation(Option<&'static str>, String),
#[cfg(feature = "sqlx")]
#[error(transparent)]
DB(#[from] SqlxError),
#[error("{resource} with {attribute} equals to \"{value}\" not found or was removed")]
ResourceNotFound {
resource: &'static str,
attribute: &'static str,
value: String,
},
#[error("{resource} with {attribute} \"{value}\" already exists")]
ResourceAlreadyExists {
resource: &'static str,
attribute: &'static str,
value: String,
},
#[error("{0}")]
Unauthorized(&'static str),
#[error("{0}")]
Forbidden(&'static str),
#[error(transparent)]
Unexpected(#[from] anyhow::Error),
}
impl ResponseError for AppError {
fn status_code(&self) -> StatusCode {
match self {
Self::StaticValidation(_) | Self::Validation(_, _) => StatusCode::BAD_REQUEST,
Self::ResourceAlreadyExists { resource: _, attribute: _, value: _ } => StatusCode::BAD_REQUEST,
Self::ResourceNotFound { resource: _, attribute: _, value: _ } => StatusCode::NOT_FOUND,
Self::Unauthorized(_) => StatusCode::UNAUTHORIZED,
Self::Forbidden(_) => StatusCode::FORBIDDEN,
Self::Unexpected(_) => StatusCode::INTERNAL_SERVER_ERROR,
#[cfg(feature = "sqlx")]
Self::DB(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn error_response(&self) -> HttpResponse {
let status_code = self.status_code();
match self {
Self::Validation(code, error) => {
match code {
None => HttpResponse::build(status_code)
.json(ValidationErrorPayload::new(error.to_owned())),
Some(c) =>
HttpResponse::build(status_code)
.json(ValidationErrorPayload::with_code(c.to_string(), error.to_owned())),
}
}
Self::StaticValidation(error)
| Self::Unauthorized(error) | Self::Forbidden(error) => {
HttpResponse::build(status_code)
.json(InternalErrorPayload::init(error))
}
Self::ResourceNotFound { resource: _, attribute: _, value: _ } => {
HttpResponse::build(status_code)
.json(ValidationErrorPayload::with_code(
"not_found".to_string(),
self.to_string(),
))
}
Self::ResourceAlreadyExists { resource: _, attribute: _, value: _ } => {
HttpResponse::build(status_code)
.json(ValidationErrorPayload::with_code(
"already_exists".to_string(),
self.to_string(),
))
}
_ => {
HttpResponse::build(status_code)
.json(InternalErrorPayload::init(
status_code.canonical_reason().unwrap_or("Unknown error")
))
}
}
}
}
pub type Result<T> = core::result::Result<T, AppError>;
pub type HttpResult = Result<HttpResponse>;
#[derive(Debug, Deserialize, Serialize)]
pub struct DeletedCount {
pub deleted: u64,
}