Skip to content

Commit

Permalink
feat(client): library-level exceptions
Browse files Browse the repository at this point in the history
  • Loading branch information
micimize committed Sep 29, 2019
1 parent 4ac8a5b commit 8976cfc
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 33 deletions.
6 changes: 3 additions & 3 deletions packages/graphql/example/bin/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ void query() async {
final QueryResult result = await _client.query(options);

if (result.hasErrors) {
stderr.writeln(result.errors);
stderr.writeln(result.graphqlErrors);
exit(2);
}

Expand Down Expand Up @@ -81,7 +81,7 @@ void starRepository(String repositoryID) async {
final QueryResult result = await _client.mutate(options);

if (result.hasErrors) {
stderr.writeln(result.errors);
stderr.writeln(result.graphqlErrors);
exit(2);
}

Expand Down Expand Up @@ -114,7 +114,7 @@ void removeStarFromRepository(String repositoryID) async {
final QueryResult result = await _client.mutate(options);

if (result.hasErrors) {
stderr.writeln(result.errors);
stderr.writeln(result.graphqlErrors);
exit(2);
}

Expand Down
3 changes: 2 additions & 1 deletion packages/graphql/lib/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ export 'package:graphql/src/cache/normalized_in_memory.dart';
export 'package:graphql/src/cache/optimistic.dart';
export 'package:graphql/src/cache/lazy_cache_map.dart';

export 'package:graphql/src/core/graphql_error.dart';
export 'package:graphql/src/core/query_manager.dart';
export 'package:graphql/src/core/query_options.dart';
export 'package:graphql/src/core/query_result.dart';

export 'package:graphql/src/exceptions/exceptions.dart';

export 'package:graphql/src/link/auth/link_auth.dart';
export 'package:graphql/src/link/http/link_http.dart';
export 'package:graphql/src/link/link.dart';
Expand Down
3 changes: 2 additions & 1 deletion packages/graphql/lib/src/cache/normalized_in_memory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ class NormalizedInMemoryCache extends InMemoryCache {
return null;
}

// TODO ideally cyclical references would be noticed and replaced with null or something
// ~TODO~ ideally cyclical references would be noticed and replaced with null or something
// @micimize: pretty sure I implemented the above
/// eagerly dereferences all cache references.
/// *WARNING* if your system allows cyclical references, this will break
dynamic denormalizedRead(String key) {
Expand Down
19 changes: 13 additions & 6 deletions packages/graphql/lib/src/core/observable_query.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:async';

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

import 'package:graphql/src/core/query_manager.dart';
Expand Down Expand Up @@ -175,21 +176,22 @@ class ObservableQuery {
writeToCache: true,
);
} catch (error) {
if (fetchMoreResult.hasErrors) {
if (fetchMoreResult.hasGraphqlErrors) {
// because the updateQuery failure might have been because of these errors,
// we just add them to the old errors
latestResult.errors = [
...(latestResult.errors ?? const []),
...fetchMoreResult.errors
];
latestResult.exception = coalesceErrors(
exception: latestResult.exception,
graphqlErrors: fetchMoreResult.graphqlErrors,
);

queryManager.addQueryResult(
queryId,
latestResult,
writeToCache: true,
);

return;
} else {
// TODO merge results OperationException
rethrow;
}
}
Expand All @@ -215,6 +217,11 @@ class ObservableQuery {

latestResult = result;

print('${options.operationName} addResult');
if (options.operationName.contains('UserEventRecords')) {
print(latestResult.data['eventRecordsThrough']['eventRecords'].length);
}

if (!controller.isClosed) {
controller.add(result);
}
Expand Down
15 changes: 8 additions & 7 deletions packages/graphql/lib/src/core/query_manager.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import 'dart:async';

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

import 'package:graphql/src/core/query_options.dart';
import 'package:graphql/src/core/query_result.dart';
import 'package:graphql/src/core/graphql_error.dart';
import 'package:graphql/src/core/observable_query.dart';

import 'package:graphql/src/scheduler/scheduler.dart';
Expand Down Expand Up @@ -189,12 +190,12 @@ class QueryManager {
queryResult.loading) {
queryResult = QueryResult(
source: QueryResultSource.Cache,
errors: [
GraphQLError(
message:
'Could not find that operation in the cache. (FetchPolicy.cacheOnly)',
exception: OperationException(
clientException: CacheMissException(
'Could not find that operation in the cache. (FetchPolicy.cacheOnly)',
cacheKey,
),
],
),
);
}
}
Expand Down Expand Up @@ -363,8 +364,8 @@ class QueryManager {

return QueryResult(
data: data,
errors: errors,
source: source,
exception: coalesceErrors(graphqlErrors: errors),
);
}

Expand Down
26 changes: 12 additions & 14 deletions packages/graphql/lib/src/core/query_result.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'dart:async' show FutureOr;

import 'package:graphql/src/core/graphql_error.dart';
import 'package:graphql/src/exceptions/exceptions.dart';

/// The source of the result data contained
///
Expand All @@ -26,7 +26,7 @@ final eagerSources = {
class QueryResult {
QueryResult({
this.data,
this.errors,
this.exception,
bool loading,
bool optimistic,
QueryResultSource source,
Expand All @@ -48,7 +48,8 @@ class QueryResult {

/// List<dynamic> or Map<String, dynamic>
dynamic data;
List<GraphQLError> errors;

OperationException exception;

/// Whether data has been specified from either the cache or network)
bool get loading => source == QueryResultSource.Loading;
Expand All @@ -58,20 +59,17 @@ class QueryResult {
bool get optimistic => source == QueryResultSource.OptimisticResult;

/// Whether the response includes any graphql errors
bool get hasErrors {
if (errors == null) {
return false;
}
bool get hasErrors => !(exception == null);

return errors.isNotEmpty;
}
/// Whether the response includes any graphql errors
bool get hasGraphqlErrors => exception?.graphqlErrors?.isNotEmpty ?? false;

/// graphql errors in the exception, if any
List<GraphQLError> get graphqlErrors => exception?.graphqlErrors;

void addError(GraphQLError graphQLError) {
if (errors != null) {
errors.add(graphQLError);
} else {
errors = <GraphQLError>[graphQLError];
}
exception ??= OperationException();
exception.addError(graphQLError);
}
}

Expand Down
24 changes: 24 additions & 0 deletions packages/graphql/lib/src/exceptions/base_exceptions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
abstract class ClientException implements Exception {}

abstract class ClientCacheException implements ClientException {}

/// A failure during the cache's entity normalization processes
class NormalizationException implements ClientCacheException {
NormalizationException(this.cause, this.overflowError, this.value);

StackOverflowError overflowError;
String cause;
Object value;

String get message => cause;
}

/// A failure to find a key in the cache when cacheOnly=true
class CacheMissException implements ClientCacheException {
CacheMissException(this.cause, this.missingKey);

String cause;
String missingKey;

String get message => cause;
}
4 changes: 4 additions & 0 deletions packages/graphql/lib/src/exceptions/exceptions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export './graphql_error.dart';
export './operation_exception.dart';
export './network_exception_stub.dart'
if (dart.library.io) './io_network_exceptions.dart';
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ class GraphQLError {

/// Constructs a [GraphQLError] from a JSON map.
GraphQLError.fromJSON(this.raw)
: message = raw['message'] is String ? raw['message'] as String : 'Invalid server response: message property needs to be of type String',
: message = raw['message'] is String
? raw['message'] as String
: 'Invalid server response: message property needs to be of type String',
locations = raw['locations'] is List<Map<String, int>>
? List<Location>.from(
(raw['locations'] as List<Map<String, int>>).map<Location>(
Expand Down
20 changes: 20 additions & 0 deletions packages/graphql/lib/src/exceptions/io_network_exception.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'dart:io' show SocketException;
import './network_exception_stub.dart' as stub;

class NetworkException extends stub.NetworkException {
SocketException wrappedException;

NetworkException.from(this.wrappedException);

String get message => wrappedException.message;
String get targetAddress => wrappedException.address.address;
int get targetPort => wrappedException.port;
}

void translateExceptions(stub.VoidCallback block) {
try {
block();
} on SocketException catch (e) {
throw NetworkException.from(e);
}
}
24 changes: 24 additions & 0 deletions packages/graphql/lib/src/exceptions/network_exception_stub.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import './base_exceptions.dart' show ClientException;

typedef VoidCallback = void Function();

class NetworkException implements ClientException {
covariant Exception wrappedException;

final String message;

final String targetAddress;
final int targetPort;

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

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

void translateExceptions(VoidCallback block) => block();
44 changes: 44 additions & 0 deletions packages/graphql/lib/src/exceptions/operation_exception.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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 = [];

// generalize to include cache error, etc
ClientException clientException;

OperationException({
this.clientException,
Iterable<GraphQLError> graphqlErrors = const [],
}) : this.graphqlErrors = graphqlErrors.toList();

void addError(GraphQLError error) => graphqlErrors.add(error);
}

/// `(graphqlErrors?, exception?) => exception?`
///
/// merges both optional graphqlErrors and an optional container
/// into a single optional container
/// NOTE: NULL returns expected
OperationException coalesceErrors({
List<GraphQLError> graphqlErrors,
ClientException clientException,
OperationException exception,
}) {
if (exception != null ||
clientException != null ||
graphqlErrors == null ||
graphqlErrors.isEmpty) {
return OperationException(
clientException: clientException ?? exception.clientException,
graphqlErrors: [
if (graphqlErrors != null) ...graphqlErrors,
if (exception?.graphqlErrors != null) ...exception.graphqlErrors
],
);
}
return null;
}

0 comments on commit 8976cfc

Please sign in to comment.