ignitia/multipart/
error.rs

1//! # Multipart Error Types
2//!
3//! This module defines error types specific to multipart form data parsing.
4//! All errors implement the necessary traits for integration with Ignitia's
5//! error handling system.
6//!
7//! ## Error Categories
8//!
9//! - **Validation Errors**: Invalid boundaries, field formats, etc.
10//! - **Size Limit Errors**: Field or request size exceeded configured limits
11//! - **I/O Errors**: File system operations and network errors
12//! - **Parsing Errors**: Malformed multipart data
13//!
14//! ## Error Handling
15//!
16//! ```
17//! use ignitia::multipart::{Multipart, MultipartError};
18//!
19//! async fn handle_upload(mut multipart: Multipart) {
20//!     match multipart.next_field().await {
21//!         Ok(Some(field)) => {
22//!             // Process field
23//!         }
24//!         Ok(None) => {
25//!             // No more fields
26//!         }
27//!         Err(MultipartError::FieldTooLarge { field_name, max_size }) => {
28//!             eprintln!("Field '{}' exceeds {} bytes", field_name, max_size);
29//!         }
30//!         Err(err) => {
31//!             eprintln!("Multipart error: {}", err);
32//!         }
33//!     }
34//! }
35//! ```
36
37use crate::error::CustomError;
38use http::StatusCode;
39use std::fmt;
40
41/// Errors that can occur during multipart form data parsing.
42///
43/// This enum covers all possible error conditions that may arise when
44/// processing multipart/form-data requests, from validation issues to
45/// resource limit violations.
46///
47/// # Error Categories
48///
49/// ## Validation Errors
50/// - `InvalidBoundary`: The boundary parameter is malformed
51/// - `InvalidField`: A field has invalid format or headers
52/// - `InvalidUtf8`: Text field contains invalid UTF-8 sequences
53///
54/// ## Size Limit Errors
55/// - `FieldTooLarge`: An individual field exceeds the configured size limit
56/// - `TooManyFields`: The request contains more fields than allowed
57/// - `RequestTooLarge`: The entire request exceeds the size limit
58///
59/// ## System Errors
60/// - `Io`: File system or network I/O errors
61/// - `Parse`: Low-level parsing errors from the multipart library
62///
63/// # Examples
64///
65/// ```
66/// use ignitia::multipart::MultipartError;
67///
68/// match error {
69///     MultipartError::FieldTooLarge { field_name, max_size } => {
70///         println!("Field '{}' is too large (max: {} bytes)", field_name, max_size);
71///     }
72///     MultipartError::TooManyFields { max_fields } => {
73///         println!("Too many fields (max: {})", max_fields);
74///     }
75///     MultipartError::InvalidBoundary => {
76///         println!("Invalid multipart boundary");
77///     }
78///     _ => {
79///         println!("Other multipart error: {}", error);
80///     }
81/// }
82/// ```
83#[derive(Debug)]
84pub enum MultipartError {
85    /// The multipart boundary parameter is invalid or malformed.
86    ///
87    /// This occurs when the Content-Type header contains an invalid
88    /// boundary parameter, or when the boundary is not properly formatted
89    /// according to RFC 2046 specifications.
90    ///
91    /// # HTTP Status
92    ///
93    /// This error maps to HTTP 400 Bad Request.
94    InvalidBoundary,
95
96    /// A field exceeds the maximum allowed size.
97    ///
98    /// This error is thrown when an individual field's content exceeds
99    /// the `max_field_size` limit configured in `MultipartConfig`.
100    ///
101    /// # Fields
102    ///
103    /// - `field_name`: The name of the field that exceeded the limit
104    /// - `max_size`: The maximum allowed size in bytes
105    ///
106    /// # HTTP Status
107    ///
108    /// This error maps to HTTP 413 Payload Too Large.
109    ///
110    /// # Examples
111    ///
112    /// ```
113    /// use ignitia::multipart::MultipartError;
114    ///
115    /// let error = MultipartError::FieldTooLarge {
116    ///     field_name: "upload_file".to_string(),
117    ///     max_size: 1024 * 1024, // 1MB
118    /// };
119    /// ```
120    FieldTooLarge {
121        /// Name of the field that exceeded the size limit
122        field_name: String,
123        /// Maximum allowed size in bytes
124        max_size: usize,
125    },
126
127    /// The request contains too many fields.
128    ///
129    /// This error occurs when the number of fields in the multipart request
130    /// exceeds the `max_fields` limit configured in `MultipartConfig`.
131    ///
132    /// # Fields
133    ///
134    /// - `max_fields`: The maximum number of fields allowed
135    ///
136    /// # HTTP Status
137    ///
138    /// This error maps to HTTP 413 Payload Too Large.
139    TooManyFields {
140        /// Maximum number of fields allowed
141        max_fields: usize,
142    },
143
144    /// The entire request size exceeds the configured limit.
145    ///
146    /// This error is thrown when the total size of all fields combined
147    /// exceeds the `max_request_size` limit configured in `MultipartConfig`.
148    ///
149    /// # Fields
150    ///
151    /// - `max_size`: The maximum allowed request size in bytes
152    ///
153    /// # HTTP Status
154    ///
155    /// This error maps to HTTP 413 Payload Too Large.
156    RequestTooLarge {
157        /// Maximum allowed request size in bytes
158        max_size: usize,
159    },
160
161    /// A field has invalid format or headers.
162    ///
163    /// This error occurs when a multipart field is malformed, has missing
164    /// required headers, or contains invalid header values.
165    ///
166    /// # HTTP Status
167    ///
168    /// This error maps to HTTP 400 Bad Request.
169    InvalidField(String),
170
171    /// An I/O error occurred during parsing.
172    ///
173    /// This wraps standard I/O errors that may occur when reading from
174    /// the request stream, writing temporary files, or performing other
175    /// file system operations.
176    ///
177    /// # HTTP Status
178    ///
179    /// This error maps to HTTP 400 Bad Request (though it could indicate
180    /// a server-side issue in some cases).
181    Io(std::io::Error),
182
183    /// A text field contains invalid UTF-8 sequences.
184    ///
185    /// This error occurs when attempting to parse a field as text, but
186    /// the field content is not valid UTF-8.
187    ///
188    /// # HTTP Status
189    ///
190    /// This error maps to HTTP 400 Bad Request.
191    InvalidUtf8(std::str::Utf8Error),
192
193    /// A low-level parsing error occurred.
194    ///
195    /// This wraps errors from the underlying multipart parsing library,
196    /// typically indicating malformed multipart data that doesn't conform
197    /// to RFC standards.
198    ///
199    /// # HTTP Status
200    ///
201    /// This error maps to HTTP 400 Bad Request.
202    Parse(String),
203}
204
205impl fmt::Display for MultipartError {
206    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207        match self {
208            MultipartError::InvalidBoundary => {
209                write!(f, "Invalid multipart boundary")
210            }
211            MultipartError::FieldTooLarge {
212                field_name,
213                max_size,
214            } => {
215                write!(
216                    f,
217                    "Field '{}' exceeds maximum size of {} bytes",
218                    field_name, max_size
219                )
220            }
221            MultipartError::TooManyFields { max_fields } => {
222                write!(f, "Too many fields, maximum allowed: {}", max_fields)
223            }
224            MultipartError::RequestTooLarge { max_size } => {
225                write!(f, "Request exceeds maximum size of {} bytes", max_size)
226            }
227            MultipartError::InvalidField(msg) => {
228                write!(f, "Invalid field: {}", msg)
229            }
230            MultipartError::Io(err) => {
231                write!(f, "I/O error: {}", err)
232            }
233            MultipartError::InvalidUtf8(err) => {
234                write!(f, "Invalid UTF-8: {}", err)
235            }
236            MultipartError::Parse(msg) => {
237                write!(f, "Parse error: {}", msg)
238            }
239        }
240    }
241}
242
243impl std::error::Error for MultipartError {
244    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
245        match self {
246            MultipartError::Io(err) => Some(err),
247            MultipartError::InvalidUtf8(err) => Some(err),
248            _ => None,
249        }
250    }
251}
252
253impl CustomError for MultipartError {
254    /// Returns the appropriate HTTP status code for this error.
255    ///
256    /// Size-related errors return 413 Payload Too Large, while
257    /// validation and parsing errors return 400 Bad Request.
258    fn status_code(&self) -> StatusCode {
259        match self {
260            MultipartError::FieldTooLarge { .. }
261            | MultipartError::TooManyFields { .. }
262            | MultipartError::RequestTooLarge { .. } => StatusCode::PAYLOAD_TOO_LARGE,
263            _ => StatusCode::BAD_REQUEST,
264        }
265    }
266
267    /// Returns a string identifier for the error type.
268    ///
269    /// This is used in structured error responses to help clients
270    /// programmatically identify and handle different error types.
271    fn error_type(&self) -> &'static str {
272        match self {
273            MultipartError::InvalidBoundary => "invalid_boundary",
274            MultipartError::FieldTooLarge { .. } => "field_too_large",
275            MultipartError::TooManyFields { .. } => "too_many_fields",
276            MultipartError::RequestTooLarge { .. } => "request_too_large",
277            MultipartError::InvalidField(_) => "invalid_field",
278            MultipartError::Io(_) => "io_error",
279            MultipartError::InvalidUtf8(_) => "invalid_utf8",
280            MultipartError::Parse(_) => "parse_error",
281        }
282    }
283
284    /// Returns additional metadata for structured error responses.
285    ///
286    /// For size-related errors, this includes the limits that were exceeded.
287    fn metadata(&self) -> Option<serde_json::Value> {
288        match self {
289            MultipartError::FieldTooLarge {
290                field_name,
291                max_size,
292            } => Some(serde_json::json!({
293                "field_name": field_name,
294                "max_size": max_size,
295                "limit_type": "field_size"
296            })),
297            MultipartError::TooManyFields { max_fields } => Some(serde_json::json!({
298                "max_fields": max_fields,
299                "limit_type": "field_count"
300            })),
301            MultipartError::RequestTooLarge { max_size } => Some(serde_json::json!({
302                "max_size": max_size,
303                "limit_type": "request_size"
304            })),
305            _ => None,
306        }
307    }
308}
309
310impl From<MultipartError> for crate::Error {
311    /// Converts a MultipartError into the framework's main Error type.
312    ///
313    /// This allows multipart errors to be seamlessly integrated with
314    /// Ignitia's error handling and response generation system.
315    fn from(err: MultipartError) -> Self {
316        crate::Error::Custom(Box::new(err))
317    }
318}
319
320impl From<std::io::Error> for MultipartError {
321    /// Converts standard I/O errors into MultipartError::Io.
322    ///
323    /// This provides automatic conversion for file system and network
324    /// errors that may occur during multipart processing.
325    fn from(err: std::io::Error) -> Self {
326        MultipartError::Io(err)
327    }
328}
329
330impl From<std::str::Utf8Error> for MultipartError {
331    /// Converts UTF-8 validation errors into MultipartError::InvalidUtf8.
332    ///
333    /// This provides automatic conversion when attempting to parse
334    /// binary field data as UTF-8 text.
335    fn from(err: std::str::Utf8Error) -> Self {
336        MultipartError::InvalidUtf8(err)
337    }
338}
339
340/// Rejection type for multipart extraction failures.
341///
342/// This type is returned when multipart data extraction fails during
343/// request processing. It wraps the underlying `MultipartError` and
344/// provides additional context for debugging.
345///
346/// # Usage
347///
348/// This type is primarily used internally by the framework's request
349/// extraction system. Application code typically works with `MultipartError`
350/// directly rather than this rejection wrapper.
351///
352/// # Examples
353///
354/// ```
355/// use ignitia::multipart::{MultipartRejection, MultipartError};
356///
357/// let rejection = MultipartRejection(MultipartError::InvalidBoundary);
358/// println!("Extraction failed: {}", rejection);
359/// ```
360#[derive(Debug)]
361pub struct MultipartRejection(pub MultipartError);
362
363impl fmt::Display for MultipartRejection {
364    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365        write!(f, "Multipart extraction failed: {}", self.0)
366    }
367}
368
369impl std::error::Error for MultipartRejection {
370    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
371        Some(&self.0)
372    }
373}
374
375impl From<MultipartError> for MultipartRejection {
376    /// Converts a MultipartError into a MultipartRejection.
377    fn from(err: MultipartError) -> Self {
378        MultipartRejection(err)
379    }
380}