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