actix_form_data/
error.rs

1/*
2 * This file is part of Actix Form Data.
3 *
4 * Copyright © 2026 asonix
5 *
6 * Actix Form Data is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Actix Form Data is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Actix Form Data.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20use 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}