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}