use crate::ApiConnExt;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::fmt::Display;
use trillium::{async_trait, Conn, Handler, Status};
#[derive(Serialize, Deserialize, Debug, Clone, thiserror::Error)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Error {
#[error("Parse error at {path}: {message}")]
ParseError {
path: String,
message: String,
},
#[error("I/O error type {kind}: {message}")]
IoError {
kind: String,
message: String,
},
#[error("Unsupported mime type: {mime_type}")]
UnsupportedMimeType {
mime_type: String,
},
#[error("Missing content type")]
MissingContentType,
#[error("{message}")]
Other {
message: String,
},
#[error("No negotiated mime type")]
FailureToNegotiateContent,
}
impl From<serde_json::Error> for Error {
fn from(value: serde_json::Error) -> Self {
Self::ParseError {
path: format!("{}:{}", value.line(), value.column()),
message: value.to_string(),
}
}
}
impl From<trillium::Error> for Error {
fn from(error: trillium::Error) -> Self {
match error {
trillium::Error::Io(e) => Self::IoError {
kind: e.kind().to_string(),
message: e.to_string(),
},
other => Self::Other {
message: other.to_string(),
},
}
}
}
impl<E: Display> From<serde_path_to_error::Error<E>> for Error {
fn from(e: serde_path_to_error::Error<E>) -> Self {
Error::ParseError {
path: e.path().to_string(),
message: e.to_string(),
}
}
}
#[cfg(feature = "forms")]
impl From<serde_urlencoded::ser::Error> for Error {
fn from(value: serde_urlencoded::ser::Error) -> Self {
Error::Other {
message: value.to_string(),
}
}
}
#[cfg(feature = "forms")]
impl From<serde_urlencoded::de::Error> for Error {
fn from(value: serde_urlencoded::de::Error) -> Self {
Error::ParseError {
path: "".into(),
message: value.to_string(),
}
}
}
#[async_trait]
impl Handler for Error {
async fn run(&self, conn: Conn) -> Conn {
conn.with_state(self.clone()).halt()
}
async fn before_send(&self, mut conn: Conn) -> Conn {
if let Some(error) = conn.take_state::<Self>() {
conn.with_json(&json!({ "error": &error }))
.with_status(&error)
} else {
conn
}
}
}
impl From<&Error> for Status {
fn from(value: &Error) -> Self {
match value {
Error::ParseError { .. } => Status::UnprocessableEntity,
Error::UnsupportedMimeType { .. } | Error::MissingContentType => {
Status::UnsupportedMediaType
}
Error::FailureToNegotiateContent => Status::NotAcceptable,
Error::IoError { .. } => Status::BadRequest,
_ => Status::InternalServerError,
}
}
}
#[async_trait]
impl crate::FromConn for Error {
async fn from_conn(conn: &mut Conn) -> Option<Self> {
conn.take_state()
}
}