From d08c3a82a3c9cb959ebd657aa5f40d5cb7ef08db Mon Sep 17 00:00:00 2001 From: Brandon Trautmann <8343465+btrautmann@users.noreply.github.com> Date: Mon, 22 Dec 2025 15:54:14 -0500 Subject: [PATCH 01/15] chore: upgrade dependencies & migrate to dart_mappable --- .fvmrc | 6 + .gitignore | 3 + .vscode/settings.json | 3 + analysis_options.yaml | 6 +- build.yaml | 21 - codecov.yml | 3 - example/main.dart | 29 +- lib/src/network_request.dart | 94 +-- lib/src/network_request.freezed.dart | 360 --------- lib/src/sturdy_http.dart | 116 +-- lib/src/sturdy_http_event_listener.dart | 63 +- .../sturdy_http_event_listener.freezed.dart | 612 --------------- lib/sturdy_http.dart | 3 - pubspec.yaml | 23 +- test/src/deserializer_test.dart | 43 +- test/src/sturdy_http_test.dart | 707 +++++++----------- test/src/sturdy_http_test.freezed.dart | 647 ---------------- test/src/sturdy_http_test.g.dart | 39 - test/src/sturdy_http_test.mapper.dart | 197 +++++ 19 files changed, 643 insertions(+), 2332 deletions(-) create mode 100644 .fvmrc create mode 100644 .vscode/settings.json delete mode 100644 build.yaml delete mode 100644 codecov.yml delete mode 100644 lib/src/network_request.freezed.dart delete mode 100644 lib/src/sturdy_http_event_listener.freezed.dart delete mode 100644 test/src/sturdy_http_test.freezed.dart delete mode 100644 test/src/sturdy_http_test.g.dart create mode 100644 test/src/sturdy_http_test.mapper.dart diff --git a/.fvmrc b/.fvmrc new file mode 100644 index 0000000..c6a1246 --- /dev/null +++ b/.fvmrc @@ -0,0 +1,6 @@ +{ + "flutter": "3.29.2", + "runPubGetOnSdkChanges": true, + "updateVscodeSettings": true, + "updateGitIgnore": false +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index d3f50d7..2f690df 100644 --- a/.gitignore +++ b/.gitignore @@ -75,3 +75,6 @@ build/ !**/ios/**/default.mode2v3 !**/ios/**/default.pbxuser !**/ios/**/default.perspectivev3 + +# FVM Version Cache +.fvm/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0f7b500 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "dart.flutterSdkPath": ".fvm/versions/3.38.5" +} \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml index 79b71e6..076e16e 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,5 +1,8 @@ include: package:lints/recommended.yaml +formatter: + page_width: 80 + linter: rules: - package_api_docs @@ -15,5 +18,4 @@ analyzer: missing_return: error missing_required_param: error exclude: - - "build/**" - - "**/*.g.dart" + - 'build/**' diff --git a/build.yaml b/build.yaml deleted file mode 100644 index 1c172e1..0000000 --- a/build.yaml +++ /dev/null @@ -1,21 +0,0 @@ -targets: - $default: - builders: - json_serializable: - options: - # Options configure how source code is generated for every - # `@JsonSerializable`-annotated class in the package. - any_map: false - checked: true - create_factory: true - create_to_json: true - disallow_unrecognized_keys: false - explicit_to_json: true - field_rename: snake - ignore_unannotated: false - include_if_null: true - generate_for: - - "**/*.dart" - freezed: - generate_for: - - "**/*.dart" diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index 20e2c1f..0000000 --- a/codecov.yml +++ /dev/null @@ -1,3 +0,0 @@ -ignore: - - "**/*.freezed.dart" - - "**/*.g.dart" \ No newline at end of file diff --git a/example/main.dart b/example/main.dart index b1b55db..b95faea 100644 --- a/example/main.dart +++ b/example/main.dart @@ -4,16 +4,8 @@ import 'package:sturdy_http/sturdy_http.dart'; void main(List args) async { // Set up some fake HTTP responses using Charlatan final charlatan = Charlatan() - ..whenGet( - '/foo', - (request) => CharlatanHttpResponse( - body: 'Hello World!', - ), - ) - ..whenPost( - '/foo', - (request) => CharlatanHttpResponse(statusCode: 204), - ); + ..whenGet('/foo', (request) => CharlatanHttpResponse(body: 'Hello World!')) + ..whenPost('/foo', (request) => CharlatanHttpResponse(statusCode: 204)); // Create your client final client = SturdyHttp( @@ -39,7 +31,7 @@ void main(List args) async { // 'mutative request success' <-- From ExampleEventListener // 'success!' await client.execute( - PostRequest('/foo', data: NetworkRequestBody.empty()), + PostRequest('/foo', data: EmptyRequestBody()), onResponse: (r) { return switch (r) { OkNoContent() => print('success!'), @@ -51,11 +43,14 @@ void main(List args) async { class ExampleEventListener implements SturdyHttpEventListener { @override - Future onEvent(SturdyHttpEvent event) { - return event.when( - decodingError: (_, __, ___) async => print('decoding error'), - authFailure: (_) async => print('auth failure'), - mutativeRequestSuccess: (_) async => print('mutative request success'), - ); + Future onEvent(SturdyHttpEvent event) async { + switch (event) { + case DecodingError(): + print('decoding error'); + case AuthFailure(): + print('auth failure'); + case MutativeRequestSuccess(): + print('mutative request success'); + } } } diff --git a/lib/src/network_request.dart b/lib/src/network_request.dart index 96757c5..9e197a1 100644 --- a/lib/src/network_request.dart +++ b/lib/src/network_request.dart @@ -1,9 +1,6 @@ import 'package:dio/dio.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:sturdy_http/sturdy_http.dart'; -part 'network_request.freezed.dart'; - /// {@template network_request_type} /// /// A type representing the available HTTP request methods. @@ -74,19 +71,14 @@ class GetRequest extends NetworkRequest { /// {@macro get_request} const GetRequest( String path, { - super.data = const NetworkRequestBody.empty(), + super.data = const EmptyRequestBody(), Map? queryParameters, super.options, super.cancelToken, super.onReceiveProgress, super.onSendProgress, super.retryBehavior, - }) : super( - type: NetworkRequestType.Get, - path: path, - shouldTriggerDataMutation: false, - queryParams: queryParameters, - ); + }) : super(type: NetworkRequestType.Get, path: path, shouldTriggerDataMutation: false, queryParams: queryParameters); } /// {@template post_request} @@ -104,11 +96,7 @@ class PostRequest extends NetworkRequest { super.onReceiveProgress, super.onSendProgress, super.retryBehavior, - }) : super( - type: NetworkRequestType.Post, - path: path, - queryParams: queryParameters, - ); + }) : super(type: NetworkRequestType.Post, path: path, queryParams: queryParameters); } /// {@template put_request} @@ -126,11 +114,7 @@ class PutRequest extends NetworkRequest { super.onReceiveProgress, super.onSendProgress, super.retryBehavior, - }) : super( - type: NetworkRequestType.Put, - path: path, - queryParams: queryParameters, - ); + }) : super(type: NetworkRequestType.Put, path: path, queryParams: queryParameters); } /// {@template delete_request} @@ -140,7 +124,7 @@ class DeleteRequest extends NetworkRequest { /// {@macro delete_request} const DeleteRequest( String path, { - super.data = const NetworkRequestBody.empty(), + super.data = const EmptyRequestBody(), Map? queryParameters, super.shouldTriggerDataMutation = true, super.options, @@ -148,11 +132,7 @@ class DeleteRequest extends NetworkRequest { super.onReceiveProgress, super.onSendProgress, super.retryBehavior, - }) : super( - type: NetworkRequestType.Delete, - path: path, - queryParams: queryParameters, - ); + }) : super(type: NetworkRequestType.Delete, path: path, queryParams: queryParameters); } /// {@template raw_request} @@ -165,7 +145,7 @@ class RawRequest extends NetworkRequest { const RawRequest( String path, { required super.type, - super.data = const NetworkRequestBody.empty(), + super.data = const EmptyRequestBody(), Map? queryParameters, super.shouldTriggerDataMutation = true, super.options, @@ -173,10 +153,7 @@ class RawRequest extends NetworkRequest { super.onReceiveProgress, super.onSendProgress, super.retryBehavior, - }) : super( - path: path, - queryParams: queryParameters, - ); + }) : super(path: path, queryParams: queryParameters); } /// The body of a [NetworkRequest]. Note that this type is aimed at providing @@ -184,20 +161,43 @@ class RawRequest extends NetworkRequest { /// with regards to the content-type header. If you want [SturdyHttp] to infer /// the content-type header, configure this via the `inferContentType` parameter /// when constructing the instance. -@Freezed(copyWith: false) -class NetworkRequestBody with _$NetworkRequestBody { - /// An empty body. Results in `null` being passed to `data` of the request. - const factory NetworkRequestBody.empty() = _Empty; - - /// A JSON body. Passed directly to `data` of the request. If `inferContentType` - /// has been provided as `true` to the [SturdyHttp] instance, will result in - /// an `application-json` `content-type`. - const factory NetworkRequestBody.json(Json data) = _Json; - - /// A raw body. Allows for nullable untyped data that is passed directly - /// to `data` of the request, useful for instances where the data type - /// is not known until runtime. If `inferContentType` has been provided as - /// `true` to the [SturdyHttp] instance *and* the [data] can be used to infer - /// the `content-type` header, it will be inferred. - const factory NetworkRequestBody.raw(Object? data) = _Raw; +sealed class NetworkRequestBody { + /// {@macro network_request_body} + const NetworkRequestBody(); +} + +/// {@template empty_request_body} +/// An empty body. Results in `null` being passed to `data` of the request. +/// {@endtemplate} +final class EmptyRequestBody extends NetworkRequestBody { + /// {@macro empty_request_body} + const EmptyRequestBody(); +} + +/// {@template json_request_body} +/// A JSON body. Passed directly to `data` of the request. If `inferContentType` +/// has been provided as `true` to the [SturdyHttp] instance, will result in +/// an `application-json` `content-type`. +/// {@endtemplate} +final class JsonRequestBody extends NetworkRequestBody { + /// The JSON data to be sent as the body of the request + final Json data; + + /// {@macro json_request_body} + const JsonRequestBody(this.data); +} + +/// {@template raw_request_body} +/// A raw body. Allows for nullable untyped data that is passed directly +/// to `data` of the request, useful for instances where the data type +/// is not known until runtime. If `inferContentType` has been provided as +/// `true` to the [SturdyHttp] instance *and* the [data] can be used to infer +/// the `content-type` header, it will be inferred. +/// {@endtemplate} +final class RawRequestBody extends NetworkRequestBody { + /// The raw data to be sent as the body of the request + final Object? data; + + /// {@macro raw_request_body} + const RawRequestBody(this.data); } diff --git a/lib/src/network_request.freezed.dart b/lib/src/network_request.freezed.dart deleted file mode 100644 index 0e99a7f..0000000 --- a/lib/src/network_request.freezed.dart +++ /dev/null @@ -1,360 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'network_request.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -/// @nodoc -mixin _$NetworkRequestBody { - @optionalTypeArgs - TResult when({ - required TResult Function() empty, - required TResult Function(Map data) json, - required TResult Function(Object? data) raw, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function()? empty, - TResult? Function(Map data)? json, - TResult? Function(Object? data)? raw, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeWhen({ - TResult Function()? empty, - TResult Function(Map data)? json, - TResult Function(Object? data)? raw, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult map({ - required TResult Function(_Empty value) empty, - required TResult Function(_Json value) json, - required TResult Function(_Raw value) raw, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(_Empty value)? empty, - TResult? Function(_Json value)? json, - TResult? Function(_Raw value)? raw, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeMap({ - TResult Function(_Empty value)? empty, - TResult Function(_Json value)? json, - TResult Function(_Raw value)? raw, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; -} - -/// @nodoc - -class _$EmptyImpl implements _Empty { - const _$EmptyImpl(); - - @override - String toString() { - return 'NetworkRequestBody.empty()'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && other is _$EmptyImpl); - } - - @override - int get hashCode => runtimeType.hashCode; - - @override - @optionalTypeArgs - TResult when({ - required TResult Function() empty, - required TResult Function(Map data) json, - required TResult Function(Object? data) raw, - }) { - return empty(); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function()? empty, - TResult? Function(Map data)? json, - TResult? Function(Object? data)? raw, - }) { - return empty?.call(); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function()? empty, - TResult Function(Map data)? json, - TResult Function(Object? data)? raw, - required TResult orElse(), - }) { - if (empty != null) { - return empty(); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(_Empty value) empty, - required TResult Function(_Json value) json, - required TResult Function(_Raw value) raw, - }) { - return empty(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(_Empty value)? empty, - TResult? Function(_Json value)? json, - TResult? Function(_Raw value)? raw, - }) { - return empty?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(_Empty value)? empty, - TResult Function(_Json value)? json, - TResult Function(_Raw value)? raw, - required TResult orElse(), - }) { - if (empty != null) { - return empty(this); - } - return orElse(); - } -} - -abstract class _Empty implements NetworkRequestBody { - const factory _Empty() = _$EmptyImpl; -} - -/// @nodoc - -class _$JsonImpl implements _Json { - const _$JsonImpl(final Map data) : _data = data; - - final Map _data; - @override - Map get data { - if (_data is EqualUnmodifiableMapView) return _data; - // ignore: implicit_dynamic_type - return EqualUnmodifiableMapView(_data); - } - - @override - String toString() { - return 'NetworkRequestBody.json(data: $data)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$JsonImpl && - const DeepCollectionEquality().equals(other._data, _data)); - } - - @override - int get hashCode => - Object.hash(runtimeType, const DeepCollectionEquality().hash(_data)); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function() empty, - required TResult Function(Map data) json, - required TResult Function(Object? data) raw, - }) { - return json(data); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function()? empty, - TResult? Function(Map data)? json, - TResult? Function(Object? data)? raw, - }) { - return json?.call(data); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function()? empty, - TResult Function(Map data)? json, - TResult Function(Object? data)? raw, - required TResult orElse(), - }) { - if (json != null) { - return json(data); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(_Empty value) empty, - required TResult Function(_Json value) json, - required TResult Function(_Raw value) raw, - }) { - return json(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(_Empty value)? empty, - TResult? Function(_Json value)? json, - TResult? Function(_Raw value)? raw, - }) { - return json?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(_Empty value)? empty, - TResult Function(_Json value)? json, - TResult Function(_Raw value)? raw, - required TResult orElse(), - }) { - if (json != null) { - return json(this); - } - return orElse(); - } -} - -abstract class _Json implements NetworkRequestBody { - const factory _Json(final Map data) = _$JsonImpl; - - Map get data; -} - -/// @nodoc - -class _$RawImpl implements _Raw { - const _$RawImpl(this.data); - - @override - final Object? data; - - @override - String toString() { - return 'NetworkRequestBody.raw(data: $data)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$RawImpl && - const DeepCollectionEquality().equals(other.data, data)); - } - - @override - int get hashCode => - Object.hash(runtimeType, const DeepCollectionEquality().hash(data)); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function() empty, - required TResult Function(Map data) json, - required TResult Function(Object? data) raw, - }) { - return raw(data); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function()? empty, - TResult? Function(Map data)? json, - TResult? Function(Object? data)? raw, - }) { - return raw?.call(data); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function()? empty, - TResult Function(Map data)? json, - TResult Function(Object? data)? raw, - required TResult orElse(), - }) { - if (raw != null) { - return raw(data); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(_Empty value) empty, - required TResult Function(_Json value) json, - required TResult Function(_Raw value) raw, - }) { - return raw(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(_Empty value)? empty, - TResult? Function(_Json value)? json, - TResult? Function(_Raw value)? raw, - }) { - return raw?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(_Empty value)? empty, - TResult Function(_Json value)? json, - TResult Function(_Raw value)? raw, - required TResult orElse(), - }) { - if (raw != null) { - return raw(this); - } - return orElse(); - } -} - -abstract class _Raw implements NetworkRequestBody { - const factory _Raw(final Object? data) = _$RawImpl; - - Object? get data; -} diff --git a/lib/src/sturdy_http.dart b/lib/src/sturdy_http.dart index 0c62f7e..66f6399 100644 --- a/lib/src/sturdy_http.dart +++ b/lib/src/sturdy_http.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:isolate'; import 'package:collection/collection.dart'; +import 'package:dart_mappable/dart_mappable.dart'; import 'package:dio/dio.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:sturdy_http/sturdy_http.dart'; @@ -31,8 +32,7 @@ class SturdyHttp { final RetryBehavior _retryBehavior; /// The interceptors provided when this [SturdyHttp] was constructed. - UnmodifiableListView get interceptors => - UnmodifiableListView(_dio.interceptors); + UnmodifiableListView get interceptors => UnmodifiableListView(_dio.interceptors); /// The base URL of the underlying [Dio] instance. String get baseUrl => _dio.options.baseUrl; @@ -51,18 +51,18 @@ class SturdyHttp { bool inferContentType = true, RetryBehavior retryBehavior = const NeverRetry(), }) : this._( - dio: _configureDio( - baseUrl: baseUrl, - deserializer: deserializer, - interceptors: interceptors, - customAdapter: customAdapter, - proxy: proxy, - inferContentType: inferContentType, - ), - deserializer: deserializer, - eventListener: eventListener, - retryBehavior: retryBehavior, - ); + dio: _configureDio( + baseUrl: baseUrl, + deserializer: deserializer, + interceptors: interceptors, + customAdapter: customAdapter, + proxy: proxy, + inferContentType: inferContentType, + ), + deserializer: deserializer, + eventListener: eventListener, + retryBehavior: retryBehavior, + ); /// {@macro http_client} SturdyHttp._({ @@ -70,10 +70,10 @@ class SturdyHttp { required Deserializer deserializer, required SturdyHttpEventListener? eventListener, required RetryBehavior retryBehavior, - }) : _dio = dio, - _deserializer = deserializer, - _eventListener = eventListener, - _retryBehavior = retryBehavior; + }) : _dio = dio, + _deserializer = deserializer, + _eventListener = eventListener, + _retryBehavior = retryBehavior; /// {@macro http_client} SturdyHttp withBaseUrl(String baseUrl) { @@ -100,37 +100,24 @@ class SturdyHttp { /// If [onResponse] fails to produce an [M] and instead throws an [Exception], /// some known failure reasons are emitted via [SturdyHttpEvent]s and the /// [Exception] is re-thrown. - Future execute( - NetworkRequest request, { - required M Function(NetworkResponse response) onResponse, - }) async { + Future execute(NetworkRequest request, {required M Function(NetworkResponse response) onResponse}) async { final responsePayload = await _handleRequest(request); try { - return await _deserializer.deserialize( - response: responsePayload.resolvedResponse, - onResponse: onResponse, - ); + return await _deserializer.deserialize(response: responsePayload.resolvedResponse, onResponse: onResponse); } on Exception catch (e) { - if (e is CheckedFromJsonException) { + if (e is MapperException || e is CheckedFromJsonException) { + final stackTrace = e is MapperException ? null : (e as CheckedFromJsonException).innerStack; await _onEvent( - SturdyHttpEvent.decodingError( - responsePayload.dioResponse!.requestOptions, - e, - e.innerStack, - ), + DecodingError(request: responsePayload.dioResponse!.requestOptions, exception: e, stackTrace: stackTrace), ); } rethrow; } } - Future<_ResponsePayload> _handleRequest( - NetworkRequest request, - ) async { - Future<(Response?, NetworkResponse)> send( - NetworkRequest request, - ) async { + Future<_ResponsePayload> _handleRequest(NetworkRequest request) async { + Future<(Response?, NetworkResponse)> send(NetworkRequest request) async { late final NetworkResponse resolvedResponse; Response? dioResponse; try { @@ -140,11 +127,11 @@ class SturdyHttp { // an empty String, which is not a subtype of Json. dioResponse = await _dio.request( request.path, - data: request.data.when( - empty: () => null, - json: (json) => json, - raw: (data) => data, - ), + data: switch (request.data) { + EmptyRequestBody() => null, + JsonRequestBody(:final data) => data, + RawRequestBody(:final data) => data, + }, queryParameters: request.queryParams, options: request.options != null ? request.options!.copyWith(method: request.type.name) @@ -168,10 +155,7 @@ class SturdyHttp { return 'Request to ${request.path} was successful but response data $messageSuffix'; } - resolvedResponse = GenericError( - message: buildErrorMessage(), - isConnectionIssue: false, - ); + resolvedResponse = GenericError(message: buildErrorMessage(), isConnectionIssue: false); } else { resolvedResponse = OkResponse(data as R); } @@ -179,7 +163,7 @@ class SturdyHttp { } on DioException catch (error) { switch (error.response?.statusCode) { case 401: - await _onEvent(SturdyHttpEvent.authFailure(error.requestOptions)); + await _onEvent(AuthFailure(request: error.requestOptions)); resolvedResponse = Unauthorized(error: error); break; case 403: @@ -189,10 +173,7 @@ class SturdyHttp { resolvedResponse = NotFound(error: error); break; case 422: - resolvedResponse = UnprocessableEntity( - error: error, - response: error.response?.data as R, - ); + resolvedResponse = UnprocessableEntity(error: error, response: error.response?.data as R); break; case 426: resolvedResponse = UpgradeRequired(error: error); @@ -205,8 +186,7 @@ class SturdyHttp { break; default: resolvedResponse = GenericError( - message: - 'Unexpected status code ${error.response?.statusCode} returned for ${request.path}', + message: 'Unexpected status code ${error.response?.statusCode} returned for ${request.path}', isConnectionIssue: error.isConnectionIssue(), error: error, ); @@ -218,17 +198,13 @@ class SturdyHttp { RetryBehavior determineRetryBehavior() { // The request's retry behavior takes precedence over the client's final priority = [request.retryBehavior, _retryBehavior]; - return priority.firstWhere( - (b) => b is! Unspecified, - orElse: () => NeverRetry(), - ); + return priority.firstWhere((b) => b is! Unspecified, orElse: () => NeverRetry()); } final retryBehavior = determineRetryBehavior(); var response = await send(request); var retryCount = 0; - while (!response.$2.isSuccess && - retryBehavior.shouldRetry(response.$1, retryCount)) { + while (!response.$2.isSuccess && retryBehavior.shouldRetry(response.$1, retryCount)) { // `retryBehavior` must be a `Retry`, otherwise we wouldn't be here. await Future.delayed((retryBehavior as Retry).retryInterval); retryCount++; @@ -236,16 +212,10 @@ class SturdyHttp { } if (response.$2.isSuccess && request.shouldTriggerDataMutation) { - await _onEvent( - SturdyHttpEvent.mutativeRequestSuccess(response.$1!.requestOptions), - ); + await _onEvent(MutativeRequestSuccess(request: response.$1!.requestOptions)); } - return _ResponsePayload( - request: request, - dioResponse: response.$1, - resolvedResponse: response.$2, - ); + return _ResponsePayload(request: request, dioResponse: response.$1, resolvedResponse: response.$2); } } @@ -254,11 +224,7 @@ class _ResponsePayload { final Response? dioResponse; final NetworkResponse resolvedResponse; - _ResponsePayload({ - required this.request, - required this.dioResponse, - required this.resolvedResponse, - }); + _ResponsePayload({required this.request, required this.dioResponse, required this.resolvedResponse}); } Dio _configureDio({ @@ -271,9 +237,7 @@ Dio _configureDio({ }) { return Dio() // Instruct Dio to use the same Isolate approach as requested of SturdyHttp - ..transformer = deserializer is MainIsolateDeserializer - ? SyncTransformer() - : BackgroundTransformer() + ..transformer = deserializer is MainIsolateDeserializer ? SyncTransformer() : BackgroundTransformer() ..options.baseUrl = baseUrl ..options.listFormat = ListFormat.multiCompatible ..interceptors.addAll(interceptors) diff --git a/lib/src/sturdy_http_event_listener.dart b/lib/src/sturdy_http_event_listener.dart index 2e996b7..998a5af 100644 --- a/lib/src/sturdy_http_event_listener.dart +++ b/lib/src/sturdy_http_event_listener.dart @@ -1,14 +1,10 @@ import 'package:dio/dio.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'sturdy_http_event_listener.freezed.dart'; +import 'package:sturdy_http/src/network_request.dart'; /// {@template http_client_event_listener} -/// /// A listener that can optionally be supplied when constructing /// an [SturdyHttp] to be notified when various events occur /// while processing [NetworkRequest]s. -/// /// {@endtemplate} abstract class SturdyHttpEventListener { /// {@macro http_client_event_listener} @@ -19,23 +15,42 @@ abstract class SturdyHttpEventListener { } /// Represents an event that occurs during a request lifecycle. -@freezed -class SturdyHttpEvent with _$SturdyHttpEvent { - /// Indicates that there was an Exception thrown when attempting to decode - /// the server response. - const factory SturdyHttpEvent.decodingError( - RequestOptions request, - Exception exception, - StackTrace? stackTrace, - ) = _JsonDecodingError; - - /// Indicates that a network request returned a response with status code 401. - const factory SturdyHttpEvent.authFailure(RequestOptions request) = - _AuthFailure; - - /// Indicates that a "mutative" request succeeded and the data on the client - /// likely does not match the data on the server. - /// See [NetworkRequest.shouldTriggerDataMutation]. - const factory SturdyHttpEvent.mutativeRequestSuccess(RequestOptions request) = - _OnMutativeRequestSuccess; +sealed class SturdyHttpEvent { + /// The [RequestOptions] associated with the request that triggered this event. + final RequestOptions request; + + SturdyHttpEvent({required this.request}); +} + +/// {@template decoding_error} +/// Indicates that there was an Exception thrown when attempting to decode +/// the server response. +/// {@endtemplate} +final class DecodingError extends SturdyHttpEvent { + /// The exception that was thrown during decoding + final Exception exception; + + /// The stack trace at the point the exception was thrown + final StackTrace? stackTrace; + + /// {@macro decoding_error} + DecodingError({required super.request, required this.exception, required this.stackTrace}); +} + +/// {@template auth_failure} +/// Indicates that a network request returned a response with status code 401. +/// {@endtemplate} +final class AuthFailure extends SturdyHttpEvent { + /// {@macro auth_failure} + AuthFailure({required super.request}); +} + +/// {@template mutative_request_success} +/// Indicates that a "mutative" request succeeded and the data on the client +/// likely does not match the data on the server. +/// See [NetworkRequest.shouldTriggerDataMutation]. +/// {@endtemplate} +final class MutativeRequestSuccess extends SturdyHttpEvent { + /// {@macro mutative_request_success} + MutativeRequestSuccess({required super.request}); } diff --git a/lib/src/sturdy_http_event_listener.freezed.dart b/lib/src/sturdy_http_event_listener.freezed.dart deleted file mode 100644 index dab4ba9..0000000 --- a/lib/src/sturdy_http_event_listener.freezed.dart +++ /dev/null @@ -1,612 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'sturdy_http_event_listener.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -/// @nodoc -mixin _$SturdyHttpEvent { - RequestOptions get request => throw _privateConstructorUsedError; - @optionalTypeArgs - TResult when({ - required TResult Function( - RequestOptions request, Exception exception, StackTrace? stackTrace) - decodingError, - required TResult Function(RequestOptions request) authFailure, - required TResult Function(RequestOptions request) mutativeRequestSuccess, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(RequestOptions request, Exception exception, - StackTrace? stackTrace)? - decodingError, - TResult? Function(RequestOptions request)? authFailure, - TResult? Function(RequestOptions request)? mutativeRequestSuccess, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(RequestOptions request, Exception exception, - StackTrace? stackTrace)? - decodingError, - TResult Function(RequestOptions request)? authFailure, - TResult Function(RequestOptions request)? mutativeRequestSuccess, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult map({ - required TResult Function(_JsonDecodingError value) decodingError, - required TResult Function(_AuthFailure value) authFailure, - required TResult Function(_OnMutativeRequestSuccess value) - mutativeRequestSuccess, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(_JsonDecodingError value)? decodingError, - TResult? Function(_AuthFailure value)? authFailure, - TResult? Function(_OnMutativeRequestSuccess value)? mutativeRequestSuccess, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeMap({ - TResult Function(_JsonDecodingError value)? decodingError, - TResult Function(_AuthFailure value)? authFailure, - TResult Function(_OnMutativeRequestSuccess value)? mutativeRequestSuccess, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; - - /// Create a copy of SturdyHttpEvent - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $SturdyHttpEventCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $SturdyHttpEventCopyWith<$Res> { - factory $SturdyHttpEventCopyWith( - SturdyHttpEvent value, $Res Function(SturdyHttpEvent) then) = - _$SturdyHttpEventCopyWithImpl<$Res, SturdyHttpEvent>; - @useResult - $Res call({RequestOptions request}); -} - -/// @nodoc -class _$SturdyHttpEventCopyWithImpl<$Res, $Val extends SturdyHttpEvent> - implements $SturdyHttpEventCopyWith<$Res> { - _$SturdyHttpEventCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of SturdyHttpEvent - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? request = null, - }) { - return _then(_value.copyWith( - request: null == request - ? _value.request - : request // ignore: cast_nullable_to_non_nullable - as RequestOptions, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$JsonDecodingErrorImplCopyWith<$Res> - implements $SturdyHttpEventCopyWith<$Res> { - factory _$$JsonDecodingErrorImplCopyWith(_$JsonDecodingErrorImpl value, - $Res Function(_$JsonDecodingErrorImpl) then) = - __$$JsonDecodingErrorImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {RequestOptions request, Exception exception, StackTrace? stackTrace}); -} - -/// @nodoc -class __$$JsonDecodingErrorImplCopyWithImpl<$Res> - extends _$SturdyHttpEventCopyWithImpl<$Res, _$JsonDecodingErrorImpl> - implements _$$JsonDecodingErrorImplCopyWith<$Res> { - __$$JsonDecodingErrorImplCopyWithImpl(_$JsonDecodingErrorImpl _value, - $Res Function(_$JsonDecodingErrorImpl) _then) - : super(_value, _then); - - /// Create a copy of SturdyHttpEvent - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? request = null, - Object? exception = null, - Object? stackTrace = freezed, - }) { - return _then(_$JsonDecodingErrorImpl( - null == request - ? _value.request - : request // ignore: cast_nullable_to_non_nullable - as RequestOptions, - null == exception - ? _value.exception - : exception // ignore: cast_nullable_to_non_nullable - as Exception, - freezed == stackTrace - ? _value.stackTrace - : stackTrace // ignore: cast_nullable_to_non_nullable - as StackTrace?, - )); - } -} - -/// @nodoc - -class _$JsonDecodingErrorImpl implements _JsonDecodingError { - const _$JsonDecodingErrorImpl(this.request, this.exception, this.stackTrace); - - @override - final RequestOptions request; - @override - final Exception exception; - @override - final StackTrace? stackTrace; - - @override - String toString() { - return 'SturdyHttpEvent.decodingError(request: $request, exception: $exception, stackTrace: $stackTrace)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$JsonDecodingErrorImpl && - (identical(other.request, request) || other.request == request) && - (identical(other.exception, exception) || - other.exception == exception) && - (identical(other.stackTrace, stackTrace) || - other.stackTrace == stackTrace)); - } - - @override - int get hashCode => Object.hash(runtimeType, request, exception, stackTrace); - - /// Create a copy of SturdyHttpEvent - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$JsonDecodingErrorImplCopyWith<_$JsonDecodingErrorImpl> get copyWith => - __$$JsonDecodingErrorImplCopyWithImpl<_$JsonDecodingErrorImpl>( - this, _$identity); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function( - RequestOptions request, Exception exception, StackTrace? stackTrace) - decodingError, - required TResult Function(RequestOptions request) authFailure, - required TResult Function(RequestOptions request) mutativeRequestSuccess, - }) { - return decodingError(request, exception, stackTrace); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(RequestOptions request, Exception exception, - StackTrace? stackTrace)? - decodingError, - TResult? Function(RequestOptions request)? authFailure, - TResult? Function(RequestOptions request)? mutativeRequestSuccess, - }) { - return decodingError?.call(request, exception, stackTrace); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(RequestOptions request, Exception exception, - StackTrace? stackTrace)? - decodingError, - TResult Function(RequestOptions request)? authFailure, - TResult Function(RequestOptions request)? mutativeRequestSuccess, - required TResult orElse(), - }) { - if (decodingError != null) { - return decodingError(request, exception, stackTrace); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(_JsonDecodingError value) decodingError, - required TResult Function(_AuthFailure value) authFailure, - required TResult Function(_OnMutativeRequestSuccess value) - mutativeRequestSuccess, - }) { - return decodingError(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(_JsonDecodingError value)? decodingError, - TResult? Function(_AuthFailure value)? authFailure, - TResult? Function(_OnMutativeRequestSuccess value)? mutativeRequestSuccess, - }) { - return decodingError?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(_JsonDecodingError value)? decodingError, - TResult Function(_AuthFailure value)? authFailure, - TResult Function(_OnMutativeRequestSuccess value)? mutativeRequestSuccess, - required TResult orElse(), - }) { - if (decodingError != null) { - return decodingError(this); - } - return orElse(); - } -} - -abstract class _JsonDecodingError implements SturdyHttpEvent { - const factory _JsonDecodingError( - final RequestOptions request, - final Exception exception, - final StackTrace? stackTrace) = _$JsonDecodingErrorImpl; - - @override - RequestOptions get request; - Exception get exception; - StackTrace? get stackTrace; - - /// Create a copy of SturdyHttpEvent - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$JsonDecodingErrorImplCopyWith<_$JsonDecodingErrorImpl> get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class _$$AuthFailureImplCopyWith<$Res> - implements $SturdyHttpEventCopyWith<$Res> { - factory _$$AuthFailureImplCopyWith( - _$AuthFailureImpl value, $Res Function(_$AuthFailureImpl) then) = - __$$AuthFailureImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({RequestOptions request}); -} - -/// @nodoc -class __$$AuthFailureImplCopyWithImpl<$Res> - extends _$SturdyHttpEventCopyWithImpl<$Res, _$AuthFailureImpl> - implements _$$AuthFailureImplCopyWith<$Res> { - __$$AuthFailureImplCopyWithImpl( - _$AuthFailureImpl _value, $Res Function(_$AuthFailureImpl) _then) - : super(_value, _then); - - /// Create a copy of SturdyHttpEvent - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? request = null, - }) { - return _then(_$AuthFailureImpl( - null == request - ? _value.request - : request // ignore: cast_nullable_to_non_nullable - as RequestOptions, - )); - } -} - -/// @nodoc - -class _$AuthFailureImpl implements _AuthFailure { - const _$AuthFailureImpl(this.request); - - @override - final RequestOptions request; - - @override - String toString() { - return 'SturdyHttpEvent.authFailure(request: $request)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$AuthFailureImpl && - (identical(other.request, request) || other.request == request)); - } - - @override - int get hashCode => Object.hash(runtimeType, request); - - /// Create a copy of SturdyHttpEvent - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$AuthFailureImplCopyWith<_$AuthFailureImpl> get copyWith => - __$$AuthFailureImplCopyWithImpl<_$AuthFailureImpl>(this, _$identity); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function( - RequestOptions request, Exception exception, StackTrace? stackTrace) - decodingError, - required TResult Function(RequestOptions request) authFailure, - required TResult Function(RequestOptions request) mutativeRequestSuccess, - }) { - return authFailure(request); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(RequestOptions request, Exception exception, - StackTrace? stackTrace)? - decodingError, - TResult? Function(RequestOptions request)? authFailure, - TResult? Function(RequestOptions request)? mutativeRequestSuccess, - }) { - return authFailure?.call(request); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(RequestOptions request, Exception exception, - StackTrace? stackTrace)? - decodingError, - TResult Function(RequestOptions request)? authFailure, - TResult Function(RequestOptions request)? mutativeRequestSuccess, - required TResult orElse(), - }) { - if (authFailure != null) { - return authFailure(request); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(_JsonDecodingError value) decodingError, - required TResult Function(_AuthFailure value) authFailure, - required TResult Function(_OnMutativeRequestSuccess value) - mutativeRequestSuccess, - }) { - return authFailure(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(_JsonDecodingError value)? decodingError, - TResult? Function(_AuthFailure value)? authFailure, - TResult? Function(_OnMutativeRequestSuccess value)? mutativeRequestSuccess, - }) { - return authFailure?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(_JsonDecodingError value)? decodingError, - TResult Function(_AuthFailure value)? authFailure, - TResult Function(_OnMutativeRequestSuccess value)? mutativeRequestSuccess, - required TResult orElse(), - }) { - if (authFailure != null) { - return authFailure(this); - } - return orElse(); - } -} - -abstract class _AuthFailure implements SturdyHttpEvent { - const factory _AuthFailure(final RequestOptions request) = _$AuthFailureImpl; - - @override - RequestOptions get request; - - /// Create a copy of SturdyHttpEvent - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$AuthFailureImplCopyWith<_$AuthFailureImpl> get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class _$$OnMutativeRequestSuccessImplCopyWith<$Res> - implements $SturdyHttpEventCopyWith<$Res> { - factory _$$OnMutativeRequestSuccessImplCopyWith( - _$OnMutativeRequestSuccessImpl value, - $Res Function(_$OnMutativeRequestSuccessImpl) then) = - __$$OnMutativeRequestSuccessImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({RequestOptions request}); -} - -/// @nodoc -class __$$OnMutativeRequestSuccessImplCopyWithImpl<$Res> - extends _$SturdyHttpEventCopyWithImpl<$Res, _$OnMutativeRequestSuccessImpl> - implements _$$OnMutativeRequestSuccessImplCopyWith<$Res> { - __$$OnMutativeRequestSuccessImplCopyWithImpl( - _$OnMutativeRequestSuccessImpl _value, - $Res Function(_$OnMutativeRequestSuccessImpl) _then) - : super(_value, _then); - - /// Create a copy of SturdyHttpEvent - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? request = null, - }) { - return _then(_$OnMutativeRequestSuccessImpl( - null == request - ? _value.request - : request // ignore: cast_nullable_to_non_nullable - as RequestOptions, - )); - } -} - -/// @nodoc - -class _$OnMutativeRequestSuccessImpl implements _OnMutativeRequestSuccess { - const _$OnMutativeRequestSuccessImpl(this.request); - - @override - final RequestOptions request; - - @override - String toString() { - return 'SturdyHttpEvent.mutativeRequestSuccess(request: $request)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$OnMutativeRequestSuccessImpl && - (identical(other.request, request) || other.request == request)); - } - - @override - int get hashCode => Object.hash(runtimeType, request); - - /// Create a copy of SturdyHttpEvent - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$OnMutativeRequestSuccessImplCopyWith<_$OnMutativeRequestSuccessImpl> - get copyWith => __$$OnMutativeRequestSuccessImplCopyWithImpl< - _$OnMutativeRequestSuccessImpl>(this, _$identity); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function( - RequestOptions request, Exception exception, StackTrace? stackTrace) - decodingError, - required TResult Function(RequestOptions request) authFailure, - required TResult Function(RequestOptions request) mutativeRequestSuccess, - }) { - return mutativeRequestSuccess(request); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(RequestOptions request, Exception exception, - StackTrace? stackTrace)? - decodingError, - TResult? Function(RequestOptions request)? authFailure, - TResult? Function(RequestOptions request)? mutativeRequestSuccess, - }) { - return mutativeRequestSuccess?.call(request); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(RequestOptions request, Exception exception, - StackTrace? stackTrace)? - decodingError, - TResult Function(RequestOptions request)? authFailure, - TResult Function(RequestOptions request)? mutativeRequestSuccess, - required TResult orElse(), - }) { - if (mutativeRequestSuccess != null) { - return mutativeRequestSuccess(request); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(_JsonDecodingError value) decodingError, - required TResult Function(_AuthFailure value) authFailure, - required TResult Function(_OnMutativeRequestSuccess value) - mutativeRequestSuccess, - }) { - return mutativeRequestSuccess(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(_JsonDecodingError value)? decodingError, - TResult? Function(_AuthFailure value)? authFailure, - TResult? Function(_OnMutativeRequestSuccess value)? mutativeRequestSuccess, - }) { - return mutativeRequestSuccess?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(_JsonDecodingError value)? decodingError, - TResult Function(_AuthFailure value)? authFailure, - TResult Function(_OnMutativeRequestSuccess value)? mutativeRequestSuccess, - required TResult orElse(), - }) { - if (mutativeRequestSuccess != null) { - return mutativeRequestSuccess(this); - } - return orElse(); - } -} - -abstract class _OnMutativeRequestSuccess implements SturdyHttpEvent { - const factory _OnMutativeRequestSuccess(final RequestOptions request) = - _$OnMutativeRequestSuccessImpl; - - @override - RequestOptions get request; - - /// Create a copy of SturdyHttpEvent - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$OnMutativeRequestSuccessImplCopyWith<_$OnMutativeRequestSuccessImpl> - get copyWith => throw _privateConstructorUsedError; -} diff --git a/lib/sturdy_http.dart b/lib/sturdy_http.dart index 1f15dcc..815a59d 100644 --- a/lib/sturdy_http.dart +++ b/lib/sturdy_http.dart @@ -1,6 +1,3 @@ -/// A strongly typed, event-based, reliable HTTP client that wraps Dio. -library sturdy_http; - export 'src/_dio_error.dart'; export 'src/network_request.dart'; export 'src/network_response.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 4b5d4eb..37a4a7f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,19 +5,18 @@ homepage: https://github.com/Betterment/sturdy_http repository: https://github.com/Betterment/sturdy_http environment: - sdk: ">=3.5.0 <4.0.0" + sdk: '>=3.10.0 <4.0.0' dependencies: - collection: ^1.18.0 - dio: ^5.7.0 - freezed_annotation: ^2.4.4 - uuid: ^4.5.0 + collection: ^1.19.1 + dart_mappable: ^4.6.1 + dio: ^5.9.0 + freezed_annotation: ^3.1.0 + uuid: ^4.5.2 dev_dependencies: - build_runner: ^2.4.12 - charlatan: ^0.4.0 - freezed: ^2.5.7 - json_annotation: ^4.9.0 - json_serializable: ^6.8.0 - lints: ^4.0.0 - test: ^1.25.8 + build_runner: ^2.10.4 + charlatan: ^0.5.0 + dart_mappable_builder: ^4.6.3 + lints: ^6.0.0 + test: ^1.28.0 diff --git a/test/src/deserializer_test.dart b/test/src/deserializer_test.dart index 6c6f858..e0ab3e1 100644 --- a/test/src/deserializer_test.dart +++ b/test/src/deserializer_test.dart @@ -2,7 +2,7 @@ import 'dart:isolate'; -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:dart_mappable/dart_mappable.dart'; import 'package:sturdy_http/sturdy_http.dart'; import 'package:test/test.dart'; @@ -10,8 +10,7 @@ import 'sturdy_http_test.dart'; void main() { group('BackgroundDeserializer', () { - test('it invokes onResponse on a non-main Isolate and sends result back', - () async { + test('it invokes onResponse on a non-main Isolate and sends result back', () async { onResponse(NetworkResponse response) { final isolateName = Isolate.current.debugName; // Hijack `Foo` to send over the `IsolateName` since @@ -21,57 +20,43 @@ void main() { return Foo(message: isolateName!); } - final response = OkResponse(const Foo(message: '--').toJson()); + final response = OkResponse(const Foo(message: '--').toMap()); final subject = BackgroundDeserializer(); - final result = await subject.deserialize( - response: response, - onResponse: onResponse, - ); + final result = await subject.deserialize(response: response, onResponse: onResponse); expect(result.message, 'sturdyHttpWorkerIsolate'); }); test('it handles multiple requests for deserialization', () async { Foo onResponse(NetworkResponse response) { return switch (response) { - OkResponse(:final response) => Foo.fromJson(response), + OkResponse(:final response) => FooMapper.fromMap(response), _ => fail('Not expected: orElse'), }; } - final responseOne = OkResponse(const Foo(message: '1').toJson()); - final responseTwo = OkResponse(const Foo(message: '2').toJson()); + final responseOne = OkResponse(const Foo(message: '1').toMap()); + final responseTwo = OkResponse(const Foo(message: '2').toMap()); final subject = BackgroundDeserializer(); - final resultOne = await subject.deserialize( - response: responseOne, - onResponse: onResponse, - ); - final resultTwo = await subject.deserialize( - response: responseTwo, - onResponse: onResponse, - ); + final resultOne = await subject.deserialize(response: responseOne, onResponse: onResponse); + final resultTwo = await subject.deserialize(response: responseTwo, onResponse: onResponse); expect(resultOne.message, '1'); expect(resultTwo.message, '2'); }); - test( - 'it throws CheckedFromJsonExceptions when deserialization issues occur', - () async { + test('it throws MapperExceptions when deserialization issues occur', () async { onResponse(NetworkResponse response) { return switch (response) { - OkResponse(:final response) => NotFoo.fromJson(response), + OkResponse(:final response) => NotFooMapper.fromMap(response), _ => fail('orElse not expected'), }; } - final response = OkResponse(const Foo(message: 'Nope').toJson()); + final response = OkResponse(const Foo(message: 'Nope').toMap()); final subject = BackgroundDeserializer(); try { - await subject.deserialize( - response: response, - onResponse: onResponse, - ); + await subject.deserialize(response: response, onResponse: onResponse); } on Exception catch (e) { - expect(e, isA()); + expect(e, isA()); } }); }); diff --git a/test/src/sturdy_http_test.dart b/test/src/sturdy_http_test.dart index de2a328..f3772af 100644 --- a/test/src/sturdy_http_test.dart +++ b/test/src/sturdy_http_test.dart @@ -1,13 +1,12 @@ import 'dart:io'; import 'package:charlatan/charlatan.dart'; +import 'package:dart_mappable/dart_mappable.dart'; import 'package:dio/dio.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:sturdy_http/sturdy_http.dart'; import 'package:test/test.dart' hide Retry; -part 'sturdy_http_test.freezed.dart'; -part 'sturdy_http_test.g.dart'; +part 'sturdy_http_test.mapper.dart'; void main() { group('SturdyHttp', () { @@ -56,10 +55,7 @@ void main() { group('interceptors', () { test('it returns the provided interceptors', () { final interceptors = [_FakeInterceptor()]; - final subject = buildSubject( - interceptors: interceptors, - inferContentType: false, - ); + final subject = buildSubject(interceptors: interceptors, inferContentType: false); expect(subject.interceptors, interceptors); }); @@ -73,8 +69,7 @@ void main() { interceptors: [ _FakeInterceptor( onRequestInvoked: (options) { - contentType = - options.headers[Headers.contentTypeHeader] as String?; + contentType = options.headers[Headers.contentTypeHeader] as String?; }, ), ], @@ -82,10 +77,7 @@ void main() { charlatan.whenPost('/infer', (request) => CharlatanHttpResponse()); - await subject.execute( - PostRequest('/infer', data: NetworkRequestBody.json({'foo': 'bar'})), - onResponse: (r) {}, - ); + await subject.execute(PostRequest('/infer', data: JsonRequestBody({'foo': 'bar'})), onResponse: (r) {}); expect(contentType, isNull); }); @@ -97,8 +89,7 @@ void main() { interceptors: [ _FakeInterceptor( onRequestInvoked: (options) { - contentType = - options.headers[Headers.contentTypeHeader] as String?; + contentType = options.headers[Headers.contentTypeHeader] as String?; }, ), ], @@ -106,10 +97,7 @@ void main() { charlatan.whenPost('/infer', (request) => CharlatanHttpResponse()); - await subject.execute( - PostRequest('/infer', data: NetworkRequestBody.json({'foo': 'bar'})), - onResponse: (r) {}, - ); + await subject.execute(PostRequest('/infer', data: JsonRequestBody({'foo': 'bar'})), onResponse: (r) {}); expect(contentType, 'application/json'); }); @@ -133,12 +121,8 @@ void main() { }); group('withBaseUrl', () { - test( - 'it returns a new instance with correct baseUrl and pre-configured settings', - () { - final oldInstance = buildSubject( - interceptors: [_FakeInterceptor()], - ); + test('it returns a new instance with correct baseUrl and pre-configured settings', () { + final oldInstance = buildSubject(interceptors: [_FakeInterceptor()]); expect(oldInstance.baseUrl != 'https://foo.com', isTrue); final newInstance = oldInstance.withBaseUrl('https://foo.com'); @@ -184,14 +168,7 @@ void main() { group('when NetworkRequestBody is json', () { test('request options contain json data', () async { await buildSubject().execute( - const GetRequest( - '/foo', - data: NetworkRequestBody.json( - { - 'foo': 'bar', - }, - ), - ), + const GetRequest('/foo', data: JsonRequestBody({'foo': 'bar'})), onResponse: (response) { return null; }, @@ -210,11 +187,7 @@ void main() { await buildSubject().execute( GetRequest( '/foo', - data: NetworkRequestBody.json( - { - 'foo': 'bar', - }, - ), + data: JsonRequestBody({'foo': 'bar'}), options: Options(extra: {'foo': 'bar'}), ), onResponse: (response) { @@ -234,10 +207,7 @@ void main() { group('queryParameters', () { test('request options contain correct query parameters ', () async { await buildSubject().execute( - const GetRequest( - '/foo', - queryParameters: {'foo': 'bar'}, - ), + const GetRequest('/foo', queryParameters: {'foo': 'bar'}), onResponse: (response) {}, ); expect( @@ -251,16 +221,10 @@ void main() { group('method', () { test('request options contain correct method ', () async { - await buildSubject().execute( - const GetRequest('/foo'), - onResponse: (response) {}, - ); + await buildSubject().execute(const GetRequest('/foo'), onResponse: (response) {}); expect(options.method, 'GET'); await buildSubject().execute( - const PostRequest( - '/bar', - data: NetworkRequestBody.empty(), - ), + const PostRequest('/bar', data: EmptyRequestBody()), onResponse: (response) {}, ); expect(options.method, 'POST'); @@ -274,70 +238,49 @@ void main() { group('when data is returned', () { setUp(() { charlatan - ..whenGet( - '/foo', - (request) => CharlatanHttpResponse( - body: Foo(message: 'Hello world').toJson(), - ), - ) + ..whenGet('/foo', (request) => CharlatanHttpResponse(body: Foo(message: 'Hello world').toMap())) ..whenGet( '/not-foo', - (request) => CharlatanHttpResponse( - body: NotFoo(notMessage: 'Hello world').toJson(), - ), + (request) => CharlatanHttpResponse(body: NotFoo(notMessage: 'Hello world').toMap()), ) - ..whenGet( - '/bar', - (request) => CharlatanHttpResponse(body: 'a raw string'), - ); + ..whenGet('/bar', (request) => CharlatanHttpResponse(body: 'a raw string')); }); group('when deserialization succeeds', () { test('it returns parsed model', () async { - final response = - await buildSubject().execute>( + final response = await buildSubject().execute>( const GetRequest('/foo'), onResponse: (response) { return switch (response) { - OkResponse(:final response) => - Result.success(Foo.fromJson(response)), - _ => const Result.failure('Not expected: orElse'), + OkResponse(:final response) => Success(FooMapper.fromMap(response)), + _ => const Failure('Not expected: orElse'), }; }, ); - response.when( - success: (foo) => expect(foo.message, 'Hello world'), - failure: fail, - ); + switch (response) { + case Success(:final success): + expect(success.message, 'Hello world'); + case Failure(): + fail('Expected Success'); + } }); }); group('when deserialization fails', () { - test( - 'it emits a decodingError event and rethrows the Exception', - () async { - final request = - buildSubject().execute>( + test('it emits a decodingError event and rethrows the Exception', () async { + final request = buildSubject().execute>( const GetRequest('/not-foo'), onResponse: (response) { return switch (response) { - OkResponse(:final response) => - Result.success(Foo.fromJson(response)), - _ => const Result.failure('Not expected: orElse'), + OkResponse(:final response) => Success(FooMapper.fromMap(response)), + _ => const Failure('Not expected: orElse'), }; }, ); - await expectLater( - request, - throwsA(isA()), - ); - - expect( - jsonDecodingErrors['/not-foo'].toString(), - contains('CheckedFromJsonException'), - ); + await expectLater(request, throwsA(isA())); + expect(jsonDecodingErrors['/not-foo'].toString(), contains('MapperException')); }); }); }); @@ -345,74 +288,55 @@ void main() { group('when no data is returned', () { group('when status code is 204', () { setUp(() { - charlatan.whenPost( - '/foo', - (request) => CharlatanHttpResponse( - statusCode: 204, - body: null, - ), - ); + charlatan.whenPost('/foo', (request) => CharlatanHttpResponse(statusCode: 204, body: null)); }); test('it returns okNoContent', () async { - final response = - await buildSubject().execute>( - const PostRequest( - '/foo', - data: NetworkRequestBody.empty(), - ), + final response = await buildSubject().execute>( + const PostRequest('/foo', data: EmptyRequestBody()), onResponse: (response) { return switch (response) { - OkNoContent() => const Result.success(true), - _ => const Result.failure('Not expected: orElse'), + OkNoContent() => const Success(true), + _ => const Failure('Not expected: orElse'), }; }, ); - response.when( - success: (s) => expect(s, isTrue), - failure: fail, - ); + switch (response) { + case Success(:final success): + expect(success, isTrue); + case Failure(): + fail('Expected Success'); + } }); }); group('when status code is non-204', () { setUp(() { - charlatan.whenPost( - '/foo', - (request) => CharlatanHttpResponse( - statusCode: 200, - body: null, - ), - ); + charlatan.whenPost('/foo', (request) => CharlatanHttpResponse(statusCode: 200, body: null)); }); - test( - 'it returns genericError and isConnectionIssue is false', - () async { - final response = await buildSubject() - .execute>( - const PostRequest( - '/foo', - data: NetworkRequestBody.empty(), - ), - onResponse: (response) { - return switch (response) { - GenericError(:final isConnectionIssue) => () { - { - expect(isConnectionIssue, isFalse); - return const Result.success(true); - } - }(), - _ => const Result.failure('Not expected: orElse'), - }; - }, - ); + test('it returns genericError and isConnectionIssue is false', () async { + final response = await buildSubject().execute>( + const PostRequest('/foo', data: EmptyRequestBody()), + onResponse: (response) { + return switch (response) { + GenericError(:final isConnectionIssue) => () { + { + expect(isConnectionIssue, isFalse); + return const Success(true); + } + }(), + _ => const Failure('Not expected: orElse'), + }; + }, + ); - response.when( - success: (s) => expect(s, isTrue), - failure: fail, - ); - }, - ); + switch (response) { + case Success(:final success): + expect(success, isTrue); + case Failure(): + fail('Expected Success'); + } + }); }); }); }); @@ -424,155 +348,94 @@ void main() { charlatan ..whenPost( '/foo', - (request) => CharlatanHttpResponse( - body: {'foo': 'bar'}, - statusCode: 200, - ), + (request) => CharlatanHttpResponse(body: {'foo': 'bar'}, statusCode: 200), ) ..whenPut( '/bar', - (request) => CharlatanHttpResponse( - body: {'foo': 'bar'}, - statusCode: 204, - ), + (request) => CharlatanHttpResponse(body: {'foo': 'bar'}, statusCode: 204), ) ..whenDelete( '/baz', - (request) => CharlatanHttpResponse( - body: {'foo': 'bar'}, - statusCode: 200, - ), + (request) => CharlatanHttpResponse(body: {'foo': 'bar'}, statusCode: 200), ); }); - test( - 'it emits a MutativeRequestSuccess event with correct path', - () async { + test('it emits a MutativeRequestSuccess event with correct path', () async { final subject = buildSubject(); await Future.wait([ subject.execute>( - const PostRequest( - '/foo', - data: NetworkRequestBody.empty(), - ), + const PostRequest('/foo', data: EmptyRequestBody()), onResponse: (response) { return switch (response) { - OkResponse(:final response) => - Result.success(response['foo'] as String), - _ => const Result.failure('Not expected: orElse'), + OkResponse(:final response) => Success(response['foo'] as String), + _ => const Failure('Not expected: orElse'), }; }, ), subject.execute>( - const PutRequest( - '/bar', - data: NetworkRequestBody.empty(), - ), + const PutRequest('/bar', data: EmptyRequestBody()), onResponse: (response) { return switch (response) { - OkResponse(:final response) => - Result.success(response['foo'] as String), - _ => const Result.failure('Not expected: orElse'), + OkResponse(:final response) => Success(response['foo'] as String), + _ => const Failure('Not expected: orElse'), }; }, ), subject.execute>( - const DeleteRequest( - '/baz', - data: NetworkRequestBody.empty(), - ), + const DeleteRequest('/baz', data: EmptyRequestBody()), onResponse: (response) { return switch (response) { - OkResponse(:final response) => - Result.success(response['foo'] as String), - _ => const Result.failure('Not expected: orElse'), + OkResponse(:final response) => Success(response['foo'] as String), + _ => const Failure('Not expected: orElse'), }; }, ), ]); - expect( - mutativeRequestSuccessRequests.map((e) => e.path), - contains('/foo'), - ); - expect( - mutativeRequestSuccessRequests.map((e) => e.path), - contains('/bar'), - ); - expect( - mutativeRequestSuccessRequests.map((e) => e.path), - contains('/baz'), - ); + expect(mutativeRequestSuccessRequests.map((e) => e.path), contains('/foo')); + expect(mutativeRequestSuccessRequests.map((e) => e.path), contains('/bar')); + expect(mutativeRequestSuccessRequests.map((e) => e.path), contains('/baz')); }); }); - group('and the response has status codes other than 200 or 204', - () { + group('and the response has status codes other than 200 or 204', () { setUp(() { charlatan - ..whenPost( - '/foo', - (request) => CharlatanHttpResponse( - body: {}, - statusCode: 404, - ), - ) - ..whenPut( - '/bar', - (request) => CharlatanHttpResponse( - body: {}, - statusCode: 422, - ), - ) + ..whenPost('/foo', (request) => CharlatanHttpResponse(body: {}, statusCode: 404)) + ..whenPut('/bar', (request) => CharlatanHttpResponse(body: {}, statusCode: 422)) ..whenDelete( '/baz', - (request) => CharlatanHttpResponse( - body: {}, - statusCode: 500, - ), + (request) => CharlatanHttpResponse(body: {}, statusCode: 500), ); }); - test('it does not emit a MutativeRequestSuccess event', - () async { + test('it does not emit a MutativeRequestSuccess event', () async { final subject = buildSubject(); await Future.wait([ subject.execute>( - const PostRequest( - '/foo', - data: NetworkRequestBody.empty(), - ), + const PostRequest('/foo', data: EmptyRequestBody()), onResponse: (response) { return switch (response) { - OkResponse(:final response) => - Result.success(response['foo'] as String), - _ => const Result.failure('Not expected: orElse'), + OkResponse(:final response) => Success(response['foo'] as String), + _ => const Failure('Not expected: orElse'), }; }, ), subject.execute>( - const PutRequest( - '/bar', - data: NetworkRequestBody.empty(), - ), + const PutRequest('/bar', data: EmptyRequestBody()), onResponse: (response) { return switch (response) { - OkResponse(:final response) => - Result.success(response['foo'] as String), - _ => const Result.failure('Not expected: orElse'), + OkResponse(:final response) => Success(response['foo'] as String), + _ => const Failure('Not expected: orElse'), }; }, ), subject.execute>( - const DeleteRequest( - '/baz', - data: NetworkRequestBody.empty(), - ), + const DeleteRequest('/baz', data: EmptyRequestBody()), onResponse: (response) { return switch (response) { - OkResponse(:final response) => - Result.success(response['foo'] as String), - _ => const Result.failure('Not expected: orElse'), + OkResponse(:final response) => Success(response['foo'] as String), + _ => const Failure('Not expected: orElse'), }; }, ), @@ -587,18 +450,8 @@ void main() { group('when response is unsuccessful', () { const defaultPath = '/foo'; - void setupErrorResponse({ - required int statusCode, - String path = defaultPath, - Object? body, - }) { - charlatan.whenGet( - path, - (request) => CharlatanHttpResponse( - statusCode: statusCode, - body: body, - ), - ); + void setupErrorResponse({required int statusCode, String path = defaultPath, Object? body}) { + charlatan.whenGet(path, (request) => CharlatanHttpResponse(statusCode: statusCode, body: body)); } group('when status code is 401', () { @@ -606,25 +459,25 @@ void main() { setupErrorResponse(statusCode: 401); }); - test('it emits an authFailure event and invokes unauthorized', - () async { - final response = - await buildSubject().execute>( + test('it emits an authFailure event and invokes unauthorized', () async { + final response = await buildSubject().execute>( const GetRequest(defaultPath), onResponse: (response) { return switch (response) { - Unauthorized() => const Result.success(true), - _ => const Result.failure('Not expected: orElse'), + Unauthorized() => const Success(true), + _ => const Failure('Not expected: orElse'), }; }, ); expect(authFailureRequests.single.path, '/foo'); - response.when( - success: (s) => expect(s, isTrue), - failure: fail, - ); + switch (response) { + case Success(:final success): + expect(success, isTrue); + case Failure(): + fail('Expected Success'); + } }); }); @@ -634,21 +487,22 @@ void main() { }); test('it returns forbidden', () async { - final response = - await buildSubject().execute>( + final response = await buildSubject().execute>( const GetRequest(defaultPath), onResponse: (response) { return switch (response) { - Forbidden() => const Result.success(true), - _ => const Result.failure('Not expected: orElse'), + Forbidden() => const Success(true), + _ => const Failure('Not expected: orElse'), }; }, ); - response.when( - success: (s) => expect(s, isTrue), - failure: fail, - ); + switch (response) { + case Success(:final success): + expect(success, isTrue); + case Failure(): + fail('Expected Success'); + } }); }); @@ -658,75 +512,72 @@ void main() { }); test('it returns notFound', () async { - final response = - await buildSubject().execute>( + final response = await buildSubject().execute>( const GetRequest(defaultPath), onResponse: (response) { return switch (response) { - NotFound() => const Result.success(true), - _ => const Result.failure('Not expected: orElse'), + NotFound() => const Success(true), + _ => const Failure('Not expected: orElse'), }; }, ); - response.when( - success: (s) => expect(s, isTrue), - failure: fail, - ); + switch (response) { + case Success(:final success): + expect(success, isTrue); + case Failure(): + fail('Expected Success'); + } }); }); group('when status code is 422', () { setUp(() { - setupErrorResponse( - statusCode: 422, - body: const Foo(message: 'error').toJson(), - ); + setupErrorResponse(statusCode: 422, body: const Foo(message: 'error').toMap()); }); test('it returns unprocessableEntity', () async { - final response = - await buildSubject().execute>( + final response = await buildSubject().execute>( const GetRequest(defaultPath), onResponse: (response) { return switch (response) { - UnprocessableEntity() => const Result.success(true), - _ => const Result.failure('Not expected: orElse'), + UnprocessableEntity() => const Success(true), + _ => const Failure('Not expected: orElse'), }; }, ); - response.when( - success: (s) => expect(s, isTrue), - failure: fail, - ); + switch (response) { + case Success(:final success): + expect(success, isTrue); + case Failure(): + fail('Expected Success'); + } }); }); group('when status code is 426', () { setUp(() { - setupErrorResponse( - statusCode: 426, - body: const Foo(message: 'error').toJson(), - ); + setupErrorResponse(statusCode: 426, body: const Foo(message: 'error').toMap()); }); test('it returns upgradeRequired', () async { - final response = - await buildSubject().execute>( + final response = await buildSubject().execute>( const GetRequest(defaultPath), onResponse: (response) { return switch (response) { - UpgradeRequired() => const Result.success(true), - _ => const Result.failure('Not expected: orElse'), + UpgradeRequired() => const Success(true), + _ => const Failure('Not expected: orElse'), }; }, ); - response.when( - success: (s) => expect(s, isTrue), - failure: fail, - ); + switch (response) { + case Success(:final success): + expect(success, isTrue); + case Failure(): + fail('Expected Success'); + } }); }); @@ -736,21 +587,22 @@ void main() { }); test('it returns serverError', () async { - final response = - await buildSubject().execute>( + final response = await buildSubject().execute>( const GetRequest(defaultPath), onResponse: (response) { return switch (response) { - ServerError() => const Result.success(true), - _ => const Result.failure('Not expected: orElse'), + ServerError() => const Success(true), + _ => const Failure('Not expected: orElse'), }; }, ); - response.when( - success: (s) => expect(s, isTrue), - failure: fail, - ); + switch (response) { + case Success(:final success): + expect(success, isTrue); + case Failure(): + fail('Expected Success'); + } }); }); @@ -760,21 +612,22 @@ void main() { }); test('it returns service unavailable', () async { - final response = - await buildSubject().execute>( + final response = await buildSubject().execute>( const GetRequest(defaultPath), onResponse: (response) { return switch (response) { - ServiceUnavailable() => const Result.success(true), - _ => const Result.failure('Not expected: orElse'), + ServiceUnavailable() => const Success(true), + _ => const Failure('Not expected: orElse'), }; }, ); - response.when( - success: (s) => expect(s, isTrue), - failure: fail, - ); + switch (response) { + case Success(:final success): + expect(success, isTrue); + case Failure(): + fail('Expected Success'); + } }); }); @@ -784,54 +637,52 @@ void main() { }); test('it returns genericError', () async { - final response = - await buildSubject().execute>( + final response = await buildSubject().execute>( const GetRequest(defaultPath), onResponse: (response) { return switch (response) { - GenericError() => const Result.success(true), - _ => const Result.failure('Not expected: orElse'), + GenericError() => const Success(true), + _ => const Failure('Not expected: orElse'), }; }, ); - response.when( - success: (s) => expect(s, isTrue), - failure: fail, - ); + switch (response) { + case Success(:final success): + expect(success, isTrue); + case Failure(): + fail('Expected Success'); + } }); }); group('when connection issue occurs', () { setUp(() { - charlatan.whenGet( - '/foo', - (request) => throw const SocketException('Oops'), - ); + charlatan.whenGet('/foo', (request) => throw const SocketException('Oops')); }); - test('it returns genericError and isConnectionIssue is true', - () async { - final response = - await buildSubject().execute>( + test('it returns genericError and isConnectionIssue is true', () async { + final response = await buildSubject().execute>( const GetRequest(defaultPath), onResponse: (response) { return switch (response) { GenericError(:final isConnectionIssue) => () { - { - expect(isConnectionIssue, isTrue); - return const Result.success(true); - } - }(), - _ => const Result.failure('Not expected: orElse'), + { + expect(isConnectionIssue, isTrue); + return const Success(true); + } + }(), + _ => const Failure('Not expected: orElse'), }; }, ); - response.when( - success: (s) => expect(s, isTrue), - failure: fail, - ); + switch (response) { + case Success(:final success): + expect(success, isTrue); + case Failure(): + fail('Expected Success'); + } }); }); @@ -839,38 +690,30 @@ void main() { group('when retry behavior is Retry', () { test('it retries maxRetries times', () async { var requestCount = 0; - charlatan.whenGet( - '/foo', - (request) { - requestCount++; - return CharlatanHttpResponse(statusCode: 522); - }, - ); + charlatan.whenGet('/foo', (request) { + requestCount++; + return CharlatanHttpResponse(statusCode: 522); + }); - final response = - await buildSubject().execute>( + final response = await buildSubject().execute>( const GetRequest( defaultPath, - retryBehavior: Retry( - maxRetries: 3, - retryInterval: Duration(milliseconds: 100), - ), + retryBehavior: Retry(maxRetries: 3, retryInterval: Duration(milliseconds: 100)), ), onResponse: (response) { return switch (response) { - GenericError() => const Result.success(true), - _ => const Result.failure('Not expected: orElse'), + GenericError() => const Success(true), + _ => const Failure('Not expected: orElse'), }; }, ); - expect( - response.when( - success: (s) => s, - failure: fail, - ), - isTrue, - ); + switch (response) { + case Success(:final success): + expect(success, isTrue); + case Failure(): + fail('Expected Success'); + } // maxRetries + 1 expect(requestCount, 4); }); @@ -879,35 +722,22 @@ void main() { group('when retry behavior is NeverRetry', () { test('it does not retry', () async { var requestCount = 0; - charlatan.whenGet( - '/foo', - (request) { - requestCount++; - return CharlatanHttpResponse(statusCode: 522); - }, - ); + charlatan.whenGet('/foo', (request) { + requestCount++; + return CharlatanHttpResponse(statusCode: 522); + }); - final response = - await buildSubject().execute>( - const GetRequest( - defaultPath, - retryBehavior: NeverRetry(), - ), + final response = await buildSubject().execute>( + const GetRequest(defaultPath, retryBehavior: NeverRetry()), onResponse: (response) { return switch (response) { - GenericError() => const Result.success(true), - _ => const Result.failure('Not expected: orElse'), + GenericError() => const Success(true), + _ => const Failure('Not expected: orElse'), }; }, ); - expect( - response.when( - success: (s) => s, - failure: fail, - ), - isTrue, - ); + expect((response as Success).success, isTrue); expect(requestCount, 1); }); }); @@ -915,39 +745,30 @@ void main() { group('RetryBehavior priority', () { test('it prefers local RetryBehavior to global', () async { var requestCount = 0; - charlatan.whenGet( - '/foo', - (request) { - requestCount++; - return CharlatanHttpResponse(statusCode: 522); - }, - ); + charlatan.whenGet('/foo', (request) { + requestCount++; + return CharlatanHttpResponse(statusCode: 522); + }); - final response = await buildSubject( - retryBehavior: NeverRetry(), - ).execute>( + final response = await buildSubject(retryBehavior: NeverRetry()).execute>( const GetRequest( defaultPath, - retryBehavior: Retry( - maxRetries: 2, - retryInterval: Duration(milliseconds: 100), - ), + retryBehavior: Retry(maxRetries: 2, retryInterval: Duration(milliseconds: 100)), ), onResponse: (response) { return switch (response) { - GenericError() => const Result.success(true), - _ => const Result.failure('Not expected: orElse'), + GenericError() => const Success(true), + _ => const Failure('Not expected: orElse'), }; }, ); - expect( - response.when( - success: (s) => s, - failure: fail, - ), - isTrue, - ); + switch (response) { + case Success(:final success): + expect(success, isTrue); + case Failure(): + fail('Expected Success'); + } expect(requestCount, 3); }); }); @@ -956,17 +777,12 @@ void main() { test('it allows overriding retryClause', () async { var requestCount = 0; final statusCode = defaultRetryStatusCodes.first; - charlatan.whenGet( - '/foo', - (request) { - requestCount++; - return CharlatanHttpResponse(statusCode: statusCode); - }, - ); + charlatan.whenGet('/foo', (request) { + requestCount++; + return CharlatanHttpResponse(statusCode: statusCode); + }); - final response = await buildSubject( - retryBehavior: NeverRetry(), - ).execute>( + final response = await buildSubject(retryBehavior: NeverRetry()).execute>( GetRequest( defaultPath, retryBehavior: Retry( @@ -980,19 +796,18 @@ void main() { ), onResponse: (response) { return switch (response) { - GenericError() => const Result.success(true), - _ => const Result.failure('Not expected: orElse'), + GenericError() => const Success(true), + _ => const Failure('Not expected: orElse'), }; }, ); - expect( - response.when( - success: (s) => s, - failure: fail, - ), - isTrue, - ); + switch (response) { + case Success(:final success): + expect(success, isTrue); + case Failure(): + fail('Expected Success'); + } expect(requestCount, 1); }); }); @@ -1005,11 +820,20 @@ void main() { /// A simple representation of the result of a procedure that can fail, like /// a network request. -@freezed -class Result with _$Result { - const factory Result.success(S success) = _Success; +sealed class Result { + const Result(); +} + +final class Success extends Result { + final S success; + + const Success(this.success); +} + +final class Failure extends Result { + final F failure; - const factory Result.failure(F failure) = _Failure; + const Failure(this.failure); } class _FakeInterceptor extends Interceptor { @@ -1037,24 +861,27 @@ class _SturdyHttpEventListener extends SturdyHttpEventListener { @override Future onEvent(SturdyHttpEvent event) async { - event.when( - decodingError: onDecodingError, - authFailure: onAuthFailure, - mutativeRequestSuccess: onMutativeRequestSuccess, - ); + switch (event) { + case DecodingError(:final request, :final exception, :final stackTrace): + onDecodingError(request, exception, stackTrace); + case AuthFailure(:final request): + onAuthFailure(request); + case MutativeRequestSuccess(:final request): + onMutativeRequestSuccess(request); + } } } -@freezed -class Foo with _$Foo { - const factory Foo({required String message}) = _Foo; +@MappableClass() +class Foo with FooMappable { + const Foo({required this.message}); - factory Foo.fromJson(Json json) => _$FooFromJson(json); + final String message; } -@freezed -class NotFoo with _$NotFoo { - const factory NotFoo({required String notMessage}) = _NotFoo; +@MappableClass() +class NotFoo with NotFooMappable { + const NotFoo({required this.notMessage}); - factory NotFoo.fromJson(Json json) => _$NotFooFromJson(json); + final String notMessage; } diff --git a/test/src/sturdy_http_test.freezed.dart b/test/src/sturdy_http_test.freezed.dart deleted file mode 100644 index c0d3331..0000000 --- a/test/src/sturdy_http_test.freezed.dart +++ /dev/null @@ -1,647 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'sturdy_http_test.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -/// @nodoc -mixin _$Result { - @optionalTypeArgs - TResult when({ - required TResult Function(S success) success, - required TResult Function(F failure) failure, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(S success)? success, - TResult? Function(F failure)? failure, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(S success)? success, - TResult Function(F failure)? failure, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult map({ - required TResult Function(_Success value) success, - required TResult Function(_Failure value) failure, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(_Success value)? success, - TResult? Function(_Failure value)? failure, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeMap({ - TResult Function(_Success value)? success, - TResult Function(_Failure value)? failure, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $ResultCopyWith { - factory $ResultCopyWith( - Result value, $Res Function(Result) then) = - _$ResultCopyWithImpl>; -} - -/// @nodoc -class _$ResultCopyWithImpl> - implements $ResultCopyWith { - _$ResultCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of Result - /// with the given fields replaced by the non-null parameter values. -} - -/// @nodoc -abstract class _$$SuccessImplCopyWith { - factory _$$SuccessImplCopyWith( - _$SuccessImpl value, $Res Function(_$SuccessImpl) then) = - __$$SuccessImplCopyWithImpl; - @useResult - $Res call({S success}); -} - -/// @nodoc -class __$$SuccessImplCopyWithImpl - extends _$ResultCopyWithImpl> - implements _$$SuccessImplCopyWith { - __$$SuccessImplCopyWithImpl( - _$SuccessImpl _value, $Res Function(_$SuccessImpl) _then) - : super(_value, _then); - - /// Create a copy of Result - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? success = freezed, - }) { - return _then(_$SuccessImpl( - freezed == success - ? _value.success - : success // ignore: cast_nullable_to_non_nullable - as S, - )); - } -} - -/// @nodoc - -class _$SuccessImpl implements _Success { - const _$SuccessImpl(this.success); - - @override - final S success; - - @override - String toString() { - return 'Result<$S, $F>.success(success: $success)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$SuccessImpl && - const DeepCollectionEquality().equals(other.success, success)); - } - - @override - int get hashCode => - Object.hash(runtimeType, const DeepCollectionEquality().hash(success)); - - /// Create a copy of Result - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$SuccessImplCopyWith> get copyWith => - __$$SuccessImplCopyWithImpl>(this, _$identity); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function(S success) success, - required TResult Function(F failure) failure, - }) { - return success(this.success); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(S success)? success, - TResult? Function(F failure)? failure, - }) { - return success?.call(this.success); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(S success)? success, - TResult Function(F failure)? failure, - required TResult orElse(), - }) { - if (success != null) { - return success(this.success); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(_Success value) success, - required TResult Function(_Failure value) failure, - }) { - return success(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(_Success value)? success, - TResult? Function(_Failure value)? failure, - }) { - return success?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(_Success value)? success, - TResult Function(_Failure value)? failure, - required TResult orElse(), - }) { - if (success != null) { - return success(this); - } - return orElse(); - } -} - -abstract class _Success implements Result { - const factory _Success(final S success) = _$SuccessImpl; - - S get success; - - /// Create a copy of Result - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - _$$SuccessImplCopyWith> get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class _$$FailureImplCopyWith { - factory _$$FailureImplCopyWith( - _$FailureImpl value, $Res Function(_$FailureImpl) then) = - __$$FailureImplCopyWithImpl; - @useResult - $Res call({F failure}); -} - -/// @nodoc -class __$$FailureImplCopyWithImpl - extends _$ResultCopyWithImpl> - implements _$$FailureImplCopyWith { - __$$FailureImplCopyWithImpl( - _$FailureImpl _value, $Res Function(_$FailureImpl) _then) - : super(_value, _then); - - /// Create a copy of Result - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? failure = freezed, - }) { - return _then(_$FailureImpl( - freezed == failure - ? _value.failure - : failure // ignore: cast_nullable_to_non_nullable - as F, - )); - } -} - -/// @nodoc - -class _$FailureImpl implements _Failure { - const _$FailureImpl(this.failure); - - @override - final F failure; - - @override - String toString() { - return 'Result<$S, $F>.failure(failure: $failure)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$FailureImpl && - const DeepCollectionEquality().equals(other.failure, failure)); - } - - @override - int get hashCode => - Object.hash(runtimeType, const DeepCollectionEquality().hash(failure)); - - /// Create a copy of Result - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$FailureImplCopyWith> get copyWith => - __$$FailureImplCopyWithImpl>(this, _$identity); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function(S success) success, - required TResult Function(F failure) failure, - }) { - return failure(this.failure); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(S success)? success, - TResult? Function(F failure)? failure, - }) { - return failure?.call(this.failure); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(S success)? success, - TResult Function(F failure)? failure, - required TResult orElse(), - }) { - if (failure != null) { - return failure(this.failure); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(_Success value) success, - required TResult Function(_Failure value) failure, - }) { - return failure(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(_Success value)? success, - TResult? Function(_Failure value)? failure, - }) { - return failure?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(_Success value)? success, - TResult Function(_Failure value)? failure, - required TResult orElse(), - }) { - if (failure != null) { - return failure(this); - } - return orElse(); - } -} - -abstract class _Failure implements Result { - const factory _Failure(final F failure) = _$FailureImpl; - - F get failure; - - /// Create a copy of Result - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - _$$FailureImplCopyWith> get copyWith => - throw _privateConstructorUsedError; -} - -Foo _$FooFromJson(Map json) { - return _Foo.fromJson(json); -} - -/// @nodoc -mixin _$Foo { - String get message => throw _privateConstructorUsedError; - - /// Serializes this Foo to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of Foo - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $FooCopyWith get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $FooCopyWith<$Res> { - factory $FooCopyWith(Foo value, $Res Function(Foo) then) = - _$FooCopyWithImpl<$Res, Foo>; - @useResult - $Res call({String message}); -} - -/// @nodoc -class _$FooCopyWithImpl<$Res, $Val extends Foo> implements $FooCopyWith<$Res> { - _$FooCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of Foo - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? message = null, - }) { - return _then(_value.copyWith( - message: null == message - ? _value.message - : message // ignore: cast_nullable_to_non_nullable - as String, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$FooImplCopyWith<$Res> implements $FooCopyWith<$Res> { - factory _$$FooImplCopyWith(_$FooImpl value, $Res Function(_$FooImpl) then) = - __$$FooImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({String message}); -} - -/// @nodoc -class __$$FooImplCopyWithImpl<$Res> extends _$FooCopyWithImpl<$Res, _$FooImpl> - implements _$$FooImplCopyWith<$Res> { - __$$FooImplCopyWithImpl(_$FooImpl _value, $Res Function(_$FooImpl) _then) - : super(_value, _then); - - /// Create a copy of Foo - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? message = null, - }) { - return _then(_$FooImpl( - message: null == message - ? _value.message - : message // ignore: cast_nullable_to_non_nullable - as String, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$FooImpl implements _Foo { - const _$FooImpl({required this.message}); - - factory _$FooImpl.fromJson(Map json) => - _$$FooImplFromJson(json); - - @override - final String message; - - @override - String toString() { - return 'Foo(message: $message)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$FooImpl && - (identical(other.message, message) || other.message == message)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash(runtimeType, message); - - /// Create a copy of Foo - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$FooImplCopyWith<_$FooImpl> get copyWith => - __$$FooImplCopyWithImpl<_$FooImpl>(this, _$identity); - - @override - Map toJson() { - return _$$FooImplToJson( - this, - ); - } -} - -abstract class _Foo implements Foo { - const factory _Foo({required final String message}) = _$FooImpl; - - factory _Foo.fromJson(Map json) = _$FooImpl.fromJson; - - @override - String get message; - - /// Create a copy of Foo - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$FooImplCopyWith<_$FooImpl> get copyWith => - throw _privateConstructorUsedError; -} - -NotFoo _$NotFooFromJson(Map json) { - return _NotFoo.fromJson(json); -} - -/// @nodoc -mixin _$NotFoo { - String get notMessage => throw _privateConstructorUsedError; - - /// Serializes this NotFoo to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of NotFoo - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $NotFooCopyWith get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $NotFooCopyWith<$Res> { - factory $NotFooCopyWith(NotFoo value, $Res Function(NotFoo) then) = - _$NotFooCopyWithImpl<$Res, NotFoo>; - @useResult - $Res call({String notMessage}); -} - -/// @nodoc -class _$NotFooCopyWithImpl<$Res, $Val extends NotFoo> - implements $NotFooCopyWith<$Res> { - _$NotFooCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of NotFoo - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? notMessage = null, - }) { - return _then(_value.copyWith( - notMessage: null == notMessage - ? _value.notMessage - : notMessage // ignore: cast_nullable_to_non_nullable - as String, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$NotFooImplCopyWith<$Res> implements $NotFooCopyWith<$Res> { - factory _$$NotFooImplCopyWith( - _$NotFooImpl value, $Res Function(_$NotFooImpl) then) = - __$$NotFooImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({String notMessage}); -} - -/// @nodoc -class __$$NotFooImplCopyWithImpl<$Res> - extends _$NotFooCopyWithImpl<$Res, _$NotFooImpl> - implements _$$NotFooImplCopyWith<$Res> { - __$$NotFooImplCopyWithImpl( - _$NotFooImpl _value, $Res Function(_$NotFooImpl) _then) - : super(_value, _then); - - /// Create a copy of NotFoo - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? notMessage = null, - }) { - return _then(_$NotFooImpl( - notMessage: null == notMessage - ? _value.notMessage - : notMessage // ignore: cast_nullable_to_non_nullable - as String, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$NotFooImpl implements _NotFoo { - const _$NotFooImpl({required this.notMessage}); - - factory _$NotFooImpl.fromJson(Map json) => - _$$NotFooImplFromJson(json); - - @override - final String notMessage; - - @override - String toString() { - return 'NotFoo(notMessage: $notMessage)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$NotFooImpl && - (identical(other.notMessage, notMessage) || - other.notMessage == notMessage)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash(runtimeType, notMessage); - - /// Create a copy of NotFoo - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$NotFooImplCopyWith<_$NotFooImpl> get copyWith => - __$$NotFooImplCopyWithImpl<_$NotFooImpl>(this, _$identity); - - @override - Map toJson() { - return _$$NotFooImplToJson( - this, - ); - } -} - -abstract class _NotFoo implements NotFoo { - const factory _NotFoo({required final String notMessage}) = _$NotFooImpl; - - factory _NotFoo.fromJson(Map json) = _$NotFooImpl.fromJson; - - @override - String get notMessage; - - /// Create a copy of NotFoo - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$NotFooImplCopyWith<_$NotFooImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/test/src/sturdy_http_test.g.dart b/test/src/sturdy_http_test.g.dart deleted file mode 100644 index da0d4d0..0000000 --- a/test/src/sturdy_http_test.g.dart +++ /dev/null @@ -1,39 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'sturdy_http_test.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$FooImpl _$$FooImplFromJson(Map json) => $checkedCreate( - r'_$FooImpl', - json, - ($checkedConvert) { - final val = _$FooImpl( - message: $checkedConvert('message', (v) => v as String), - ); - return val; - }, - ); - -Map _$$FooImplToJson(_$FooImpl instance) => { - 'message': instance.message, - }; - -_$NotFooImpl _$$NotFooImplFromJson(Map json) => $checkedCreate( - r'_$NotFooImpl', - json, - ($checkedConvert) { - final val = _$NotFooImpl( - notMessage: $checkedConvert('not_message', (v) => v as String), - ); - return val; - }, - fieldKeyMap: const {'notMessage': 'not_message'}, - ); - -Map _$$NotFooImplToJson(_$NotFooImpl instance) => - { - 'not_message': instance.notMessage, - }; diff --git a/test/src/sturdy_http_test.mapper.dart b/test/src/sturdy_http_test.mapper.dart new file mode 100644 index 0000000..fead1b2 --- /dev/null +++ b/test/src/sturdy_http_test.mapper.dart @@ -0,0 +1,197 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// dart format off +// ignore_for_file: type=lint +// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member +// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter + +part of 'sturdy_http_test.dart'; + +class FooMapper extends ClassMapperBase { + FooMapper._(); + + static FooMapper? _instance; + static FooMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = FooMapper._()); + } + return _instance!; + } + + @override + final String id = 'Foo'; + + static String _$message(Foo v) => v.message; + static const Field _f$message = Field('message', _$message); + + @override + final MappableFields fields = const {#message: _f$message}; + + static Foo _instantiate(DecodingData data) { + return Foo(message: data.dec(_f$message)); + } + + @override + final Function instantiate = _instantiate; + + static Foo fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static Foo fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin FooMappable { + String toJson() { + return FooMapper.ensureInitialized().encodeJson(this as Foo); + } + + Map toMap() { + return FooMapper.ensureInitialized().encodeMap(this as Foo); + } + + FooCopyWith get copyWith => + _FooCopyWithImpl(this as Foo, $identity, $identity); + @override + String toString() { + return FooMapper.ensureInitialized().stringifyValue(this as Foo); + } + + @override + bool operator ==(Object other) { + return FooMapper.ensureInitialized().equalsValue(this as Foo, other); + } + + @override + int get hashCode { + return FooMapper.ensureInitialized().hashValue(this as Foo); + } +} + +extension FooValueCopy<$R, $Out> on ObjectCopyWith<$R, Foo, $Out> { + FooCopyWith<$R, Foo, $Out> get $asFoo => + $base.as((v, t, t2) => _FooCopyWithImpl<$R, $Out>(v, t, t2)); +} + +abstract class FooCopyWith<$R, $In extends Foo, $Out> + implements ClassCopyWith<$R, $In, $Out> { + $R call({String? message}); + FooCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +} + +class _FooCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Foo, $Out> + implements FooCopyWith<$R, Foo, $Out> { + _FooCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = FooMapper.ensureInitialized(); + @override + $R call({String? message}) => + $apply(FieldCopyWithData({if (message != null) #message: message})); + @override + Foo $make(CopyWithData data) => + Foo(message: data.get(#message, or: $value.message)); + + @override + FooCopyWith<$R2, Foo, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => + _FooCopyWithImpl<$R2, $Out2>($value, $cast, t); +} + +class NotFooMapper extends ClassMapperBase { + NotFooMapper._(); + + static NotFooMapper? _instance; + static NotFooMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = NotFooMapper._()); + } + return _instance!; + } + + @override + final String id = 'NotFoo'; + + static String _$notMessage(NotFoo v) => v.notMessage; + static const Field _f$notMessage = Field( + 'notMessage', + _$notMessage, + ); + + @override + final MappableFields fields = const {#notMessage: _f$notMessage}; + + static NotFoo _instantiate(DecodingData data) { + return NotFoo(notMessage: data.dec(_f$notMessage)); + } + + @override + final Function instantiate = _instantiate; + + static NotFoo fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static NotFoo fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin NotFooMappable { + String toJson() { + return NotFooMapper.ensureInitialized().encodeJson(this as NotFoo); + } + + Map toMap() { + return NotFooMapper.ensureInitialized().encodeMap(this as NotFoo); + } + + NotFooCopyWith get copyWith => + _NotFooCopyWithImpl(this as NotFoo, $identity, $identity); + @override + String toString() { + return NotFooMapper.ensureInitialized().stringifyValue(this as NotFoo); + } + + @override + bool operator ==(Object other) { + return NotFooMapper.ensureInitialized().equalsValue(this as NotFoo, other); + } + + @override + int get hashCode { + return NotFooMapper.ensureInitialized().hashValue(this as NotFoo); + } +} + +extension NotFooValueCopy<$R, $Out> on ObjectCopyWith<$R, NotFoo, $Out> { + NotFooCopyWith<$R, NotFoo, $Out> get $asNotFoo => + $base.as((v, t, t2) => _NotFooCopyWithImpl<$R, $Out>(v, t, t2)); +} + +abstract class NotFooCopyWith<$R, $In extends NotFoo, $Out> + implements ClassCopyWith<$R, $In, $Out> { + $R call({String? notMessage}); + NotFooCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +} + +class _NotFooCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, NotFoo, $Out> + implements NotFooCopyWith<$R, NotFoo, $Out> { + _NotFooCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = NotFooMapper.ensureInitialized(); + @override + $R call({String? notMessage}) => $apply( + FieldCopyWithData({if (notMessage != null) #notMessage: notMessage}), + ); + @override + NotFoo $make(CopyWithData data) => + NotFoo(notMessage: data.get(#notMessage, or: $value.notMessage)); + + @override + NotFooCopyWith<$R2, NotFoo, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => + _NotFooCopyWithImpl<$R2, $Out2>($value, $cast, t); +} + From 5d231312261f21dcb099c1d531d9daa887075756 Mon Sep 17 00:00:00 2001 From: Brandon Trautmann <8343465+btrautmann@users.noreply.github.com> Date: Mon, 22 Dec 2025 16:02:05 -0500 Subject: [PATCH 02/15] remove dart_mappable --- lib/src/_dio_error.dart | 3 +- lib/src/deserializer.dart | 22 +- lib/src/network_request.dart | 25 +- lib/src/retry_behavior.dart | 6 +- lib/src/sturdy_http.dart | 69 +- lib/src/sturdy_http_event_listener.dart | 6 +- pubspec.yaml | 2 - test/src/deserializer_test.dart | 77 ++- test/src/retry_behavior_test.dart | 5 +- test/src/sturdy_http_test.dart | 820 ++++++++++++++---------- 10 files changed, 628 insertions(+), 407 deletions(-) diff --git a/lib/src/_dio_error.dart b/lib/src/_dio_error.dart index c9a2a1c..9241342 100644 --- a/lib/src/_dio_error.dart +++ b/lib/src/_dio_error.dart @@ -17,7 +17,8 @@ extension DioExceptionX on DioException { DioExceptionType.sendTimeout, DioExceptionType.unknown, ]; - final isKnownExceptionType = error is HandshakeException || + final isKnownExceptionType = + error is HandshakeException || error is SocketException || error is HttpException; return knownBadConnectionTypes.contains(type) || isKnownExceptionType; diff --git a/lib/src/deserializer.dart b/lib/src/deserializer.dart index c473468..b4331d1 100644 --- a/lib/src/deserializer.dart +++ b/lib/src/deserializer.dart @@ -99,9 +99,7 @@ class BackgroundDeserializer implements Deserializer { // Use BroadcastStream to facilitate multiple requests for // deserialization over the same SendPort. _results ??= _mainReceivePort.asBroadcastStream(); - _workerIsolate ??= await _spawnIsolate( - mainReceivePort: _mainReceivePort, - ); + _workerIsolate ??= await _spawnIsolate(mainReceivePort: _mainReceivePort); _workerSendPort ??= await _results!.first as SendPort; } } @@ -162,10 +160,8 @@ class _IsolateRequest { final SendPort resultPort; final dynamic Function() decoder; - _IsolateRequest({ - required this.resultPort, - required this.decoder, - }) : id = const Uuid().v4(); + _IsolateRequest({required this.resultPort, required this.decoder}) + : id = const Uuid().v4(); } /// The result of an attempt to deserialize a response. @@ -184,16 +180,8 @@ class _IsolateResult { }); factory _IsolateResult.ok(dynamic value, String requestId) => - _IsolateResult._( - response: value, - error: null, - requestId: requestId, - ); + _IsolateResult._(response: value, error: null, requestId: requestId); factory _IsolateResult.error(Exception error, String requestId) => - _IsolateResult._( - response: null, - error: error, - requestId: requestId, - ); + _IsolateResult._(response: null, error: error, requestId: requestId); } diff --git a/lib/src/network_request.dart b/lib/src/network_request.dart index 9e197a1..d25513b 100644 --- a/lib/src/network_request.dart +++ b/lib/src/network_request.dart @@ -78,7 +78,12 @@ class GetRequest extends NetworkRequest { super.onReceiveProgress, super.onSendProgress, super.retryBehavior, - }) : super(type: NetworkRequestType.Get, path: path, shouldTriggerDataMutation: false, queryParams: queryParameters); + }) : super( + type: NetworkRequestType.Get, + path: path, + shouldTriggerDataMutation: false, + queryParams: queryParameters, + ); } /// {@template post_request} @@ -96,7 +101,11 @@ class PostRequest extends NetworkRequest { super.onReceiveProgress, super.onSendProgress, super.retryBehavior, - }) : super(type: NetworkRequestType.Post, path: path, queryParams: queryParameters); + }) : super( + type: NetworkRequestType.Post, + path: path, + queryParams: queryParameters, + ); } /// {@template put_request} @@ -114,7 +123,11 @@ class PutRequest extends NetworkRequest { super.onReceiveProgress, super.onSendProgress, super.retryBehavior, - }) : super(type: NetworkRequestType.Put, path: path, queryParams: queryParameters); + }) : super( + type: NetworkRequestType.Put, + path: path, + queryParams: queryParameters, + ); } /// {@template delete_request} @@ -132,7 +145,11 @@ class DeleteRequest extends NetworkRequest { super.onReceiveProgress, super.onSendProgress, super.retryBehavior, - }) : super(type: NetworkRequestType.Delete, path: path, queryParams: queryParameters); + }) : super( + type: NetworkRequestType.Delete, + path: path, + queryParams: queryParameters, + ); } /// {@template raw_request} diff --git a/lib/src/retry_behavior.dart b/lib/src/retry_behavior.dart index b7f483b..c57e138 100644 --- a/lib/src/retry_behavior.dart +++ b/lib/src/retry_behavior.dart @@ -66,9 +66,9 @@ extension RetryBehaviorX on RetryBehavior { bool shouldRetry(Response? response, int retryCount) { return switch (this) { Retry(:final maxRetries, :final retryClause) => () { - if (retryCount >= maxRetries) return false; - return retryClause?.call(response) ?? defaultRetryClause(response); - }(), + if (retryCount >= maxRetries) return false; + return retryClause?.call(response) ?? defaultRetryClause(response); + }(), _ => false, }; } diff --git a/lib/src/sturdy_http.dart b/lib/src/sturdy_http.dart index 66f6399..e649def 100644 --- a/lib/src/sturdy_http.dart +++ b/lib/src/sturdy_http.dart @@ -32,7 +32,8 @@ class SturdyHttp { final RetryBehavior _retryBehavior; /// The interceptors provided when this [SturdyHttp] was constructed. - UnmodifiableListView get interceptors => UnmodifiableListView(_dio.interceptors); + UnmodifiableListView get interceptors => + UnmodifiableListView(_dio.interceptors); /// The base URL of the underlying [Dio] instance. String get baseUrl => _dio.options.baseUrl; @@ -100,24 +101,37 @@ class SturdyHttp { /// If [onResponse] fails to produce an [M] and instead throws an [Exception], /// some known failure reasons are emitted via [SturdyHttpEvent]s and the /// [Exception] is re-thrown. - Future execute(NetworkRequest request, {required M Function(NetworkResponse response) onResponse}) async { + Future execute( + NetworkRequest request, { + required M Function(NetworkResponse response) onResponse, + }) async { final responsePayload = await _handleRequest(request); try { - return await _deserializer.deserialize(response: responsePayload.resolvedResponse, onResponse: onResponse); + return await _deserializer.deserialize( + response: responsePayload.resolvedResponse, + onResponse: onResponse, + ); } on Exception catch (e) { if (e is MapperException || e is CheckedFromJsonException) { - final stackTrace = e is MapperException ? null : (e as CheckedFromJsonException).innerStack; await _onEvent( - DecodingError(request: responsePayload.dioResponse!.requestOptions, exception: e, stackTrace: stackTrace), + DecodingError( + request: responsePayload.dioResponse!.requestOptions, + exception: e, + stackTrace: StackTrace.current, + ), ); } rethrow; } } - Future<_ResponsePayload> _handleRequest(NetworkRequest request) async { - Future<(Response?, NetworkResponse)> send(NetworkRequest request) async { + Future<_ResponsePayload> _handleRequest( + NetworkRequest request, + ) async { + Future<(Response?, NetworkResponse)> send( + NetworkRequest request, + ) async { late final NetworkResponse resolvedResponse; Response? dioResponse; try { @@ -155,7 +169,10 @@ class SturdyHttp { return 'Request to ${request.path} was successful but response data $messageSuffix'; } - resolvedResponse = GenericError(message: buildErrorMessage(), isConnectionIssue: false); + resolvedResponse = GenericError( + message: buildErrorMessage(), + isConnectionIssue: false, + ); } else { resolvedResponse = OkResponse(data as R); } @@ -173,7 +190,10 @@ class SturdyHttp { resolvedResponse = NotFound(error: error); break; case 422: - resolvedResponse = UnprocessableEntity(error: error, response: error.response?.data as R); + resolvedResponse = UnprocessableEntity( + error: error, + response: error.response?.data as R, + ); break; case 426: resolvedResponse = UpgradeRequired(error: error); @@ -186,7 +206,8 @@ class SturdyHttp { break; default: resolvedResponse = GenericError( - message: 'Unexpected status code ${error.response?.statusCode} returned for ${request.path}', + message: + 'Unexpected status code ${error.response?.statusCode} returned for ${request.path}', isConnectionIssue: error.isConnectionIssue(), error: error, ); @@ -198,13 +219,17 @@ class SturdyHttp { RetryBehavior determineRetryBehavior() { // The request's retry behavior takes precedence over the client's final priority = [request.retryBehavior, _retryBehavior]; - return priority.firstWhere((b) => b is! Unspecified, orElse: () => NeverRetry()); + return priority.firstWhere( + (b) => b is! Unspecified, + orElse: () => NeverRetry(), + ); } final retryBehavior = determineRetryBehavior(); var response = await send(request); var retryCount = 0; - while (!response.$2.isSuccess && retryBehavior.shouldRetry(response.$1, retryCount)) { + while (!response.$2.isSuccess && + retryBehavior.shouldRetry(response.$1, retryCount)) { // `retryBehavior` must be a `Retry`, otherwise we wouldn't be here. await Future.delayed((retryBehavior as Retry).retryInterval); retryCount++; @@ -212,10 +237,16 @@ class SturdyHttp { } if (response.$2.isSuccess && request.shouldTriggerDataMutation) { - await _onEvent(MutativeRequestSuccess(request: response.$1!.requestOptions)); + await _onEvent( + MutativeRequestSuccess(request: response.$1!.requestOptions), + ); } - return _ResponsePayload(request: request, dioResponse: response.$1, resolvedResponse: response.$2); + return _ResponsePayload( + request: request, + dioResponse: response.$1, + resolvedResponse: response.$2, + ); } } @@ -224,7 +255,11 @@ class _ResponsePayload { final Response? dioResponse; final NetworkResponse resolvedResponse; - _ResponsePayload({required this.request, required this.dioResponse, required this.resolvedResponse}); + _ResponsePayload({ + required this.request, + required this.dioResponse, + required this.resolvedResponse, + }); } Dio _configureDio({ @@ -237,7 +272,9 @@ Dio _configureDio({ }) { return Dio() // Instruct Dio to use the same Isolate approach as requested of SturdyHttp - ..transformer = deserializer is MainIsolateDeserializer ? SyncTransformer() : BackgroundTransformer() + ..transformer = deserializer is MainIsolateDeserializer + ? SyncTransformer() + : BackgroundTransformer() ..options.baseUrl = baseUrl ..options.listFormat = ListFormat.multiCompatible ..interceptors.addAll(interceptors) diff --git a/lib/src/sturdy_http_event_listener.dart b/lib/src/sturdy_http_event_listener.dart index 998a5af..6257231 100644 --- a/lib/src/sturdy_http_event_listener.dart +++ b/lib/src/sturdy_http_event_listener.dart @@ -34,7 +34,11 @@ final class DecodingError extends SturdyHttpEvent { final StackTrace? stackTrace; /// {@macro decoding_error} - DecodingError({required super.request, required this.exception, required this.stackTrace}); + DecodingError({ + required super.request, + required this.exception, + required this.stackTrace, + }); } /// {@template auth_failure} diff --git a/pubspec.yaml b/pubspec.yaml index 37a4a7f..2a870e7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,7 +9,6 @@ environment: dependencies: collection: ^1.19.1 - dart_mappable: ^4.6.1 dio: ^5.9.0 freezed_annotation: ^3.1.0 uuid: ^4.5.2 @@ -17,6 +16,5 @@ dependencies: dev_dependencies: build_runner: ^2.10.4 charlatan: ^0.5.0 - dart_mappable_builder: ^4.6.3 lints: ^6.0.0 test: ^1.28.0 diff --git a/test/src/deserializer_test.dart b/test/src/deserializer_test.dart index e0ab3e1..b489bd7 100644 --- a/test/src/deserializer_test.dart +++ b/test/src/deserializer_test.dart @@ -10,21 +10,27 @@ import 'sturdy_http_test.dart'; void main() { group('BackgroundDeserializer', () { - test('it invokes onResponse on a non-main Isolate and sends result back', () async { - onResponse(NetworkResponse response) { - final isolateName = Isolate.current.debugName; - // Hijack `Foo` to send over the `IsolateName` since - // isolates don't share memory (so we can't set a late - // field that lives outside this isolate function - // scope) - return Foo(message: isolateName!); - } + test( + 'it invokes onResponse on a non-main Isolate and sends result back', + () async { + onResponse(NetworkResponse response) { + final isolateName = Isolate.current.debugName; + // Hijack `Foo` to send over the `IsolateName` since + // isolates don't share memory (so we can't set a late + // field that lives outside this isolate function + // scope) + return Foo(message: isolateName!); + } - final response = OkResponse(const Foo(message: '--').toMap()); - final subject = BackgroundDeserializer(); - final result = await subject.deserialize(response: response, onResponse: onResponse); - expect(result.message, 'sturdyHttpWorkerIsolate'); - }); + final response = OkResponse(const Foo(message: '--').toMap()); + final subject = BackgroundDeserializer(); + final result = await subject.deserialize( + response: response, + onResponse: onResponse, + ); + expect(result.message, 'sturdyHttpWorkerIsolate'); + }, + ); test('it handles multiple requests for deserialization', () async { Foo onResponse(NetworkResponse response) { @@ -37,27 +43,36 @@ void main() { final responseOne = OkResponse(const Foo(message: '1').toMap()); final responseTwo = OkResponse(const Foo(message: '2').toMap()); final subject = BackgroundDeserializer(); - final resultOne = await subject.deserialize(response: responseOne, onResponse: onResponse); - final resultTwo = await subject.deserialize(response: responseTwo, onResponse: onResponse); + final resultOne = await subject.deserialize( + response: responseOne, + onResponse: onResponse, + ); + final resultTwo = await subject.deserialize( + response: responseTwo, + onResponse: onResponse, + ); expect(resultOne.message, '1'); expect(resultTwo.message, '2'); }); - test('it throws MapperExceptions when deserialization issues occur', () async { - onResponse(NetworkResponse response) { - return switch (response) { - OkResponse(:final response) => NotFooMapper.fromMap(response), - _ => fail('orElse not expected'), - }; - } + test( + 'it throws MapperExceptions when deserialization issues occur', + () async { + onResponse(NetworkResponse response) { + return switch (response) { + OkResponse(:final response) => NotFooMapper.fromMap(response), + _ => fail('orElse not expected'), + }; + } - final response = OkResponse(const Foo(message: 'Nope').toMap()); - final subject = BackgroundDeserializer(); - try { - await subject.deserialize(response: response, onResponse: onResponse); - } on Exception catch (e) { - expect(e, isA()); - } - }); + final response = OkResponse(const Foo(message: 'Nope').toMap()); + final subject = BackgroundDeserializer(); + try { + await subject.deserialize(response: response, onResponse: onResponse); + } on Exception catch (e) { + expect(e, isA()); + } + }, + ); }); } diff --git a/test/src/retry_behavior_test.dart b/test/src/retry_behavior_test.dart index daf9daa..e9ee74c 100644 --- a/test/src/retry_behavior_test.dart +++ b/test/src/retry_behavior_test.dart @@ -34,10 +34,7 @@ void main() { }); group('Retry', () { test('maxRetries is respected', () { - final retry = const Retry( - maxRetries: 1, - retryInterval: Duration.zero, - ); + final retry = const Retry(maxRetries: 1, retryInterval: Duration.zero); expect( retry.shouldRetry( diff --git a/test/src/sturdy_http_test.dart b/test/src/sturdy_http_test.dart index f3772af..e60c7da 100644 --- a/test/src/sturdy_http_test.dart +++ b/test/src/sturdy_http_test.dart @@ -1,13 +1,10 @@ import 'dart:io'; import 'package:charlatan/charlatan.dart'; -import 'package:dart_mappable/dart_mappable.dart'; import 'package:dio/dio.dart'; import 'package:sturdy_http/sturdy_http.dart'; import 'package:test/test.dart' hide Retry; -part 'sturdy_http_test.mapper.dart'; - void main() { group('SturdyHttp', () { late Charlatan charlatan; @@ -55,7 +52,10 @@ void main() { group('interceptors', () { test('it returns the provided interceptors', () { final interceptors = [_FakeInterceptor()]; - final subject = buildSubject(interceptors: interceptors, inferContentType: false); + final subject = buildSubject( + interceptors: interceptors, + inferContentType: false, + ); expect(subject.interceptors, interceptors); }); @@ -69,7 +69,8 @@ void main() { interceptors: [ _FakeInterceptor( onRequestInvoked: (options) { - contentType = options.headers[Headers.contentTypeHeader] as String?; + contentType = + options.headers[Headers.contentTypeHeader] as String?; }, ), ], @@ -77,7 +78,10 @@ void main() { charlatan.whenPost('/infer', (request) => CharlatanHttpResponse()); - await subject.execute(PostRequest('/infer', data: JsonRequestBody({'foo': 'bar'})), onResponse: (r) {}); + await subject.execute( + PostRequest('/infer', data: JsonRequestBody({'foo': 'bar'})), + onResponse: (r) {}, + ); expect(contentType, isNull); }); @@ -89,7 +93,8 @@ void main() { interceptors: [ _FakeInterceptor( onRequestInvoked: (options) { - contentType = options.headers[Headers.contentTypeHeader] as String?; + contentType = + options.headers[Headers.contentTypeHeader] as String?; }, ), ], @@ -97,7 +102,10 @@ void main() { charlatan.whenPost('/infer', (request) => CharlatanHttpResponse()); - await subject.execute(PostRequest('/infer', data: JsonRequestBody({'foo': 'bar'})), onResponse: (r) {}); + await subject.execute( + PostRequest('/infer', data: JsonRequestBody({'foo': 'bar'})), + onResponse: (r) {}, + ); expect(contentType, 'application/json'); }); @@ -121,16 +129,19 @@ void main() { }); group('withBaseUrl', () { - test('it returns a new instance with correct baseUrl and pre-configured settings', () { - final oldInstance = buildSubject(interceptors: [_FakeInterceptor()]); - expect(oldInstance.baseUrl != 'https://foo.com', isTrue); - - final newInstance = oldInstance.withBaseUrl('https://foo.com'); - expect(identical(oldInstance, newInstance), isFalse); - expect(newInstance.baseUrl, 'https://foo.com'); - expect(newInstance.interceptors, oldInstance.interceptors); - expect(newInstance.httpClientAdapter, oldInstance.httpClientAdapter); - }); + test( + 'it returns a new instance with correct baseUrl and pre-configured settings', + () { + final oldInstance = buildSubject(interceptors: [_FakeInterceptor()]); + expect(oldInstance.baseUrl != 'https://foo.com', isTrue); + + final newInstance = oldInstance.withBaseUrl('https://foo.com'); + expect(identical(oldInstance, newInstance), isFalse); + expect(newInstance.baseUrl, 'https://foo.com'); + expect(newInstance.interceptors, oldInstance.interceptors); + expect(newInstance.httpClientAdapter, oldInstance.httpClientAdapter); + }, + ); }); group('execute', () { @@ -168,7 +179,10 @@ void main() { group('when NetworkRequestBody is json', () { test('request options contain json data', () async { await buildSubject().execute( - const GetRequest('/foo', data: JsonRequestBody({'foo': 'bar'})), + const GetRequest( + '/foo', + data: JsonRequestBody({'foo': 'bar'}), + ), onResponse: (response) { return null; }, @@ -207,7 +221,10 @@ void main() { group('queryParameters', () { test('request options contain correct query parameters ', () async { await buildSubject().execute( - const GetRequest('/foo', queryParameters: {'foo': 'bar'}), + const GetRequest( + '/foo', + queryParameters: {'foo': 'bar'}, + ), onResponse: (response) {}, ); expect( @@ -221,7 +238,10 @@ void main() { group('method', () { test('request options contain correct method ', () async { - await buildSubject().execute(const GetRequest('/foo'), onResponse: (response) {}); + await buildSubject().execute( + const GetRequest('/foo'), + onResponse: (response) {}, + ); expect(options.method, 'GET'); await buildSubject().execute( const PostRequest('/bar', data: EmptyRequestBody()), @@ -238,25 +258,38 @@ void main() { group('when data is returned', () { setUp(() { charlatan - ..whenGet('/foo', (request) => CharlatanHttpResponse(body: Foo(message: 'Hello world').toMap())) + ..whenGet( + '/foo', + (request) => CharlatanHttpResponse( + body: Foo(message: 'Hello world').toMap(), + ), + ) ..whenGet( '/not-foo', - (request) => CharlatanHttpResponse(body: NotFoo(notMessage: 'Hello world').toMap()), + (request) => CharlatanHttpResponse( + body: NotFoo(notMessage: 'Hello world').toMap(), + ), ) - ..whenGet('/bar', (request) => CharlatanHttpResponse(body: 'a raw string')); + ..whenGet( + '/bar', + (request) => CharlatanHttpResponse(body: 'a raw string'), + ); }); group('when deserialization succeeds', () { test('it returns parsed model', () async { - final response = await buildSubject().execute>( - const GetRequest('/foo'), - onResponse: (response) { - return switch (response) { - OkResponse(:final response) => Success(FooMapper.fromMap(response)), - _ => const Failure('Not expected: orElse'), - }; - }, - ); + final response = await buildSubject() + .execute>( + const GetRequest('/foo'), + onResponse: (response) { + return switch (response) { + OkResponse(:final response) => Success( + Foo.fromMap(response), + ), + _ => const Failure('Not expected: orElse'), + }; + }, + ); switch (response) { case Success(:final success): @@ -268,38 +301,48 @@ void main() { }); group('when deserialization fails', () { - test('it emits a decodingError event and rethrows the Exception', () async { - final request = buildSubject().execute>( - const GetRequest('/not-foo'), - onResponse: (response) { - return switch (response) { - OkResponse(:final response) => Success(FooMapper.fromMap(response)), - _ => const Failure('Not expected: orElse'), - }; - }, - ); - - await expectLater(request, throwsA(isA())); - expect(jsonDecodingErrors['/not-foo'].toString(), contains('MapperException')); - }); + test( + 'it emits a decodingError event and rethrows the Exception', + () async { + final request = buildSubject() + .execute>( + const GetRequest('/not-foo'), + onResponse: (response) { + return switch (response) { + OkResponse(:final response) => Success( + Foo.fromMap(response), + ), + _ => const Failure('Not expected: orElse'), + }; + }, + ); + + await expectLater(request, throwsA(isA())); + }, + ); }); }); group('when no data is returned', () { group('when status code is 204', () { setUp(() { - charlatan.whenPost('/foo', (request) => CharlatanHttpResponse(statusCode: 204, body: null)); + charlatan.whenPost( + '/foo', + (request) => + CharlatanHttpResponse(statusCode: 204, body: null), + ); }); test('it returns okNoContent', () async { - final response = await buildSubject().execute>( - const PostRequest('/foo', data: EmptyRequestBody()), - onResponse: (response) { - return switch (response) { - OkNoContent() => const Success(true), - _ => const Failure('Not expected: orElse'), - }; - }, - ); + final response = await buildSubject() + .execute>( + const PostRequest('/foo', data: EmptyRequestBody()), + onResponse: (response) { + return switch (response) { + OkNoContent() => const Success(true), + _ => const Failure('Not expected: orElse'), + }; + }, + ); switch (response) { case Success(:final success): @@ -312,31 +355,39 @@ void main() { group('when status code is non-204', () { setUp(() { - charlatan.whenPost('/foo', (request) => CharlatanHttpResponse(statusCode: 200, body: null)); - }); - test('it returns genericError and isConnectionIssue is false', () async { - final response = await buildSubject().execute>( - const PostRequest('/foo', data: EmptyRequestBody()), - onResponse: (response) { - return switch (response) { - GenericError(:final isConnectionIssue) => () { - { - expect(isConnectionIssue, isFalse); - return const Success(true); - } - }(), - _ => const Failure('Not expected: orElse'), - }; - }, + charlatan.whenPost( + '/foo', + (request) => + CharlatanHttpResponse(statusCode: 200, body: null), ); - - switch (response) { - case Success(:final success): - expect(success, isTrue); - case Failure(): - fail('Expected Success'); - } }); + test( + 'it returns genericError and isConnectionIssue is false', + () async { + final response = await buildSubject() + .execute>( + const PostRequest('/foo', data: EmptyRequestBody()), + onResponse: (response) { + return switch (response) { + GenericError(:final isConnectionIssue) => () { + { + expect(isConnectionIssue, isFalse); + return const Success(true); + } + }(), + _ => const Failure('Not expected: orElse'), + }; + }, + ); + + switch (response) { + case Success(:final success): + expect(success, isTrue); + case Failure(): + fail('Expected Success'); + } + }, + ); }); }); }); @@ -348,110 +399,172 @@ void main() { charlatan ..whenPost( '/foo', - (request) => CharlatanHttpResponse(body: {'foo': 'bar'}, statusCode: 200), + (request) => CharlatanHttpResponse( + body: {'foo': 'bar'}, + statusCode: 200, + ), ) ..whenPut( '/bar', - (request) => CharlatanHttpResponse(body: {'foo': 'bar'}, statusCode: 204), + (request) => CharlatanHttpResponse( + body: {'foo': 'bar'}, + statusCode: 204, + ), ) ..whenDelete( '/baz', - (request) => CharlatanHttpResponse(body: {'foo': 'bar'}, statusCode: 200), + (request) => CharlatanHttpResponse( + body: {'foo': 'bar'}, + statusCode: 200, + ), ); }); - test('it emits a MutativeRequestSuccess event with correct path', () async { - final subject = buildSubject(); - await Future.wait([ - subject.execute>( - const PostRequest('/foo', data: EmptyRequestBody()), - onResponse: (response) { - return switch (response) { - OkResponse(:final response) => Success(response['foo'] as String), - _ => const Failure('Not expected: orElse'), - }; - }, - ), - subject.execute>( - const PutRequest('/bar', data: EmptyRequestBody()), - onResponse: (response) { - return switch (response) { - OkResponse(:final response) => Success(response['foo'] as String), - _ => const Failure('Not expected: orElse'), - }; - }, - ), - subject.execute>( - const DeleteRequest('/baz', data: EmptyRequestBody()), - onResponse: (response) { - return switch (response) { - OkResponse(:final response) => Success(response['foo'] as String), - _ => const Failure('Not expected: orElse'), - }; - }, - ), - ]); - - expect(mutativeRequestSuccessRequests.map((e) => e.path), contains('/foo')); - expect(mutativeRequestSuccessRequests.map((e) => e.path), contains('/bar')); - expect(mutativeRequestSuccessRequests.map((e) => e.path), contains('/baz')); - }); - }); - - group('and the response has status codes other than 200 or 204', () { - setUp(() { - charlatan - ..whenPost('/foo', (request) => CharlatanHttpResponse(body: {}, statusCode: 404)) - ..whenPut('/bar', (request) => CharlatanHttpResponse(body: {}, statusCode: 422)) - ..whenDelete( - '/baz', - (request) => CharlatanHttpResponse(body: {}, statusCode: 500), + test( + 'it emits a MutativeRequestSuccess event with correct path', + () async { + final subject = buildSubject(); + await Future.wait([ + subject.execute>( + const PostRequest('/foo', data: EmptyRequestBody()), + onResponse: (response) { + return switch (response) { + OkResponse(:final response) => Success( + response['foo'] as String, + ), + _ => const Failure('Not expected: orElse'), + }; + }, + ), + subject.execute>( + const PutRequest('/bar', data: EmptyRequestBody()), + onResponse: (response) { + return switch (response) { + OkResponse(:final response) => Success( + response['foo'] as String, + ), + _ => const Failure('Not expected: orElse'), + }; + }, + ), + subject.execute>( + const DeleteRequest('/baz', data: EmptyRequestBody()), + onResponse: (response) { + return switch (response) { + OkResponse(:final response) => Success( + response['foo'] as String, + ), + _ => const Failure('Not expected: orElse'), + }; + }, + ), + ]); + + expect( + mutativeRequestSuccessRequests.map((e) => e.path), + contains('/foo'), ); - }); - - test('it does not emit a MutativeRequestSuccess event', () async { - final subject = buildSubject(); - await Future.wait([ - subject.execute>( - const PostRequest('/foo', data: EmptyRequestBody()), - onResponse: (response) { - return switch (response) { - OkResponse(:final response) => Success(response['foo'] as String), - _ => const Failure('Not expected: orElse'), - }; - }, - ), - subject.execute>( - const PutRequest('/bar', data: EmptyRequestBody()), - onResponse: (response) { - return switch (response) { - OkResponse(:final response) => Success(response['foo'] as String), - _ => const Failure('Not expected: orElse'), - }; - }, - ), - subject.execute>( - const DeleteRequest('/baz', data: EmptyRequestBody()), - onResponse: (response) { - return switch (response) { - OkResponse(:final response) => Success(response['foo'] as String), - _ => const Failure('Not expected: orElse'), - }; - }, - ), - ]); - - expect(mutativeRequestSuccessRequests.isEmpty, true); - }); + expect( + mutativeRequestSuccessRequests.map((e) => e.path), + contains('/bar'), + ); + expect( + mutativeRequestSuccessRequests.map((e) => e.path), + contains('/baz'), + ); + }, + ); }); + + group( + 'and the response has status codes other than 200 or 204', + () { + setUp(() { + charlatan + ..whenPost( + '/foo', + (request) => CharlatanHttpResponse( + body: {}, + statusCode: 404, + ), + ) + ..whenPut( + '/bar', + (request) => CharlatanHttpResponse( + body: {}, + statusCode: 422, + ), + ) + ..whenDelete( + '/baz', + (request) => CharlatanHttpResponse( + body: {}, + statusCode: 500, + ), + ); + }); + + test( + 'it does not emit a MutativeRequestSuccess event', + () async { + final subject = buildSubject(); + await Future.wait([ + subject.execute>( + const PostRequest('/foo', data: EmptyRequestBody()), + onResponse: (response) { + return switch (response) { + OkResponse(:final response) => Success( + response['foo'] as String, + ), + _ => const Failure('Not expected: orElse'), + }; + }, + ), + subject.execute>( + const PutRequest('/bar', data: EmptyRequestBody()), + onResponse: (response) { + return switch (response) { + OkResponse(:final response) => Success( + response['foo'] as String, + ), + _ => const Failure('Not expected: orElse'), + }; + }, + ), + subject.execute>( + const DeleteRequest('/baz', data: EmptyRequestBody()), + onResponse: (response) { + return switch (response) { + OkResponse(:final response) => Success( + response['foo'] as String, + ), + _ => const Failure('Not expected: orElse'), + }; + }, + ), + ]); + + expect(mutativeRequestSuccessRequests.isEmpty, true); + }, + ); + }, + ); }); }); }); group('when response is unsuccessful', () { const defaultPath = '/foo'; - void setupErrorResponse({required int statusCode, String path = defaultPath, Object? body}) { - charlatan.whenGet(path, (request) => CharlatanHttpResponse(statusCode: statusCode, body: body)); + void setupErrorResponse({ + required int statusCode, + String path = defaultPath, + Object? body, + }) { + charlatan.whenGet( + path, + (request) => + CharlatanHttpResponse(statusCode: statusCode, body: body), + ); } group('when status code is 401', () { @@ -459,26 +572,30 @@ void main() { setupErrorResponse(statusCode: 401); }); - test('it emits an authFailure event and invokes unauthorized', () async { - final response = await buildSubject().execute>( - const GetRequest(defaultPath), - onResponse: (response) { - return switch (response) { - Unauthorized() => const Success(true), - _ => const Failure('Not expected: orElse'), - }; - }, - ); + test( + 'it emits an authFailure event and invokes unauthorized', + () async { + final response = await buildSubject() + .execute>( + const GetRequest(defaultPath), + onResponse: (response) { + return switch (response) { + Unauthorized() => const Success(true), + _ => const Failure('Not expected: orElse'), + }; + }, + ); - expect(authFailureRequests.single.path, '/foo'); + expect(authFailureRequests.single.path, '/foo'); - switch (response) { - case Success(:final success): - expect(success, isTrue); - case Failure(): - fail('Expected Success'); - } - }); + switch (response) { + case Success(:final success): + expect(success, isTrue); + case Failure(): + fail('Expected Success'); + } + }, + ); }); group('when status code is 403', () { @@ -487,15 +604,16 @@ void main() { }); test('it returns forbidden', () async { - final response = await buildSubject().execute>( - const GetRequest(defaultPath), - onResponse: (response) { - return switch (response) { - Forbidden() => const Success(true), - _ => const Failure('Not expected: orElse'), - }; - }, - ); + final response = await buildSubject() + .execute>( + const GetRequest(defaultPath), + onResponse: (response) { + return switch (response) { + Forbidden() => const Success(true), + _ => const Failure('Not expected: orElse'), + }; + }, + ); switch (response) { case Success(:final success): @@ -512,15 +630,16 @@ void main() { }); test('it returns notFound', () async { - final response = await buildSubject().execute>( - const GetRequest(defaultPath), - onResponse: (response) { - return switch (response) { - NotFound() => const Success(true), - _ => const Failure('Not expected: orElse'), - }; - }, - ); + final response = await buildSubject() + .execute>( + const GetRequest(defaultPath), + onResponse: (response) { + return switch (response) { + NotFound() => const Success(true), + _ => const Failure('Not expected: orElse'), + }; + }, + ); switch (response) { case Success(:final success): @@ -533,19 +652,23 @@ void main() { group('when status code is 422', () { setUp(() { - setupErrorResponse(statusCode: 422, body: const Foo(message: 'error').toMap()); + setupErrorResponse( + statusCode: 422, + body: const Foo(message: 'error').toMap(), + ); }); test('it returns unprocessableEntity', () async { - final response = await buildSubject().execute>( - const GetRequest(defaultPath), - onResponse: (response) { - return switch (response) { - UnprocessableEntity() => const Success(true), - _ => const Failure('Not expected: orElse'), - }; - }, - ); + final response = await buildSubject() + .execute>( + const GetRequest(defaultPath), + onResponse: (response) { + return switch (response) { + UnprocessableEntity() => const Success(true), + _ => const Failure('Not expected: orElse'), + }; + }, + ); switch (response) { case Success(:final success): @@ -558,19 +681,23 @@ void main() { group('when status code is 426', () { setUp(() { - setupErrorResponse(statusCode: 426, body: const Foo(message: 'error').toMap()); + setupErrorResponse( + statusCode: 426, + body: const Foo(message: 'error').toMap(), + ); }); test('it returns upgradeRequired', () async { - final response = await buildSubject().execute>( - const GetRequest(defaultPath), - onResponse: (response) { - return switch (response) { - UpgradeRequired() => const Success(true), - _ => const Failure('Not expected: orElse'), - }; - }, - ); + final response = await buildSubject() + .execute>( + const GetRequest(defaultPath), + onResponse: (response) { + return switch (response) { + UpgradeRequired() => const Success(true), + _ => const Failure('Not expected: orElse'), + }; + }, + ); switch (response) { case Success(:final success): @@ -587,15 +714,16 @@ void main() { }); test('it returns serverError', () async { - final response = await buildSubject().execute>( - const GetRequest(defaultPath), - onResponse: (response) { - return switch (response) { - ServerError() => const Success(true), - _ => const Failure('Not expected: orElse'), - }; - }, - ); + final response = await buildSubject() + .execute>( + const GetRequest(defaultPath), + onResponse: (response) { + return switch (response) { + ServerError() => const Success(true), + _ => const Failure('Not expected: orElse'), + }; + }, + ); switch (response) { case Success(:final success): @@ -612,15 +740,16 @@ void main() { }); test('it returns service unavailable', () async { - final response = await buildSubject().execute>( - const GetRequest(defaultPath), - onResponse: (response) { - return switch (response) { - ServiceUnavailable() => const Success(true), - _ => const Failure('Not expected: orElse'), - }; - }, - ); + final response = await buildSubject() + .execute>( + const GetRequest(defaultPath), + onResponse: (response) { + return switch (response) { + ServiceUnavailable() => const Success(true), + _ => const Failure('Not expected: orElse'), + }; + }, + ); switch (response) { case Success(:final success): @@ -637,15 +766,16 @@ void main() { }); test('it returns genericError', () async { - final response = await buildSubject().execute>( - const GetRequest(defaultPath), - onResponse: (response) { - return switch (response) { - GenericError() => const Success(true), - _ => const Failure('Not expected: orElse'), - }; - }, - ); + final response = await buildSubject() + .execute>( + const GetRequest(defaultPath), + onResponse: (response) { + return switch (response) { + GenericError() => const Success(true), + _ => const Failure('Not expected: orElse'), + }; + }, + ); switch (response) { case Success(:final success): @@ -658,32 +788,39 @@ void main() { group('when connection issue occurs', () { setUp(() { - charlatan.whenGet('/foo', (request) => throw const SocketException('Oops')); + charlatan.whenGet( + '/foo', + (request) => throw const SocketException('Oops'), + ); }); - test('it returns genericError and isConnectionIssue is true', () async { - final response = await buildSubject().execute>( - const GetRequest(defaultPath), - onResponse: (response) { - return switch (response) { - GenericError(:final isConnectionIssue) => () { - { - expect(isConnectionIssue, isTrue); - return const Success(true); - } - }(), - _ => const Failure('Not expected: orElse'), - }; - }, - ); + test( + 'it returns genericError and isConnectionIssue is true', + () async { + final response = await buildSubject() + .execute>( + const GetRequest(defaultPath), + onResponse: (response) { + return switch (response) { + GenericError(:final isConnectionIssue) => () { + { + expect(isConnectionIssue, isTrue); + return const Success(true); + } + }(), + _ => const Failure('Not expected: orElse'), + }; + }, + ); - switch (response) { - case Success(:final success): - expect(success, isTrue); - case Failure(): - fail('Expected Success'); - } - }); + switch (response) { + case Success(:final success): + expect(success, isTrue); + case Failure(): + fail('Expected Success'); + } + }, + ); }); group('RetryBehavior', () { @@ -695,18 +832,22 @@ void main() { return CharlatanHttpResponse(statusCode: 522); }); - final response = await buildSubject().execute>( - const GetRequest( - defaultPath, - retryBehavior: Retry(maxRetries: 3, retryInterval: Duration(milliseconds: 100)), - ), - onResponse: (response) { - return switch (response) { - GenericError() => const Success(true), - _ => const Failure('Not expected: orElse'), - }; - }, - ); + final response = await buildSubject() + .execute>( + const GetRequest( + defaultPath, + retryBehavior: Retry( + maxRetries: 3, + retryInterval: Duration(milliseconds: 100), + ), + ), + onResponse: (response) { + return switch (response) { + GenericError() => const Success(true), + _ => const Failure('Not expected: orElse'), + }; + }, + ); switch (response) { case Success(:final success): @@ -727,15 +868,19 @@ void main() { return CharlatanHttpResponse(statusCode: 522); }); - final response = await buildSubject().execute>( - const GetRequest(defaultPath, retryBehavior: NeverRetry()), - onResponse: (response) { - return switch (response) { - GenericError() => const Success(true), - _ => const Failure('Not expected: orElse'), - }; - }, - ); + final response = await buildSubject() + .execute>( + const GetRequest( + defaultPath, + retryBehavior: NeverRetry(), + ), + onResponse: (response) { + return switch (response) { + GenericError() => const Success(true), + _ => const Failure('Not expected: orElse'), + }; + }, + ); expect((response as Success).success, isTrue); expect(requestCount, 1); @@ -750,18 +895,22 @@ void main() { return CharlatanHttpResponse(statusCode: 522); }); - final response = await buildSubject(retryBehavior: NeverRetry()).execute>( - const GetRequest( - defaultPath, - retryBehavior: Retry(maxRetries: 2, retryInterval: Duration(milliseconds: 100)), - ), - onResponse: (response) { - return switch (response) { - GenericError() => const Success(true), - _ => const Failure('Not expected: orElse'), - }; - }, - ); + final response = await buildSubject(retryBehavior: NeverRetry()) + .execute>( + const GetRequest( + defaultPath, + retryBehavior: Retry( + maxRetries: 2, + retryInterval: Duration(milliseconds: 100), + ), + ), + onResponse: (response) { + return switch (response) { + GenericError() => const Success(true), + _ => const Failure('Not expected: orElse'), + }; + }, + ); switch (response) { case Success(:final success): @@ -782,25 +931,26 @@ void main() { return CharlatanHttpResponse(statusCode: statusCode); }); - final response = await buildSubject(retryBehavior: NeverRetry()).execute>( - GetRequest( - defaultPath, - retryBehavior: Retry( - maxRetries: 2, - retryInterval: Duration(milliseconds: 100), - retryClause: (r) { - // Body will be `null`; essentially disallow retrying - return r != null; + final response = await buildSubject(retryBehavior: NeverRetry()) + .execute>( + GetRequest( + defaultPath, + retryBehavior: Retry( + maxRetries: 2, + retryInterval: Duration(milliseconds: 100), + retryClause: (r) { + // Body will be `null`; essentially disallow retrying + return r != null; + }, + ), + ), + onResponse: (response) { + return switch (response) { + GenericError() => const Success(true), + _ => const Failure('Not expected: orElse'), + }; }, - ), - ), - onResponse: (response) { - return switch (response) { - GenericError() => const Success(true), - _ => const Failure('Not expected: orElse'), - }; - }, - ); + ); switch (response) { case Success(:final success): @@ -872,16 +1022,30 @@ class _SturdyHttpEventListener extends SturdyHttpEventListener { } } -@MappableClass() -class Foo with FooMappable { +class Foo { const Foo({required this.message}); final String message; + + static Foo fromMap(Map map) { + return Foo(message: map['message'] as String); + } + + Map toMap() { + return {'message': message}; + } } -@MappableClass() -class NotFoo with NotFooMappable { +class NotFoo { const NotFoo({required this.notMessage}); final String notMessage; + + static NotFoo fromMap(Map map) { + return NotFoo(notMessage: map['notMessage'] as String); + } + + Map toMap() { + return {'notMessage': notMessage}; + } } From db4ff6d270f5c202db85fd1fb76212a912cdfc76 Mon Sep 17 00:00:00 2001 From: Brandon Trautmann <8343465+btrautmann@users.noreply.github.com> Date: Mon, 22 Dec 2025 16:02:43 -0500 Subject: [PATCH 03/15] bump version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 2a870e7..cb78b82 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: sturdy_http description: A strongly typed, event-based, reliable HTTP client that wraps `Dio`. -version: 0.5.1 +version: 0.6.0 homepage: https://github.com/Betterment/sturdy_http repository: https://github.com/Betterment/sturdy_http From 3eb86a42a790d5f3012850109a042cad9ac7f13b Mon Sep 17 00:00:00 2001 From: Brandon Trautmann <8343465+btrautmann@users.noreply.github.com> Date: Mon, 22 Dec 2025 16:06:17 -0500 Subject: [PATCH 04/15] bump dart version on CI --- .github/workflows/ci.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a52bfc7..d2cad43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,18 +7,17 @@ name: CI on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] env: - DART_VERSION: 3.5.0 + DART_VERSION: 3.10.4 jobs: ci: runs-on: ubuntu-latest steps: - - name: Checkout uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 @@ -42,7 +41,7 @@ jobs: - uses: codecov/codecov-action@40a12dcee2df644d47232dde008099a3e9e4f865 #v3.1.2 with: files: coverage/lcov.info - + pana: runs-on: ubuntu-latest steps: From 39c21b1852ff67862d42a36bedacd163c53daae0 Mon Sep 17 00:00:00 2001 From: Brandon Trautmann <8343465+btrautmann@users.noreply.github.com> Date: Mon, 22 Dec 2025 16:15:52 -0500 Subject: [PATCH 05/15] remove mapper files --- test/src/sturdy_http_test.mapper.dart | 197 -------------------------- 1 file changed, 197 deletions(-) delete mode 100644 test/src/sturdy_http_test.mapper.dart diff --git a/test/src/sturdy_http_test.mapper.dart b/test/src/sturdy_http_test.mapper.dart deleted file mode 100644 index fead1b2..0000000 --- a/test/src/sturdy_http_test.mapper.dart +++ /dev/null @@ -1,197 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// dart format off -// ignore_for_file: type=lint -// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member -// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter - -part of 'sturdy_http_test.dart'; - -class FooMapper extends ClassMapperBase { - FooMapper._(); - - static FooMapper? _instance; - static FooMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = FooMapper._()); - } - return _instance!; - } - - @override - final String id = 'Foo'; - - static String _$message(Foo v) => v.message; - static const Field _f$message = Field('message', _$message); - - @override - final MappableFields fields = const {#message: _f$message}; - - static Foo _instantiate(DecodingData data) { - return Foo(message: data.dec(_f$message)); - } - - @override - final Function instantiate = _instantiate; - - static Foo fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static Foo fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin FooMappable { - String toJson() { - return FooMapper.ensureInitialized().encodeJson(this as Foo); - } - - Map toMap() { - return FooMapper.ensureInitialized().encodeMap(this as Foo); - } - - FooCopyWith get copyWith => - _FooCopyWithImpl(this as Foo, $identity, $identity); - @override - String toString() { - return FooMapper.ensureInitialized().stringifyValue(this as Foo); - } - - @override - bool operator ==(Object other) { - return FooMapper.ensureInitialized().equalsValue(this as Foo, other); - } - - @override - int get hashCode { - return FooMapper.ensureInitialized().hashValue(this as Foo); - } -} - -extension FooValueCopy<$R, $Out> on ObjectCopyWith<$R, Foo, $Out> { - FooCopyWith<$R, Foo, $Out> get $asFoo => - $base.as((v, t, t2) => _FooCopyWithImpl<$R, $Out>(v, t, t2)); -} - -abstract class FooCopyWith<$R, $In extends Foo, $Out> - implements ClassCopyWith<$R, $In, $Out> { - $R call({String? message}); - FooCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); -} - -class _FooCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Foo, $Out> - implements FooCopyWith<$R, Foo, $Out> { - _FooCopyWithImpl(super.value, super.then, super.then2); - - @override - late final ClassMapperBase $mapper = FooMapper.ensureInitialized(); - @override - $R call({String? message}) => - $apply(FieldCopyWithData({if (message != null) #message: message})); - @override - Foo $make(CopyWithData data) => - Foo(message: data.get(#message, or: $value.message)); - - @override - FooCopyWith<$R2, Foo, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => - _FooCopyWithImpl<$R2, $Out2>($value, $cast, t); -} - -class NotFooMapper extends ClassMapperBase { - NotFooMapper._(); - - static NotFooMapper? _instance; - static NotFooMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = NotFooMapper._()); - } - return _instance!; - } - - @override - final String id = 'NotFoo'; - - static String _$notMessage(NotFoo v) => v.notMessage; - static const Field _f$notMessage = Field( - 'notMessage', - _$notMessage, - ); - - @override - final MappableFields fields = const {#notMessage: _f$notMessage}; - - static NotFoo _instantiate(DecodingData data) { - return NotFoo(notMessage: data.dec(_f$notMessage)); - } - - @override - final Function instantiate = _instantiate; - - static NotFoo fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static NotFoo fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin NotFooMappable { - String toJson() { - return NotFooMapper.ensureInitialized().encodeJson(this as NotFoo); - } - - Map toMap() { - return NotFooMapper.ensureInitialized().encodeMap(this as NotFoo); - } - - NotFooCopyWith get copyWith => - _NotFooCopyWithImpl(this as NotFoo, $identity, $identity); - @override - String toString() { - return NotFooMapper.ensureInitialized().stringifyValue(this as NotFoo); - } - - @override - bool operator ==(Object other) { - return NotFooMapper.ensureInitialized().equalsValue(this as NotFoo, other); - } - - @override - int get hashCode { - return NotFooMapper.ensureInitialized().hashValue(this as NotFoo); - } -} - -extension NotFooValueCopy<$R, $Out> on ObjectCopyWith<$R, NotFoo, $Out> { - NotFooCopyWith<$R, NotFoo, $Out> get $asNotFoo => - $base.as((v, t, t2) => _NotFooCopyWithImpl<$R, $Out>(v, t, t2)); -} - -abstract class NotFooCopyWith<$R, $In extends NotFoo, $Out> - implements ClassCopyWith<$R, $In, $Out> { - $R call({String? notMessage}); - NotFooCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); -} - -class _NotFooCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, NotFoo, $Out> - implements NotFooCopyWith<$R, NotFoo, $Out> { - _NotFooCopyWithImpl(super.value, super.then, super.then2); - - @override - late final ClassMapperBase $mapper = NotFooMapper.ensureInitialized(); - @override - $R call({String? notMessage}) => $apply( - FieldCopyWithData({if (notMessage != null) #notMessage: notMessage}), - ); - @override - NotFoo $make(CopyWithData data) => - NotFoo(notMessage: data.get(#notMessage, or: $value.notMessage)); - - @override - NotFooCopyWith<$R2, NotFoo, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => - _NotFooCopyWithImpl<$R2, $Out2>($value, $cast, t); -} - From fe5126b8b10f42e5f5c5125627135b46e0291a79 Mon Sep 17 00:00:00 2001 From: Brandon Trautmann <8343465+btrautmann@users.noreply.github.com> Date: Mon, 22 Dec 2025 16:16:20 -0500 Subject: [PATCH 06/15] remove imports --- lib/src/sturdy_http.dart | 1 - test/src/deserializer_test.dart | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/src/sturdy_http.dart b/lib/src/sturdy_http.dart index e649def..a0528ba 100644 --- a/lib/src/sturdy_http.dart +++ b/lib/src/sturdy_http.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'dart:isolate'; import 'package:collection/collection.dart'; -import 'package:dart_mappable/dart_mappable.dart'; import 'package:dio/dio.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:sturdy_http/sturdy_http.dart'; diff --git a/test/src/deserializer_test.dart b/test/src/deserializer_test.dart index b489bd7..83ee558 100644 --- a/test/src/deserializer_test.dart +++ b/test/src/deserializer_test.dart @@ -2,7 +2,6 @@ import 'dart:isolate'; -import 'package:dart_mappable/dart_mappable.dart'; import 'package:sturdy_http/sturdy_http.dart'; import 'package:test/test.dart'; From d76e84394de59f71b1f61e32fc0aa86ce0a597c3 Mon Sep 17 00:00:00 2001 From: Brandon Trautmann <8343465+btrautmann@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:20:57 -0500 Subject: [PATCH 07/15] fix up tests, remove freezed_annotation --- lib/src/sturdy_http.dart | 17 ++++++------- pubspec.yaml | 1 - test/src/deserializer_test.dart | 44 +++++++++++++++++++-------------- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/lib/src/sturdy_http.dart b/lib/src/sturdy_http.dart index a0528ba..6588fc8 100644 --- a/lib/src/sturdy_http.dart +++ b/lib/src/sturdy_http.dart @@ -3,7 +3,6 @@ import 'dart:isolate'; import 'package:collection/collection.dart'; import 'package:dio/dio.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:sturdy_http/sturdy_http.dart'; import 'package:uuid/uuid.dart'; @@ -112,15 +111,13 @@ class SturdyHttp { onResponse: onResponse, ); } on Exception catch (e) { - if (e is MapperException || e is CheckedFromJsonException) { - await _onEvent( - DecodingError( - request: responsePayload.dioResponse!.requestOptions, - exception: e, - stackTrace: StackTrace.current, - ), - ); - } + await _onEvent( + DecodingError( + request: responsePayload.dioResponse!.requestOptions, + exception: e, + stackTrace: StackTrace.current, + ), + ); rethrow; } } diff --git a/pubspec.yaml b/pubspec.yaml index cb78b82..2375edb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,6 @@ environment: dependencies: collection: ^1.19.1 dio: ^5.9.0 - freezed_annotation: ^3.1.0 uuid: ^4.5.2 dev_dependencies: diff --git a/test/src/deserializer_test.dart b/test/src/deserializer_test.dart index 83ee558..e3acdb0 100644 --- a/test/src/deserializer_test.dart +++ b/test/src/deserializer_test.dart @@ -34,7 +34,7 @@ void main() { test('it handles multiple requests for deserialization', () async { Foo onResponse(NetworkResponse response) { return switch (response) { - OkResponse(:final response) => FooMapper.fromMap(response), + OkResponse(:final response) => Foo.fromMap(response), _ => fail('Not expected: orElse'), }; } @@ -54,24 +54,30 @@ void main() { expect(resultTwo.message, '2'); }); - test( - 'it throws MapperExceptions when deserialization issues occur', - () async { - onResponse(NetworkResponse response) { - return switch (response) { - OkResponse(:final response) => NotFooMapper.fromMap(response), - _ => fail('orElse not expected'), - }; - } + test('it rethrows Exceptions when deserialization issues occur', () async { + onResponse(NetworkResponse response) { + return switch (response) { + OkResponse(:final response) => NotFoo.fromMap(response), + _ => fail('orElse not expected'), + }; + } - final response = OkResponse(const Foo(message: 'Nope').toMap()); - final subject = BackgroundDeserializer(); - try { - await subject.deserialize(response: response, onResponse: onResponse); - } on Exception catch (e) { - expect(e, isA()); - } - }, - ); + final response = OkResponse(const Foo(message: 'Nope').toMap()); + final subject = BackgroundDeserializer(); + try { + await subject.deserialize(response: response, onResponse: onResponse); + } on Exception catch (e) { + expect( + e, + isA().having( + (e) => e.toString(), + 'description', + contains( + "sturdyHttpWorkerIsolate type 'Null' is not a subtype of type 'String'", + ), + ), + ); + } + }); }); } From 8de8f672ee95689ec9efde647087ee23690303c9 Mon Sep 17 00:00:00 2001 From: Brandon Trautmann <8343465+btrautmann@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:22:59 -0500 Subject: [PATCH 08/15] Update README.md --- README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 20db63f..beb8c96 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,12 @@ Then, execute requests: Future> fetch({required int id}) async { return _client.execute( GetRequest('/v6/data/${id}'), - onResponse: (r) => r.maybeWhen( - ok: (json) => Result.success(MyData.fromJson), - orElse: () => Result.failure(r), - ), + onResponse: (r) { + return switch (r) { + OkResponse(:final response) => Result.success(MyData.fromJson(response)), + _ => Result.failure(r), + }; + }, ); } ``` @@ -69,9 +71,6 @@ extension SturdyHttpX on SturdyHttp { } ``` -> **Warning** -> We're considering open-sourcing our `Result` type and integrating it into `SturdyHttp`. If we did, we'd likely change `execute`'s return type to be a `Result`, and offer `executeUnsafe` as the non-`Result` (i.e `Exception` throwing) alternative. This would be a breaking change. - ## Contributing If you run into a bug or limitation when using `SturdyHttp`, we'd love your help in resolving it. First, it would be awesome if you could [open an issue](https://github.com/Betterment/sturdy_http/issues/new/choose) to discuss. If we feel like we should move forward with a change and you're willing to contribute, create a fork of `SturdyHttp` and open a PR against the main repo. From 09e2bc00da974f22972217e8396c3d6499abc92d Mon Sep 17 00:00:00 2001 From: Brandon Trautmann <8343465+btrautmann@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:31:28 -0500 Subject: [PATCH 09/15] remove build_runner --- pubspec.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 2375edb..da0f3f7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,7 +13,6 @@ dependencies: uuid: ^4.5.2 dev_dependencies: - build_runner: ^2.10.4 charlatan: ^0.5.0 lints: ^6.0.0 test: ^1.28.0 From 4f57999f09728d0f39f645d4c759b5fbb625d68c Mon Sep 17 00:00:00 2001 From: Brandon Trautmann <8343465+btrautmann@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:39:12 -0500 Subject: [PATCH 10/15] fix doc reference --- lib/src/network_request.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/network_request.dart b/lib/src/network_request.dart index d25513b..877ccc2 100644 --- a/lib/src/network_request.dart +++ b/lib/src/network_request.dart @@ -40,7 +40,7 @@ abstract class NetworkRequest { final NetworkRequestBody data; /// Whether this request should be considered as mutative. If false, - /// [SturdyHttp] will not emit an [SturdyHttpEvent.mutativeRequestSuccess] + /// [SturdyHttp] will not emit an [MutativeRequestSuccess] /// event. final bool shouldTriggerDataMutation; From c67bd3269d71f3a0b9cbdc529ef0756f4476fb58 Mon Sep 17 00:00:00 2001 From: Brandon Trautmann <8343465+btrautmann@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:46:24 -0500 Subject: [PATCH 11/15] add 0.6.0 to CHANGELOG to comply with pana --- CHANGELOG.md | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4f7964..d98e2cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,50 +1,64 @@ +## 0.6.0 + +## Unreleased + +- Removed `freezed` and `freezed_annotation` dependencies. +- Updated `NetworkRequestBody` and `SturdyHttpEvent` to be a sealed classes. +- Changed behavior of when `DecodingError`s are emitted. + ## 0.5.1 ## What's Changed -* fix: loosen collection constraint by @btrautmann in https://github.com/Betterment/sturdy_http/pull/16 +- fix: loosen collection constraint by @btrautmann in https://github.com/Betterment/sturdy_http/pull/16 **Full Changelog**: https://github.com/Betterment/sturdy_http/compare/v0.5.0...v0.5.1 ## 0.5.0 ## What's Changed -* refactor: make network response a sealed class by @btrautmann in https://github.com/Betterment/sturdy_http/pull/14 +- refactor: make network response a sealed class by @btrautmann in https://github.com/Betterment/sturdy_http/pull/14 **Full Changelog**: https://github.com/Betterment/sturdy_http/compare/v0.4.0...v0.5.0 ## 0.4.0 ## What's Changed -* feat: Add support for `426: Upgrade Required` by @willlockwood in https://github.com/Betterment/sturdy_http/pull/12 + +- feat: Add support for `426: Upgrade Required` by @willlockwood in https://github.com/Betterment/sturdy_http/pull/12 ## New Contributors -* @willlockwood made their first contribution in https://github.com/Betterment/sturdy_http/pull/12 + +- @willlockwood made their first contribution in https://github.com/Betterment/sturdy_http/pull/12 **Full Changelog**: https://github.com/Betterment/sturdy_http/compare/v0.3.1...v0.4.0 ## 0.3.1 ## What's Changed -* fix: export retry_behavior by @btrautmann in https://github.com/Betterment/sturdy_http/pull/10 + +- fix: export retry_behavior by @btrautmann in https://github.com/Betterment/sturdy_http/pull/10 **Full Changelog**: https://github.com/Betterment/sturdy_http/compare/v0.3.0...v0.3.1 ## 0.3.0 ## What's Changed -* feat: support retrying failed requests by @btrautmann in https://github.com/Betterment/sturdy_http/pull/8 + +- feat: support retrying failed requests by @btrautmann in https://github.com/Betterment/sturdy_http/pull/8 **Full Changelog**: https://github.com/Betterment/sturdy_http/compare/v0.2.0...v0.3.0 ## 0.2.0 ## What's Changed -* chore: update outdated dependencies by @ClaireDavis in https://github.com/Betterment/sturdy_http/pull/6 + +- chore: update outdated dependencies by @ClaireDavis in https://github.com/Betterment/sturdy_http/pull/6 ## New Contributors -* @ClaireDavis made their first contribution in https://github.com/Betterment/sturdy_http/pull/6 + +- @ClaireDavis made their first contribution in https://github.com/Betterment/sturdy_http/pull/6 **Full Changelog**: https://github.com/Betterment/sturdy_http/compare/v0.1.0...v0.2.0 From 19a957404ba9fe7d5ce19bd0aaf66a7391e924bf Mon Sep 17 00:00:00 2001 From: Brandon Trautmann <8343465+btrautmann@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:50:42 -0500 Subject: [PATCH 12/15] Ignore settings.json --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2f690df..64c89e7 100644 --- a/.gitignore +++ b/.gitignore @@ -77,4 +77,6 @@ build/ !**/ios/**/default.perspectivev3 # FVM Version Cache -.fvm/ \ No newline at end of file +.fvm/ +# FVM Flutter SDK Configuration +.vscode/settings.json From 630f0d3493b8dea291bf7403f1874a15d3a4ce33 Mon Sep 17 00:00:00 2001 From: Brandon Trautmann <8343465+btrautmann@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:50:59 -0500 Subject: [PATCH 13/15] Apply .gitignore --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 0f7b500..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "dart.flutterSdkPath": ".fvm/versions/3.38.5" -} \ No newline at end of file From 317c3c2dc2b441cd2b53e478ca194e1911341e06 Mon Sep 17 00:00:00 2001 From: Brandon Trautmann <8343465+btrautmann@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:51:54 -0500 Subject: [PATCH 14/15] Ignore .fvmrc --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 64c89e7..dc7cc48 100644 --- a/.gitignore +++ b/.gitignore @@ -78,5 +78,7 @@ build/ # FVM Version Cache .fvm/ +# FVM Configuration +.fvmrc # FVM Flutter SDK Configuration .vscode/settings.json From 9a40b4336203828cd885dd18be5a9405aaa12d42 Mon Sep 17 00:00:00 2001 From: Brandon Trautmann <8343465+btrautmann@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:52:06 -0500 Subject: [PATCH 15/15] Ignore .fvmrc --- .fvmrc | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 .fvmrc diff --git a/.fvmrc b/.fvmrc deleted file mode 100644 index c6a1246..0000000 --- a/.fvmrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "flutter": "3.29.2", - "runPubGetOnSdkChanges": true, - "updateVscodeSettings": true, - "updateGitIgnore": false -} \ No newline at end of file