1#[cfg(any(feature = "serde_json", feature = "sonic-rs"))]
2use crate::ApiConnExt;
3use serde::{Deserialize, Serialize};
4use std::fmt::Display;
5use trillium::{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
54#[cfg(feature = "serde_json")]
55#[cfg_attr(docsrs, doc(cfg(feature = "serde_json")))]
56impl From<serde_json::Error> for Error {
57 fn from(value: serde_json::Error) -> Self {
58 Self::ParseError {
59 path: format!("{}:{}", value.line(), value.column()),
60 message: value.to_string(),
61 }
62 }
63}
64
65#[cfg(feature = "sonic-rs")]
66#[cfg_attr(docsrs, doc(cfg(feature = "sonic-rs")))]
67impl From<sonic_rs::Error> for Error {
68 fn from(value: sonic_rs::Error) -> Self {
69 Self::ParseError {
70 path: format!("{}:{}", value.line(), value.column()),
71 message: value.to_string(),
72 }
73 }
74}
75
76impl From<trillium::Error> for Error {
77 fn from(error: trillium::Error) -> Self {
78 match error {
79 trillium::Error::Io(e) => Self::IoError {
80 kind: e.kind().to_string(),
81 message: e.to_string(),
82 },
83
84 other => Self::Other {
85 message: other.to_string(),
86 },
87 }
88 }
89}
90
91impl<E: Display> From<serde_path_to_error::Error<E>> for Error {
92 fn from(e: serde_path_to_error::Error<E>) -> Self {
93 Error::ParseError {
94 path: e.path().to_string(),
95 message: e.to_string(),
96 }
97 }
98}
99
100#[cfg(feature = "forms")]
101#[cfg_attr(docsrs, doc(cfg(feature = "forms")))]
102impl From<serde_urlencoded::ser::Error> for Error {
103 fn from(value: serde_urlencoded::ser::Error) -> Self {
104 Error::Other {
105 message: value.to_string(),
106 }
107 }
108}
109#[cfg(feature = "forms")]
110impl From<serde_urlencoded::de::Error> for Error {
111 fn from(value: serde_urlencoded::de::Error) -> Self {
112 Error::ParseError {
113 path: "".into(),
114 message: value.to_string(),
115 }
116 }
117}
118
119impl Handler for Error {
120 async fn run(&self, conn: Conn) -> Conn {
121 conn.with_state(self.clone()).halt()
122 }
123
124 #[cfg(any(feature = "serde_json", feature = "sonic-rs"))]
125 async fn before_send(&self, mut conn: Conn) -> Conn {
126 if let Some(error) = conn.take_state::<Self>() {
127 conn.with_json(&crate::json!({ "error": &error }))
128 .with_status(&error)
129 } else {
130 conn
131 }
132 }
133
134 #[cfg(not(any(feature = "serde_json", feature = "sonic-rs")))]
135 async fn before_send(&self, mut conn: Conn) -> Conn {
136 if let Some(error) = conn.take_state::<Self>() {
137 conn.with_body(error.to_string()).with_status(&error)
138 } else {
139 conn
140 }
141 }
142}
143
144impl From<&Error> for Status {
145 fn from(value: &Error) -> Self {
146 match value {
147 Error::ParseError { .. } => Status::UnprocessableEntity,
148 Error::UnsupportedMimeType { .. } | Error::MissingContentType => {
149 Status::UnsupportedMediaType
150 }
151 Error::FailureToNegotiateContent => Status::NotAcceptable,
152 Error::IoError { .. } => Status::BadRequest,
153 _ => Status::InternalServerError,
154 }
155 }
156}
157
158impl crate::FromConn for Error {
159 async fn from_conn(conn: &mut Conn) -> Option<Self> {
160 conn.take_state()
161 }
162}