Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[dio] Test typed responses #1755

Merged
merged 8 commits into from
Dec 28, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions dio/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ See the [Migration Guide][] for the complete breaking changes list.**

- The minimum supported Dart version has been bumped from `2.15.0` to `2.19.0`.
- Remove `DefaultHttpClientAdapter` which was deprecated in `5.0.0`.
- Remove `IOHttpClientAdapter.onHttpClientCreate` which was deprecated in `5.2.0`
- Remove `IOHttpClientAdapter.onHttpClientCreate` which was deprecated in `5.2.0`.
- Remove `DioError` and `DioErrorType` which was deprecated in `5.2.0`.
- Remove `DefaultTransformer` which was deprecated in `5.0.0`.
- `IOHttpClientAdapter` no longer sets a custom `HttpClient.idleTimeout`. A custom `HttpClient` can be provided via
`IOHttpClientAdapter.createHttpClient` if customisation is required.
- Make use of Isolate.run for the BackgroundTransformer
- Make use of `Isolate.run` in the BackgroundTransformer.
- Respect nullability of generic type parameter in `dio`s request methods like `get<String()` vs `get<String?>()`.
Follow the [Migration Guide][] for more details.

## Unreleased

Expand Down
23 changes: 23 additions & 0 deletions dio/doc/migration_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,29 @@ When new content need to be added to the migration guide, make sure they're foll
- `IOHttpClientAdapter.onHttpClientCreate` which was deprecated in `5.2.0` has been removed - use `IOHttpClientAdapter.createHttpClient` instead.
- `DioError` and `DioErrorType` which was deprecated in `5.2.0` has been removed - use `DioException` and `DioExceptionType` instead.
- `DefaultTransformer` which was deprecated in `5.0.0` has been removed - use `BackgroundTransformer` instead.
- The nullability of the generic type parameter in `dio`s request methods is now respected for the responses' `data`.

> [!WARNING]
> The migration depends on your use case and API responses.
> If in doubt make the type nullable and correctly handle the null case.
> DO NOT just delete the null checks if they are shown to be redundant, but think about the impact.

Before:
```dart
Response<String?> response = await Dio().get<String>('https://example.com');
String data = response.data!;
```

After:
```dart
Response<String> response = await Dio().get<String>('https://example.com');
String data = response.data;
```
Or:
```dart
Response<String?> response = await Dio().get<String?>('https://example.com');
String? data = response.data;
```

## 5.0.0

