Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

refactor: make network response a sealed class #14

Merged
merged 10 commits into from
Sep 17, 2024
Merged
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
Loading