Skip to content
This repository has been archived by the owner on Dec 30, 2024. It is now read-only.

Commit

Permalink
Refactored error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonlessenich committed Mar 14, 2024
1 parent 46b0e40 commit 0557893
Show file tree
Hide file tree
Showing 18 changed files with 187 additions and 126 deletions.
6 changes: 5 additions & 1 deletion lib/restrr.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ export 'src/api/events/request_event.dart';
export 'src/api/events/restrr_event.dart';
export 'src/api/events/session_delete_event.dart';

/* [ /src/api/exceptions ] */
export 'src/api/exceptions/client_exception.dart';
export 'src/api/exceptions/restrr_exception.dart';
export 'src/api/exceptions/server_exception.dart';

/* [ /src/api/requests ] */
export 'src/api/requests/request_handler.dart';
export 'src/api/requests/restrr_errors.dart';
export 'src/api/requests/route.dart';
export 'src/api/requests/route_definitions.dart';
2 changes: 1 addition & 1 deletion lib/src/api/entities/currency/custom_currency.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ abstract class CustomCurrency extends Currency {

Future<bool> delete();

Future<Currency?> update({String? name, String? symbol, String? isoCode, int? decimalPlaces});
Future<Currency> update({String? name, String? symbol, String? isoCode, int? decimalPlaces});
}
12 changes: 12 additions & 0 deletions lib/src/api/exceptions/client_exception.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'package:restrr/restrr.dart';

import '../../internal/requests/client_errors.dart';

class ClientException extends RestrrException {
final ClientError error;

ClientException(this.error) : super(error.message);

@override
String toString() => '${runtimeType.toString()}: $message';
}
8 changes: 8 additions & 0 deletions lib/src/api/exceptions/restrr_exception.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
abstract class RestrrException implements Exception {
final String? message;

RestrrException(this.message);

@override
String toString() => '${runtimeType.toString()}: $message';
}
11 changes: 11 additions & 0 deletions lib/src/api/exceptions/server_exception.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import '../../../restrr.dart';
import '../../internal/requests/restrr_errors.dart';

class ServerException extends RestrrException {
final RestrrError error;

ServerException(this.error) : super(error.message);

@override
String toString() => '${runtimeType.toString()}: $message (${error.code})';
}
56 changes: 26 additions & 30 deletions lib/src/api/requests/request_handler.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import 'package:dio/dio.dart';
import 'package:restrr/src/internal/requests/client_errors.dart';
import 'package:restrr/src/internal/requests/responses/error_response.dart';
import 'package:restrr/src/internal/requests/responses/paginated_response.dart';
import 'package:restrr/src/internal/requests/responses/rest_response.dart';

import '../../../restrr.dart';
import '../../internal/requests/restrr_errors.dart';

/// Utility class for handling requests.
class RequestHandler {
Expand All @@ -19,15 +22,14 @@ class RequestHandler {
required T Function(dynamic) mapper,
required RouteOptions routeOptions,
String? bearerToken,
Map<int, RestrrError> errorMap = const {},
dynamic body,
String contentType = 'application/json'}) async {
try {
final Response<dynamic> response = await route.submit(
routeOptions: routeOptions, body: body, bearerToken: bearerToken, contentType: contentType);
return RestResponse(data: mapper.call(response.data), statusCode: response.statusCode);
} on DioException catch (e) {
return _handleDioException(e, errorMap);
return _handleDioException(e);
}
}

Expand All @@ -44,7 +46,6 @@ class RequestHandler {
routeOptions: api.routeOptions,
bearerToken: bearerTokenOverride ?? (noAuth ? null : api.session.token),
mapper: mapper,
errorMap: errorMap,
body: body,
contentType: contentType);
}
Expand All @@ -64,7 +65,7 @@ class RequestHandler {
routeOptions: routeOptions, body: body, bearerToken: bearerToken, contentType: contentType);
return RestResponse(data: true, statusCode: response.statusCode);
} on DioException catch (e) {
return _handleDioException(e, errorMap);
return _handleDioException(e);
}
}

Expand Down Expand Up @@ -93,7 +94,6 @@ class RequestHandler {
required RouteOptions routeOptions,
String? bearerToken,
required T Function(dynamic) mapper,
Map<int, RestrrError> errorMap = const {},
dynamic body,
String contentType = 'application/json'}) async {
try {
Expand All @@ -106,7 +106,7 @@ class RequestHandler {
data: (response.data as List<dynamic>).map((single) => mapper.call(single)).toList(),
statusCode: response.statusCode);
} on DioException catch (e) {
return _handleDioException(e, errorMap);
return _handleDioException(e);
}
}

