use serde::{Deserialize, Serialize}; use serde_json; use std::{ convert::From, fmt::{self, Display}, }; use validator::ValidationErrors; use warp::{http::StatusCode, reject::Reject}; #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub struct Error { pub code: u16, pub message: String, /// Sneedstr version. #[serde(skip_serializing_if = "Option::is_none")] pub sneedstr_version: Option, } impl Error { pub fn new(code: StatusCode, message: String) -> Self { Self { code: code.as_u16(), message, sneedstr_version: None, } } pub fn from_anyhow_error(code: StatusCode, err: anyhow::Error) -> Self { Self::new(code, err.to_string()) } pub fn bad_request(msg: S) -> Self { Self::new(StatusCode::BAD_REQUEST, msg.to_string()) } pub fn internal_with_message(msg: S) -> Self { Self::new(StatusCode::INTERNAL_SERVER_ERROR, msg.to_string()) } pub fn validation_error(msg: ValidationErrors) -> Self { let message = serde_json::to_string(&msg.field_errors()).unwrap_or("Validation Error".to_string()); Self::new(StatusCode::BAD_REQUEST, message) } pub fn not_found(resource: &str, identifier: S, service_version: u16) -> Self { Self::new( StatusCode::NOT_FOUND, format!("{} not found by {}", resource, identifier), ) .sneedstr_version(service_version) } pub fn invalid_param(name: &str, value: S) -> Self { Self::bad_request(format!("invalid parameter {}: {}", name, value)) } pub fn invalid_request_body(msg: S) -> Self { Self::bad_request(format!("invalid request body: {}", msg)) } pub fn internal(err: anyhow::Error) -> Self { Self::from_anyhow_error(StatusCode::INTERNAL_SERVER_ERROR, err) } pub fn status_code(&self) -> StatusCode { StatusCode::from_u16(self.code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR) } pub fn sneedstr_version(mut self, service_version: u16) -> Self { self.sneedstr_version = Some(service_version); self } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}: {}", self.status_code(), &self.message)?; if let Some(val) = &self.sneedstr_version { write!(f, "\ndiem ledger version: {}", val)?; } Ok(()) } } impl Reject for Error {} impl From for Error { fn from(e: anyhow::Error) -> Self { Self::internal(e) } } impl From for Error { fn from(err: serde_json::error::Error) -> Self { Self::internal(err.into()) } } #[cfg(test)] mod tests { use super::Error; use warp::http::StatusCode; #[test] fn test_to_string() { let err = Error::new(StatusCode::BAD_REQUEST, "invalid address".to_owned()); assert_eq!(err.to_string(), "400 Bad Request: invalid address") } #[test] fn test_from_anyhow_error_as_internal_error() { let err = Error::from(anyhow::format_err!("hello")); assert_eq!(err.to_string(), "500 Internal Server Error: hello") } #[test] fn test_to_string_with_sneedstr_version() { let err = Error::new(StatusCode::BAD_REQUEST, "invalid address".to_owned()).sneedstr_version(123); assert_eq!( err.to_string(), "400 Bad Request: invalid address\ndiem ledger version: 123" ) } #[test] fn test_internal_error() { let err = Error::internal(anyhow::format_err!("hello")); assert_eq!(err.to_string(), "500 Internal Server Error: hello") } }