use crate::types::MurRes;
use http::StatusCode;
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::Response;
#[derive(Debug)]
pub enum MurError {
Hyper(hyper::Error),
Serde(String),
NotFound(String),
Unauthorized(String),
Forbidden(String),
BadRequest(String),
Internal(String),
NoEnv(&'static str),
Custom(StatusCode, String),
}
impl std::fmt::Display for MurError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MurError::Hyper(e) => write!(f, "HTTP error: {}", e),
MurError::Serde(e) => write!(f, "Serialization error: {}", e),
MurError::NotFound(e) => write!(f, "Not found: {}", e),
MurError::Unauthorized(e) => write!(f, "Unauthorized: {}", e),
MurError::Forbidden(e) => write!(f, "Forbidden: {}", e),
MurError::BadRequest(e) => write!(f, "Bad request: {}", e),
MurError::Internal(e) => write!(f, "Internal error: {}", e),
MurError::NoEnv(e) => write!(f, "No Environment Internal error: {}", e),
MurError::Custom(status, e) => write!(f, "Error {}: {}", status.as_u16(), e),
}
}
}
impl std::error::Error for MurError {}
impl From<hyper::Error> for MurError {
fn from(err: hyper::Error) -> Self {
MurError::Hyper(err)
}
}
impl From<serde_json::Error> for MurError {
fn from(err: serde_json::Error) -> Self {
MurError::Serde(err.to_string())
}
}
impl From<std::io::Error> for MurError {
fn from(err: std::io::Error) -> Self {
MurError::Internal(err.to_string())
}
}
impl From<std::string::FromUtf8Error> for MurError {
fn from(err: std::string::FromUtf8Error) -> Self {
MurError::BadRequest(format!("Invalid UTF-8: {}", err))
}
}
impl From<String> for MurError {
fn from(msg: String) -> Self {
MurError::Internal(msg)
}
}
impl From<&str> for MurError {
fn from(msg: &str) -> Self {
MurError::Internal(msg.to_string())
}
}
impl MurError {
pub fn status_code(&self) -> StatusCode {
match self {
MurError::Hyper(_) => StatusCode::INTERNAL_SERVER_ERROR,
MurError::Serde(_) => StatusCode::BAD_REQUEST,
MurError::NotFound(_) => StatusCode::NOT_FOUND,
MurError::Unauthorized(_) => StatusCode::UNAUTHORIZED,
MurError::Forbidden(_) => StatusCode::FORBIDDEN,
MurError::BadRequest(_) => StatusCode::BAD_REQUEST,
MurError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
MurError::NoEnv(_) => StatusCode::INTERNAL_SERVER_ERROR,
MurError::Custom(status, _) => *status,
}
}
pub fn message(&self) -> &str {
match self {
MurError::Hyper(_) => "HTTP transport error",
MurError::Serde(msg) => msg,
MurError::NotFound(msg) => msg,
MurError::Unauthorized(msg) => msg,
MurError::Forbidden(msg) => msg,
MurError::BadRequest(msg) => msg,
MurError::Internal(msg) => msg,
MurError::NoEnv(msg) => msg,
MurError::Custom(_, msg) => msg,
}
}
pub fn kind(&self) -> &'static str {
match self {
MurError::Hyper(_) => "hyper",
MurError::Serde(_) => "serde",
MurError::NotFound(_) => "not_found",
MurError::Unauthorized(_) => "unauthorized",
MurError::Forbidden(_) => "forbidden",
MurError::BadRequest(_) => "bad_request",
MurError::Internal(_) => "internal",
MurError::NoEnv(_) => "internal",
MurError::Custom(_, _) => "custom",
}
}
pub fn not_found(msg: impl Into<String>) -> Self {
MurError::NotFound(msg.into())
}
pub fn bad_request(msg: impl Into<String>) -> Self {
MurError::BadRequest(msg.into())
}
pub fn unauthorized(msg: impl Into<String>) -> Self {
MurError::Unauthorized(msg.into())
}
pub fn forbidden(msg: impl Into<String>) -> Self {
MurError::Forbidden(msg.into())
}
pub fn internal(msg: impl Into<String>) -> Self {
MurError::Internal(msg.into())
}
pub fn validation(msg: impl Into<String>) -> Self {
MurError::BadRequest(msg.into())
}
pub fn custom(status: StatusCode, msg: impl Into<String>) -> Self {
MurError::Custom(status, msg.into())
}
pub fn conflict(msg: impl Into<String>) -> Self {
MurError::Custom(StatusCode::CONFLICT, msg.into())
}
pub fn gone(msg: impl Into<String>) -> Self {
MurError::Custom(StatusCode::GONE, msg.into())
}
pub fn unprocessable(msg: impl Into<String>) -> Self {
MurError::Custom(StatusCode::UNPROCESSABLE_ENTITY, msg.into())
}
pub fn too_many_requests(msg: impl Into<String>) -> Self {
MurError::Custom(StatusCode::TOO_MANY_REQUESTS, msg.into())
}
pub fn service_unavailable(msg: impl Into<String>) -> Self {
MurError::Custom(StatusCode::SERVICE_UNAVAILABLE, msg.into())
}
pub fn into_response(self) -> MurRes {
let status = self.status_code();
let kind = self.kind();
let message = self.to_string();
let body = serde_json::json!({
"error": message,
"status": status.as_u16(),
"kind": kind
});
Ok(Response::builder()
.status(status)
.header("Content-Type", "application/json")
.body(Full::new(Bytes::from(
serde_json::to_string(&body).unwrap_or_else(|_| {
format!(r#"{{"error":"{}","status":{}}}"#, message, status.as_u16())
}),
)))
.unwrap())
}
pub fn into_response_with_context(self, context: serde_json::Value) -> MurRes {
let status = self.status_code();
let kind = self.kind();
let message = self.to_string();
let body = serde_json::json!({
"error": message,
"status": status.as_u16(),
"kind": kind,
"context": context
});
Ok(Response::builder()
.status(status)
.header("Content-Type", "application/json")
.body(Full::new(Bytes::from(
serde_json::to_string(&body).unwrap_or_else(|_| {
format!(r#"{{"error":"{}","status":{}}}"#, message, status.as_u16())
}),
)))
.unwrap())
}
}
pub type MurResult<T> = Result<T, MurError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_status_codes() {
assert_eq!(MurError::not_found("").status_code(), StatusCode::NOT_FOUND);
assert_eq!(
MurError::bad_request("").status_code(),
StatusCode::BAD_REQUEST
);
assert_eq!(
MurError::unauthorized("").status_code(),
StatusCode::UNAUTHORIZED
);
assert_eq!(MurError::forbidden("").status_code(), StatusCode::FORBIDDEN);
assert_eq!(
MurError::internal("").status_code(),
StatusCode::INTERNAL_SERVER_ERROR
);
assert_eq!(MurError::conflict("").status_code(), StatusCode::CONFLICT);
}
#[test]
fn test_error_display() {
let err = MurError::not_found("User not found");
assert_eq!(err.to_string(), "Not found: User not found");
let err = MurError::bad_request("Invalid input");
assert_eq!(err.to_string(), "Bad request: Invalid input");
}
#[test]
fn test_error_kind() {
assert_eq!(MurError::not_found("").kind(), "not_found");
assert_eq!(MurError::bad_request("").kind(), "bad_request");
assert_eq!(MurError::internal("").kind(), "internal");
}
#[test]
fn test_from_string() {
let err: MurError = "Something went wrong".into();
assert_eq!(err.status_code(), StatusCode::INTERNAL_SERVER_ERROR);
}
}