Expand All @@ -115,15 +115,13 @@ class RequestHandler {
required T Function(dynamic) mapper,
String? bearerTokenOverride,
bool noAuth = false,
Map<int, RestrrError> errorMap = const {},
dynamic body,
String contentType = 'application/json'}) async {
return RequestHandler.multiRequest(
route: route,
routeOptions: api.routeOptions,
bearerToken: bearerTokenOverride ?? (noAuth ? null : api.session.token),
mapper: mapper,
errorMap: errorMap,
body: body,
contentType: contentType);
}
Expand All @@ -133,7 +131,6 @@ class RequestHandler {
required RouteOptions routeOptions,
String? bearerToken,
required T Function(dynamic) mapper,
Map<int, RestrrError> errorMap = const {},
dynamic body,
String contentType = 'application/json'}) async {
try {
Expand All @@ -147,7 +144,7 @@ class RequestHandler {
data: (response.data['data'] as List<dynamic>).map((single) => mapper.call(single)).toList(),
statusCode: response.statusCode);
} on DioException catch (e) {
return _handleDioException(e, errorMap);
return _handleDioException(e);
}
}

Expand All @@ -156,45 +153,44 @@ class RequestHandler {
required T Function(dynamic) mapper,
String? bearerTokenOverride,
bool noAuth = false,
Map<int, RestrrError> errorMap = const {},
dynamic body,
String contentType = 'application/json'}) async {
return RequestHandler.paginatedRequest(
route: route,
routeOptions: api.routeOptions,
bearerToken: bearerTokenOverride ?? (noAuth ? null : api.session.token),
mapper: mapper,
errorMap: errorMap,
body: body,
contentType: contentType);
}

