Skip to content

Commit

Permalink
✨ Make MultipartFile clonable (#1889)
Browse files Browse the repository at this point in the history
- Add restoration capability to MultipartFile
- Add tests to ensure MultipartFile is not changed during cloning, it
just gets a new instance that can be consumed by FormData

Possible next steps: Create method in FormData to make it
recoverable/clonable as well.

### New Pull Request Checklist

- [x] I have read the
[Documentation](https://pub.dev/documentation/dio/latest/)
- [x] I have searched for a similar pull request in the
[project](https://github.com/cfug/dio/pulls) and found none
- [x] I have updated this branch with the latest `main` branch to avoid
conflicts (via merge from master or rebase)
- [x] I have added the required tests to prove the fix/feature I'm
adding
- [x] I have updated the documentation (if necessary)
- [x] I have run the tests without failures
- [x] I have updated the `CHANGELOG.md` in the corresponding package

---------

Signed-off-by: Gabriel Araujo <[email protected]>
Signed-off-by: Peter Leibiger <[email protected]>
Co-authored-by: Peter Leibiger <[email protected]>
  • Loading branch information
gabrielaraujoz and kuhnroyal authored Jul 14, 2023
1 parent c301ffe commit 4a93745
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 1 deletion.
3 changes: 2 additions & 1 deletion dio/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ See the [Migration Guide][] for the complete breaking changes list.**
## Unreleased

- Remove `http` from `dev_dependencies`.
- Add support for cloning `MultipartFile` from `FormData`.
- Only produce null response body when `ResponseType.json`.

## 5.2.1+1
Expand All @@ -29,7 +30,7 @@ See the [Migration Guide][] for the complete breaking changes list.**
Dio 6.0.0 - Please use the replacement `IOHttpClientAdapter.createHttpClient` instead.
- Using `CancelToken` no longer closes and re-creates `HttpClient` for each request when `IOHttpClientAdapter` is used.
- Fix timeout handling for browser `receiveTimeout`.
- Improve performance when sending binary data (`List<int>`/`Uint8List`).
- Improve performance when sending binary data (`List<int>`/`Uint8List`).

## 5.1.2

Expand Down
11 changes: 11 additions & 0 deletions dio/lib/src/multipart_file.dart
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,15 @@ class MultipartFile {
_isFinalized = true;
return _stream;
}

/// Clone MultipartFile, returning a new instance of the same object.
/// This is useful if your request failed and you wish to retry it,
/// such as an unauthorized exception can be solved by refreshing the token.
MultipartFile clone() => MultipartFile(
_stream,
length,
filename: filename,
contentType: contentType,
headers: headers,
);
}
93 changes: 93 additions & 0 deletions dio/test/formdata_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,99 @@ void main() async {
testOn: 'vm',
);

// Cloned multipart files should be able to be read again and be the same
// as the original ones.
test(
'complex with cloning',
() async {
final multipartFile1 = MultipartFile.fromString(
'hello world.',
headers: {
'test': <String>['a']
},
);
final multipartFile2 = await MultipartFile.fromFile(
'test/mock/_testfile',
filename: '1.txt',
headers: {
'test': <String>['b']
},
);
final multipartFile3 = MultipartFile.fromFileSync(
'test/mock/_testfile',
filename: '2.txt',
headers: {
'test': <String>['c']
},
);

final fm = FormData.fromMap({
'name': 'wendux',
'age': 25,
'path': '/图片空间/地址',
'file': multipartFile1,
'files': [
multipartFile2,
multipartFile3,
]
});
final fmStr = await fm.readAsBytes();

// Files are finalized after being read.
try {
multipartFile1.finalize();
fail('Should not be able to finalize a file twice.');
} catch (e) {
expect(e, isA<StateError>());
expect(
(e as StateError).message,
'The MultipartFile has already been finalized. This typically '
'means you are using the same MultipartFile in repeated requests.',
);
}

final fm1 = FormData();
fm1.fields.add(MapEntry('name', 'wendux'));
fm1.fields.add(MapEntry('age', '25'));
fm1.fields.add(MapEntry('path', '/图片空间/地址'));
fm1.files.add(
MapEntry(
'file',
multipartFile1.clone(),
),
);
fm1.files.add(
MapEntry(
'files',
multipartFile2.clone(),
),
);
fm1.files.add(
MapEntry(
'files',
multipartFile3.clone(),
),
);
expect(fmStr.length, fm1.length);

// The cloned multipart files should be able to be read again.
expect(fm.files[0].value.isFinalized, true);
expect(fm.files[1].value.isFinalized, true);
expect(fm.files[2].value.isFinalized, true);
expect(fm1.files[0].value.isFinalized, false);
expect(fm1.files[1].value.isFinalized, false);
expect(fm1.files[2].value.isFinalized, false);

// The cloned multipart files' properties should be the same as the
// original ones.
expect(fm1.files[0].value.filename, multipartFile1.filename);
expect(fm1.files[0].value.contentType, multipartFile1.contentType);
expect(fm1.files[0].value.length, multipartFile1.length);
expect(fm1.files[0].value.headers, multipartFile1.headers);
},
testOn: 'vm',
);

test('encodes maps correctly', () async {
final fd = FormData.fromMap(
{
Expand Down

0 comments on commit 4a93745

Please sign in to comment.