1- /// This test verifies that async multipart file uploads use tokio::fs::read
2- /// and reqwest::multipart::Part::bytes instead of the deprecated Form.file() method.
1+ /// This test verifies that async multipart file uploads use streaming
2+ /// (TokioFile + FramedRead + Part::stream) instead of buffering the entire file.
3+ ///
4+ /// This approach:
5+ /// - Streams files instead of loading into memory (important for large files)
6+ /// - Uses the same pattern as body file uploads
7+ /// - Avoids the deprecated Form.file() method
38///
49/// Regression test for: https://github.com/OpenAPITools/openapi-generator/issues/XXXXX
510
611#[ tokio:: test]
7- async fn test_multipart_file_reading ( ) {
12+ async fn test_multipart_file_streaming ( ) {
13+ use tokio:: fs:: File as TokioFile ;
14+ use tokio_util:: codec:: { BytesCodec , FramedRead } ;
15+
816 // Create a temporary file
917 let temp_dir = std:: env:: temp_dir ( ) ;
1018 let test_file = temp_dir. join ( "test_upload.txt" ) ;
1119 let test_content = b"Hello, multipart upload test!" ;
1220
1321 std:: fs:: write ( & test_file, test_content) . expect ( "Failed to create test file" ) ;
1422
15- // Verify tokio can read the file (what the generated code does)
16- let file_bytes = tokio :: fs :: read ( & test_file)
23+ // Verify the streaming pattern works (what the generated code does)
24+ let file = TokioFile :: open ( & test_file)
1725 . await
18- . expect ( "Failed to read file with tokio::fs" ) ;
19- assert_eq ! ( file_bytes, test_content) ;
20-
21- // Verify we can create a Part::bytes (what the generated code does)
26+ . expect ( "Failed to open file with TokioFile" ) ;
27+ let stream = FramedRead :: new ( file, BytesCodec :: new ( ) ) ;
2228 let file_name = test_file
2329 . file_name ( )
2430 . map ( |n| n. to_string_lossy ( ) . to_string ( ) )
2531 . unwrap_or_default ( ) ;
2632 assert_eq ! ( file_name, "test_upload.txt" ) ;
2733
28- let file_part = reqwest:: multipart:: Part :: bytes ( file_bytes) . file_name ( file_name) ;
34+ // Create Part with streaming body
35+ let file_part = reqwest:: multipart:: Part :: stream ( reqwest:: Body :: wrap_stream ( stream) )
36+ . file_name ( file_name) ;
2937
30- // Verify we can create a form with the part
38+ // Verify we can create a form with the streaming part
3139 let form = reqwest:: multipart:: Form :: new ( ) . part ( "file" , file_part) ;
3240
33- // If we got here, the API calls work correctly
34- assert ! ( true , "Multipart form created successfully with Part::bytes " ) ;
41+ // If we got here, the streaming API calls work correctly
42+ assert ! ( true , "Multipart form created successfully with streaming " ) ;
3543
3644 // Cleanup
3745 std:: fs:: remove_file ( test_file) . ok ( ) ;
3846}
3947
40- /// Test that optional file parameters work correctly
48+ /// Test that optional file parameters work correctly with streaming
4149#[ tokio:: test]
4250async fn test_optional_file_parameter ( ) {
51+ use tokio:: fs:: File as TokioFile ;
52+ use tokio_util:: codec:: { BytesCodec , FramedRead } ;
53+
4354 let temp_dir = std:: env:: temp_dir ( ) ;
4455 let test_file = temp_dir. join ( "optional_test.txt" ) ;
4556 std:: fs:: write ( & test_file, b"optional content" ) . unwrap ( ) ;
@@ -50,22 +61,27 @@ async fn test_optional_file_parameter() {
5061 let mut form = reqwest:: multipart:: Form :: new ( ) ;
5162
5263 if let Some ( ref param_value) = file_param {
53- let file_bytes = tokio:: fs:: read ( param_value) . await . unwrap ( ) ;
64+ let file = TokioFile :: open ( param_value) . await . unwrap ( ) ;
65+ let stream = FramedRead :: new ( file, BytesCodec :: new ( ) ) ;
5466 let file_name = param_value
5567 . file_name ( )
5668 . map ( |n| n. to_string_lossy ( ) . to_string ( ) )
5769 . unwrap_or_default ( ) ;
58- let file_part = reqwest:: multipart:: Part :: bytes ( file_bytes) . file_name ( file_name) ;
70+ let file_part = reqwest:: multipart:: Part :: stream ( reqwest:: Body :: wrap_stream ( stream) )
71+ . file_name ( file_name) ;
5972 form = form. part ( "file" , file_part) ;
6073 }
6174
6275 // If we got here, optional file handling works
6376 std:: fs:: remove_file ( test_file) . ok ( ) ;
6477}
6578
66- /// Test form with multiple fields (file + metadata)
79+ /// Test form with multiple fields (file + metadata) using streaming
6780#[ tokio:: test]
6881async fn test_multipart_with_metadata ( ) {
82+ use tokio:: fs:: File as TokioFile ;
83+ use tokio_util:: codec:: { BytesCodec , FramedRead } ;
84+
6985 let temp_dir = std:: env:: temp_dir ( ) ;
7086 let test_file = temp_dir. join ( "with_metadata.txt" ) ;
7187 std:: fs:: write ( & test_file, b"file with metadata" ) . unwrap ( ) ;
@@ -76,48 +92,57 @@ async fn test_multipart_with_metadata() {
7692 // Add text field
7793 form = form. text ( "description" , "Test description" ) ;
7894
79- // Add file field
80- let file_bytes = tokio:: fs:: read ( & test_file) . await . unwrap ( ) ;
95+ // Add file field with streaming
96+ let file = TokioFile :: open ( & test_file) . await . unwrap ( ) ;
97+ let stream = FramedRead :: new ( file, BytesCodec :: new ( ) ) ;
8198 let file_name = test_file
8299 . file_name ( )
83100 . map ( |n| n. to_string_lossy ( ) . to_string ( ) )
84101 . unwrap_or_default ( ) ;
85- let file_part = reqwest:: multipart:: Part :: bytes ( file_bytes) . file_name ( file_name) ;
102+ let file_part = reqwest:: multipart:: Part :: stream ( reqwest:: Body :: wrap_stream ( stream) )
103+ . file_name ( file_name) ;
86104 form = form. part ( "file" , file_part) ;
87105
88106 // Verify form was created successfully
89107 std:: fs:: remove_file ( test_file) . ok ( ) ;
90108}
91109
92- /// Test multiple files in the same form
110+ /// Test multiple files in the same form with streaming
93111#[ tokio:: test]
94112async fn test_multiple_files ( ) {
113+ use tokio:: fs:: File as TokioFile ;
114+ use tokio_util:: codec:: { BytesCodec , FramedRead } ;
115+
95116 let temp_dir = std:: env:: temp_dir ( ) ;
96117 let primary_file = temp_dir. join ( "primary.txt" ) ;
97118 let thumbnail_file = temp_dir. join ( "thumbnail.txt" ) ;
98119
99120 std:: fs:: write ( & primary_file, b"primary content" ) . unwrap ( ) ;
100121 std:: fs:: write ( & thumbnail_file, b"thumbnail content" ) . unwrap ( ) ;
101122
102- // Build form with multiple files
123+ // Build form with multiple files using streaming
103124 let mut form = reqwest:: multipart:: Form :: new ( ) ;
104125
105- // Add primary file (required)
106- let file_bytes = tokio:: fs:: read ( & primary_file) . await . unwrap ( ) ;
126+ // Add primary file (required) with streaming
127+ let file = TokioFile :: open ( & primary_file) . await . unwrap ( ) ;
128+ let stream = FramedRead :: new ( file, BytesCodec :: new ( ) ) ;
107129 let file_name = primary_file
108130 . file_name ( )
109131 . map ( |n| n. to_string_lossy ( ) . to_string ( ) )
110132 . unwrap_or_default ( ) ;
111- let file_part = reqwest:: multipart:: Part :: bytes ( file_bytes) . file_name ( file_name) ;
133+ let file_part = reqwest:: multipart:: Part :: stream ( reqwest:: Body :: wrap_stream ( stream) )
134+ . file_name ( file_name) ;
112135 form = form. part ( "primaryFile" , file_part) ;
113136
114- // Add thumbnail file (optional)
115- let file_bytes = tokio:: fs:: read ( & thumbnail_file) . await . unwrap ( ) ;
137+ // Add thumbnail file (optional) with streaming
138+ let file = TokioFile :: open ( & thumbnail_file) . await . unwrap ( ) ;
139+ let stream = FramedRead :: new ( file, BytesCodec :: new ( ) ) ;
116140 let file_name = thumbnail_file
117141 . file_name ( )
118142 . map ( |n| n. to_string_lossy ( ) . to_string ( ) )
119143 . unwrap_or_default ( ) ;
120- let file_part = reqwest:: multipart:: Part :: bytes ( file_bytes) . file_name ( file_name) ;
144+ let file_part = reqwest:: multipart:: Part :: stream ( reqwest:: Body :: wrap_stream ( stream) )
145+ . file_name ( file_name) ;
121146 form = form. part ( "thumbnail" , file_part) ;
122147
123148 // Cleanup
0 commit comments