From 78f3813a8d8948887198ef628e5a2e2039489b03 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Fri, 10 Nov 2023 17:58:52 -0500 Subject: [PATCH] feat(dio): Allow ResponseDecoder and RequestEncoder to be async (#2015) ### 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 - [ ] 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 ### Additional context and info (if any) Gives more flexibility to the developer if they want to do some async work in the response decoder. I don't think tests are needed for this. I don't _think_ this is a breaking change as the return type of `ResponseDecoder` is now `FutureOr`. --------- Signed-off-by: Benjamin --- dio/CHANGELOG.md | 1 + dio/lib/src/dio_mixin.dart | 8 +++- dio/lib/src/options.dart | 6 ++- .../src/transformers/sync_transformer.dart | 8 +++- dio/test/options_test.dart | 46 +++++++++++++++++++ 5 files changed, 65 insertions(+), 4 deletions(-) diff --git a/dio/CHANGELOG.md b/dio/CHANGELOG.md index 0e30b887f..b361c1b93 100644 --- a/dio/CHANGELOG.md +++ b/dio/CHANGELOG.md @@ -7,6 +7,7 @@ See the [Migration Guide][] for the complete breaking changes list.** - Raise warning for `Map`s other than `Map` when encoding request data. - Improve exception messages +- Allow `ResponseDecoder` and `RequestEncoder` to be async ## 5.3.3 diff --git a/dio/lib/src/dio_mixin.dart b/dio/lib/src/dio_mixin.dart index 89f20fd70..0387540a4 100644 --- a/dio/lib/src/dio_mixin.dart +++ b/dio/lib/src/dio_mixin.dart @@ -627,7 +627,13 @@ abstract class DioMixin implements Dio { // Call the request transformer. final transformed = await transformer.transformRequest(options); if (options.requestEncoder != null) { - bytes = options.requestEncoder!(transformed, options); + final encoded = options.requestEncoder!(transformed, options); + + if (encoded is Future) { + bytes = await encoded; + } else { + bytes = encoded; + } } else { // Converts the data to UTF-8 by default. bytes = utf8.encode(transformed); diff --git a/dio/lib/src/options.dart b/dio/lib/src/options.dart index c4aa0c2eb..d455afc36 100644 --- a/dio/lib/src/options.dart +++ b/dio/lib/src/options.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:meta/meta.dart'; import 'adapter.dart'; @@ -76,14 +78,14 @@ enum ListFormat { typedef ValidateStatus = bool Function(int? status); /// The type of a response decoding callback. -typedef ResponseDecoder = String? Function( +typedef ResponseDecoder = FutureOr Function( List responseBytes, RequestOptions options, ResponseBody responseBody, ); /// The type of a response encoding callback. -typedef RequestEncoder = List Function( +typedef RequestEncoder = FutureOr> Function( String request, RequestOptions options, ); diff --git a/dio/lib/src/transformers/sync_transformer.dart b/dio/lib/src/transformers/sync_transformer.dart index 54da8ccfb..e2c6f336c 100644 --- a/dio/lib/src/transformers/sync_transformer.dart +++ b/dio/lib/src/transformers/sync_transformer.dart @@ -125,11 +125,17 @@ class SyncTransformer extends Transformer { ); final String? response; if (options.responseDecoder != null) { - response = options.responseDecoder!( + final decodeResponse = options.responseDecoder!( responseBytes, options, responseBody..stream = Stream.empty(), ); + + if (decodeResponse is Future) { + response = await decodeResponse; + } else { + response = decodeResponse; + } } else if (!isJsonContent || responseBytes.isNotEmpty) { response = utf8.decode(responseBytes, allowMalformed: true); } else { diff --git a/dio/test/options_test.dart b/dio/test/options_test.dart index 0a30464c6..c31229bf9 100644 --- a/dio/test/options_test.dart +++ b/dio/test/options_test.dart @@ -1,4 +1,6 @@ @TestOn('vm') +import 'dart:convert'; + import 'package:dio/dio.dart'; import 'package:test/test.dart'; @@ -399,6 +401,50 @@ void main() { expect(response.data, null); }); + test('responseDecoder can return Future', () async { + final dio = Dio(); + dio.options.responseDecoder = (_, __, ___) => Future.value('example'); + dio.options.baseUrl = EchoAdapter.mockBase; + dio.httpClientAdapter = EchoAdapter(); + + final Response response = await dio.get(''); + + expect(response.data, 'example'); + }); + + test('responseDecoder can return String?', () async { + final dio = Dio(); + dio.options.responseDecoder = (_, __, ___) => 'example'; + dio.options.baseUrl = EchoAdapter.mockBase; + dio.httpClientAdapter = EchoAdapter(); + + final Response response = await dio.get(''); + + expect(response.data, 'example'); + }); + + test('requestEncoder can return Future>', () async { + final dio = Dio(); + dio.options.requestEncoder = (data, _) => Future.value(utf8.encode(data)); + dio.options.baseUrl = EchoAdapter.mockBase; + dio.httpClientAdapter = EchoAdapter(); + + final Response response = await dio.get(''); + + expect(response.statusCode, 200); + }); + + test('requestEncoder can return List', () async { + final dio = Dio(); + dio.options.requestEncoder = (data, _) => utf8.encode(data); + dio.options.baseUrl = EchoAdapter.mockBase; + dio.httpClientAdapter = EchoAdapter(); + + final Response response = await dio.get(''); + + expect(response.statusCode, 200); + }); + test('invalid response type throws exceptions', () async { final dio = Dio( BaseOptions(