Skip to content

Commit

Permalink
feat(client): library-level exception handling
Browse files Browse the repository at this point in the history
BREAKING CHANGE: replaces result.errors with result.exception
  • Loading branch information
micimize committed Sep 29, 2019
1 parent 8976cfc commit 20e57bd
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 51 deletions.
43 changes: 14 additions & 29 deletions packages/graphql/lib/src/core/query_manager.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'dart:async';

import 'package:graphql/src/exceptions/base_exceptions.dart';
import 'package:graphql/src/exceptions/exceptions.dart';
import 'package:meta/meta.dart';

Expand Down Expand Up @@ -37,12 +36,6 @@ class QueryManager {
Map<String, ObservableQuery> queries = <String, ObservableQuery>{};

ObservableQuery watchQuery(WatchQueryOptions options) {
if (options.document == null) {
throw Exception(
'document option is required. You must specify your GraphQL document in the query options.',
);
}

final ObservableQuery observableQuery = ObservableQuery(
queryManager: this,
options: options,
Expand Down Expand Up @@ -121,6 +114,9 @@ class QueryManager {
);
}

// TODO this should never happen right?
// if there is an error reaching the server, there should be an error thrown,
// if not, data should not be null.
if (fetchResult.data == null &&
fetchResult.errors == null &&
(options.fetchPolicy == FetchPolicy.noCache ||
Expand All @@ -135,10 +131,14 @@ class QueryManager {
options,
source: QueryResultSource.Network,
);
} catch (error) {
} catch (failure) {
// we set the source to indicate where the source of failure
queryResult ??= QueryResult(source: QueryResultSource.Network);
queryResult.addError(_attemptToWrapError(error));

queryResult.exception = coalesceErrors(
exception: queryResult.exception,
clientException: translateFailure(failure),
);
}

// cleanup optimistic results
Expand Down Expand Up @@ -199,8 +199,11 @@ class QueryManager {
);
}
}
} catch (error) {
queryResult.addError(_attemptToWrapError(error));
} catch (failure) {
queryResult.exception = coalesceErrors(
exception: queryResult.exception,
clientException: translateFailure(failure),
);
}

// If not a regular eager cache resolution,
Expand All @@ -227,24 +230,6 @@ class QueryManager {
return null;
}

GraphQLError _attemptToWrapError(dynamic error) {
String errorMessage;

// not all errors thrown above are GraphQL errors,
// so try/catch to avoid "could not access message"
try {
errorMessage = error.message as String;
assert(errorMessage != null);
assert(errorMessage.isNotEmpty);
} catch (e) {
throw error;
}

return GraphQLError(
message: errorMessage,
);
}

/// Add a result to the query specified by `queryId`, if it exists
void addQueryResult(
String queryId,
Expand Down
3 changes: 2 additions & 1 deletion packages/graphql/lib/src/core/raw_operation_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ class RawOperationData {
@required this.document,
Map<String, dynamic> variables,
String operationName,
}) : _operationName = operationName,
}) : assert(document != null),
_operationName = operationName,
variables = SplayTreeMap<String, dynamic>.of(
variables ?? const <String, dynamic>{},
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
abstract class ClientException implements Exception {}

//
// Cache exceptions
//

abstract class ClientCacheException implements ClientException {}

/// A failure during the cache's entity normalization processes
Expand All @@ -22,3 +26,20 @@ class CacheMissException implements ClientCacheException {

String get message => cause;
}

//
// end cache exceptions
//

class UnhandledFailureWrapper implements ClientException {
covariant Object failure;
UnhandledFailureWrapper(this.failure);
}

ClientException translateFailure(dynamic failure) {
if (failure is ClientException) {
return failure;
}

return UnhandledFailureWrapper(failure);
}
11 changes: 10 additions & 1 deletion packages/graphql/lib/src/exceptions/exceptions.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import './_base_exceptions.dart' as _b;
import 'package:graphql/src/exceptions/io_network_exception.dart' as _n;

export './_base_exceptions.dart' hide translateFailure;
export './graphql_error.dart';
export './operation_exception.dart';
export './network_exception_stub.dart'
if (dart.library.io) './io_network_exceptions.dart';
if (dart.library.io) './io_network_exceptions.dart'
hide translateNetworkFailure;

_b.ClientException translateFailure(dynamic failure) {
return _n.translateNetworkFailure(failure) ?? _b.translateFailure(failure);
}
16 changes: 9 additions & 7 deletions packages/graphql/lib/src/exceptions/io_network_exception.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:io' show SocketException;
import 'dart:io';
import './network_exception_stub.dart' as stub;

class NetworkException extends stub.NetworkException {
Expand All @@ -7,14 +8,15 @@ class NetworkException extends stub.NetworkException {
NetworkException.from(this.wrappedException);

String get message => wrappedException.message;
String get targetAddress => wrappedException.address.address;
int get targetPort => wrappedException.port;
Uri get uri => Uri(
host: wrappedException.address.host,
port: wrappedException.port,
);
}

void translateExceptions(stub.VoidCallback block) {
try {
block();
} on SocketException catch (e) {
throw NetworkException.from(e);
NetworkException translateNetworkFailure(dynamic failure) {
if (failure is SocketException) {
return NetworkException.from(failure);
}
return stub.translateNetworkFailure(failure);
}
23 changes: 14 additions & 9 deletions packages/graphql/lib/src/exceptions/network_exception_stub.dart
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
import './base_exceptions.dart' show ClientException;
import 'package:http/http.dart' as http;

typedef VoidCallback = void Function();
import './_base_exceptions.dart' show ClientException;

class NetworkException implements ClientException {
covariant Exception wrappedException;

final String message;

final String targetAddress;
final int targetPort;
final Uri uri;

NetworkException({
this.wrappedException,
this.message,
this.targetAddress,
this.targetPort,
this.uri,
});

String toString() =>
'Failed to connect to $targetAddress:$targetPort: $message';
String toString() => 'Failed to connect to $uri: $message';
}

void translateExceptions(VoidCallback block) => block();
NetworkException translateNetworkFailure(dynamic failure) {
if (failure is http.ClientException) {
return NetworkException(
wrappedException: failure,
message: failure.message,
uri: failure.uri,
);
}
}
5 changes: 1 addition & 4 deletions packages/graphql/lib/src/exceptions/operation_exception.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import 'package:graphql/src/exceptions/base_exceptions.dart';

import 'package:graphql/src/exceptions/_base_exceptions.dart';
import './graphql_error.dart';
import './network_exception_stub.dart'
if (dart.library.io) './io_network_exceptions.dart';

class OperationException implements Exception {
List<GraphQLError> graphqlErrors = [];
Expand Down

0 comments on commit 20e57bd

Please sign in to comment.