Update lints, test JS & Wasm (#116)
* Update lints, test JS & Wasm
* more bumps
* require dart 3.4
diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml
index 0978fe2..c8e65b6 100644
--- a/.github/workflows/test-package.yml
+++ b/.github/workflows/test-package.yml
@@ -47,7 +47,7 @@
matrix:
# Add macos-latest and/or windows-latest if relevant for this package.
os: [ubuntu-latest]
- sdk: [2.19.0, dev]
+ sdk: [3.4, dev]
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29
- uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30
@@ -59,3 +59,6 @@
- name: Run VM tests
run: dart test --platform vm
if: always() && steps.install.outcome == 'success'
+ - name: Run browser tests
+ run: dart test --platform chrome --compiler dart2wasm,dart2js
+ if: always() && steps.install.outcome == 'success'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 751a8f8..aec8201 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,6 @@
-## 3.0.3-dev
+## 3.0.3-wip
-* Require Dart 2.19
+* Require Dart 3.4
## 3.0.2
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 3339609..873cf41 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -12,4 +12,3 @@
- avoid_unused_constructor_parameters
- cancel_subscriptions
- package_api_docs
- - test_types_in_equals
diff --git a/lib/error_code.dart b/lib/error_code.dart
index 14e0543..5f90791 100644
--- a/lib/error_code.dart
+++ b/lib/error_code.dart
@@ -4,7 +4,9 @@
// ignore_for_file: constant_identifier_names
-/// Error codes defined in the [JSON-RPC 2.0 specificiation][spec].
+import 'src/exception.dart';
+
+/// Error codes defined in the [JSON-RPC 2.0 specification][spec].
///
/// These codes are generally used for protocol-level communication. Most of
/// them shouldn't be used by the application. Those that should have
@@ -38,20 +40,12 @@
/// Returns a human-readable name for [errorCode] if it's one specified by the
/// JSON-RPC 2.0 spec.
///
-/// If [errorCode] isn't defined in the JSON-RPC 2.0 spec, returns null.
-String? name(int errorCode) {
- switch (errorCode) {
- case PARSE_ERROR:
- return 'parse error';
- case INVALID_REQUEST:
- return 'invalid request';
- case METHOD_NOT_FOUND:
- return 'method not found';
- case INVALID_PARAMS:
- return 'invalid parameters';
- case INTERNAL_ERROR:
- return 'internal error';
- default:
- return null;
- }
-}
+/// If [errorCode] isn't defined in the JSON-RPC 2.0 spec, returns `null`.
+String? name(int errorCode) => switch (errorCode) {
+ PARSE_ERROR => 'parse error',
+ INVALID_REQUEST => 'invalid request',
+ METHOD_NOT_FOUND => 'method not found',
+ INVALID_PARAMS => 'invalid parameters',
+ INTERNAL_ERROR => 'internal error',
+ _ => null
+ };
diff --git a/lib/src/client.dart b/lib/src/client.dart
index a3637dd..182f945 100644
--- a/lib/src/client.dart
+++ b/lib/src/client.dart
@@ -47,19 +47,19 @@
/// Creates a [Client] that communicates over [channel].
///
- /// Note that the client won't begin listening to [responses] until
+ /// Note that the client won't begin listening to [channel] until
/// [Client.listen] is called.
Client(StreamChannel<String> channel)
: this.withoutJson(
jsonDocument.bind(channel).transformStream(ignoreFormatExceptions));
/// Creates a [Client] that communicates using decoded messages over
- /// [channel].
+ /// [_channel].
///
/// Unlike [Client.new], this doesn't read or write JSON strings. Instead, it
/// reads and writes decoded maps or lists.
///
- /// Note that the client won't begin listening to [responses] until
+ /// Note that the client won't begin listening to [_channel] until
/// [Client.listen] is called.
Client.withoutJson(this._channel) {
done.whenComplete(() {
@@ -80,7 +80,8 @@
///
/// [listen] may only be called once.
Future listen() {
- _channel.stream.listen(_handleResponse, onError: (error, stackTrace) {
+ _channel.stream.listen(_handleResponse,
+ onError: (Object error, StackTrace stackTrace) {
_done.completeError(error, stackTrace);
_channel.sink.close();
}, onDone: () {
@@ -113,11 +114,11 @@
///
/// Throws a [StateError] if the client is closed while the request is in
/// flight, or if the client is closed when this method is called.
- Future sendRequest(String method, [Object? parameters]) {
+ Future<Object?> sendRequest(String method, [Object? parameters]) {
var id = _id++;
_send(method, parameters, id);
- var completer = Completer.sync();
+ var completer = Completer<Object?>.sync();
_pendingRequests[id] = _Request(method, completer, Chain.current());
return completer.future;
}
@@ -141,7 +142,7 @@
///
/// Sends a request to invoke [method] with [parameters]. If [id] is given,
/// the request uses that id.
- void _send(String method, parameters, [int? id]) {
+ void _send(String method, Object? parameters, [int? id]) {
if (parameters is Iterable) parameters = parameters.toList();
if (parameters is! Map && parameters is! List && parameters != null) {
throw ArgumentError('Only maps and lists may be used as JSON-RPC '
@@ -172,7 +173,7 @@
/// If this is called in the context of another [withBatch] call, it just
/// invokes [callback] without creating another batch. This means that
/// responses are batched until the first batch ends.
- void withBatch(Function() callback) {
+ void withBatch(FutureOr<void> Function() callback) {
if (_batch != null) {
callback();
return;
@@ -186,7 +187,7 @@
}
/// Handles a decoded response from the server.
- void _handleResponse(response) {
+ void _handleResponse(Object? response) {
if (response is List) {
response.forEach(_handleSingleResponse);
} else {
@@ -196,8 +197,9 @@
/// Handles a decoded response from the server after batches have been
/// resolved.
- void _handleSingleResponse(response) {
- if (!_isResponseValid(response)) return;
+ void _handleSingleResponse(Object? response_) {
+ if (!_isResponseValid(response_)) return;
+ final response = response_ as Map;
var id = response['id'];
id = (id is String) ? int.parse(id) : id;
var request = _pendingRequests.remove(id)!;
@@ -212,7 +214,7 @@
}
/// Determines whether the server's response is valid per the spec.
- bool _isResponseValid(response) {
+ bool _isResponseValid(Object? response) {
if (response is! Map) return false;
if (response['jsonrpc'] != '2.0') return false;
var id = response['id'];
diff --git a/lib/src/exception.dart b/lib/src/exception.dart
index 3b9c0ec..906a053 100644
--- a/lib/src/exception.dart
+++ b/lib/src/exception.dart
@@ -46,8 +46,10 @@
Map<String, dynamic> serialize(Object? request) {
dynamic modifiedData;
if (data is Map && !(data as Map).containsKey('request')) {
- modifiedData = Map.from(data as Map);
- modifiedData['request'] = request;
+ modifiedData = {
+ ...data as Map,
+ 'request': request,
+ };
} else if (data == null) {
modifiedData = {'request': request};
} else {
diff --git a/lib/src/parameters.dart b/lib/src/parameters.dart
index f747b98..0a18882 100644
--- a/lib/src/parameters.dart
+++ b/lib/src/parameters.dart
@@ -109,8 +109,8 @@
// The parent parameters, used to construct [_path].
final Parameters _parent;
- /// The key used to access [this], used to construct [_path].
- final dynamic _key;
+ /// The key used to access `this`, used to construct [_path].
+ final Object _key;
/// A human-readable representation of the path of getters used to get this.
///
@@ -130,20 +130,22 @@
return _key is int ? (_key + 1).toString() : jsonEncode(_key);
}
- String quoteKey(key) {
+ String quoteKey(String key) {
if (key.contains(RegExp(r'[^a-zA-Z0-9_-]'))) return jsonEncode(key);
return key;
}
- String computePath(params) {
+ String computePath(Parameter params) {
if (params._parent is! Parameter) {
- return params._key is int ? '[${params._key}]' : quoteKey(params._key);
+ return params._key is int
+ ? '[${params._key}]'
+ : quoteKey(params._key as String);
}
var path = computePath(params._parent);
return params._key is int
? '$path[${params._key}]'
- : '$path.${quoteKey(params._key)}';
+ : '$path.${quoteKey(params._key as String)}';
}
return computePath(this);
@@ -152,8 +154,7 @@
/// Whether this parameter exists.
bool get exists => true;
- Parameter._(String method, value, this._parent, this._key)
- : super(method, value);
+ Parameter._(super.method, super.value, this._parent, this._key);
/// Returns [value], or [defaultValue] if this parameter wasn't passed.
dynamic valueOr(Object? defaultValue) => value;
@@ -260,8 +261,7 @@
/// If [value] doesn't exist, this returns [defaultValue].
Uri asUriOr(Uri defaultValue) => asUri;
- /// Get a parameter named [named] that matches [test], or the value of calling
- /// [orElse].
+ /// Get a parameter named [type] that matches [test].
///
/// [type] is used for the error message. It should begin with an indefinite
/// article.
@@ -271,7 +271,7 @@
'"$method" must be $type, but was ${jsonEncode(value)}.');
}
- dynamic _getParsed(String description, Function(String) parse) {
+ dynamic _getParsed(String description, void Function(String) parse) {
var string = asString;
try {
return parse(string);
@@ -316,7 +316,7 @@
@override
bool get exists => false;
- _MissingParameter(String method, Parameters parent, key)
+ _MissingParameter(String method, Parameters parent, Object key)
: super._(method, null, parent, key);
@override
diff --git a/lib/src/peer.dart b/lib/src/peer.dart
index 002af31..677b6e1 100644
--- a/lib/src/peer.dart
+++ b/lib/src/peer.dart
@@ -29,11 +29,11 @@
/// A stream controller that forwards incoming messages to [_server] if
/// they're requests.
- final _serverIncomingForwarder = StreamController(sync: true);
+ final _serverIncomingForwarder = StreamController<Object?>(sync: true);
/// A stream controller that forwards incoming messages to [_client] if
/// they're responses.
- final _clientIncomingForwarder = StreamController(sync: true);
+ final _clientIncomingForwarder = StreamController<Object?>(sync: true);
@override
late final Future done = Future.wait([_client.done, _server.done]);
@@ -66,12 +66,12 @@
onUnhandledError: onUnhandledError,
strictProtocolChecks: strictProtocolChecks);
- /// Creates a [Peer] that communicates using decoded messages over [channel].
+ /// Creates a [Peer] that communicates using decoded messages over [_channel].
///
/// Unlike [Peer.new], this doesn't read or write JSON strings. Instead, it
/// reads and writes decoded maps or lists.
///
- /// Note that the peer won't begin listening to [channel] until
+ /// Note that the peer won't begin listening to [_channel] until
/// [Peer.listen] is called.
///
/// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError].
@@ -102,7 +102,7 @@
_client.sendNotification(method, parameters);
@override
- void withBatch(Function() callback) => _client.withBatch(callback);
+ void withBatch(void Function() callback) => _client.withBatch(callback);
// Server methods.
@@ -111,7 +111,7 @@
_server.registerMethod(name, callback);
@override
- void registerFallback(Function(Parameters parameters) callback) =>
+ void registerFallback(void Function(Parameters parameters) callback) =>
_server.registerFallback(callback);
// Shared methods.
@@ -141,7 +141,7 @@
// server since it knows how to send error responses.
_serverIncomingForwarder.add(message);
}
- }, onError: (error, stackTrace) {
+ }, onError: (Object error, StackTrace stackTrace) {
_serverIncomingForwarder.addError(error, stackTrace);
}, onDone: close);
return done;
diff --git a/lib/src/server.dart b/lib/src/server.dart
index 14ef02e..2c58b79 100644
--- a/lib/src/server.dart
+++ b/lib/src/server.dart
@@ -21,8 +21,7 @@
///
/// A server exposes methods that are called by requests, to which it provides
/// responses. Methods can be registered using [registerMethod] and
-/// [registerFallback]. Requests can be handled using [handleRequest] and
-/// [parseRequest].
+/// [registerFallback].
///
/// Note that since requests can arrive asynchronously and methods can run
/// asynchronously, it's possible for multiple methods to be invoked at the same
@@ -72,7 +71,7 @@
/// Creates a [Server] that communicates over [channel].
///
- /// Note that the server won't begin listening to [requests] until
+ /// Note that the server won't begin listening to [channel] until
/// [Server.listen] is called.
///
/// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError].
@@ -89,12 +88,12 @@
strictProtocolChecks: strictProtocolChecks);
/// Creates a [Server] that communicates using decoded messages over
- /// [channel].
+ /// [_channel].
///
/// Unlike [Server.new], this doesn't read or write JSON strings. Instead, it
/// reads and writes decoded maps or lists.
///
- /// Note that the server won't begin listening to [requests] until
+ /// Note that the server won't begin listening to [_channel] until
/// [Server.listen] is called.
///
/// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError].
@@ -113,7 +112,8 @@
///
/// [listen] may only be called once.
Future listen() {
- _channel.stream.listen(_handleRequest, onError: (error, stackTrace) {
+ _channel.stream.listen(_handleRequest,
+ onError: (Object error, StackTrace stackTrace) {
_done.completeError(error, stackTrace);
_channel.sink.close();
}, onDone: () {
@@ -160,7 +160,7 @@
/// completes to a JSON-serializable object. Any errors in [callback] will be
/// reported to the client as JSON-RPC 2.0 errors. [callback] may send custom
/// errors by throwing an [RpcException].
- void registerFallback(Function(Parameters parameters) callback) {
+ void registerFallback(void Function(Parameters parameters) callback) {
_fallbacks.add(callback);
}
@@ -169,9 +169,8 @@
/// [request] is expected to be a JSON-serializable object representing a
/// request sent by a client. This calls the appropriate method or methods for
/// handling that request and returns a JSON-serializable response, or `null`
- /// if no response should be sent. [callback] may send custom
- /// errors by throwing an [RpcException].
- Future _handleRequest(request) async {
+ /// if no response should be sent.
+ Future _handleRequest(Object? request) async {
dynamic response;
if (request is List) {
if (request.isEmpty) {
@@ -241,7 +240,7 @@
}
/// Validates that [request] matches the JSON-RPC spec.
- void _validateRequest(request) {
+ void _validateRequest(Object? request) {
if (request is! Map) {
throw RpcException(
error_code.INVALID_REQUEST,
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index c7cd10f..28bbf21 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -9,7 +9,7 @@
import '../error_code.dart' as error_code;
import 'exception.dart';
-typedef ZeroArgumentFunction = Function();
+typedef ZeroArgumentFunction = FutureOr Function();
/// A regular expression to match the exception prefix that some exceptions'
/// [Object.toString] values contain.
@@ -18,7 +18,7 @@
/// Get a string description of an exception.
///
/// Many exceptions include the exception class name at the beginning of their
-/// [toString], so we remove that if it exists.
+/// `toString`, so we remove that if it exists.
String getErrorMessage(Object error) =>
error.toString().replaceFirst(_exceptionPrefix, '');
@@ -27,7 +27,7 @@
///
/// This is synchronicity-agnostic relative to [body]. If [body] returns a
/// [Future], this wil run asynchronously; otherwise it will run synchronously.
-void tryFinally(Function() body, Function() whenComplete) {
+void tryFinally(dynamic Function() body, void Function() whenComplete) {
dynamic result;
try {
result = body();
diff --git a/pubspec.yaml b/pubspec.yaml
index 53f5885..ae3ee94 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,17 +1,17 @@
name: json_rpc_2
-version: 3.0.3-dev
+version: 3.0.3-wip
description: >-
Utilities to write a client or server using the JSON-RPC 2.0 spec.
repository: https://github.com/dart-lang/json_rpc_2
environment:
- sdk: ">=2.19.0 <3.0.0"
+ sdk: ^3.4.0
dependencies:
stack_trace: ^1.10.0
stream_channel: ^2.1.0
dev_dependencies:
- dart_flutter_team_lints: ^1.0.0
- test: ^1.16.0
- web_socket_channel: ^2.0.0
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.25.5
+ web_socket_channel: ^3.0.0
diff --git a/test/client/client_test.dart b/test/client/client_test.dart
index df9c033..1a4f65d 100644
--- a/test/client/client_test.dart
+++ b/test/client/client_test.dart
@@ -89,7 +89,7 @@
test('sends a synchronous batch of requests', () {
controller.expectRequest((request) {
- expect(request, TypeMatcher<List>());
+ expect(request, isA<List>());
expect(request, hasLength(3));
expect(request[0], equals({'jsonrpc': '2.0', 'method': 'foo'}));
expect(
@@ -121,7 +121,7 @@
test('sends an asynchronous batch of requests', () {
controller.expectRequest((request) {
- expect(request, TypeMatcher<List>());
+ expect(request, isA<List>());
expect(request, hasLength(3));
expect(request[0], equals({'jsonrpc': '2.0', 'method': 'foo'}));
expect(
@@ -143,14 +143,12 @@
});
controller.client.withBatch(() {
- return Future.value().then((_) {
+ return Future<void>.value().then<void>((_) {
controller.client.sendNotification('foo');
- return Future.value();
- }).then((_) {
+ }).then<void>((_) {
expect(controller.client.sendRequest('bar', {'param': 'value'}),
completion(equals('bar response')));
- return Future.value();
- }).then((_) {
+ }).then<void>((_) {
expect(controller.client.sendRequest('baz'),
completion(equals('baz response')));
});
@@ -177,7 +175,7 @@
expect(
controller.client.sendRequest('foo', {'param': 'value'}),
- throwsA(TypeMatcher<json_rpc.RpcException>()
+ throwsA(isA<json_rpc.RpcException>()
.having((e) => e.code, 'code', error_code.SERVER_ERROR)
.having((e) => e.message, 'message', 'you are bad at requests')
.having((e) => e.data, 'data', 'some junk')));
diff --git a/test/client/utils.dart b/test/client/utils.dart
index acbfbe2..38e187f 100644
--- a/test/client/utils.dart
+++ b/test/client/utils.dart
@@ -32,7 +32,7 @@
/// returns a String, that's sent as the response directly. If it returns
/// null, no response is sent. Otherwise, the return value is encoded and sent
/// as the response.
- void expectRequest(Function(dynamic) callback) {
+ void expectRequest(FutureOr Function(dynamic) callback) {
expect(
_requestController.stream.first.then((request) {
return callback(jsonDecode(request));
@@ -49,7 +49,7 @@
sendJsonResponse(jsonEncode(response));
}
- /// Sends [response], a JSON-encoded response, to [client].
+ /// Sends [request], a JSON-encoded response, to [client].
void sendJsonResponse(String request) {
_responseController.add(request);
}
diff --git a/test/peer_test.dart b/test/peer_test.dart
index 4b4c44a..0df6056 100644
--- a/test/peer_test.dart
+++ b/test/peer_test.dart
@@ -2,6 +2,8 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+// ignore_for_file: inference_failure_on_instance_creation
+
import 'dart:async';
import 'dart:convert';
@@ -86,10 +88,10 @@
});
test('requests terminates when the channel is closed', () async {
- var incomingController = StreamController();
+ var incomingController = StreamController<void>();
var channel = StreamChannel.withGuarantees(
incomingController.stream,
- StreamController(),
+ StreamController<void>(),
);
var peer = json_rpc.Peer.withoutJson(channel);
unawaited(peer.listen());
diff --git a/test/server/batch_test.dart b/test/server/batch_test.dart
index b8b8fea..af883c4 100644
--- a/test/server/batch_test.dart
+++ b/test/server/batch_test.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:json_rpc_2/error_code.dart' as error_code;
+import 'package:json_rpc_2/src/parameters.dart' show Parameters;
import 'package:test/test.dart';
import 'utils.dart';
@@ -14,8 +15,8 @@
controller = ServerController();
controller.server
..registerMethod('foo', () => 'foo')
- ..registerMethod('id', (params) => params.value)
- ..registerMethod('arg', (params) => params['arg'].value);
+ ..registerMethod('id', (Parameters params) => params.value)
+ ..registerMethod('arg', (Parameters params) => params['arg'].value);
});
test('handles a batch of requests', () {
diff --git a/test/server/server_test.dart b/test/server/server_test.dart
index 10f2678..b3166ce 100644
--- a/test/server/server_test.dart
+++ b/test/server/server_test.dart
@@ -16,7 +16,7 @@
setUp(() => controller = ServerController());
test('calls a registered method with the given name', () {
- controller.server.registerMethod('foo', (params) {
+ controller.server.registerMethod('foo', (json_rpc.Parameters params) {
return {'params': params.value};
});
@@ -59,14 +59,19 @@
expectErrorResponse(
controller,
- {'jsonrpc': '2.0', 'method': 'foo', 'params': {}, 'id': 1234},
+ {
+ 'jsonrpc': '2.0',
+ 'method': 'foo',
+ 'params': <String, dynamic>{},
+ 'id': 1234
+ },
error_code.INVALID_PARAMS,
'No parameters are allowed for method "foo".');
});
test('an unexpected error in a method is captured', () {
controller.server
- .registerMethod('foo', () => throw FormatException('bad format'));
+ .registerMethod('foo', () => throw const FormatException('bad format'));
expect(
controller
@@ -80,7 +85,7 @@
'data': {
'request': {'jsonrpc': '2.0', 'method': 'foo', 'id': 1234},
'full': 'FormatException: bad format',
- 'stack': TypeMatcher<String>()
+ 'stack': isA<String>()
}
}
}));
@@ -90,8 +95,8 @@
controller.server.registerMethod('foo', (args) => 'result');
expect(
- controller
- .handleRequest({'jsonrpc': '2.0', 'method': 'foo', 'params': {}}),
+ controller.handleRequest(
+ {'jsonrpc': '2.0', 'method': 'foo', 'params': <String, dynamic>{}}),
doesNotComplete);
});
@@ -102,7 +107,12 @@
expectErrorResponse(
controller,
- {'jsonrpc': '2.0', 'method': 'foo', 'params': {}, 'id': 1234},
+ {
+ 'jsonrpc': '2.0',
+ 'method': 'foo',
+ 'params': <String, dynamic>{},
+ 'id': 1234
+ },
5,
'Error message.',
data: 'data value');
@@ -164,7 +174,7 @@
test('an unexpected error in a fallback is captured', () {
controller.server
- .registerFallback((_) => throw FormatException('bad format'));
+ .registerFallback((_) => throw const FormatException('bad format'));
expect(
controller
@@ -178,7 +188,7 @@
'data': {
'request': {'jsonrpc': '2.0', 'method': 'foo', 'id': 1234},
'full': 'FormatException: bad format',
- 'stack': TypeMatcher<String>()
+ 'stack': isA<String>()
}
}
}));
diff --git a/test/server/stream_test.dart b/test/server/stream_test.dart
index 2f95150..832e13c 100644
--- a/test/server/stream_test.dart
+++ b/test/server/stream_test.dart
@@ -23,7 +23,7 @@
test('.withoutJson supports decoded stream and sink', () {
server.listen();
- server.registerMethod('foo', (params) {
+ server.registerMethod('foo', (json_rpc.Parameters params) {
return {'params': params.value};
});
diff --git a/test/server/utils.dart b/test/server/utils.dart
index 9fe6eed..c94628e 100644
--- a/test/server/utils.dart
+++ b/test/server/utils.dart
@@ -65,6 +65,6 @@
/// Returns a matcher that matches a [json_rpc.RpcException] with an
/// `invalid_params` error code.
Matcher throwsInvalidParams(String message) =>
- throwsA(TypeMatcher<json_rpc.RpcException>()
+ throwsA(isA<json_rpc.RpcException>()
.having((e) => e.code, 'code', error_code.INVALID_PARAMS)
.having((e) => e.message, 'message', message));