sneedstr/src/utils/error.rs
2024-01-12 09:35:31 -06:00

133 lines
3.7 KiB
Rust

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<u16>,
}
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<S: Display>(msg: S) -> Self {
Self::new(StatusCode::BAD_REQUEST, msg.to_string())
}
pub fn internal_with_message<S: Display>(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<S: Display>(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<S: Display>(name: &str, value: S) -> Self {
Self::bad_request(format!("invalid parameter {}: {}", name, value))
}
pub fn invalid_request_body<S: Display>(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<anyhow::Error> for Error {
fn from(e: anyhow::Error) -> Self {
Self::internal(e)
}
}
impl From<serde_json::error::Error> 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")
}
}