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

Commit

Permalink
Add an unhandledError callback (#37)
Browse files Browse the repository at this point in the history
Allows clients to handle or rethrow errors instead of silently swallowing them.
  • Loading branch information
dnfield authored and natebosch committed Apr 23, 2019
1 parent 2bf8e2e commit 6c1aa29
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 10 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 2.1.0

* `Server` and related classes can now take an `onUnhandledError` callback to
notify callers of unhandled exceptions.

## 2.0.10

* Allow `stream_channel` version 2.x
Expand Down
19 changes: 15 additions & 4 deletions lib/src/peer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,20 @@ class Peer implements Client, Server {
Future get done => _manager.done;
bool get isClosed => _manager.isClosed;

@override
ErrorCallback get onUnhandledError => _server?.onUnhandledError;

/// Creates a [Peer] that communicates over [channel].
///
/// Note that the peer won't begin listening to [channel] until [Peer.listen]
/// is called.
Peer(StreamChannel<String> channel)
///
/// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError].
/// If this is not provided, unhandled exceptions will be swallowed.
Peer(StreamChannel<String> channel, {ErrorCallback onUnhandledError})
: this.withoutJson(
jsonDocument.bind(channel).transform(respondToFormatExceptions));
jsonDocument.bind(channel).transform(respondToFormatExceptions),
onUnhandledError: onUnhandledError);

/// Creates a [Peer] that communicates using decoded messages over [channel].
///
Expand All @@ -54,10 +61,14 @@ class Peer implements Client, Server {
///
/// Note that the peer won't begin listening to [channel] until
/// [Peer.listen] is called.
Peer.withoutJson(StreamChannel channel)
///
/// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError].
/// If this is not provided, unhandled exceptions will be swallowed.
Peer.withoutJson(StreamChannel channel, {ErrorCallback onUnhandledError})
: _manager = new ChannelManager("Peer", channel) {
_server = new Server.withoutJson(
new StreamChannel(_serverIncomingForwarder.stream, channel.sink));
new StreamChannel(_serverIncomingForwarder.stream, channel.sink),
onUnhandledError: onUnhandledError);
_client = new Client.withoutJson(
new StreamChannel(_clientIncomingForwarder.stream, channel.sink));
}
Expand Down
25 changes: 22 additions & 3 deletions lib/src/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import 'exception.dart';
import 'parameters.dart';
import 'utils.dart';

/// A callback for unhandled exceptions.
typedef ErrorCallback = void Function(dynamic error, dynamic stackTrace);

/// A JSON-RPC 2.0 server.
///
/// A server exposes methods that are called by requests, to which it provides
Expand Down Expand Up @@ -51,13 +54,24 @@ class Server {
/// endpoint closes the connection.
bool get isClosed => _manager.isClosed;

/// A callback that is fired on unhandled exceptions.
///
/// In the case where a user provided callback results in an exception that
/// cannot be properly routed back to the client, this handler will be
/// invoked. If it is not set, the exception will be swallowed.
final ErrorCallback onUnhandledError;

/// Creates a [Server] that communicates over [channel].
///
/// Note that the server won't begin listening to [requests] until
/// [Server.listen] is called.
Server(StreamChannel<String> channel)
///
/// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError].
/// If this is not provided, unhandled exceptions will be swallowed.
Server(StreamChannel<String> channel, {ErrorCallback onUnhandledError})
: this.withoutJson(
jsonDocument.bind(channel).transform(respondToFormatExceptions));
jsonDocument.bind(channel).transform(respondToFormatExceptions),
onUnhandledError: onUnhandledError);

/// Creates a [Server] that communicates using decoded messages over
/// [channel].
Expand All @@ -67,7 +81,10 @@ class Server {
///
/// Note that the server won't begin listening to [requests] until
/// [Server.listen] is called.
Server.withoutJson(StreamChannel channel)
///
/// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError].
/// If this is not provided, unhandled exceptions will be swallowed.
Server.withoutJson(StreamChannel channel, {this.onUnhandledError})
: _manager = new ChannelManager("Server", channel);

/// Starts listening to the underlying stream.
Expand Down Expand Up @@ -175,9 +192,11 @@ class Server {
request.containsKey('id')) {
return error.serialize(request);
} else {
onUnhandledError?.call(error, stackTrace);
return null;
}
} else if (!request.containsKey('id')) {
onUnhandledError?.call(error, stackTrace);
return null;
}
final chain = new Chain.forTrace(stackTrace);
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: json_rpc_2
version: 2.0.10
version: 2.1.0
author: Dart Team <[email protected]>
description: >-
Utilities to write a client or server using the JSON-RPC 2.0 spec.
Expand Down
20 changes: 20 additions & 0 deletions test/peer_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,24 @@ void main() {
incoming.add({"completely": "wrong"});
});
});

test("can notify on unhandled errors for if the method throws", () async {
Exception exception = Exception('test exception');
var incomingController = new StreamController();
var outgoingController = new StreamController();
final Completer<Exception> completer = Completer<Exception>();
peer = new json_rpc.Peer.withoutJson(
new StreamChannel(incomingController.stream, outgoingController),
onUnhandledError: (error, stack) {
completer.complete(error);
},
);
peer
..registerMethod('foo', () => throw exception)
..listen();

incomingController.add({'jsonrpc': '2.0', 'method': 'foo'});
Exception receivedException = await completer.future;
expect(receivedException, equals(exception));
});
}
5 changes: 3 additions & 2 deletions test/server/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ class ServerController {
json_rpc.Server get server => _server;
json_rpc.Server _server;

ServerController() {
ServerController({json_rpc.ErrorCallback onUnhandledError}) {
_server = new json_rpc.Server(
new StreamChannel(_requestController.stream, _responseController.sink));
new StreamChannel(_requestController.stream, _responseController.sink),
onUnhandledError: onUnhandledError);
_server.listen();
}

Expand Down

0 comments on commit 6c1aa29

Please sign in to comment.