@@ -6,6 +6,7 @@ use super::{FromRequest, Request};
6
6
use crate :: body:: Bytes ;
7
7
use axum_core:: {
8
8
__composite_rejection as composite_rejection, __define_rejection as define_rejection,
9
+ extract:: OptionalFromRequest ,
9
10
response:: { IntoResponse , Response } ,
10
11
RequestExt ,
11
12
} ;
@@ -71,13 +72,37 @@ where
71
72
type Rejection = MultipartRejection ;
72
73
73
74
async fn from_request ( req : Request , _state : & S ) -> Result < Self , Self :: Rejection > {
74
- let boundary = parse_boundary ( req. headers ( ) ) . ok_or ( InvalidBoundary ) ?;
75
+ let boundary = content_type_str ( req. headers ( ) )
76
+ . and_then ( |content_type| multer:: parse_boundary ( content_type) . ok ( ) )
77
+ . ok_or ( InvalidBoundary ) ?;
75
78
let stream = req. with_limited_body ( ) . into_body ( ) ;
76
79
let multipart = multer:: Multipart :: new ( stream. into_data_stream ( ) , boundary) ;
77
80
Ok ( Self { inner : multipart } )
78
81
}
79
82
}
80
83
84
+ impl < S > OptionalFromRequest < S > for Multipart
85
+ where
86
+ S : Send + Sync ,
87
+ {
88
+ type Rejection = MultipartRejection ;
89
+
90
+ async fn from_request ( req : Request , _state : & S ) -> Result < Option < Self > , Self :: Rejection > {
91
+ let Some ( content_type) = content_type_str ( req. headers ( ) ) else {
92
+ return Ok ( None ) ;
93
+ } ;
94
+ match multer:: parse_boundary ( content_type) {
95
+ Ok ( boundary) => {
96
+ let stream = req. with_limited_body ( ) . into_body ( ) ;
97
+ let multipart = multer:: Multipart :: new ( stream. into_data_stream ( ) , boundary) ;
98
+ Ok ( Some ( Self { inner : multipart } ) )
99
+ }
100
+ Err ( multer:: Error :: NoMultipart ) => Ok ( None ) ,
101
+ Err ( _) => Err ( MultipartRejection :: InvalidBoundary ( InvalidBoundary ) ) ,
102
+ }
103
+ }
104
+ }
105
+
81
106
impl Multipart {
82
107
/// Yields the next [`Field`] if available.
83
108
pub async fn next_field ( & mut self ) -> Result < Option < Field < ' _ > > , MultipartError > {
@@ -282,9 +307,8 @@ impl IntoResponse for MultipartError {
282
307
}
283
308
}
284
309
285
- fn parse_boundary ( headers : & HeaderMap ) -> Option < String > {
286
- let content_type = headers. get ( CONTENT_TYPE ) ?. to_str ( ) . ok ( ) ?;
287
- multer:: parse_boundary ( content_type) . ok ( )
310
+ fn content_type_str ( headers : & HeaderMap ) -> Option < & str > {
311
+ headers. get ( CONTENT_TYPE ) ?. to_str ( ) . ok ( )
288
312
}
289
313
290
314
composite_rejection ! {
@@ -378,4 +402,31 @@ mod tests {
378
402
let res = client. post ( "/" ) . multipart ( form) . await ;
379
403
assert_eq ! ( res. status( ) , StatusCode :: PAYLOAD_TOO_LARGE ) ;
380
404
}
405
+
406
+ #[ crate :: test]
407
+ async fn optional_multipart ( ) {
408
+ const BYTES : & [ u8 ] = "<!doctype html><title>🦀</title>" . as_bytes ( ) ;
409
+
410
+ async fn handle ( multipart : Option < Multipart > ) -> Result < StatusCode , MultipartError > {
411
+ if let Some ( mut multipart) = multipart {
412
+ while let Some ( field) = multipart. next_field ( ) . await ? {
413
+ field. bytes ( ) . await ?;
414
+ }
415
+ Ok ( StatusCode :: OK )
416
+ } else {
417
+ Ok ( StatusCode :: NO_CONTENT )
418
+ }
419
+ }
420
+
421
+ let app = Router :: new ( ) . route ( "/" , post ( handle) ) ;
422
+ let client = TestClient :: new ( app) ;
423
+ let form =
424
+ reqwest:: multipart:: Form :: new ( ) . part ( "file" , reqwest:: multipart:: Part :: bytes ( BYTES ) ) ;
425
+
426
+ let res = client. post ( "/" ) . multipart ( form) . await ;
427
+ assert_eq ! ( res. status( ) , StatusCode :: OK ) ;
428
+
429
+ let res = client. post ( "/" ) . await ;
430
+ assert_eq ! ( res. status( ) , StatusCode :: NO_CONTENT ) ;
431
+ }
381
432
}
0 commit comments