static Future<RestResponse<T>> _handleDioException<T>(DioException ex, Map<int, RestrrError> errorMap) async {
// check status code
static Future<RestResponse<T>> _handleDioException<T>(DioException ex) async {
// check if error response is present
final ErrorResponse? errorResponse = ErrorResponse.tryFromJson(ex.response?.data);
if (errorResponse != null) {
if (errorResponse.error != null) {
return errorResponse.error!.toResponse(statusCode: ex.response?.statusCode);
}
Restrr.log.warning('Encountered unknown ErrorResponse: ${errorResponse.details}');
}
// if not, check status code
final int? statusCode = ex.response?.statusCode;
if (statusCode != null) {
if (errorMap.containsKey(statusCode)) {
return _errorToRestResponse(errorMap[statusCode]!, statusCode: statusCode);
}
final RestrrError? err = switch (statusCode) {
400 => RestrrError.badRequest,
500 => RestrrError.internalServerError,
503 => RestrrError.serviceUnavailable,
final RestrrException? ex = switch (statusCode) {
400 => ClientError.badRequest.toException(),
500 => RestrrError.internalServerError.toException(),
503 => RestrrError.serviceUnavailable.toException(),
_ => null
};
if (err != null) {
return _errorToRestResponse(err, statusCode: statusCode);
if (ex != null) {
return RestResponse(error: ex, statusCode: statusCode);
}
}
// check timeout
// if this also fails, check timeout
if (ex.type == DioExceptionType.connectionTimeout || ex.type == DioExceptionType.receiveTimeout) {
return _errorToRestResponse(RestrrError.serverUnreachable, statusCode: statusCode);
return ClientError.serverUnreachable.toResponse(statusCode: statusCode);
}
Restrr.log.warning('Unknown error occurred: ${ex.message}, ${ex.stackTrace}');
return _errorToRestResponse(RestrrError.unknown, statusCode: statusCode);
}

static RestResponse<T> _errorToRestResponse<T>(RestrrError error, {int? statusCode}) {
return RestResponse(error: error, statusCode: statusCode);
return RestrrError.unknown.toResponse(statusCode: statusCode);
}
}
29 changes: 0 additions & 29 deletions lib/src/api/requests/restrr_errors.dart

This file was deleted.

9 changes: 7 additions & 2 deletions lib/src/api/restrr.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:logging/logging.dart';
import 'package:restrr/src/internal/requests/responses/rest_response.dart';

import '../../restrr.dart';
import 'exceptions/restrr_exception.dart';

class RestrrOptions {
final bool isWeb;
Expand All @@ -28,12 +29,16 @@ abstract class Restrr {

/// Checks whether the specified URI is valid and points to a valid
/// financrr API.
static Future<RestResponse<ServerInfo>> checkUri(Uri uri, {bool isWeb = false}) async {
return await RequestHandler.request(
static Future<ServerInfo> checkUri(Uri uri, {bool isWeb = false}) async {
final RestResponse<ServerInfo> response = await RequestHandler.request(
route: StatusRoutes.health.compile(),
mapper: (json) => ServerInfo.fromJson(json),
routeOptions: RouteOptions(hostUri: uri),
);
if (response.hasError) {
throw response.error!;
}
return response.data!;
}

void on<T extends RestrrEvent>(Type type, void Function(T) func);
Expand Down
35 changes: 10 additions & 25 deletions lib/src/api/restrr_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class RestrrBuilder {
return this;
}

Future<RestResponse<Restrr>> login({required String username, required String password, String? sessionName}) async {
Future<Restrr> login({required String username, required String password, String? sessionName}) async {
return _handleAuthProcess(authFunction: (apiImpl) {
return apiImpl.requestHandler.apiRequest(
route: SessionRoutes.create.compile(),
Expand All @@ -28,51 +28,36 @@ class RestrrBuilder {
'session_name': sessionName,
},
noAuth: true,
mapper: (json) => apiImpl.entityBuilder.buildSession(json),
errorMap: {
404: RestrrError.invalidCredentials,
401: RestrrError.invalidCredentials,
});
mapper: (json) => apiImpl.entityBuilder.buildSession(json));
});
}

Future<RestResponse<Restrr>> refresh({required String sessionToken}) async {
Future<Restrr> refresh({required String sessionToken}) async {
return _handleAuthProcess(authFunction: (apiImpl) {
return apiImpl.requestHandler.apiRequest(
route: SessionRoutes.refresh.compile(),
bearerTokenOverride: sessionToken,
mapper: (json) => apiImpl.entityBuilder.buildSession(json),
errorMap: {
404: RestrrError.invalidCredentials,
401: RestrrError.invalidCredentials,
});
mapper: (json) => apiImpl.entityBuilder.buildSession(json));
});
}

Future<RestResponse<Restrr>> _handleAuthProcess(
Future<Restrr> _handleAuthProcess(
{required Future<RestResponse<Session>> Function(RestrrImpl) authFunction}) async {
// check if the URI is valid and the API is healthy
final RestResponse<ServerInfo> statusResponse = await Restrr.checkUri(uri, isWeb: options.isWeb);
if (statusResponse.hasError) {
Restrr.log.warning('Invalid financrr URI: $uri');
return (statusResponse.error == RestrrError.unknown
? RestrrError.invalidUri
: statusResponse.error ?? RestrrError.invalidUri)
.toRestResponse(statusCode: statusResponse.statusCode);
}
Restrr.log.config('Host: $uri, API v${statusResponse.data!.apiVersion}');
final ServerInfo statusResponse = await Restrr.checkUri(uri, isWeb: options.isWeb);
Restrr.log.config('Host: $uri, API v${statusResponse.apiVersion}');
// build api instance
final RestrrImpl apiImpl = RestrrImpl(
options: options,
routeOptions: RouteOptions(hostUri: uri, apiVersion: statusResponse.data!.apiVersion),
routeOptions: RouteOptions(hostUri: uri, apiVersion: statusResponse.apiVersion),
eventMap: _eventMap);
// call auth function
final RestResponse<Session> response = await authFunction(apiImpl);
if (response.hasError) {
return response.error?.toRestResponse(statusCode: response.statusCode) ?? RestrrError.unknown.toRestResponse();
throw response.error!;
}
apiImpl.session = response.data!;
apiImpl.eventHandler.fire(ReadyEvent(api: apiImpl));
return RestResponse(data: apiImpl, statusCode: response.statusCode);
return apiImpl;
}
}
17 changes: 6 additions & 11 deletions lib/src/internal/entities/currency/custom_currency_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,12 @@ class CustomCurrencyImpl extends CurrencyImpl implements CustomCurrency {

@override
Future<bool> delete() async {
final RestResponse<bool> response =
await api.requestHandler.noResponseApiRequest(route: CurrencyRoutes.deleteById.compile(params: [id]), errorMap: {
401: RestrrError.notSignedIn,
404: RestrrError.notFound,
});
final RestResponse<bool> response = await api.requestHandler.noResponseApiRequest(route: CurrencyRoutes.deleteById.compile(params: [id]));
return response.hasData && response.data!;
}

@override
Future<Currency?> update({String? name, String? symbol, String? isoCode, int? decimalPlaces}) async {
Future<Currency> update({String? name, String? symbol, String? isoCode, int? decimalPlaces}) async {
if (name == null && symbol == null && isoCode == null && decimalPlaces == null) {
throw ArgumentError('At least one field must be set');
}
Expand All @@ -43,11 +39,10 @@ class CustomCurrencyImpl extends CurrencyImpl implements CustomCurrency {
if (symbol != null) 'symbol': symbol,
if (isoCode != null) 'iso_code': isoCode,
if (decimalPlaces != null) 'decimal_places': decimalPlaces,
},
errorMap: {
401: RestrrError.notSignedIn,
404: RestrrError.notFound,
});
return response.data;
if (response.hasError) {
throw response.error!;
}
return response.data!;
}
}
18 changes: 18 additions & 0 deletions lib/src/internal/requests/client_errors.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'package:restrr/restrr.dart';
import 'package:restrr/src/internal/requests/responses/rest_response.dart';

enum ClientError {
badRequest('Bad request'),

noNetworkConnection('No network connection'),
serverUnreachable('Server is unreachable'),
invalidUri('Invalid URI'),
;

final String message;

const ClientError(this.message);

RestrrException toException() => ClientException(this);
RestResponse<T> toResponse<T>({required int? statusCode}) => RestResponse<T>(error: toException(), statusCode: statusCode);
}
Loading

0 comments on commit 0557893

Please sign in to comment.