Expand Down
2 changes: 1 addition & 1 deletion dio/lib/src/dio/dio_for_native.dart
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class DioForNative extends DioMixin implements Dio {
int received = 0;

// Stream<Uint8List>
final stream = response.data!.stream;
final stream = response.data.stream;
bool compressed = false;
int total = 0;
final contentEncoding = response.headers.value(
Expand Down
20 changes: 2 additions & 18 deletions dio/lib/src/dio_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,7 @@ abstract class DioMixin implements Dio {
// Make sure headers and [ResponseBody.headers] are the same instance.
responseBody.headers = headers.map;
final ret = Response<dynamic>(
data: null,
headers: headers,
requestOptions: reqOpt,
redirects: responseBody.redirects ?? [],
Expand Down Expand Up @@ -567,7 +568,6 @@ abstract class DioMixin implements Dio {
}

bool _isValidToken(String token) {
_checkNotNullable(token, 'token');
// from https://www.rfc-editor.org/rfc/rfc2616#page-15
//
// CTL = <any US-ASCII control character
Expand Down Expand Up @@ -714,7 +714,7 @@ abstract class DioMixin implements Dio {
requestOptions: requestOptions,
);
} else if (response is! Response<T>) {
final T? data = response.data as T?;
final T data = response.data as T;
final Headers headers;
if (data is ResponseBody) {
headers = Headers.fromMap(
Expand All @@ -739,22 +739,6 @@ abstract class DioMixin implements Dio {
}
}

/// A null-check function for function parameters in Null Safety enabled code.
///
/// Because Dart does not have full null safety until all legacy code has been
/// removed from a program, a non-nullable parameter can still end up with a
/// `null` value. This function can be used to guard those functions against
/// null arguments. It throws a [TypeError] because we are really seeing
/// the failure to assign `null` to a non-nullable type.
///
/// See http://dartbug.com/40614 for context.
T _checkNotNullable<T extends Object>(T value, String name) {
if ((value as dynamic) == null) {
throw NotNullableError<T>(name);
}
return value;
}

/// A [TypeError] thrown by [_checkNotNullable].
class NotNullableError<T> extends Error implements TypeError {
NotNullableError(this._name);
Expand Down
4 changes: 2 additions & 2 deletions dio/lib/src/response.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import 'redirect_record.dart';
/// in anytime, typically by [Interceptor] and [Transformer].
class Response<T> {
Response({
this.data,
required this.data,
required this.requestOptions,
this.statusCode,
this.statusMessage,
Expand All @@ -27,7 +27,7 @@ class Response<T> {
///
/// The content could have been transformed by the [Transformer]
/// before it can use eventually.
T? data;
T data;

/// The [RequestOptions] used for the corresponding request.
RequestOptions requestOptions;
Expand Down
4 changes: 2 additions & 2 deletions dio/test/options_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -357,9 +357,9 @@ void main() {
// Regression: https://github.com/cfug/dio/issues/1834
final r11 = await dio.get('');
expect(r11.data, '');
final r12 = await dio.get<Map>('');
final r12 = await dio.get<Map?>('');
expect(r12.data, null);
final r13 = await dio.get<Map<String, Object>>('');
final r13 = await dio.get<Map<String, Object>?>('');
expect(r13.data, null);
});

Expand Down
2 changes: 1 addition & 1 deletion dio/test/request_integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ void main() {
);
expect(response.data, isA<List>());
expect(response.data, isNotEmpty);
expect(response.data![0], 1);
expect(response.data[0], 1);
});
});

Expand Down
14 changes: 8 additions & 6 deletions dio/test/timeout_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,16 @@ void main() {
);

dio.options.connectTimeout = Duration(milliseconds: 5);
await dio
.get('/')
.catchError((e) => Response(requestOptions: RequestOptions()));
await dio.get('/').catchError((e) => Response(
requestOptions: RequestOptions(),
data: null,
));
expect(client.connectionTimeout, dio.options.connectTimeout);
dio.options.connectTimeout = Duration(milliseconds: 10);
await dio
.get('/')
.catchError((e) => Response(requestOptions: RequestOptions()));
await dio.get('/').catchError((e) => Response(
requestOptions: RequestOptions(),
data: null,
));
expect(client.connectionTimeout, dio.options.connectTimeout);
}, testOn: 'vm');
});
Expand Down
19 changes: 19 additions & 0 deletions dio/test/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,25 @@ Future<void> startServer() async {
return;
}

if (path == '/null-response') {
response.headers.contentType = ContentType('text', 'plain');
response
..statusCode = 200
..contentLength = -1;
response.close();
return;
}

if (path == '/non-null-response') {
response.headers.contentType = ContentType('text', 'plain');
response
..statusCode = 200
..contentLength = -1
..write('response');
response.close();
return;
}

final requestBodyBytes = await ByteStream(request).toBytes();
final encodingName = request.uri.queryParameters['response-encoding'];
final outputEncoding = encodingName == null
Expand Down
2 changes: 1 addition & 1 deletion example/lib/generic.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,5 @@ void main() async {

// This is the recommended way.
final r = await dio.get<String>('https://baidu.com');
print(r.data?.length);
print(r.data.length);
}
2 changes: 1 addition & 1 deletion example_flutter_app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class _MyHomePageState extends State<MyHomePage> {
.then((r) {
setState(() {
print(r.data);
_text = r.data!.replaceAll(RegExp(r'\s'), '');
_text = r.data.replaceAll(RegExp(r'\s'), '');
});
});
} catch (e) {
Expand Down
2 changes: 1 addition & 1 deletion example_flutter_app/lib/routes/request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class _RequestRouteState extends State<RequestRoute> {
onPressed: () {
dio.get<String>('https://httpbin.org/get').then((r) {
setState(() {
_text = r.data!;
_text = r.data;
});
});
},
Expand Down
2 changes: 2 additions & 0 deletions plugins/cookie_manager/test/cookies_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ void main() {
final firstRequestOptions = RequestOptions(baseUrl: exampleUrl);

final mockResponse = Response(
data: null,
requestOptions: firstRequestOptions,
headers: Headers.fromMap(
{HttpHeaders.setCookieHeader: mockFirstRequestCookies},
Expand Down Expand Up @@ -77,6 +78,7 @@ void main() {
final requestOptions = RequestOptions(baseUrl: exampleUrl);

final mockResponse = Response(
data: null,
requestOptions: requestOptions,
headers: Headers.fromMap(
{HttpHeaders.setCookieHeader: mockResponseCookies},
Expand Down
4 changes: 2 additions & 2 deletions plugins/native_dio_adapter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class _MyHomePageState extends State<MyHomePage> {
return AlertDialog(
title: Text('Response ${response.statusCode}'),
content: SingleChildScrollView(
child: Text(response.data ?? 'No response'),
child: Text(response.data),
),
actions: [
MaterialButton(
Expand Down Expand Up @@ -120,7 +120,7 @@ class _MyHomePageState extends State<MyHomePage> {
return AlertDialog(
title: Text('Response ${response.statusCode}'),
content: SingleChildScrollView(
child: Text(response.data ?? 'No response'),
child: Text(response.data),
),
actions: [
MaterialButton(
Expand Down