1use actix_web::{
21 error::{PayloadError, ResponseError},
22 http::StatusCode,
23 HttpResponse,
24};
25
26pub(crate) trait ResultExt<T, E> {
27 fn or_raise(self, kind: ErrorKind) -> Result<T, Error>
28 where
29 Self: Sized,
30 E: std::error::Error + Send + Sync + 'static;
31}
32
33impl<T, E> ResultExt<T, E> for Result<T, E> {
34 fn or_raise(self, kind: ErrorKind) -> Result<T, Error>
35 where
36 Self: Sized,
37 E: std::error::Error + Send + Sync + 'static,
38 {
39 self.map_err(|error| Error::new_with(kind, error))
40 }
41}
42
43#[derive(Debug)]
44pub struct Error {
45 kind: ErrorKind,
46 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
47}
48
49impl Error {
50 pub(crate) fn new(kind: ErrorKind) -> Self {
51 Self { kind, source: None }
52 }
53
54 pub(crate) fn new_with<E>(kind: ErrorKind, error: E) -> Self
55 where
56 E: std::error::Error + Send + Sync + 'static,
57 {
58 Self {
59 kind,
60 source: Some(Box::new(error)),
61 }
62 }
63
64 pub(crate) fn new_with_unsend<E>(kind: ErrorKind, error: E) -> Self
65 where
66 E: std::error::Error + 'static,
67 {
68 Self {
69 kind,
70 source: Some(Box::new(StringError::new(&error))),
71 }
72 }
73
74 pub fn kind(&self) -> ErrorKind {
75 self.kind
76 }
77}
78
79#[derive(Copy, Clone, Debug)]
80pub enum ErrorKind {
81 Multipart,
82 ParseField,
83 ParseInt,
84 ParseFloat,
85 ContentDisposition,
86 Field,
87 FieldCount,
88 FieldSize,
89 FieldType,
90 Filename,
91 FileCount,
92 FileSize,
93 Panicked,
94}
95
96impl std::fmt::Display for Error {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 match self.kind {
99 ErrorKind::Multipart => f.write_str("Failed to read multipart body"),
100 ErrorKind::ParseField => f.write_str("Failed to parse multipart field"),
101 ErrorKind::ParseInt => f.write_str("Failed to parse int"),
102 ErrorKind::ParseFloat => f.write_str("Failed to parse float"),
103 ErrorKind::ContentDisposition => f.write_str("Failed to parse Content-Disposition"),
104 ErrorKind::Field => f.write_str("Failed to parse field name"),
105 ErrorKind::FieldCount => f.write_str("Too many fields in request"),
106 ErrorKind::FieldSize => f.write_str("Field too large"),
107 ErrorKind::FieldType => f.write_str("Found field with unexpected name or type"),
108 ErrorKind::Filename => f.write_str("Filename is missing"),
109 ErrorKind::FileCount => f.write_str("Too many files in request"),
110 ErrorKind::FileSize => f.write_str("File too large"),
111 ErrorKind::Panicked => f.write_str("Task panicked"),
112 }
113 }
114}
115
116impl std::error::Error for Error {
117 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
118 self.source.as_ref().map(|e| e.as_ref() as _)
119 }
120}
121
122#[derive(Debug)]
123pub enum MultipartError {
124 NoContentDisposition,
125 NoContentType,
126 ParseContentType,
127 Boundary,
128 Nested,
129 Incomplete,
130 NotConsumed,
131 DuplicateField(String),
132 MissingField(String),
133 UnsupportedField(String),
134}
135
136impl std::fmt::Display for MultipartError {
137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138 match self {
139 Self::NoContentDisposition => f.write_str("No Content-Disposition `form-data` header"),
140 Self::NoContentType => f.write_str("No Content-Type `form-data` header"),
141 Self::ParseContentType => {
142 f.write_str("Failed to parse Content-Type `form-data` header")
143 }
144 Self::Boundary => f.write_str("Multipart boundary is missing"),
145 Self::Nested => f.write_str("Nested multipart is not supported"),
146 Self::Incomplete => f.write_str("Multipart stream is incomplete"),
147 Self::NotConsumed => f.write_str("Multipart stream was not consumed"),
148 Self::DuplicateField(field) => write!(f, "Duplicate field with name `{field}` found"),
149 Self::MissingField(field) => write!(f, "Field with name `{field}` is required"),
150 Self::UnsupportedField(field) => {
151 write!(f, "Found unsupported field with name `{field}`")
152 }
153 }
154 }
155}
156
157impl std::error::Error for MultipartError {}
158
159#[derive(Debug)]
160pub(crate) struct StringError {
161 display: String,
162 source: Option<Box<StringError>>,
163}
164
165impl StringError {
166 fn new(error: &(dyn std::error::Error + 'static)) -> Self {
167 let display = format!("{error}");
168
169 let source = error.source().map(StringError::new).map(Box::new);
170
171 Self { display, source }
172 }
173}
174
175impl std::fmt::Display for StringError {
176 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177 f.write_str(&self.display)
178 }
179}
180
181impl std::error::Error for StringError {
182 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
183 self.source.as_ref().map(|e| e as _)
184 }
185}
186
187#[derive(Debug)]
188pub struct FieldError {
189 name: String,
190 source: StringError,
191}
192
193impl FieldError {
194 fn new(name: String, source: actix_web::error::Error) -> Self {
195 Self {
196 name,
197 source: StringError::new(&source),
198 }
199 }
200}
201
202impl std::fmt::Display for FieldError {
203 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204 write!(f, "Failed processing field with name `{}`", self.name)
205 }
206}
207
208impl std::error::Error for FieldError {
209 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
210 Some(&self.source)
211 }
212}
213
214impl From<actix_multipart::MultipartError> for Error {
215 fn from(value: actix_multipart::MultipartError) -> Self {
216 match value {
217 actix_multipart::MultipartError::ContentDispositionMissing => {
218 Error::new_with(ErrorKind::Multipart, MultipartError::NoContentDisposition)
219 }
220 actix_multipart::MultipartError::ContentTypeMissing => {
221 Error::new_with(ErrorKind::Multipart, MultipartError::NoContentType)
222 }
223 actix_multipart::MultipartError::ContentTypeParse => {
224 Error::new_with(ErrorKind::Multipart, MultipartError::ParseContentType)
225 }
226 actix_multipart::MultipartError::BoundaryMissing => {
227 Error::new_with(ErrorKind::Multipart, MultipartError::Boundary)
228 }
229 actix_multipart::MultipartError::Nested => {
230 Error::new_with(ErrorKind::Multipart, MultipartError::Nested)
231 }
232 actix_multipart::MultipartError::Incomplete => {
233 Error::new_with(ErrorKind::Multipart, MultipartError::Incomplete)
234 }
235 actix_multipart::MultipartError::Parse(e) => Error::new_with(ErrorKind::Multipart, e),
236 actix_multipart::MultipartError::Payload(e) => Error::new_with(ErrorKind::Multipart, e),
237 actix_multipart::MultipartError::NotConsumed => {
238 Error::new_with(ErrorKind::Multipart, MultipartError::NotConsumed)
239 }
240 actix_multipart::MultipartError::Field { name, source } => {
241 Error::new_with(ErrorKind::Multipart, FieldError::new(name, source))
242 }
243 actix_multipart::MultipartError::DuplicateField(s) => {
244 Error::new_with(ErrorKind::Multipart, MultipartError::DuplicateField(s))
245 }
246 actix_multipart::MultipartError::MissingField(s) => {
247 Error::new_with(ErrorKind::Multipart, MultipartError::MissingField(s))
248 }
249 actix_multipart::MultipartError::UnknownField(s) => {
250 Error::new_with(ErrorKind::Multipart, MultipartError::UnsupportedField(s))
251 }
252 e => Error::new_with_unsend(ErrorKind::Multipart, e),
253 }
254 }
255}
256
257impl ResponseError for Error {
258 fn status_code(&self) -> StatusCode {
259 if let Some(source) = &self.source {
260 if let Some(payload) = source.downcast_ref::<PayloadError>() {
261 return payload.status_code();
262 }
263 }
264
265 StatusCode::BAD_REQUEST
266 }
267
268 fn error_response(&self) -> HttpResponse {
269 if let Some(source) = &self.source {
270 if source.is::<tokio::task::JoinError>() {
271 return HttpResponse::InternalServerError().finish();
272 }
273
274 if let Some(payload) = source.downcast_ref::<PayloadError>() {
275 return payload.error_response();
276 }
277 }
278
279 HttpResponse::BadRequest().finish()
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::Error;
286
287 #[test]
288 fn assert_send() {
289 fn is_send<E: Send>() {}
290 is_send::<Error>();
291 }
292
293 #[test]
294 fn assert_sync() {
295 fn is_sync<E: Sync>() {}
296 is_sync::<Error>()
297 }
298
299 #[test]
300 fn assert_error() {
301 fn is_error<E: std::error::Error>() {}
302 is_error::<Error>()
303 }
304}