Skip to main content

actix_multipart/
error.rs

1//! Error and Result module
2
3use actix_web::{
4    error::{ParseError, PayloadError},
5    http::StatusCode,
6    ResponseError,
7};
8use derive_more::{Display, Error, From};
9
10/// A set of errors that can occur during parsing multipart streams.
11#[derive(Debug, Display, From, Error)]
12#[non_exhaustive]
13pub enum Error {
14    /// Could not find Content-Type header.
15    #[display("Could not find Content-Type header")]
16    ContentTypeMissing,
17
18    /// Could not parse Content-Type header.
19    #[display("Could not parse Content-Type header")]
20    ContentTypeParse,
21
22    /// Parsed Content-Type did not have "multipart" top-level media type.
23    ///
24    /// Also raised when extracting a [`MultipartForm`] from a request that does not have the
25    /// "multipart/form-data" media type.
26    ///
27    /// [`MultipartForm`]: struct@crate::form::MultipartForm
28    #[display("Parsed Content-Type did not have 'multipart' top-level media type")]
29    ContentTypeIncompatible,
30
31    /// Multipart boundary is not found.
32    #[display("Multipart boundary is not found")]
33    BoundaryMissing,
34
35    /// Content-Disposition header was not found or not of disposition type "form-data" when parsing
36    /// a "form-data" field.
37    ///
38    /// As per [RFC 7578 §4.2], a "multipart/form-data" field's Content-Disposition header must
39    /// always be present and have a disposition type of "form-data".
40    ///
41    /// [RFC 7578 §4.2]: https://datatracker.ietf.org/doc/html/rfc7578#section-4.2
42    #[display("Content-Disposition header was not found when parsing a \"form-data\" field")]
43    ContentDispositionMissing,
44
45    /// Content-Disposition name parameter was not found when parsing a "form-data" field.
46    ///
47    /// As per [RFC 7578 §4.2], a "multipart/form-data" field's Content-Disposition header must
48    /// always include a "name" parameter.
49    ///
50    /// [RFC 7578 §4.2]: https://datatracker.ietf.org/doc/html/rfc7578#section-4.2
51    #[display(
52        "Content-Disposition 'name' parameter was not found when parsing a \"form-data\" field"
53    )]
54    ContentDispositionNameMissing,
55
56    /// Nested multipart is not supported.
57    #[display("Nested multipart is not supported")]
58    Nested,
59
60    /// Multipart stream is incomplete.
61    #[display("Multipart stream is incomplete")]
62    Incomplete,
63
64    /// Field parsing failed.
65    #[display("Error during field parsing")]
66    Parse(ParseError),
67
68    /// HTTP payload error.
69    #[display("Payload error")]
70    Payload(PayloadError),
71
72    /// Stream is not consumed.
73    #[display("Stream is not consumed")]
74    NotConsumed,
75
76    /// Form field handler raised error.
77    #[display("An error occurred processing field \"{name}\"")]
78    Field {
79        name: String,
80        source: actix_web::Error,
81    },
82
83    /// Duplicate field found (for structure that opted-in to denying duplicate fields).
84    #[display("Duplicate field found \"{_0}\"")]
85    #[from(ignore)]
86    DuplicateField(#[error(not(source))] String),
87
88    /// Required field is missing.
89    #[display("Required field is missing \"{_0}\"")]
90    #[from(ignore)]
91    MissingField(#[error(not(source))] String),
92
93    /// Unknown field (for structure that opted-in to denying unknown fields).
94    #[display("Unknown field \"{_0}\"")]
95    #[from(ignore)]
96    UnknownField(#[error(not(source))] String),
97}
98
99impl ResponseError for Error {
100    fn status_code(&self) -> StatusCode {
101        match &self {
102            Error::Field { source, .. } => source.as_response_error().status_code(),
103            Error::ContentTypeIncompatible => StatusCode::UNSUPPORTED_MEDIA_TYPE,
104            _ => StatusCode::BAD_REQUEST,
105        }
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use err_report::Report;
112
113    use super::*;
114
115    #[test]
116    fn test_multipart_error() {
117        let resp = Error::BoundaryMissing.error_response();
118        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
119
120        let err = Error::Payload(PayloadError::Overflow);
121        assert_eq!(
122            Report::new(&err).to_string(),
123            "Payload error: payload reached size limit",
124        );
125
126        let err = Error::Parse(ParseError::Incomplete);
127        assert_eq!(
128            Report::new(&err).to_string(),
129            "Error during field parsing: message is incomplete"
130        );
131
132        let err = Error::ContentDispositionNameMissing;
133        assert_eq!(
134            Report::new(&err).to_string(),
135            "Content-Disposition 'name' parameter was not found when parsing a \"form-data\" field"
136        );
137
138        let err = Error::Field {
139            name: "avatar".to_owned(),
140            source: actix_web::error::ErrorBadRequest("invalid file format"),
141        };
142        assert_eq!(
143            Report::new(&err).to_string(),
144            "An error occurred processing field \"avatar\": invalid file format"
145        );
146    }
147}