1use crate::ApiConnExt;
2use serde::{Deserialize, Serialize};
3use serde_json::json;
4use std::fmt::Display;
5use trillium::{async_trait, Conn, Handler, Status};
6
7#[derive(Serialize, Deserialize, Debug, Clone, thiserror::Error)]
9#[serde(tag = "type", rename_all = "snake_case")]
10pub enum Error {
11 #[error("Parse error at {path}: {message}")]
13 ParseError {
14 path: String,
16 message: String,
18 },
19 #[error("I/O error type {kind}: {message}")]
22 IoError {
23 kind: String,
25 message: String,
27 },
28 #[error("Unsupported mime type: {mime_type}")]
31 UnsupportedMimeType {
32 mime_type: String,
34 },
35 #[error("Missing content type")]
37 MissingContentType,
38 #[error("{message}")]
42 Other {
43 message: String,
45 },
46
47 #[error("No negotiated mime type")]
48 FailureToNegotiateContent,
52}
53
54impl From<serde_json::Error> for Error {
55 fn from(value: serde_json::Error) -> Self {
56 Self::ParseError {
57 path: format!("{}:{}", value.line(), value.column()),
58 message: value.to_string(),
59 }
60 }
61}
62
63impl From<trillium::Error> for Error {
64 fn from(error: trillium::Error) -> Self {
65 match error {
66 trillium::Error::Io(e) => Self::IoError {
67 kind: e.kind().to_string(),
68 message: e.to_string(),
69 },
70
71 other => Self::Other {
72 message: other.to_string(),
73 },
74 }
75 }
76}
77
78impl<E: Display> From<serde_path_to_error::Error<E>> for Error {
79 fn from(e: serde_path_to_error::Error<E>) -> Self {
80 Error::ParseError {
81 path: e.path().to_string(),
82 message: e.to_string(),
83 }
84 }
85}
86
87#[cfg(feature = "forms")]
88impl From<serde_urlencoded::ser::Error> for Error {
89 fn from(value: serde_urlencoded::ser::Error) -> Self {
90 Error::Other {
91 message: value.to_string(),
92 }
93 }
94}
95#[cfg(feature = "forms")]
96impl From<serde_urlencoded::de::Error> for Error {
97 fn from(value: serde_urlencoded::de::Error) -> Self {
98 Error::ParseError {
99 path: "".into(),
100 message: value.to_string(),
101 }
102 }
103}
104
105#[async_trait]
106impl Handler for Error {
107 async fn run(&self, conn: Conn) -> Conn {
108 conn.with_state(self.clone()).halt()
109 }
110
111 async fn before_send(&self, mut conn: Conn) -> Conn {
112 if let Some(error) = conn.take_state::<Self>() {
113 conn.with_json(&json!({ "error": &error }))
114 .with_status(&error)
115 } else {
116 conn
117 }
118 }
119}
120
121impl From<&Error> for Status {
122 fn from(value: &Error) -> Self {
123 match value {
124 Error::ParseError { .. } => Status::UnprocessableEntity,
125 Error::UnsupportedMimeType { .. } | Error::MissingContentType => {
126 Status::UnsupportedMediaType
127 }
128 Error::FailureToNegotiateContent => Status::NotAcceptable,
129 Error::IoError { .. } => Status::BadRequest,
130 _ => Status::InternalServerError,
131 }
132 }
133}
134
135#[async_trait]
136impl crate::FromConn for Error {
137 async fn from_conn(conn: &mut Conn) -> Option<Self> {
138 conn.take_state()
139 }
140}