Skip to content

Commit

Permalink
refactor: make network response a sealed class (#14)
Browse files Browse the repository at this point in the history
* wip: mostly working, may need to add generic response type back to execute invocation

* add type back to top level response types

* Make errors required on all non generic errors

* rename Ok and NoContent

* cleanup

* bump version constraints on most deps

* bump dart constraint

* install correct dart version on CI

* bump dart constraint

* bump CI dart version

---------

Co-authored-by: nhannah <ne.hannah@gmail.com>
  • Loading branch information
btrautmann and nhannah authored Sep 17, 2024
1 parent fdce1b7 commit 023a01b
Show file tree
Hide file tree
Showing 11 changed files with 362 additions and 2,310 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ on:
pull_request:
branches: [ main ]

env:
DART_VERSION: 3.5.0

jobs:
ci:
runs-on: ubuntu-latest
Expand All @@ -22,7 +25,7 @@ jobs:
- name: Setup dart
uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f # v1.5.0
with:
sdk: 3.3.1
sdk: ${{ env.DART_VERSION }}

- name: Install dependencies
run: dart pub get
Expand All @@ -49,7 +52,7 @@ jobs:
- name: Setup dart
uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f # v1.5.0
with:
sdk: 3.3.1
sdk: ${{ env.DART_VERSION }}

- name: Install dependencies
run: |
Expand Down
24 changes: 11 additions & 13 deletions example/main.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import 'package:charlatan/charlatan.dart';
import 'package:sturdy_http/src/network_request.dart';
import 'package:sturdy_http/src/sturdy_http.dart';
import 'package:sturdy_http/src/sturdy_http_event_listener.dart';
import 'package:sturdy_http/sturdy_http.dart';

void main(List<String> args) async {
// Set up some fake HTTP responses using Charlatan
Expand All @@ -25,13 +23,13 @@ void main(List<String> args) async {
);

// A GetRequest. Prints 'Hello World!'.
await client.execute<String, void>(
await client.execute<Json, void>(
GetRequest('/foo'),
onResponse: (r) {
r.maybeWhen(
ok: (message) => print(message),
orElse: () => print('GET /foo failed: $r'),
);
return switch (r) {
OkResponse(:final response) => print(response),
_ => print('GET /foo failed: $r'),
};
},
);

Expand All @@ -40,13 +38,13 @@ void main(List<String> args) async {
// Prints:
// 'mutative request success' <-- From ExampleEventListener
// 'success!'
await client.execute<String, void>(
await client.execute<void, void>(
PostRequest('/foo', data: NetworkRequestBody.empty()),
onResponse: (r) {
r.maybeWhen(
okNoContent: () => print('success!'),
orElse: () => print('POST /foo failed: $r'),
);
return switch (r) {
OkNoContent() => print('success!'),
_ => print('POST /foo failed: $r'),
};
},
);
}
Expand Down
147 changes: 93 additions & 54 deletions lib/src/network_response.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import 'package:dio/dio.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:sturdy_http/sturdy_http.dart';
// ignore_for_file: public_member_api_docs

part 'network_response.freezed.dart';
import 'package:dio/dio.dart';

/// The produced object after [SturdyHttp] processes a [NetworkRequest].
///
Expand All @@ -12,58 +10,99 @@ part 'network_response.freezed.dart';
/// Most often the call sites executing a [NetworkRequest] will only be interested
/// in a select few of these, and will resolve their error cases via a `maybeWhen`
/// using the `orElse` clause.
@freezed
class NetworkResponse<R> with _$NetworkResponse<R> {
/// 200 - for successful responses that include a body.
const factory NetworkResponse.ok(R response) = _Ok;

/// 204 - for successful responses that don't include a body.
const factory NetworkResponse.okNoContent() = _OkNoContent;

/// 401 - for responses when the request was missing required authentication.
const factory NetworkResponse.unauthorized(DioException error) =
_Unauthorized;

/// 403 - for responses when the request was authenticated but the
/// action is not authorized/allowed.
const factory NetworkResponse.forbidden(DioException error) = _Forbidden;

/// 404 - for responses when we could not locate a resource, or when
/// someone would attempt to access a forbidden resource due to a bug.
const factory NetworkResponse.notFound(DioException error) = _NotFound;

/// 422 - for responses when the request inputs failed our validations.
const factory NetworkResponse.unprocessableEntity({
required DioException error,
required R response,
}) = _UnprocessableEntity;

/// 426 - for responses when access to a resource requires a client upgrade.
const factory NetworkResponse.upgradeRequired(DioException error) =
_UpgradeRequired;

/// 500 - for responses where the service had an error while processing
/// the request.
const factory NetworkResponse.serverError(DioException error) = _ServerError;

/// 503 - for responses when an underlying service issue prevents us from
/// fulfilling the request.
const factory NetworkResponse.serviceUnavailable(DioException error) =
_ServiceUnavailable;

/// An error designated as a fallback in the event that we receive a status code
/// we don't explicitly handle *or* a request or response otherwise fails to meet
/// our expectations as "valid". The [message] will describe the condition and a
/// [DioException] will be present if it was available as a result of the request.
const factory NetworkResponse.genericError({
required String message,
required bool isConnectionIssue,
DioException? error,
}) = _GenericError;
sealed class NetworkResponse<R> {
const NetworkResponse();
}

sealed class NetworkResponseSuccess<R> extends NetworkResponse<R> {
const NetworkResponseSuccess();
}

final class OkNoContent<R> extends NetworkResponseSuccess<R> {
const OkNoContent();
}

final class OkResponse<T> extends NetworkResponseSuccess<T> {
final T response;

const OkResponse(this.response);
}

sealed class NetworkResponseFailure<R> extends NetworkResponse<R> {
const NetworkResponseFailure();
}

/// 401 - for responses when the request was missing required authentication.
final class Unauthorized<R> extends NetworkResponseFailure<R> {
final DioException error;

const Unauthorized({required this.error});
}

/// 403 - for responses when the request was authenticated but the
/// action is not authorized/allowed.
final class Forbidden<R> extends NetworkResponseFailure<R> {
final DioException error;

const Forbidden({required this.error});
}

/// 404 - for responses when we could not locate a resource, or when
/// someone would attempt to access a forbidden resource due to a bug.
final class NotFound<R> extends NetworkResponseFailure<R> {
final DioException error;

const NotFound({required this.error});
}

/// 422 - for responses when the request inputs failed our validations.
final class UnprocessableEntity<R> extends NetworkResponseFailure<R> {
final DioException error;
final R response;

const UnprocessableEntity({required this.error, required this.response});
}

/// 426 - for responses when a client version upgrade is required
final class UpgradeRequired<R> extends NetworkResponseFailure<R> {
final DioException error;

const UpgradeRequired({required this.error});
}

/// 500 - for responses where the service had an error while processing
/// the request.
final class ServerError<R> extends NetworkResponseFailure<R> {
final DioException error;

const ServerError({required this.error});
}

/// 503 - for responses when an underlying service issue prevents us from
/// fulfilling the request.
final class ServiceUnavailable<R> extends NetworkResponseFailure<R> {
final DioException error;

const ServiceUnavailable({required this.error});
}

/// Any "other" error not covered by the above cases. If a [DioException] is present,
/// it has an error status we don't handle. Often this error will occur when no
/// response was received from the server.
final class GenericError<R> extends NetworkResponseFailure<R> {
const GenericError({
this.error,
required this.message,
required this.isConnectionIssue,
});

final DioException? error;
final String message;
final bool isConnectionIssue;
}

/// Extensions on the [NetworkResponse] type
extension NetworkResponseX<M> on NetworkResponse<M> {
extension NetworkResponseX<R> on NetworkResponse<R> {
/// Whether this [NetworkResponse] should be considered successful
bool get isSuccess => this is _Ok || this is _OkNoContent;
bool get isSuccess => this is NetworkResponseSuccess;
}
Loading

0 comments on commit 023a01b

Please sign in to comment.