From 073d78fd099ee777b1fb4e38bfc316a6303eda03 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Fri, 21 Mar 2014 01:24:32 +0000 Subject: [PATCH 001/127] Create a package that implements a JSON-RPC 2.0 server. R=rnystrom@google.com BUG=17492 Review URL: https://codereview.chromium.org//205533005 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/json_rpc_2@34223 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/json_rpc_2/LICENSE | 26 ++ pkgs/json_rpc_2/README.md | 98 ++++++ pkgs/json_rpc_2/lib/error_code.dart | 36 +++ pkgs/json_rpc_2/lib/json_rpc_2.dart | 9 + pkgs/json_rpc_2/lib/src/exception.dart | 65 ++++ pkgs/json_rpc_2/lib/src/parameters.dart | 283 ++++++++++++++++ pkgs/json_rpc_2/lib/src/server.dart | 220 +++++++++++++ pkgs/json_rpc_2/lib/src/utils.dart | 45 +++ pkgs/json_rpc_2/pubspec.yaml | 13 + pkgs/json_rpc_2/test/server/batch_test.dart | 105 ++++++ .../test/server/invalid_request_test.dart | 86 +++++ .../test/server/parameters_test.dart | 305 ++++++++++++++++++ pkgs/json_rpc_2/test/server/server_test.dart | 203 ++++++++++++ pkgs/json_rpc_2/test/server/utils.dart | 32 ++ 14 files changed, 1526 insertions(+) create mode 100644 pkgs/json_rpc_2/LICENSE create mode 100644 pkgs/json_rpc_2/README.md create mode 100644 pkgs/json_rpc_2/lib/error_code.dart create mode 100644 pkgs/json_rpc_2/lib/json_rpc_2.dart create mode 100644 pkgs/json_rpc_2/lib/src/exception.dart create mode 100644 pkgs/json_rpc_2/lib/src/parameters.dart create mode 100644 pkgs/json_rpc_2/lib/src/server.dart create mode 100644 pkgs/json_rpc_2/lib/src/utils.dart create mode 100644 pkgs/json_rpc_2/pubspec.yaml create mode 100644 pkgs/json_rpc_2/test/server/batch_test.dart create mode 100644 pkgs/json_rpc_2/test/server/invalid_request_test.dart create mode 100644 pkgs/json_rpc_2/test/server/parameters_test.dart create mode 100644 pkgs/json_rpc_2/test/server/server_test.dart create mode 100644 pkgs/json_rpc_2/test/server/utils.dart diff --git a/pkgs/json_rpc_2/LICENSE b/pkgs/json_rpc_2/LICENSE new file mode 100644 index 000000000..5c60afea3 --- /dev/null +++ b/pkgs/json_rpc_2/LICENSE @@ -0,0 +1,26 @@ +Copyright 2014, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pkgs/json_rpc_2/README.md b/pkgs/json_rpc_2/README.md new file mode 100644 index 000000000..3d09d0f39 --- /dev/null +++ b/pkgs/json_rpc_2/README.md @@ -0,0 +1,98 @@ +A library that implements the [JSON-RPC 2.0 spec][spec]. + +[spec]: http://www.jsonrpc.org/specification + +## Server + +A JSON-RPC 2.0 server exposes a set of methods that can be called by clients. +These methods can be registered using `Server.registerMethod`: + +```dart +import "package:json_rpc_2/json_rpc_2.dart" as json_rpc; + +var server = new json_rpc.Server(); + +// Any string may be used as a method name. JSON-RPC 2.0 methods are +// case-sensitive. +var i = 0; +server.registerMethod("count", () { + // Just return the value to be sent as a response to the client. This can be + // anything JSON-serializable, or a Future that completes to something + // JSON-serializable. + return i++; +}); + +// Methods can take parameters. They're presented as a [Parameters] object which +// makes it easy to validate that the expected parameters exist. +server.registerMethod("echo", (params) { + // If the request doesn't have a "message" parameter, this will automatically + // send a response notifying the client that the request was invalid. + return params.getNamed("message"); +}); + +// [Parameters] has methods for verifying argument types. +server.registerMethod("subtract", (params) { + // If "minuend" or "subtrahend" aren't numbers, this will reject the request. + return params.getNum("minuend") - params.getNum("subtrahend"); +}); + +// [Parameters] also supports optional arguments. +server.registerMethod("sort", (params) { + var list = params.getList("list"); + list.sort(); + if (params.getBool("descending", orElse: () => false)) { + return params.list.reversed; + } else { + return params.list; + } +}); + +// A method can send an error response by throwing a `json_rpc.RpcException`. +// Any positive number may be used as an application-defined error code. +const DIVIDE_BY_ZERO = 1; +server.registerMethod("divide", (params) { + var divisor = params.getNum("divisor"); + if (divisor == 0) { + throw new json_rpc.RpcException(DIVIDE_BY_ZERO, "Cannot divide by zero."); + } + + return params.getNum("dividend") / divisor; +}); +``` + +Once you've registered your methods, you can handle requests with +`Server.parseRequest`: + +```dart +import 'dart:io'; + +WebSocket.connect('ws://localhost:4321').then((socket) { + socket.listen((message) { + server.parseRequest(message).then((response) { + if (response != null) socket.add(response); + }); + }); +}); +``` + +If you're communicating with objects that haven't been serialized to a string, +you can also call `Server.handleRequest` directly: + +```dart +import 'dart:isolate'; + +var receive = new ReceivePort(); +Isolate.spawnUri('path/to/client.dart', [], receive.sendPort).then((_) { + receive.listen((message) { + server.handleRequest(message['request']).then((response) { + if (response != null) message['respond'].send(response); + }); + }); +}) +``` + +## Client + +Currently this package does not contain an implementation of a JSON-RPC 2.0 +client. + diff --git a/pkgs/json_rpc_2/lib/error_code.dart b/pkgs/json_rpc_2/lib/error_code.dart new file mode 100644 index 000000000..40d673377 --- /dev/null +++ b/pkgs/json_rpc_2/lib/error_code.dart @@ -0,0 +1,36 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// 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. + +/// Error codes defined in the [JSON-RPC 2.0 specificiation][spec]. +/// +/// These codes are generally used for protocol-level communication. Most of +/// them shouldn't be used by the application. Those that should have +/// convenience constructors in [RpcException]. +/// +/// [spec]: http://www.jsonrpc.org/specification#error_object +library json_rpc_2.error_code; + +/// An error code indicating that invalid JSON was received by the server. +const PARSE_ERROR = -32700; + +/// An error code indicating that the request JSON was invalid according to the +/// JSON-RPC 2.0 spec. +const INVALID_REQUEST = -32600; + +/// An error code indicating that the requested method does not exist or is +/// unavailable. +const METHOD_NOT_FOUND = -32601; + +/// An error code indicating that the request paramaters are invalid for the +/// requested method. +const INVALID_PARAMS = -32602; + +/// An internal JSON-RPC error. +const INTERNAL_ERROR = -32603; + +/// An unexpected error occurred on the server. +/// +/// The spec reserves the range from -32000 to -32099 for implementation-defined +/// server exceptions, but for now we only use one of those values. +const SERVER_ERROR = -32000; diff --git a/pkgs/json_rpc_2/lib/json_rpc_2.dart b/pkgs/json_rpc_2/lib/json_rpc_2.dart new file mode 100644 index 000000000..04e4a523e --- /dev/null +++ b/pkgs/json_rpc_2/lib/json_rpc_2.dart @@ -0,0 +1,9 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// 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. + +library json_rpc_2; + +export 'src/exception.dart'; +export 'src/parameters.dart'; +export 'src/server.dart'; diff --git a/pkgs/json_rpc_2/lib/src/exception.dart b/pkgs/json_rpc_2/lib/src/exception.dart new file mode 100644 index 000000000..fb1cd2fce --- /dev/null +++ b/pkgs/json_rpc_2/lib/src/exception.dart @@ -0,0 +1,65 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// 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. + +library json_rpc_2.exception; + +import '../error_code.dart' as error_code; + +/// An exception from a JSON-RPC server that can be translated into an error +/// response. +class RpcException implements Exception { + /// The error code. + /// + /// All non-negative error codes are available for use by application + /// developers. + final int code; + + /// The error message. + /// + /// This should be limited to a concise single sentence. Further information + /// should be supplied via [data]. + final String message; + + /// Extra application-defined information about the error. + /// + /// This must be a JSON-serializable object. If it's a [Map] without a + /// `"request"` key, a copy of the request that caused the error will + /// automatically be injected. + final data; + + RpcException(this.code, this.message, {this.data}); + + /// An exception indicating that the method named [methodName] was not found. + /// + /// This should usually be used only by fallback handlers. + RpcException.methodNotFound(String methodName) + : this(error_code.METHOD_NOT_FOUND, 'Unknown method "$methodName".'); + + /// An exception indicating that the parameters for the requested method were + /// invalid. + /// + /// Methods can use this to reject requests with invalid parameters. + RpcException.invalidParams(String message) + : this(error_code.INVALID_PARAMS, message); + + /// Converts this exception into a JSON-serializable object that's a valid + /// JSON-RPC 2.0 error response. + serialize(request) { + var modifiedData; + if (data is Map && !data.containsKey('request')) { + modifiedData = new Map.from(data); + modifiedData['request'] = request; + } else if (data == null) { + modifiedData = {'request': request}; + } + + var id = request is Map ? request['id'] : null; + if (id is! String && id is! num) id = null; + return { + 'jsonrpc': '2.0', + 'error': {'code': code, 'message': message, 'data': modifiedData}, + 'id': id + }; + } +} diff --git a/pkgs/json_rpc_2/lib/src/parameters.dart b/pkgs/json_rpc_2/lib/src/parameters.dart new file mode 100644 index 000000000..afc4a402c --- /dev/null +++ b/pkgs/json_rpc_2/lib/src/parameters.dart @@ -0,0 +1,283 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// 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. + +library json_rpc_2.parameters; + +import 'dart:convert'; + +import 'exception.dart'; + +/// A wrapper for the parameters to a server method. +/// +/// JSON-RPC 2.0 allows parameters that are either a list or a map. This class +/// provides functions that not only assert that the parameters object is the +/// correct type, but also that the expected arguments exist and are themselves +/// the correct type. +/// +/// Example usage: +/// +/// server.registerMethod("subtract", (params) { +/// return params["minuend"].asNum - params["subtrahend"].asNum; +/// }); +class Parameters { + /// The name of the method that this request called. + final String method; + + /// The underlying value of the parameters object. + /// + /// If this is accessed for a [Parameter] that was not passed, the request + /// will be automatically rejected. To avoid this, use [Parameter.valueOr]. + final value; + + Parameters(this.method, this.value); + + /// Returns a single parameter. + /// + /// If [key] is a [String], the request is expected to provide named + /// parameters. If it's an [int], the request is expected to provide + /// positional parameters. Requests that don't do so will be rejected + /// automatically. + /// + /// Whether or not the given parameter exists, this returns a [Parameter] + /// object. If a parameter's value is accessed through a getter like [value] + /// or [Parameter.asNum], the request will be rejected if that parameter + /// doesn't exist. On the other hand, if it's accessed through a method with a + /// default value like [Parameter.valueOr] or [Parameter.asNumOr], the default + /// value will be returned. + Parameter operator [](key) { + if (key is int) { + _assertPositional(); + if (key < value.length) { + return new Parameter._(method, value[key], this, key); + } else { + return new _MissingParameter(method, this, key); + } + } else if (key is String) { + _assertNamed(); + if (value.containsKey(key)) { + return new Parameter._(method, value[key], this, key); + } else { + return new _MissingParameter(method, this, key); + } + } else { + throw new ArgumentError('Parameters[] only takes an int or a string, was ' + '"$key".'); + } + } + + /// Asserts that [value] exists and is a [List] and returns it. + List get asList { + _assertPositional(); + return value; + } + + /// Asserts that [value] exists and is a [Map] and returns it. + Map get asMap { + _assertNamed(); + return value; + } + + /// Asserts that [value] is a positional argument list. + void _assertPositional() { + if (value is List) return; + throw new RpcException.invalidParams('Parameters for method "$method" ' + 'must be passed by position.'); + } + + /// Asserts that [value] is a named argument map. + void _assertNamed() { + if (value is Map) return; + throw new RpcException.invalidParams('Parameters for method "$method" ' + 'must be passed by name.'); + } +} + +/// A wrapper for a single parameter to a server method. +/// +/// This provides numerous functions for asserting the type of the parameter in +/// question. These functions each have a version that asserts that the +/// parameter exists (for example, [asNum] and [asString]) and a version that +/// returns a default value if the parameter doesn't exist (for example, +/// [asNumOr] and [asStringOr]). If an assertion fails, the request is +/// automatically rejected. +/// +/// This extends [Parameters] to make it easy to access nested parameters. For +/// example: +/// +/// // "params.value" is "{'scores': {'home': [5, 10, 17]}}" +/// params['scores']['home'][2].asInt // => 17 +class Parameter extends Parameters { + // The parent parameters, used to construct [_path]. + final Parameters _parent; + + /// The key used to access [this], used to construct [_path]. + final _key; + + /// A human-readable representation of the path of getters used to get this. + /// + /// Named parameters are represented as `.name`, whereas positional parameters + /// are represented as `[index]`. For example: `"foo[0].bar.baz"`. Named + /// parameters that contain characters that are neither alphanumeric, + /// underscores, or hyphens will be JSON-encoded. For example: `"foo + /// bar"."baz.bang"`. If quotes are used for an individual component, they + /// won't be used for the entire string. + /// + /// An exception is made for single-level parameters. A single-level + /// positional parameter is just represented by the index plus one, because + /// "parameter 1" is clearer than "parameter [0]". A single-level named + /// parameter is represented by that name in quotes. + String get _path { + if (_parent is! Parameter) { + return _key is int ? (_key + 1).toString() : JSON.encode(_key); + } + + quoteKey(key) { + if (key.contains(new RegExp(r'[^a-zA-Z0-9_-]'))) return JSON.encode(key); + return key; + } + + computePath(params) { + if (params._parent is! Parameter) { + return params._key is int ? "[${params._key}]" : quoteKey(params._key); + } + + var path = computePath(params._parent); + return params._key is int ? + "$path[${params._key}]" : "$path.${quoteKey(params._key)}"; + } + + return computePath(this); + } + + /// Whether this parameter exists. + final exists = true; + + Parameter._(String method, value, this._parent, this._key) + : super(method, value); + + /// Returns [value], or [defaultValue] if this parameter wasn't passed. + valueOr(defaultValue) => value; + + /// Asserts that [value] exists and is a number and returns it. + /// + /// [asNumOr] may be used to provide a default value instead of rejecting the + /// request if [value] doesn't exist. + num get asNum => _getTyped('a number', (value) => value is num); + + /// Asserts that [value] is a number and returns it. + /// + /// If [value] doesn't exist, this returns [defaultValue]. + num asNumOr(num defaultValue) => asNum; + + /// Asserts that [value] exists and is an integer and returns it. + /// + /// [asIntOr] may be used to provide a default value instead of rejecting the + /// request if [value] doesn't exist. + /// + /// Note that which values count as integers varies between the Dart VM and + /// dart2js. The value `1.0` will be considered an integer under dart2js but + /// not under the VM. + int get asInt => _getTyped('an integer', (value) => value is int); + + /// Asserts that [value] is an integer and returns it. + /// + /// If [value] doesn't exist, this returns [defaultValue]. + /// + /// Note that which values count as integers varies between the Dart VM and + /// dart2js. The value `1.0` will be considered an integer under dart2js but + /// not under the VM. + int asIntOr(int defaultValue) => asInt; + + /// Asserts that [value] exists and is a boolean and returns it. + /// + /// [asBoolOr] may be used to provide a default value instead of rejecting the + /// request if [value] doesn't exist. + bool get asBool => _getTyped('a boolean', (value) => value is bool); + + /// Asserts that [value] is a boolean and returns it. + /// + /// If [value] doesn't exist, this returns [defaultValue]. + bool asBoolOr(bool defaultValue) => asBool; + + /// Asserts that [value] exists and is a string and returns it. + /// + /// [asStringOr] may be used to provide a default value instead of rejecting + /// the request if [value] doesn't exist. + String get asString => _getTyped('a string', (value) => value is String); + + /// Asserts that [value] is a string and returns it. + /// + /// If [value] doesn't exist, this returns [defaultValue]. + String asStringOr(String defaultValue) => asString; + + /// Asserts that [value] exists and is a [List] and returns it. + /// + /// [asListOr] may be used to provide a default value instead of rejecting the + /// request if [value] doesn't exist. + List get asList => _getTyped('an Array', (value) => value is List); + + /// Asserts that [value] is a [List] and returns it. + /// + /// If [value] doesn't exist, this returns [defaultValue]. + List asListOr(List defaultValue) => asList; + + /// Asserts that [value] exists and is a [Map] and returns it. + /// + /// [asListOr] may be used to provide a default value instead of rejecting the + /// request if [value] doesn't exist. + Map get asMap => _getTyped('an Object', (value) => value is Map); + + /// Asserts that [value] is a [Map] and returns it. + /// + /// If [value] doesn't exist, this returns [defaultValue]. + Map asMapOr(Map defaultValue) => asMap; + + /// Get a parameter named [named] that matches [test], or the value of calling + /// [orElse]. + /// + /// [type] is used for the error message. It should begin with an indefinite + /// article. + _getTyped(String type, bool test(value)) { + if (test(value)) return value; + throw new RpcException.invalidParams('Parameter $_path for method ' + '"$method" must be $type, but was ${JSON.encode(value)}.'); + } + + void _assertPositional() { + // Throw the standard exception for a mis-typed list. + asList; + } + + void _assertNamed() { + // Throw the standard exception for a mis-typed map. + asMap; + } +} + +/// A subclass of [Parameter] representing a missing parameter. +class _MissingParameter extends Parameter { + get value { + throw new RpcException.invalidParams('Request for method "$method" is ' + 'missing required parameter $_path.'); + } + + final exists = false; + + _MissingParameter(String method, Parameters parent, key) + : super._(method, null, parent, key); + + valueOr(defaultValue) => defaultValue; + + num asNumOr(num defaultValue) => defaultValue; + + int asIntOr(int defaultValue) => defaultValue; + + bool asBoolOr(bool defaultValue) => defaultValue; + + String asStringOr(String defaultValue) => defaultValue; + + List asListOr(List defaultValue) => defaultValue; + + Map asMapOr(Map defaultValue) => defaultValue; +} diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart new file mode 100644 index 000000000..d05c54f9e --- /dev/null +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -0,0 +1,220 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// 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. + +library json_rpc_2.server; + +import 'dart:async'; +import 'dart:collection'; +import 'dart:convert'; + +import 'package:stack_trace/stack_trace.dart'; + +import '../error_code.dart' as error_code; +import 'exception.dart'; +import 'parameters.dart'; +import 'utils.dart'; + +/// A JSON-RPC 2.0 server. +/// +/// 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]. +/// +/// Note that since requests can arrive asynchronously and methods can run +/// asynchronously, it's possible for multiple methods to be invoked at the same +/// time, or even for a single method to be invoked multiple times at once. +class Server { + /// The methods registered for this server. + final _methods = new Map(); + + /// The fallback methods for this server. + /// + /// These are tried in order until one of them doesn't throw a + /// [RpcException.methodNotFound] exception. + final _fallbacks = new Queue(); + + Server(); + + /// Registers a method named [name] on this server. + /// + /// [callback] can take either zero or one arguments. If it takes zero, any + /// requests for that method that include parameters will be rejected. If it + /// takes one, it will be passed a [Parameters] object. + /// + /// [callback] can return either a JSON-serializable object or a Future that + /// completes to a JSON-serializable object. Any errors in [callback] will be + /// reported to the client as JSON-RPC 2.0 errors. + void registerMethod(String name, Function callback) { + if (_methods.containsKey(name)) { + throw new ArgumentError('There\'s already a method named "$name".'); + } + + _methods[name] = callback; + } + + /// Registers a fallback method on this server. + /// + /// A server may have any number of fallback methods. When a request comes in + /// that doesn't match any named methods, each fallback is tried in order. A + /// fallback can pass on handling a request by throwing a + /// [RpcException.methodNotFound] exception. + /// + /// [callback] can return either a JSON-serializable object or a Future that + /// 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(callback(Parameters parameters)) { + _fallbacks.add(callback); + } + + /// Handle a request that's already been parsed from JSON. + /// + /// [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) { + return syncFuture(() { + if (request is! List) return _handleSingleRequest(request); + if (request.isEmpty) { + return new RpcException(error_code.INVALID_REQUEST, 'A batch must ' + 'contain at least one request.').serialize(request); + } + + return Future.wait(request.map(_handleSingleRequest)).then((results) { + var nonNull = results.where((result) => result != null); + return nonNull.isEmpty ? null : nonNull.toList(); + }); + }); + } + + /// Parses and handles a JSON serialized request. + /// + /// This calls the appropriate method or methods for handling that request and + /// returns a JSON string, or `null` if no response should be sent. + Future parseRequest(String request) { + return syncFuture(() { + var decodedRequest; + try { + decodedRequest = JSON.decode(request); + } on FormatException catch (error) { + return new RpcException(error_code.PARSE_ERROR, 'Invalid JSON: ' + '${error.message}').serialize(request); + } + + return handleRequest(decodedRequest); + }).then((response) { + if (response == null) return null; + return JSON.encode(response); + }); + } + + /// Handles an individual parsed request. + Future _handleSingleRequest(request) { + return syncFuture(() { + _validateRequest(request); + + var name = request['method']; + var method = _methods[name]; + if (method == null) method = _tryFallbacks; + + if (method is ZeroArgumentFunction) { + if (!request.containsKey('params')) return method(); + throw new RpcException.invalidParams('No parameters are allowed for ' + 'method "$name".'); + } + + return method(new Parameters(name, request['params'])); + }).then((result) { + // A request without an id is a notification, which should not be sent a + // response, even if one is generated on the server. + if (!request.containsKey('id')) return null; + + return { + 'jsonrpc': '2.0', + 'result': result, + 'id': request['id'] + }; + }).catchError((error, stackTrace) { + if (error is! RpcException) { + error = new RpcException( + error_code.SERVER_ERROR, getErrorMessage(error), data: { + 'full': error.toString(), + 'stack': new Chain.forTrace(stackTrace).toString() + }); + } + + if (error.code != error_code.INVALID_REQUEST && + !request.containsKey('id')) { + return null; + } else { + return error.serialize(request); + } + }); + } + + /// Validates that [request] matches the JSON-RPC spec. + void _validateRequest(request) { + if (request is! Map) { + throw new RpcException(error_code.INVALID_REQUEST, 'Request must be ' + 'an Array or an Object.'); + } + + if (!request.containsKey('jsonrpc')) { + throw new RpcException(error_code.INVALID_REQUEST, 'Request must ' + 'contain a "jsonrpc" key.'); + } + + if (request['jsonrpc'] != '2.0') { + throw new RpcException(error_code.INVALID_REQUEST, 'Invalid JSON-RPC ' + 'version ${JSON.encode(request['jsonrpc'])}, expected "2.0".'); + } + + if (!request.containsKey('method')) { + throw new RpcException(error_code.INVALID_REQUEST, 'Request must ' + 'contain a "method" key.'); + } + + var method = request['method']; + if (request['method'] is! String) { + throw new RpcException(error_code.INVALID_REQUEST, 'Request method must ' + 'be a string, but was ${JSON.encode(method)}.'); + } + + var params = request['params']; + if (request.containsKey('params') && params is! List && params is! Map) { + throw new RpcException(error_code.INVALID_REQUEST, 'Request params must ' + 'be an Array or an Object, but was ${JSON.encode(params)}.'); + } + + var id = request['id']; + if (id != null && id is! String && id is! num) { + throw new RpcException(error_code.INVALID_REQUEST, 'Request id must be a ' + 'string, number, or null, but was ${JSON.encode(id)}.'); + } + } + + /// Try all the fallback methods in order. + Future _tryFallbacks(Parameters params) { + var iterator = _fallbacks.toList().iterator; + + _tryNext() { + if (!iterator.moveNext()) { + return new Future.error( + new RpcException.methodNotFound(params.method), + new Chain.current()); + } + + return syncFuture(() => iterator.current(params)).catchError((error) { + if (error is! RpcException) throw error; + if (error.code != error_code.METHOD_NOT_FOUND) throw error; + return _tryNext(); + }); + } + + return _tryNext(); + } +} diff --git a/pkgs/json_rpc_2/lib/src/utils.dart b/pkgs/json_rpc_2/lib/src/utils.dart new file mode 100644 index 000000000..1eff004f1 --- /dev/null +++ b/pkgs/json_rpc_2/lib/src/utils.dart @@ -0,0 +1,45 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// 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. + +library json_rpc_2.utils; + +import 'dart:async'; + +import 'package:stack_trace/stack_trace.dart'; + +typedef ZeroArgumentFunction(); + +/// Like [new Future.sync], but automatically wraps the future in a +/// [Chain.track] call. +Future syncFuture(callback()) => Chain.track(new Future.sync(callback)); + +/// Returns a sentence fragment listing the elements of [iter]. +/// +/// This converts each element of [iter] to a string and separates them with +/// commas and/or "and" where appropriate. +String toSentence(Iterable iter) { + if (iter.length == 1) return iter.first.toString(); + return iter.take(iter.length - 1).join(", ") + " and ${iter.last}"; +} + +/// Returns [name] if [number] is 1, or the plural of [name] otherwise. +/// +/// By default, this just adds "s" to the end of [name] to get the plural. If +/// [plural] is passed, that's used instead. +String pluralize(String name, int number, {String plural}) { + if (number == 1) return name; + if (plural != null) return plural; + return '${name}s'; +} + +/// A regular expression to match the exception prefix that some exceptions' +/// [Object.toString] values contain. +final _exceptionPrefix = new RegExp(r'^([A-Z][a-zA-Z]*)?(Exception|Error): '); + +/// 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. +String getErrorMessage(error) => + error.toString().replaceFirst(_exceptionPrefix, ''); diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml new file mode 100644 index 000000000..0919ac128 --- /dev/null +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -0,0 +1,13 @@ +name: json_rpc_2 +version: 0.0.1 +author: Dart Team +description: An implementation of the JSON-RPC 2.0 spec. +homepage: http://www.dartlang.org +documentation: http://api.dartlang.org/docs/pkg/json_rpc_2 +dependencies: + stack_trace: '>=0.9.1 <0.10.0' +dev_dependencies: + unittest: ">=0.9.0 <0.10.0" +environment: + sdk: ">=1.2.0 <2.0.0" + diff --git a/pkgs/json_rpc_2/test/server/batch_test.dart b/pkgs/json_rpc_2/test/server/batch_test.dart new file mode 100644 index 000000000..441df5893 --- /dev/null +++ b/pkgs/json_rpc_2/test/server/batch_test.dart @@ -0,0 +1,105 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// 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. + +library json_rpc_2.test.server.batch_test; + +import 'dart:convert'; + +import 'package:unittest/unittest.dart'; +import 'package:json_rpc_2/error_code.dart' as error_code; +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; + +import 'utils.dart'; + +void main() { + var server; + setUp(() { + server = new json_rpc.Server() + ..registerMethod('foo', () => 'foo') + ..registerMethod('id', (params) => params.value) + ..registerMethod('arg', (params) => params['arg'].value); + }); + + test('handles a batch of requests', () { + expect(server.handleRequest([ + {'jsonrpc': '2.0', 'method': 'foo', 'id': 1}, + {'jsonrpc': '2.0', 'method': 'id', 'params': ['value'], 'id': 2}, + {'jsonrpc': '2.0', 'method': 'arg', 'params': {'arg': 'value'}, 'id': 3} + ]), completion(equals([ + {'jsonrpc': '2.0', 'result': 'foo', 'id': 1}, + {'jsonrpc': '2.0', 'result': ['value'], 'id': 2}, + {'jsonrpc': '2.0', 'result': 'value', 'id': 3} + ]))); + }); + + test('handles errors individually', () { + expect(server.handleRequest([ + {'jsonrpc': '2.0', 'method': 'foo', 'id': 1}, + {'jsonrpc': '2.0', 'method': 'zap', 'id': 2}, + {'jsonrpc': '2.0', 'method': 'arg', 'params': {'arg': 'value'}, 'id': 3} + ]), completion(equals([ + {'jsonrpc': '2.0', 'result': 'foo', 'id': 1}, + { + 'jsonrpc': '2.0', + 'id': 2, + 'error': { + 'code': error_code.METHOD_NOT_FOUND, + 'message': 'Unknown method "zap".', + 'data': {'request': {'jsonrpc': '2.0', 'method': 'zap', 'id': 2}}, + } + }, + {'jsonrpc': '2.0', 'result': 'value', 'id': 3} + ]))); + }); + + test('handles notifications individually', () { + expect(server.handleRequest([ + {'jsonrpc': '2.0', 'method': 'foo', 'id': 1}, + {'jsonrpc': '2.0', 'method': 'id', 'params': ['value']}, + {'jsonrpc': '2.0', 'method': 'arg', 'params': {'arg': 'value'}, 'id': 3} + ]), completion(equals([ + {'jsonrpc': '2.0', 'result': 'foo', 'id': 1}, + {'jsonrpc': '2.0', 'result': 'value', 'id': 3} + ]))); + }); + + test('returns nothing if every request is a notification', () { + expect(server.handleRequest([ + {'jsonrpc': '2.0', 'method': 'foo'}, + {'jsonrpc': '2.0', 'method': 'id', 'params': ['value']}, + {'jsonrpc': '2.0', 'method': 'arg', 'params': {'arg': 'value'}} + ]), completion(isNull)); + }); + + test('returns an error if the batch is empty', () { + expectErrorResponse(server, [], error_code.INVALID_REQUEST, + 'A batch must contain at least one request.'); + }); + + test('handles a batch of requests parsed from JSON', () { + expect(server.parseRequest(JSON.encode([ + {'jsonrpc': '2.0', 'method': 'foo', 'id': 1}, + {'jsonrpc': '2.0', 'method': 'id', 'params': ['value'], 'id': 2}, + {'jsonrpc': '2.0', 'method': 'arg', 'params': {'arg': 'value'}, 'id': 3} + ])), completion(equals(JSON.encode([ + {'jsonrpc': '2.0', 'result': 'foo', 'id': 1}, + {'jsonrpc': '2.0', 'result': ['value'], 'id': 2}, + {'jsonrpc': '2.0', 'result': 'value', 'id': 3} + ])))); + }); + + test('disallows nested batches', () { + expect(server.handleRequest([ + [{'jsonrpc': '2.0', 'method': 'foo', 'id': 1}] + ]), completion(equals([{ + 'jsonrpc': '2.0', + 'id': null, + 'error': { + 'code': error_code.INVALID_REQUEST, + 'message': 'Request must be an Array or an Object.', + 'data': {'request': [{'jsonrpc': '2.0', 'method': 'foo', 'id': 1}]} + } + }]))); + }); +} \ No newline at end of file diff --git a/pkgs/json_rpc_2/test/server/invalid_request_test.dart b/pkgs/json_rpc_2/test/server/invalid_request_test.dart new file mode 100644 index 000000000..feeefea9c --- /dev/null +++ b/pkgs/json_rpc_2/test/server/invalid_request_test.dart @@ -0,0 +1,86 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// 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. + +library json_rpc_2.test.server.invalid_request_test; + +import 'dart:convert'; + +import 'package:unittest/unittest.dart'; +import 'package:json_rpc_2/error_code.dart' as error_code; +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; + +import 'utils.dart'; + +void main() { + var server; + setUp(() => server = new json_rpc.Server()); + + test("a non-Array/Object request is invalid", () { + expectErrorResponse(server, 'foo', error_code.INVALID_REQUEST, + 'Request must be an Array or an Object.'); + }); + + test("requests must have a jsonrpc key", () { + expectErrorResponse(server, { + 'method': 'foo', + 'id': 1234 + }, error_code.INVALID_REQUEST, 'Request must contain a "jsonrpc" key.'); + }); + + test("the jsonrpc version must be 2.0", () { + expectErrorResponse(server, { + 'jsonrpc': '1.0', + 'method': 'foo', + 'id': 1234 + }, error_code.INVALID_REQUEST, + 'Invalid JSON-RPC version "1.0", expected "2.0".'); + }); + + test("requests must have a method key", () { + expectErrorResponse(server, { + 'jsonrpc': '2.0', + 'id': 1234 + }, error_code.INVALID_REQUEST, 'Request must contain a "method" key.'); + }); + + test("request method must be a string", () { + expectErrorResponse(server, { + 'jsonrpc': '2.0', + 'method': 1234, + 'id': 1234 + }, error_code.INVALID_REQUEST, + 'Request method must be a string, but was 1234.'); + }); + + test("request params must be an Array or Object", () { + expectErrorResponse(server, { + 'jsonrpc': '2.0', + 'method': 'foo', + 'params': 1234, + 'id': 1234 + }, error_code.INVALID_REQUEST, + 'Request params must be an Array or an Object, but was 1234.'); + }); + + test("request id may not be an Array or Object", () { + expect(server.handleRequest({ + 'jsonrpc': '2.0', + 'method': 'foo', + 'id': {'bad': 'id'} + }), completion(equals({ + 'jsonrpc': '2.0', + 'id': null, + 'error': { + 'code': error_code.INVALID_REQUEST, + 'message': 'Request id must be a string, number, or null, but was ' + '{"bad":"id"}.', + 'data': {'request': { + 'jsonrpc': '2.0', + 'method': 'foo', + 'id': {'bad': 'id'} + }} + } + }))); + }); +} diff --git a/pkgs/json_rpc_2/test/server/parameters_test.dart b/pkgs/json_rpc_2/test/server/parameters_test.dart new file mode 100644 index 000000000..92194756c --- /dev/null +++ b/pkgs/json_rpc_2/test/server/parameters_test.dart @@ -0,0 +1,305 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// 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. + +library json_rpc_2.test.server.parameters_test; + +import 'package:unittest/unittest.dart'; +import 'package:json_rpc_2/error_code.dart' as error_code; +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; + +import 'utils.dart'; + +void main() { + group("with named parameters", () { + var parameters; + setUp(() { + parameters = new json_rpc.Parameters("foo", { + "num": 1.5, + "int": 1, + "bool": true, + "string": "zap", + "list": [1, 2, 3], + "map": { + "num": 4.2, + "bool": false + } + }); + }); + + test("value returns the wrapped value", () { + expect(parameters.value, equals({ + "num": 1.5, + "int": 1, + "bool": true, + "string": "zap", + "list": [1, 2, 3], + "map": { + "num": 4.2, + "bool": false + } + })); + }); + + test("[int] throws a parameter error", () { + expect(() => parameters[0], + throwsInvalidParams('Parameters for method "foo" must be passed by ' + 'position.')); + }); + + test("[].value returns existing parameters", () { + expect(parameters['num'].value, equals(1.5)); + }); + + test("[].valueOr returns existing parameters", () { + expect(parameters['num'].valueOr(7), equals(1.5)); + }); + + test("[].value fails for absent parameters", () { + expect(() => parameters['fblthp'].value, + throwsInvalidParams('Request for method "foo" is missing required ' + 'parameter "fblthp".')); + }); + + test("[].valueOr succeeds for absent parameters", () { + expect(parameters['fblthp'].valueOr(7), equals(7)); + }); + + test("[].exists returns true for existing parameters", () { + expect(parameters['num'].exists, isTrue); + }); + + test("[].exists returns false for missing parameters", () { + expect(parameters['fblthp'].exists, isFalse); + }); + + test("[].asNum returns numeric parameters", () { + expect(parameters['num'].asNum, equals(1.5)); + expect(parameters['int'].asNum, equals(1)); + }); + + test("[].asNumOr returns numeric parameters", () { + expect(parameters['num'].asNumOr(7), equals(1.5)); + }); + + test("[].asNum fails for non-numeric parameters", () { + expect(() => parameters['bool'].asNum, + throwsInvalidParams('Parameter "bool" for method "foo" must be a ' + 'number, but was true.')); + }); + + test("[].asNumOr fails for non-numeric parameters", () { + expect(() => parameters['bool'].asNumOr(7), + throwsInvalidParams('Parameter "bool" for method "foo" must be a ' + 'number, but was true.')); + }); + + test("[].asNum fails for absent parameters", () { + expect(() => parameters['fblthp'].asNum, + throwsInvalidParams('Request for method "foo" is missing required ' + 'parameter "fblthp".')); + }); + + test("[].asNumOr succeeds for absent parameters", () { + expect(parameters['fblthp'].asNumOr(7), equals(7)); + }); + + test("[].asInt returns integer parameters", () { + expect(parameters['int'].asInt, equals(1)); + }); + + test("[].asIntOr returns integer parameters", () { + expect(parameters['int'].asIntOr(7), equals(1)); + }); + + test("[].asInt fails for non-integer parameters", () { + expect(() => parameters['bool'].asInt, + throwsInvalidParams('Parameter "bool" for method "foo" must be an ' + 'integer, but was true.')); + }); + + test("[].asIntOr succeeds for absent parameters", () { + expect(parameters['fblthp'].asIntOr(7), equals(7)); + }); + + test("[].asBool returns boolean parameters", () { + expect(parameters['bool'].asBool, isTrue); + }); + + test("[].asBoolOr returns boolean parameters", () { + expect(parameters['bool'].asBoolOr(false), isTrue); + }); + + test("[].asBoolOr fails for non-boolean parameters", () { + expect(() => parameters['int'].asBool, + throwsInvalidParams('Parameter "int" for method "foo" must be a ' + 'boolean, but was 1.')); + }); + + test("[].asBoolOr succeeds for absent parameters", () { + expect(parameters['fblthp'].asBoolOr(false), isFalse); + }); + + test("[].asString returns string parameters", () { + expect(parameters['string'].asString, equals("zap")); + }); + + test("[].asStringOr returns string parameters", () { + expect(parameters['string'].asStringOr("bap"), equals("zap")); + }); + + test("[].asString fails for non-string parameters", () { + expect(() => parameters['int'].asString, + throwsInvalidParams('Parameter "int" for method "foo" must be a ' + 'string, but was 1.')); + }); + + test("[].asStringOr succeeds for absent parameters", () { + expect(parameters['fblthp'].asStringOr("bap"), equals("bap")); + }); + + test("[].asList returns list parameters", () { + expect(parameters['list'].asList, equals([1, 2, 3])); + }); + + test("[].asListOr returns list parameters", () { + expect(parameters['list'].asListOr([5, 6, 7]), equals([1, 2, 3])); + }); + + test("[].asList fails for non-list parameters", () { + expect(() => parameters['int'].asList, + throwsInvalidParams('Parameter "int" for method "foo" must be an ' + 'Array, but was 1.')); + }); + + test("[].asListOr succeeds for absent parameters", () { + expect(parameters['fblthp'].asListOr([5, 6, 7]), equals([5, 6, 7])); + }); + + test("[].asMap returns map parameters", () { + expect(parameters['map'].asMap, equals({"num": 4.2, "bool": false})); + }); + + test("[].asMapOr returns map parameters", () { + expect(parameters['map'].asMapOr({}), + equals({"num": 4.2, "bool": false})); + }); + + test("[].asMap fails for non-map parameters", () { + expect(() => parameters['int'].asMap, + throwsInvalidParams('Parameter "int" for method "foo" must be an ' + 'Object, but was 1.')); + }); + + test("[].asMapOr succeeds for absent parameters", () { + expect(parameters['fblthp'].asMapOr({}), equals({})); + }); + + group("with a nested parameter map", () { + var nested; + setUp(() => nested = parameters['map']); + + test("[int] fails with a type error", () { + expect(() => nested[0], + throwsInvalidParams('Parameter "map" for method "foo" must be an ' + 'Array, but was {"num":4.2,"bool":false}.')); + }); + + test("[].value returns existing parameters", () { + expect(nested['num'].value, equals(4.2)); + expect(nested['bool'].value, isFalse); + }); + + test("[].value fails for absent parameters", () { + expect(() => nested['fblthp'].value, + throwsInvalidParams('Request for method "foo" is missing required ' + 'parameter map.fblthp.')); + }); + + test("typed getters return correctly-typed parameters", () { + expect(nested['num'].asNum, equals(4.2)); + }); + + test("typed getters fail for incorrectly-typed parameters", () { + expect(() => nested['bool'].asNum, + throwsInvalidParams('Parameter map.bool for method "foo" must be ' + 'a number, but was false.')); + }); + }); + + group("with a nested parameter list", () { + var nested; + setUp(() => nested = parameters['list']); + + test("[string] fails with a type error", () { + expect(() => nested['foo'], + throwsInvalidParams('Parameter "list" for method "foo" must be an ' + 'Object, but was [1,2,3].')); + }); + + test("[].value returns existing parameters", () { + expect(nested[0].value, equals(1)); + expect(nested[1].value, equals(2)); + }); + + test("[].value fails for absent parameters", () { + expect(() => nested[5].value, + throwsInvalidParams('Request for method "foo" is missing required ' + 'parameter list[5].')); + }); + + test("typed getters return correctly-typed parameters", () { + expect(nested[0].asInt, equals(1)); + }); + + test("typed getters fail for incorrectly-typed parameters", () { + expect(() => nested[0].asBool, + throwsInvalidParams('Parameter list[0] for method "foo" must be ' + 'a boolean, but was 1.')); + }); + }); + }); + + group("with positional parameters", () { + var parameters; + setUp(() => parameters = new json_rpc.Parameters("foo", [1, 2, 3, 4, 5])); + + test("value returns the wrapped value", () { + expect(parameters.value, equals([1, 2, 3, 4, 5])); + }); + + test("[string] throws a parameter error", () { + expect(() => parameters['foo'], + throwsInvalidParams('Parameters for method "foo" must be passed by ' + 'name.')); + }); + + test("[].value returns existing parameters", () { + expect(parameters[2].value, equals(3)); + }); + + test("[].value fails for out-of-range parameters", () { + expect(() => parameters[10].value, + throwsInvalidParams('Request for method "foo" is missing required ' + 'parameter 11.')); + }); + + test("[].exists returns true for existing parameters", () { + expect(parameters[0].exists, isTrue); + }); + + test("[].exists returns false for missing parameters", () { + expect(parameters[10].exists, isFalse); + }); + }); + + test("with a complex parameter path", () { + var parameters = new json_rpc.Parameters("foo", { + 'bar baz': [0, 1, 2, {'bang.zap': {'\n': 'qux'}}] + }); + + expect(() => parameters['bar baz'][3]['bang.zap']['\n']['bip'], + throwsInvalidParams('Parameter "bar baz"[3]."bang.zap"."\\n" for ' + 'method "foo" must be an Object, but was "qux".')); + }); +} diff --git a/pkgs/json_rpc_2/test/server/server_test.dart b/pkgs/json_rpc_2/test/server/server_test.dart new file mode 100644 index 000000000..f1e0af5c7 --- /dev/null +++ b/pkgs/json_rpc_2/test/server/server_test.dart @@ -0,0 +1,203 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// 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. + +library json_rpc_2.test.server.server_test; + +import 'dart:convert'; + +import 'package:unittest/unittest.dart'; +import 'package:json_rpc_2/error_code.dart' as error_code; +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; + +import 'utils.dart'; + +void main() { + var server; + setUp(() => server = new json_rpc.Server()); + + test("calls a registered method with the given name", () { + server.registerMethod('foo', (params) { + return {'params': params.value}; + }); + + expect(server.handleRequest({ + 'jsonrpc': '2.0', + 'method': 'foo', + 'params': {'param': 'value'}, + 'id': 1234 + }), completion(equals({ + 'jsonrpc': '2.0', + 'result': {'params': {'param': 'value'}}, + 'id': 1234 + }))); + }); + + test("calls a method that takes no parameters", () { + server.registerMethod('foo', () => 'foo'); + + expect(server.handleRequest({ + 'jsonrpc': '2.0', + 'method': 'foo', + 'id': 1234 + }), completion(equals({ + 'jsonrpc': '2.0', + 'result': 'foo', + 'id': 1234 + }))); + }); + + test("a method that takes no parameters rejects parameters", () { + server.registerMethod('foo', () => 'foo'); + + expectErrorResponse(server, { + 'jsonrpc': '2.0', + 'method': 'foo', + 'params': {}, + 'id': 1234 + }, + error_code.INVALID_PARAMS, + 'No parameters are allowed for method "foo".'); + }); + + test("an unexpected error in a method is captured", () { + server.registerMethod('foo', () => throw new FormatException('bad format')); + + expect(server.handleRequest({ + 'jsonrpc': '2.0', + 'method': 'foo', + 'id': 1234 + }), completion({ + 'jsonrpc': '2.0', + 'id': 1234, + 'error': { + 'code': error_code.SERVER_ERROR, + 'message': 'bad format', + 'data': { + 'request': {'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}, + 'full': 'FormatException: bad format', + 'stack': contains('server_test.dart') + } + } + })); + }); + + test("doesn't return a result for a notification", () { + server.registerMethod('foo', (args) => 'result'); + + expect(server.handleRequest({ + 'jsonrpc': '2.0', + 'method': 'foo', + 'params': {} + }), completion(isNull)); + }); + + group("JSON", () { + test("handles a request parsed from JSON", () { + server.registerMethod('foo', (params) { + return {'params': params.value}; + }); + + expect(server.parseRequest(JSON.encode({ + 'jsonrpc': '2.0', + 'method': 'foo', + 'params': {'param': 'value'}, + 'id': 1234 + })), completion(equals(JSON.encode({ + 'jsonrpc': '2.0', + 'result': {'params': {'param': 'value'}}, + 'id': 1234 + })))); + }); + + test("handles a notification parsed from JSON", () { + server.registerMethod('foo', (params) { + return {'params': params}; + }); + + expect(server.parseRequest(JSON.encode({ + 'jsonrpc': '2.0', + 'method': 'foo', + 'params': {'param': 'value'} + })), completion(isNull)); + }); + + test("a JSON parse error is rejected", () { + expect(server.parseRequest('invalid json {'), + completion(equals(JSON.encode({ + 'jsonrpc': '2.0', + 'error': { + 'code': error_code.PARSE_ERROR, + 'message': "Invalid JSON: Unexpected character at 0: 'invalid json " + "{'", + 'data': {'request': 'invalid json {'} + }, + 'id': null + })))); + }); + }); + + group("fallbacks", () { + test("calls a fallback if no method matches", () { + server.registerMethod('foo', () => 'foo'); + server.registerMethod('bar', () => 'foo'); + server.registerFallback((params) => {'fallback': params.value}); + + expect(server.handleRequest({ + 'jsonrpc': '2.0', + 'method': 'baz', + 'params': {'param': 'value'}, + 'id': 1234 + }), completion(equals({ + 'jsonrpc': '2.0', + 'result': {'fallback': {'param': 'value'}}, + 'id': 1234 + }))); + }); + + test("calls the first matching fallback", () { + server.registerFallback((params) => + throw new json_rpc.RpcException.methodNotFound(params.method)); + + server.registerFallback((params) => 'fallback 2'); + server.registerFallback((params) => 'fallback 3'); + + expect(server.handleRequest({ + 'jsonrpc': '2.0', + 'method': 'fallback 2', + 'id': 1234 + }), completion(equals({ + 'jsonrpc': '2.0', + 'result': 'fallback 2', + 'id': 1234 + }))); + }); + + test("an unexpected error in a fallback is captured", () { + server.registerFallback((_) => throw new FormatException('bad format')); + + expect(server.handleRequest({ + 'jsonrpc': '2.0', + 'method': 'foo', + 'id': 1234 + }), completion({ + 'jsonrpc': '2.0', + 'id': 1234, + 'error': { + 'code': error_code.SERVER_ERROR, + 'message': 'bad format', + 'data': { + 'request': {'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}, + 'full': 'FormatException: bad format', + 'stack': contains('server_test.dart') + } + } + })); + }); + }); + + test("disallows multiple methods with the same name", () { + server.registerMethod('foo', () => null); + expect(() => server.registerMethod('foo', () => null), throwsArgumentError); + }); +} diff --git a/pkgs/json_rpc_2/test/server/utils.dart b/pkgs/json_rpc_2/test/server/utils.dart new file mode 100644 index 000000000..07f571ce6 --- /dev/null +++ b/pkgs/json_rpc_2/test/server/utils.dart @@ -0,0 +1,32 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// 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. + +library json_rpc_2.test.server.util; + +import 'package:unittest/unittest.dart'; +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; + +void expectErrorResponse(json_rpc.Server server, request, int errorCode, + String message) { + var id; + if (request is Map) id = request['id']; + + expect(server.handleRequest(request), completion(equals({ + 'jsonrpc': '2.0', + 'id': id, + 'error': { + 'code': errorCode, + 'message': message, + 'data': {'request': request} + } + }))); +} + +Matcher throwsInvalidParams(String message) { + return throwsA(predicate((error) { + expect(error, new isInstanceOf()); + expect(error.message, equals(message)); + return true; + })); +} From 29ea50b1d16b8c5576e1ca4d0f7010450d109a91 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Fri, 21 Mar 2014 02:34:07 +0000 Subject: [PATCH 002/127] Make the json_rpc_2 server test dart2js-compatible. R=rnystrom@google.com TBR Review URL: https://codereview.chromium.org//207323004 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/json_rpc_2@34226 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/json_rpc_2/test/server/server_test.dart | 26 ++++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/pkgs/json_rpc_2/test/server/server_test.dart b/pkgs/json_rpc_2/test/server/server_test.dart index f1e0af5c7..fc3adb527 100644 --- a/pkgs/json_rpc_2/test/server/server_test.dart +++ b/pkgs/json_rpc_2/test/server/server_test.dart @@ -76,7 +76,7 @@ void main() { 'data': { 'request': {'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}, 'full': 'FormatException: bad format', - 'stack': contains('server_test.dart') + 'stack': new isInstanceOf() } } })); @@ -123,17 +123,17 @@ void main() { }); test("a JSON parse error is rejected", () { - expect(server.parseRequest('invalid json {'), - completion(equals(JSON.encode({ - 'jsonrpc': '2.0', - 'error': { - 'code': error_code.PARSE_ERROR, - 'message': "Invalid JSON: Unexpected character at 0: 'invalid json " - "{'", - 'data': {'request': 'invalid json {'} - }, - 'id': null - })))); + return server.parseRequest('invalid json {').then((result) { + expect(JSON.decode(result), { + 'jsonrpc': '2.0', + 'error': { + 'code': error_code.PARSE_ERROR, + 'message': startsWith("Invalid JSON: "), + 'data': {'request': 'invalid json {'} + }, + 'id': null + }); + }); }); }); @@ -189,7 +189,7 @@ void main() { 'data': { 'request': {'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}, 'full': 'FormatException: bad format', - 'stack': contains('server_test.dart') + 'stack': new isInstanceOf() } } })); From 0ac0248528f5d58a63ec78e9b54ed1b1f5aa43ec Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Tue, 25 Mar 2014 00:03:39 +0000 Subject: [PATCH 003/127] Add helper getters for URIs and date/times to json_rpc_2. R=rnystrom@google.com BUG=17700 Review URL: https://codereview.chromium.org//205713007 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/json_rpc_2@34341 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/json_rpc_2/lib/src/parameters.dart | 53 +++++++++++++- pkgs/json_rpc_2/pubspec.yaml | 2 +- .../test/server/parameters_test.dart | 71 +++++++++++++++++++ 3 files changed, 124 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/lib/src/parameters.dart b/pkgs/json_rpc_2/lib/src/parameters.dart index afc4a402c..1e2522013 100644 --- a/pkgs/json_rpc_2/lib/src/parameters.dart +++ b/pkgs/json_rpc_2/lib/src/parameters.dart @@ -224,7 +224,7 @@ class Parameter extends Parameters { /// Asserts that [value] exists and is a [Map] and returns it. /// - /// [asListOr] may be used to provide a default value instead of rejecting the + /// [asMapOr] may be used to provide a default value instead of rejecting the /// request if [value] doesn't exist. Map get asMap => _getTyped('an Object', (value) => value is Map); @@ -233,6 +233,32 @@ class Parameter extends Parameters { /// If [value] doesn't exist, this returns [defaultValue]. Map asMapOr(Map defaultValue) => asMap; + /// Asserts that [value] exists, is a string, and can be parsed as a + /// [DateTime] and returns it. + /// + /// [asDateTimeOr] may be used to provide a default value instead of rejecting + /// the request if [value] doesn't exist. + DateTime get asDateTime => _getParsed('date/time', DateTime.parse); + + /// Asserts that [value] exists, is a string, and can be parsed as a + /// [DateTime] and returns it. + /// + /// If [value] doesn't exist, this returns [defaultValue]. + DateTime asDateTimeOr(DateTime defaultValue) => asDateTime; + + /// Asserts that [value] exists, is a string, and can be parsed as a + /// [Uri] and returns it. + /// + /// [asUriOr] may be used to provide a default value instead of rejecting the + /// request if [value] doesn't exist. + Uri get asUri => _getParsed('URI', Uri.parse); + + /// Asserts that [value] exists, is a string, and can be parsed as a + /// [Uri] and returns it. + /// + /// 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]. /// @@ -244,6 +270,27 @@ class Parameter extends Parameters { '"$method" must be $type, but was ${JSON.encode(value)}.'); } + _getParsed(String description, parse(String value)) { + var string = asString; + try { + return parse(string); + } on FormatException catch (error) { + // DateTime.parse doesn't actually include any useful information in the + // FormatException, just the string that was being parsed. There's no use + // in including that in the RPC exception. See issue 17753. + var message = error.message; + if (message == string) { + message = ''; + } else { + message = '\n$message'; + } + + throw new RpcException.invalidParams('Parameter $_path for method ' + '"$method" must be a valid $description, but was ' + '${JSON.encode(string)}.$message'); + } + } + void _assertPositional() { // Throw the standard exception for a mis-typed list. asList; @@ -280,4 +327,8 @@ class _MissingParameter extends Parameter { List asListOr(List defaultValue) => defaultValue; Map asMapOr(Map defaultValue) => defaultValue; + + DateTime asDateTimeOr(DateTime defaultValue) => defaultValue; + + Uri asUriOr(Uri defaultValue) => defaultValue; } diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 0919ac128..f12be64a8 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 0.0.1 +version: 0.0.2 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://www.dartlang.org diff --git a/pkgs/json_rpc_2/test/server/parameters_test.dart b/pkgs/json_rpc_2/test/server/parameters_test.dart index 92194756c..8bd126ab1 100644 --- a/pkgs/json_rpc_2/test/server/parameters_test.dart +++ b/pkgs/json_rpc_2/test/server/parameters_test.dart @@ -20,6 +20,9 @@ void main() { "bool": true, "string": "zap", "list": [1, 2, 3], + "date-time": "1990-01-01 00:00:00.000", + "uri": "http://dartlang.org", + "invalid-uri": "http://[::1", "map": { "num": 4.2, "bool": false @@ -34,6 +37,9 @@ void main() { "bool": true, "string": "zap", "list": [1, 2, 3], + "date-time": "1990-01-01 00:00:00.000", + "uri": "http://dartlang.org", + "invalid-uri": "http://[::1", "map": { "num": 4.2, "bool": false @@ -195,6 +201,71 @@ void main() { expect(parameters['fblthp'].asMapOr({}), equals({})); }); + test("[].asDateTime returns date/time parameters", () { + expect(parameters['date-time'].asDateTime, equals(new DateTime(1990))); + }); + + test("[].asDateTimeOr returns date/time parameters", () { + expect(parameters['date-time'].asDateTimeOr(new DateTime(2014)), + equals(new DateTime(1990))); + }); + + test("[].asDateTime fails for non-date/time parameters", () { + expect(() => parameters['int'].asDateTime, + throwsInvalidParams('Parameter "int" for method "foo" must be a ' + 'string, but was 1.')); + }); + + test("[].asDateTimeOr succeeds for absent parameters", () { + expect(parameters['fblthp'].asDateTimeOr(new DateTime(2014)), + equals(new DateTime(2014))); + }); + + test("[].asDateTime fails for non-date/time parameters", () { + expect(() => parameters['int'].asDateTime, + throwsInvalidParams('Parameter "int" for method "foo" must be a ' + 'string, but was 1.')); + }); + + test("[].asDateTime fails for invalid date/times", () { + expect(() => parameters['string'].asDateTime, + throwsInvalidParams('Parameter "string" for method "foo" must be a ' + 'valid date/time, but was "zap".')); + }); + + test("[].asUri returns URI parameters", () { + expect(parameters['uri'].asUri, equals(Uri.parse('http://dartlang.org'))); + }); + + test("[].asUriOr returns URI parameters", () { + expect(parameters['uri'].asUriOr(Uri.parse('http://google.com')), + equals(Uri.parse('http://dartlang.org'))); + }); + + test("[].asUri fails for non-URI parameters", () { + expect(() => parameters['int'].asUri, + throwsInvalidParams('Parameter "int" for method "foo" must be a ' + 'string, but was 1.')); + }); + + test("[].asUriOr succeeds for absent parameters", () { + expect(parameters['fblthp'].asUriOr(Uri.parse('http://google.com')), + equals(Uri.parse('http://google.com'))); + }); + + test("[].asUri fails for non-URI parameters", () { + expect(() => parameters['int'].asUri, + throwsInvalidParams('Parameter "int" for method "foo" must be a ' + 'string, but was 1.')); + }); + + test("[].asUri fails for invalid URIs", () { + expect(() => parameters['invalid-uri'].asUri, + throwsInvalidParams('Parameter "invalid-uri" for method "foo" must ' + 'be a valid URI, but was "http://[::1".\n' + 'Bad end of IPv6 host')); + }); + group("with a nested parameter map", () { var nested; setUp(() => nested = parameters['map']); From 4a4c5adcfa0606b2cf32753b5fd300f9d2cb80df Mon Sep 17 00:00:00 2001 From: "rnystrom@google.com" Date: Thu, 3 Apr 2014 21:31:32 +0000 Subject: [PATCH 004/127] Fix typo in doc comment. R=nweiz@google.com Review URL: https://codereview.chromium.org//214663010 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/json_rpc_2@34720 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/json_rpc_2/lib/error_code.dart | 2 +- pkgs/json_rpc_2/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/lib/error_code.dart b/pkgs/json_rpc_2/lib/error_code.dart index 40d673377..96cb909a3 100644 --- a/pkgs/json_rpc_2/lib/error_code.dart +++ b/pkgs/json_rpc_2/lib/error_code.dart @@ -22,7 +22,7 @@ const INVALID_REQUEST = -32600; /// unavailable. const METHOD_NOT_FOUND = -32601; -/// An error code indicating that the request paramaters are invalid for the +/// An error code indicating that the request parameters are invalid for the /// requested method. const INVALID_PARAMS = -32602; diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index f12be64a8..6d059f874 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 0.0.2 +version: 0.0.2+1 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://www.dartlang.org From 43d4e29a945edc8bf7a27a10618b53de9e412837 Mon Sep 17 00:00:00 2001 From: "rnystrom@google.com" Date: Fri, 4 Apr 2014 22:09:27 +0000 Subject: [PATCH 005/127] Pass RpcException error data through if it's not a map. R=nweiz@google.com Review URL: https://codereview.chromium.org//224903010 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/json_rpc_2@34748 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/json_rpc_2/CHANGELOG.md | 3 +++ pkgs/json_rpc_2/lib/src/exception.dart | 2 ++ pkgs/json_rpc_2/pubspec.yaml | 2 +- pkgs/json_rpc_2/test/server/parameters_test.dart | 1 - pkgs/json_rpc_2/test/server/server_test.dart | 16 ++++++++++++++++ pkgs/json_rpc_2/test/server/utils.dart | 5 +++-- 6 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 pkgs/json_rpc_2/CHANGELOG.md diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md new file mode 100644 index 000000000..3b0694e73 --- /dev/null +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.2+2 + +* Fix error response to include data from `RpcException` when not a map. diff --git a/pkgs/json_rpc_2/lib/src/exception.dart b/pkgs/json_rpc_2/lib/src/exception.dart index fb1cd2fce..be9c2d3e2 100644 --- a/pkgs/json_rpc_2/lib/src/exception.dart +++ b/pkgs/json_rpc_2/lib/src/exception.dart @@ -52,6 +52,8 @@ class RpcException implements Exception { modifiedData['request'] = request; } else if (data == null) { modifiedData = {'request': request}; + } else { + modifiedData = data; } var id = request is Map ? request['id'] : null; diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 6d059f874..b340db3a6 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 0.0.2+1 +version: 0.0.2+2 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://www.dartlang.org diff --git a/pkgs/json_rpc_2/test/server/parameters_test.dart b/pkgs/json_rpc_2/test/server/parameters_test.dart index 8bd126ab1..49e457355 100644 --- a/pkgs/json_rpc_2/test/server/parameters_test.dart +++ b/pkgs/json_rpc_2/test/server/parameters_test.dart @@ -5,7 +5,6 @@ library json_rpc_2.test.server.parameters_test; import 'package:unittest/unittest.dart'; -import 'package:json_rpc_2/error_code.dart' as error_code; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'utils.dart'; diff --git a/pkgs/json_rpc_2/test/server/server_test.dart b/pkgs/json_rpc_2/test/server/server_test.dart index fc3adb527..c18a8caeb 100644 --- a/pkgs/json_rpc_2/test/server/server_test.dart +++ b/pkgs/json_rpc_2/test/server/server_test.dart @@ -92,6 +92,22 @@ void main() { }), completion(isNull)); }); + test("includes the error data in the response", () { + server.registerMethod('foo', (params) { + throw new json_rpc.RpcException(5, 'Error message.', data: 'data value'); + }); + + expectErrorResponse(server, { + 'jsonrpc': '2.0', + 'method': 'foo', + 'params': {}, + 'id': 1234 + }, + 5, + 'Error message.', + data: 'data value'); + }); + group("JSON", () { test("handles a request parsed from JSON", () { server.registerMethod('foo', (params) { diff --git a/pkgs/json_rpc_2/test/server/utils.dart b/pkgs/json_rpc_2/test/server/utils.dart index 07f571ce6..6f92c0a54 100644 --- a/pkgs/json_rpc_2/test/server/utils.dart +++ b/pkgs/json_rpc_2/test/server/utils.dart @@ -8,9 +8,10 @@ import 'package:unittest/unittest.dart'; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; void expectErrorResponse(json_rpc.Server server, request, int errorCode, - String message) { + String message, {data}) { var id; if (request is Map) id = request['id']; + if (data == null) data = {'request': request}; expect(server.handleRequest(request), completion(equals({ 'jsonrpc': '2.0', @@ -18,7 +19,7 @@ void expectErrorResponse(json_rpc.Server server, request, int errorCode, 'error': { 'code': errorCode, 'message': message, - 'data': {'request': request} + 'data': data } }))); } From 2eefb59260425e94d10ca19063b8e051ebd6c23d Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Mon, 2 Jun 2014 18:40:25 +0000 Subject: [PATCH 006/127] Release stack_trace 1.0.0. R=kevmoo@google.com, rnystrom@google.com Review URL: https://codereview.chromium.org//308763003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/json_rpc_2@36891 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/json_rpc_2/CHANGELOG.md | 4 ++++ pkgs/json_rpc_2/pubspec.yaml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 3b0694e73..ba15fb319 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.2+3 + +* Widen the version constraint for `stack_trace`. + ## 0.0.2+2 * Fix error response to include data from `RpcException` when not a map. diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index b340db3a6..2cf1bc83d 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,11 +1,11 @@ name: json_rpc_2 -version: 0.0.2+2 +version: 0.0.2+3 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://www.dartlang.org documentation: http://api.dartlang.org/docs/pkg/json_rpc_2 dependencies: - stack_trace: '>=0.9.1 <0.10.0' + stack_trace: '>=0.9.1 <2.0.0' dev_dependencies: unittest: ">=0.9.0 <0.10.0" environment: From 719899efe53fbe49f8fd4866507209bc9a07d034 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Wed, 4 Jun 2014 20:40:45 +0000 Subject: [PATCH 007/127] Convert json_rpc.Server to take a Stream and StreamSink. R=rnystrom@google.com Review URL: https://codereview.chromium.org//309503005 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/json_rpc_2@37012 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/json_rpc_2/CHANGELOG.md | 12 ++ pkgs/json_rpc_2/lib/src/server.dart | 140 ++++++++++++++---- pkgs/json_rpc_2/lib/src/utils.dart | 21 +++ pkgs/json_rpc_2/pubspec.yaml | 4 +- pkgs/json_rpc_2/test/server/batch_test.dart | 29 ++-- .../test/server/invalid_request_test.dart | 18 +-- pkgs/json_rpc_2/test/server/server_test.dart | 108 +++++--------- pkgs/json_rpc_2/test/server/stream_test.dart | 98 ++++++++++++ pkgs/json_rpc_2/test/server/utils.dart | 58 +++++++- 9 files changed, 361 insertions(+), 127 deletions(-) create mode 100644 pkgs/json_rpc_2/test/server/stream_test.dart diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index ba15fb319..34123f98d 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,15 @@ +## 0.1.0 + +* Remove `Server.handleRequest()` and `Server.parseRequest()`. Instead, `new + Server()` takes a `Stream` and a `StreamSink` and uses those behind-the-scenes + for its communication. + +* Add `Server.listen()`, which causes the server to begin listening to the + underlying request stream. + +* Add `Server.close()`, which closes the underlying request stream and response + sink. + ## 0.0.2+3 * Widen the version constraint for `stack_trace`. diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index d05c54f9e..c7ece5b95 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -26,6 +26,20 @@ import 'utils.dart'; /// asynchronously, it's possible for multiple methods to be invoked at the same /// time, or even for a single method to be invoked multiple times at once. class Server { + /// The stream for decoded requests. + final Stream _requests; + + /// The subscription to the decoded request stream. + StreamSubscription _requestSubscription; + + /// The sink for decoded responses. + final StreamSink _responses; + + /// The completer for [listen]. + /// + /// This is non-`null` after [listen] has been called. + Completer _listenCompleter; + /// The methods registered for this server. final _methods = new Map(); @@ -35,7 +49,104 @@ class Server { /// [RpcException.methodNotFound] exception. final _fallbacks = new Queue(); - Server(); + /// Creates a [Server] that reads requests from [requests] and writes + /// responses to [responses]. + /// + /// If [requests] is a [StreamSink] as well as a [Stream] (for example, a + /// `WebSocket`), [responses] may be omitted. + /// + /// Note that the server won't begin listening to [requests] until + /// [Server.listen] is called. + factory Server(Stream requests, [StreamSink responses]) { + if (responses == null) { + if (requests is! StreamSink) { + throw new ArgumentError("Either `requests` must be a StreamSink or " + "`responses` must be passed."); + } + responses = requests as StreamSink; + } + + var wrappedResponses = mapStreamSink(responses, JSON.encode); + return new Server.withoutJson(requests.expand((request) { + var decodedRequest; + try { + decodedRequest = JSON.decode(request); + } on FormatException catch (error) { + wrappedResponses.add(new RpcException(error_code.PARSE_ERROR, + 'Invalid JSON: ${error.message}').serialize(request)); + return []; + } + + return [decodedRequest]; + }), wrappedResponses); + } + + /// Creates a [Server] that reads decoded requests from [requests] and writes + /// decoded responses to [responses]. + /// + /// Unlike [new Server], this doesn't read or write JSON strings. Instead, it + /// reads and writes decoded maps or lists. + /// + /// If [requests] is a [StreamSink] as well as a [Stream], [responses] may be + /// omitted. + /// + /// Note that the server won't begin listening to [requests] until + /// [Server.listen] is called. + Server.withoutJson(Stream requests, [StreamSink responses]) + : _requests = requests, + _responses = responses == null && requests is StreamSink ? + requests : responses { + if (_responses == null) { + throw new ArgumentError("Either `requests` must be a StreamSink or " + "`responses` must be passed."); + } + } + + /// Starts listening to the underlying stream. + /// + /// Returns a [Future] that will complete when the stream is closed or when it + /// has an error. + /// + /// [listen] may only be called once. + Future listen() { + if (_listenCompleter != null) { + throw new StateError( + "Can only call Server.listen once on a given server."); + } + + _listenCompleter = new Completer(); + _requestSubscription = _requests.listen(_handleRequest, + onError: (error, stackTrace) { + if (_listenCompleter.isCompleted) return; + _responses.close(); + _listenCompleter.completeError(error, stackTrace); + }, onDone: () { + if (_listenCompleter.isCompleted) return; + _responses.close(); + _listenCompleter.complete(); + }, cancelOnError: true); + + return _listenCompleter.future; + } + + /// Closes the server's request subscription and response sink. + /// + /// Returns a [Future] that completes when all resources have been released. + /// + /// A server can't be closed before [listen] has been called. + Future close() { + if (_listenCompleter == null) { + throw new StateError("Can't call Server.close before Server.listen."); + } + + if (!_listenCompleter.isCompleted) _listenCompleter.complete(); + + var subscriptionFuture = _requestSubscription.cancel(); + // TODO(nweiz): include the response future in the return value when issue + // 19095 is fixed. + _responses.close(); + return subscriptionFuture == null ? new Future.value() : subscriptionFuture; + } /// Registers a method named [name] on this server. /// @@ -69,14 +180,14 @@ class Server { _fallbacks.add(callback); } - /// Handle a request that's already been parsed from JSON. + /// Handle a request. /// /// [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) { + Future _handleRequest(request) { return syncFuture(() { if (request is! List) return _handleSingleRequest(request); if (request.isEmpty) { @@ -88,28 +199,7 @@ class Server { var nonNull = results.where((result) => result != null); return nonNull.isEmpty ? null : nonNull.toList(); }); - }); - } - - /// Parses and handles a JSON serialized request. - /// - /// This calls the appropriate method or methods for handling that request and - /// returns a JSON string, or `null` if no response should be sent. - Future parseRequest(String request) { - return syncFuture(() { - var decodedRequest; - try { - decodedRequest = JSON.decode(request); - } on FormatException catch (error) { - return new RpcException(error_code.PARSE_ERROR, 'Invalid JSON: ' - '${error.message}').serialize(request); - } - - return handleRequest(decodedRequest); - }).then((response) { - if (response == null) return null; - return JSON.encode(response); - }); + }).then(_responses.add); } /// Handles an individual parsed request. diff --git a/pkgs/json_rpc_2/lib/src/utils.dart b/pkgs/json_rpc_2/lib/src/utils.dart index 1eff004f1..a212f58d8 100644 --- a/pkgs/json_rpc_2/lib/src/utils.dart +++ b/pkgs/json_rpc_2/lib/src/utils.dart @@ -43,3 +43,24 @@ final _exceptionPrefix = new RegExp(r'^([A-Z][a-zA-Z]*)?(Exception|Error): '); /// [toString], so we remove that if it exists. String getErrorMessage(error) => error.toString().replaceFirst(_exceptionPrefix, ''); + +/// Returns a [StreamSink] that wraps [sink] and maps each event added using +/// [callback]. +StreamSink mapStreamSink(StreamSink sink, callback(event)) => + new _MappedStreamSink(sink, callback); + +/// A [StreamSink] wrapper that maps each event added to the sink. +class _MappedStreamSink implements StreamSink { + final StreamSink _inner; + final Function _callback; + + Future get done => _inner.done; + + _MappedStreamSink(this._inner, this._callback); + + void add(event) => _inner.add(_callback(event)); + void addError(error, [StackTrace stackTrace]) => + _inner.addError(error, stackTrace); + Future addStream(Stream stream) => _inner.addStream(stream.map(_callback)); + Future close() => _inner.close(); +} diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 2cf1bc83d..df1a8a06a 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 0.0.2+3 +version: 0.1.0 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://www.dartlang.org @@ -7,7 +7,7 @@ documentation: http://api.dartlang.org/docs/pkg/json_rpc_2 dependencies: stack_trace: '>=0.9.1 <2.0.0' dev_dependencies: - unittest: ">=0.9.0 <0.10.0" + unittest: ">=0.9.0 <0.12.0" environment: sdk: ">=1.2.0 <2.0.0" diff --git a/pkgs/json_rpc_2/test/server/batch_test.dart b/pkgs/json_rpc_2/test/server/batch_test.dart index 441df5893..7dda84f34 100644 --- a/pkgs/json_rpc_2/test/server/batch_test.dart +++ b/pkgs/json_rpc_2/test/server/batch_test.dart @@ -13,16 +13,17 @@ import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'utils.dart'; void main() { - var server; + var controller; setUp(() { - server = new json_rpc.Server() + controller = new ServerController(); + controller.server ..registerMethod('foo', () => 'foo') ..registerMethod('id', (params) => params.value) ..registerMethod('arg', (params) => params['arg'].value); }); test('handles a batch of requests', () { - expect(server.handleRequest([ + expect(controller.handleRequest([ {'jsonrpc': '2.0', 'method': 'foo', 'id': 1}, {'jsonrpc': '2.0', 'method': 'id', 'params': ['value'], 'id': 2}, {'jsonrpc': '2.0', 'method': 'arg', 'params': {'arg': 'value'}, 'id': 3} @@ -34,7 +35,7 @@ void main() { }); test('handles errors individually', () { - expect(server.handleRequest([ + expect(controller.handleRequest([ {'jsonrpc': '2.0', 'method': 'foo', 'id': 1}, {'jsonrpc': '2.0', 'method': 'zap', 'id': 2}, {'jsonrpc': '2.0', 'method': 'arg', 'params': {'arg': 'value'}, 'id': 3} @@ -54,7 +55,7 @@ void main() { }); test('handles notifications individually', () { - expect(server.handleRequest([ + expect(controller.handleRequest([ {'jsonrpc': '2.0', 'method': 'foo', 'id': 1}, {'jsonrpc': '2.0', 'method': 'id', 'params': ['value']}, {'jsonrpc': '2.0', 'method': 'arg', 'params': {'arg': 'value'}, 'id': 3} @@ -65,7 +66,7 @@ void main() { }); test('returns nothing if every request is a notification', () { - expect(server.handleRequest([ + expect(controller.handleRequest([ {'jsonrpc': '2.0', 'method': 'foo'}, {'jsonrpc': '2.0', 'method': 'id', 'params': ['value']}, {'jsonrpc': '2.0', 'method': 'arg', 'params': {'arg': 'value'}} @@ -73,24 +74,12 @@ void main() { }); test('returns an error if the batch is empty', () { - expectErrorResponse(server, [], error_code.INVALID_REQUEST, + expectErrorResponse(controller, [], error_code.INVALID_REQUEST, 'A batch must contain at least one request.'); }); - test('handles a batch of requests parsed from JSON', () { - expect(server.parseRequest(JSON.encode([ - {'jsonrpc': '2.0', 'method': 'foo', 'id': 1}, - {'jsonrpc': '2.0', 'method': 'id', 'params': ['value'], 'id': 2}, - {'jsonrpc': '2.0', 'method': 'arg', 'params': {'arg': 'value'}, 'id': 3} - ])), completion(equals(JSON.encode([ - {'jsonrpc': '2.0', 'result': 'foo', 'id': 1}, - {'jsonrpc': '2.0', 'result': ['value'], 'id': 2}, - {'jsonrpc': '2.0', 'result': 'value', 'id': 3} - ])))); - }); - test('disallows nested batches', () { - expect(server.handleRequest([ + expect(controller.handleRequest([ [{'jsonrpc': '2.0', 'method': 'foo', 'id': 1}] ]), completion(equals([{ 'jsonrpc': '2.0', diff --git a/pkgs/json_rpc_2/test/server/invalid_request_test.dart b/pkgs/json_rpc_2/test/server/invalid_request_test.dart index feeefea9c..74cf86fec 100644 --- a/pkgs/json_rpc_2/test/server/invalid_request_test.dart +++ b/pkgs/json_rpc_2/test/server/invalid_request_test.dart @@ -13,23 +13,23 @@ import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'utils.dart'; void main() { - var server; - setUp(() => server = new json_rpc.Server()); + var controller; + setUp(() => controller = new ServerController()); test("a non-Array/Object request is invalid", () { - expectErrorResponse(server, 'foo', error_code.INVALID_REQUEST, + expectErrorResponse(controller, 'foo', error_code.INVALID_REQUEST, 'Request must be an Array or an Object.'); }); test("requests must have a jsonrpc key", () { - expectErrorResponse(server, { + expectErrorResponse(controller, { 'method': 'foo', 'id': 1234 }, error_code.INVALID_REQUEST, 'Request must contain a "jsonrpc" key.'); }); test("the jsonrpc version must be 2.0", () { - expectErrorResponse(server, { + expectErrorResponse(controller, { 'jsonrpc': '1.0', 'method': 'foo', 'id': 1234 @@ -38,14 +38,14 @@ void main() { }); test("requests must have a method key", () { - expectErrorResponse(server, { + expectErrorResponse(controller, { 'jsonrpc': '2.0', 'id': 1234 }, error_code.INVALID_REQUEST, 'Request must contain a "method" key.'); }); test("request method must be a string", () { - expectErrorResponse(server, { + expectErrorResponse(controller, { 'jsonrpc': '2.0', 'method': 1234, 'id': 1234 @@ -54,7 +54,7 @@ void main() { }); test("request params must be an Array or Object", () { - expectErrorResponse(server, { + expectErrorResponse(controller, { 'jsonrpc': '2.0', 'method': 'foo', 'params': 1234, @@ -64,7 +64,7 @@ void main() { }); test("request id may not be an Array or Object", () { - expect(server.handleRequest({ + expect(controller.handleRequest({ 'jsonrpc': '2.0', 'method': 'foo', 'id': {'bad': 'id'} diff --git a/pkgs/json_rpc_2/test/server/server_test.dart b/pkgs/json_rpc_2/test/server/server_test.dart index c18a8caeb..a89a0d993 100644 --- a/pkgs/json_rpc_2/test/server/server_test.dart +++ b/pkgs/json_rpc_2/test/server/server_test.dart @@ -13,15 +13,15 @@ import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'utils.dart'; void main() { - var server; - setUp(() => server = new json_rpc.Server()); + var controller; + setUp(() => controller = new ServerController()); test("calls a registered method with the given name", () { - server.registerMethod('foo', (params) { + controller.server.registerMethod('foo', (params) { return {'params': params.value}; }); - expect(server.handleRequest({ + expect(controller.handleRequest({ 'jsonrpc': '2.0', 'method': 'foo', 'params': {'param': 'value'}, @@ -34,9 +34,9 @@ void main() { }); test("calls a method that takes no parameters", () { - server.registerMethod('foo', () => 'foo'); + controller.server.registerMethod('foo', () => 'foo'); - expect(server.handleRequest({ + expect(controller.handleRequest({ 'jsonrpc': '2.0', 'method': 'foo', 'id': 1234 @@ -48,9 +48,9 @@ void main() { }); test("a method that takes no parameters rejects parameters", () { - server.registerMethod('foo', () => 'foo'); + controller.server.registerMethod('foo', () => 'foo'); - expectErrorResponse(server, { + expectErrorResponse(controller, { 'jsonrpc': '2.0', 'method': 'foo', 'params': {}, @@ -61,9 +61,9 @@ void main() { }); test("an unexpected error in a method is captured", () { - server.registerMethod('foo', () => throw new FormatException('bad format')); + controller.server.registerMethod('foo', () => throw new FormatException('bad format')); - expect(server.handleRequest({ + expect(controller.handleRequest({ 'jsonrpc': '2.0', 'method': 'foo', 'id': 1234 @@ -83,9 +83,9 @@ void main() { }); test("doesn't return a result for a notification", () { - server.registerMethod('foo', (args) => 'result'); + controller.server.registerMethod('foo', (args) => 'result'); - expect(server.handleRequest({ + expect(controller.handleRequest({ 'jsonrpc': '2.0', 'method': 'foo', 'params': {} @@ -93,11 +93,11 @@ void main() { }); test("includes the error data in the response", () { - server.registerMethod('foo', (params) { + controller.server.registerMethod('foo', (params) { throw new json_rpc.RpcException(5, 'Error message.', data: 'data value'); }); - expectErrorResponse(server, { + expectErrorResponse(controller, { 'jsonrpc': '2.0', 'method': 'foo', 'params': {}, @@ -108,58 +108,28 @@ void main() { data: 'data value'); }); - group("JSON", () { - test("handles a request parsed from JSON", () { - server.registerMethod('foo', (params) { - return {'params': params.value}; - }); - - expect(server.parseRequest(JSON.encode({ + test("a JSON parse error is rejected", () { + return controller.handleJsonRequest('invalid json {').then((result) { + expect(JSON.decode(result), { 'jsonrpc': '2.0', - 'method': 'foo', - 'params': {'param': 'value'}, - 'id': 1234 - })), completion(equals(JSON.encode({ - 'jsonrpc': '2.0', - 'result': {'params': {'param': 'value'}}, - 'id': 1234 - })))); - }); - - test("handles a notification parsed from JSON", () { - server.registerMethod('foo', (params) { - return {'params': params}; - }); - - expect(server.parseRequest(JSON.encode({ - 'jsonrpc': '2.0', - 'method': 'foo', - 'params': {'param': 'value'} - })), completion(isNull)); - }); - - test("a JSON parse error is rejected", () { - return server.parseRequest('invalid json {').then((result) { - expect(JSON.decode(result), { - 'jsonrpc': '2.0', - 'error': { - 'code': error_code.PARSE_ERROR, - 'message': startsWith("Invalid JSON: "), - 'data': {'request': 'invalid json {'} - }, - 'id': null - }); + 'error': { + 'code': error_code.PARSE_ERROR, + 'message': startsWith("Invalid JSON: "), + 'data': {'request': 'invalid json {'} + }, + 'id': null }); }); }); group("fallbacks", () { test("calls a fallback if no method matches", () { - server.registerMethod('foo', () => 'foo'); - server.registerMethod('bar', () => 'foo'); - server.registerFallback((params) => {'fallback': params.value}); + controller.server + ..registerMethod('foo', () => 'foo') + ..registerMethod('bar', () => 'foo') + ..registerFallback((params) => {'fallback': params.value}); - expect(server.handleRequest({ + expect(controller.handleRequest({ 'jsonrpc': '2.0', 'method': 'baz', 'params': {'param': 'value'}, @@ -172,13 +142,13 @@ void main() { }); test("calls the first matching fallback", () { - server.registerFallback((params) => - throw new json_rpc.RpcException.methodNotFound(params.method)); - - server.registerFallback((params) => 'fallback 2'); - server.registerFallback((params) => 'fallback 3'); + controller.server + ..registerFallback((params) => + throw new json_rpc.RpcException.methodNotFound(params.method)) + ..registerFallback((params) => 'fallback 2') + ..registerFallback((params) => 'fallback 3'); - expect(server.handleRequest({ + expect(controller.handleRequest({ 'jsonrpc': '2.0', 'method': 'fallback 2', 'id': 1234 @@ -190,9 +160,10 @@ void main() { }); test("an unexpected error in a fallback is captured", () { - server.registerFallback((_) => throw new FormatException('bad format')); + controller.server.registerFallback((_) => + throw new FormatException('bad format')); - expect(server.handleRequest({ + expect(controller.handleRequest({ 'jsonrpc': '2.0', 'method': 'foo', 'id': 1234 @@ -213,7 +184,8 @@ void main() { }); test("disallows multiple methods with the same name", () { - server.registerMethod('foo', () => null); - expect(() => server.registerMethod('foo', () => null), throwsArgumentError); + controller.server.registerMethod('foo', () => null); + expect(() => controller.server.registerMethod('foo', () => null), + throwsArgumentError); }); } diff --git a/pkgs/json_rpc_2/test/server/stream_test.dart b/pkgs/json_rpc_2/test/server/stream_test.dart new file mode 100644 index 000000000..5459e3e6a --- /dev/null +++ b/pkgs/json_rpc_2/test/server/stream_test.dart @@ -0,0 +1,98 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// 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. + +library json_rpc_2.test.server.stream_test; + +import 'dart:async'; + +import 'package:unittest/unittest.dart'; +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; + +import 'utils.dart'; + +void main() { + test(".withoutJson supports decoded stream and sink", () { + var requestController = new StreamController(); + var responseController = new StreamController(); + var server = new json_rpc.Server.withoutJson( + requestController.stream, responseController.sink); + server.listen(); + + server.registerMethod('foo', (params) { + return {'params': params.value}; + }); + + requestController.add({ + 'jsonrpc': '2.0', + 'method': 'foo', + 'params': {'param': 'value'}, + 'id': 1234 + }); + + expect(responseController.stream.first, completion(equals({ + 'jsonrpc': '2.0', + 'result': {'params': {'param': 'value'}}, + 'id': 1234 + }))); + }); + + test(".listen returns when the controller is closed", () { + var requestController = new StreamController(); + var responseController = new StreamController(); + var server = new json_rpc.Server( + requestController.stream, responseController.sink); + + var hasListenCompeted = false; + expect(server.listen().then((_) => hasListenCompeted = true), completes); + + return pumpEventQueue().then((_) { + expect(hasListenCompeted, isFalse); + + // This should cause listen to complete. + return requestController.close(); + }); + }); + + test(".listen returns a stream error", () { + var requestController = new StreamController(); + var responseController = new StreamController(); + var server = new json_rpc.Server( + requestController.stream, responseController.sink); + + expect(server.listen(), throwsA('oh no')); + requestController.addError('oh no'); + }); + + test(".listen can't be called twice", () { + var requestController = new StreamController(); + var responseController = new StreamController(); + var server = new json_rpc.Server( + requestController.stream, responseController.sink); + server.listen(); + + expect(() => server.listen(), throwsStateError); + }); + + test(".close cancels the stream subscription and closes the sink", () { + var requestController = new StreamController(); + var responseController = new StreamController(); + var server = new json_rpc.Server( + requestController.stream, responseController.sink); + + expect(server.listen(), completes); + expect(server.close(), completes); + + expect(() => requestController.stream.listen((_) {}), throwsStateError); + expect(responseController.isClosed, isTrue); + }); + + test(".close can't be called before .listen", () { + var requestController = new StreamController(); + var responseController = new StreamController(); + var server = new json_rpc.Server( + requestController.stream, responseController.sink); + + expect(() => server.close(), throwsStateError); + }); +} diff --git a/pkgs/json_rpc_2/test/server/utils.dart b/pkgs/json_rpc_2/test/server/utils.dart index 6f92c0a54..a4fd36af6 100644 --- a/pkgs/json_rpc_2/test/server/utils.dart +++ b/pkgs/json_rpc_2/test/server/utils.dart @@ -4,16 +4,53 @@ library json_rpc_2.test.server.util; -import 'package:unittest/unittest.dart'; +import 'dart:async'; +import 'dart:convert'; + import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; +import 'package:json_rpc_2/error_code.dart' as error_code; +import 'package:unittest/unittest.dart'; + +/// A controller used to test a [json_rpc.Server]. +class ServerController { + /// The controller for the server's request stream. + final _requestController = new StreamController(); + + /// The controller for the server's response sink. + final _responseController = new StreamController(); + + /// The server. + json_rpc.Server get server => _server; + json_rpc.Server _server; -void expectErrorResponse(json_rpc.Server server, request, int errorCode, + ServerController() { + _server = new json_rpc.Server( + _requestController.stream, _responseController.sink); + _server.listen(); + } + + /// Passes [request], a decoded request, to [server] and returns its decoded + /// response. + Future handleRequest(request) => + handleJsonRequest(JSON.encode(request)).then(JSON.decode); + + /// Passes [request], a JSON-encoded request, to [server] and returns its + /// encoded response. + Future handleJsonRequest(String request) { + _requestController.add(request); + return _responseController.stream.first; + } +} + +/// Expects that [controller]'s server will return an error response to +/// [request] with the given [errorCode], [message], and [data]. +void expectErrorResponse(ServerController controller, request, int errorCode, String message, {data}) { var id; if (request is Map) id = request['id']; if (data == null) data = {'request': request}; - expect(server.handleRequest(request), completion(equals({ + expect(controller.handleRequest(request), completion(equals({ 'jsonrpc': '2.0', 'id': id, 'error': { @@ -24,10 +61,25 @@ void expectErrorResponse(json_rpc.Server server, request, int errorCode, }))); } +/// Returns a matcher that matches a [json_rpc.RpcException] with an +/// `invalid_params` error code. Matcher throwsInvalidParams(String message) { return throwsA(predicate((error) { expect(error, new isInstanceOf()); + expect(error.code, equals(error_code.INVALID_PARAMS)); expect(error.message, equals(message)); return true; })); } + +/// Returns a [Future] that completes after pumping the event queue [times] +/// times. By default, this should pump the event queue enough times to allow +/// any code to run, as long as it's not waiting on some external event. +Future pumpEventQueue([int times = 20]) { + if (times == 0) return new Future.value(); + // We use a delayed future to allow microtask events to finish. The + // Future.value or Future() constructors use scheduleMicrotask themselves and + // would therefore not wait for microtask callbacks that are scheduled after + // invoking this method. + return new Future.delayed(Duration.ZERO, () => pumpEventQueue(times - 1)); +} From 0dfe7ba058d423a42a16709cb28446fc562123af Mon Sep 17 00:00:00 2001 From: "lrn@google.com" Date: Mon, 16 Jun 2014 07:00:22 +0000 Subject: [PATCH 008/127] New, more validating, parser for URI. BUG= https://code.google.com/p/dart/issues/detail?id=16081 R=ajohnsen@google.com, fschneider@google.com, sgjesse@google.com Review URL: https://codereview.chromium.org//321543003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/json_rpc_2@37346 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/json_rpc_2/test/server/parameters_test.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkgs/json_rpc_2/test/server/parameters_test.dart b/pkgs/json_rpc_2/test/server/parameters_test.dart index 49e457355..4afe0724c 100644 --- a/pkgs/json_rpc_2/test/server/parameters_test.dart +++ b/pkgs/json_rpc_2/test/server/parameters_test.dart @@ -262,7 +262,9 @@ void main() { expect(() => parameters['invalid-uri'].asUri, throwsInvalidParams('Parameter "invalid-uri" for method "foo" must ' 'be a valid URI, but was "http://[::1".\n' - 'Bad end of IPv6 host')); + 'Unmatched [ in host name at position 7.\n' + 'http://[::1\n' + ' ^')); }); group("with a nested parameter map", () { From 5ac0ce86f69ddc8389cc368d420c659e8ddcde38 Mon Sep 17 00:00:00 2001 From: "lrn@google.com" Date: Mon, 16 Jun 2014 12:32:35 +0000 Subject: [PATCH 009/127] Revert "New, more validating, parser for URI." and follow-up patches. R=sgjesse@google.com Review URL: https://codereview.chromium.org//337033003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/json_rpc_2@37359 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/json_rpc_2/test/server/parameters_test.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkgs/json_rpc_2/test/server/parameters_test.dart b/pkgs/json_rpc_2/test/server/parameters_test.dart index 4afe0724c..49e457355 100644 --- a/pkgs/json_rpc_2/test/server/parameters_test.dart +++ b/pkgs/json_rpc_2/test/server/parameters_test.dart @@ -262,9 +262,7 @@ void main() { expect(() => parameters['invalid-uri'].asUri, throwsInvalidParams('Parameter "invalid-uri" for method "foo" must ' 'be a valid URI, but was "http://[::1".\n' - 'Unmatched [ in host name at position 7.\n' - 'http://[::1\n' - ' ^')); + 'Bad end of IPv6 host')); }); group("with a nested parameter map", () { From a2d024c886793ceefe878b004b7e32027b3c1ddf Mon Sep 17 00:00:00 2001 From: "lrn@google.com" Date: Thu, 19 Jun 2014 09:05:07 +0000 Subject: [PATCH 010/127] New Uri.parse and validation. Ensures that the resulting URI is valid. Escapes invalid characters except the gen-delim group of delimites, where escapes are allowed. R=ajohnsen@google.com, sgjesse@google.com Review URL: https://codereview.chromium.org//335373003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/json_rpc_2@37479 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/json_rpc_2/test/server/parameters_test.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkgs/json_rpc_2/test/server/parameters_test.dart b/pkgs/json_rpc_2/test/server/parameters_test.dart index 49e457355..64fe23260 100644 --- a/pkgs/json_rpc_2/test/server/parameters_test.dart +++ b/pkgs/json_rpc_2/test/server/parameters_test.dart @@ -262,7 +262,9 @@ void main() { expect(() => parameters['invalid-uri'].asUri, throwsInvalidParams('Parameter "invalid-uri" for method "foo" must ' 'be a valid URI, but was "http://[::1".\n' - 'Bad end of IPv6 host')); + 'Missing end `]` to match `[` in host at position 7.\n' + 'http://[::1\n' + ' ^')); }); group("with a nested parameter map", () { From ec9f3f732991851c00f3c4a9fc5ca72b6557b5b2 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Fri, 27 Jun 2014 00:18:53 +0000 Subject: [PATCH 011/127] Extract out a StreamManager class from json_rpc.Server. This can then be used to more easily implement Client. R=rnystrom@google.com Review URL: https://codereview.chromium.org//333683003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/json_rpc_2@37773 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/json_rpc_2/lib/src/server.dart | 90 ++----------- pkgs/json_rpc_2/lib/src/two_way_stream.dart | 134 ++++++++++++++++++++ pkgs/json_rpc_2/pubspec.yaml | 2 +- 3 files changed, 148 insertions(+), 78 deletions(-) create mode 100644 pkgs/json_rpc_2/lib/src/two_way_stream.dart diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index c7ece5b95..bd243de73 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -13,6 +13,7 @@ import 'package:stack_trace/stack_trace.dart'; import '../error_code.dart' as error_code; import 'exception.dart'; import 'parameters.dart'; +import 'two_way_stream.dart'; import 'utils.dart'; /// A JSON-RPC 2.0 server. @@ -26,19 +27,7 @@ import 'utils.dart'; /// asynchronously, it's possible for multiple methods to be invoked at the same /// time, or even for a single method to be invoked multiple times at once. class Server { - /// The stream for decoded requests. - final Stream _requests; - - /// The subscription to the decoded request stream. - StreamSubscription _requestSubscription; - - /// The sink for decoded responses. - final StreamSink _responses; - - /// The completer for [listen]. - /// - /// This is non-`null` after [listen] has been called. - Completer _listenCompleter; + TwoWayStream _streams; /// The methods registered for this server. final _methods = new Map(); @@ -57,28 +46,12 @@ class Server { /// /// Note that the server won't begin listening to [requests] until /// [Server.listen] is called. - factory Server(Stream requests, [StreamSink responses]) { - if (responses == null) { - if (requests is! StreamSink) { - throw new ArgumentError("Either `requests` must be a StreamSink or " - "`responses` must be passed."); - } - responses = requests as StreamSink; - } - - var wrappedResponses = mapStreamSink(responses, JSON.encode); - return new Server.withoutJson(requests.expand((request) { - var decodedRequest; - try { - decodedRequest = JSON.decode(request); - } on FormatException catch (error) { - wrappedResponses.add(new RpcException(error_code.PARSE_ERROR, - 'Invalid JSON: ${error.message}').serialize(request)); - return []; - } - - return [decodedRequest]; - }), wrappedResponses); + Server(Stream requests, [StreamSink responses]) { + _streams = new TwoWayStream("Server", requests, "requests", + responses, "responses", onInvalidInput: (message, error) { + _streams.add(new RpcException(error_code.PARSE_ERROR, + 'Invalid JSON: ${error.message}').serialize(message)); + }); } /// Creates a [Server] that reads decoded requests from [requests] and writes @@ -93,14 +66,8 @@ class Server { /// Note that the server won't begin listening to [requests] until /// [Server.listen] is called. Server.withoutJson(Stream requests, [StreamSink responses]) - : _requests = requests, - _responses = responses == null && requests is StreamSink ? - requests : responses { - if (_responses == null) { - throw new ArgumentError("Either `requests` must be a StreamSink or " - "`responses` must be passed."); - } - } + : _streams = new TwoWayStream.withoutJson( + "Server", requests, "requests", responses, "responses"); /// Starts listening to the underlying stream. /// @@ -108,45 +75,14 @@ class Server { /// has an error. /// /// [listen] may only be called once. - Future listen() { - if (_listenCompleter != null) { - throw new StateError( - "Can only call Server.listen once on a given server."); - } - - _listenCompleter = new Completer(); - _requestSubscription = _requests.listen(_handleRequest, - onError: (error, stackTrace) { - if (_listenCompleter.isCompleted) return; - _responses.close(); - _listenCompleter.completeError(error, stackTrace); - }, onDone: () { - if (_listenCompleter.isCompleted) return; - _responses.close(); - _listenCompleter.complete(); - }, cancelOnError: true); - - return _listenCompleter.future; - } + Future listen() => _streams.listen(_handleRequest); /// Closes the server's request subscription and response sink. /// /// Returns a [Future] that completes when all resources have been released. /// /// A server can't be closed before [listen] has been called. - Future close() { - if (_listenCompleter == null) { - throw new StateError("Can't call Server.close before Server.listen."); - } - - if (!_listenCompleter.isCompleted) _listenCompleter.complete(); - - var subscriptionFuture = _requestSubscription.cancel(); - // TODO(nweiz): include the response future in the return value when issue - // 19095 is fixed. - _responses.close(); - return subscriptionFuture == null ? new Future.value() : subscriptionFuture; - } + Future close() => _streams.close(); /// Registers a method named [name] on this server. /// @@ -199,7 +135,7 @@ class Server { var nonNull = results.where((result) => result != null); return nonNull.isEmpty ? null : nonNull.toList(); }); - }).then(_responses.add); + }).then(_streams.add); } /// Handles an individual parsed request. diff --git a/pkgs/json_rpc_2/lib/src/two_way_stream.dart b/pkgs/json_rpc_2/lib/src/two_way_stream.dart new file mode 100644 index 000000000..f470c2fce --- /dev/null +++ b/pkgs/json_rpc_2/lib/src/two_way_stream.dart @@ -0,0 +1,134 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// 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. + +library json_rpc_2.two_way_stream; + +import 'dart:async'; +import 'dart:convert'; + +import 'utils.dart'; + +/// A class for managing a stream of input messages and a sink for output +/// messages. +/// +/// This contains stream logic that's shared between [Server] and [Client]. +class TwoWayStream { + /// The name of the component whose streams are being managed (e.g. "Server"). + /// + /// Used for error reporting. + final String _name; + + /// The input stream. + /// + /// This is a stream of decoded JSON objects. + final Stream _input; + + /// The subscription to [_input]. + StreamSubscription _inputSubscription; + + /// The output sink. + /// + /// This takes decoded JSON objects. + final StreamSink _output; + + /// The completer for [listen]. + /// + /// This is non-`null` after [listen] has been called. + Completer _listenCompleter; + + /// Creates a two-way stream. + /// + /// [input] and [output] should emit and take (respectively) JSON-encoded + /// strings. + /// + /// [inputName] is used in error messages as the name of the input parameter. + /// [outputName] is likewise used as the name of the output parameter. + /// + /// If [onInvalidInput] is passed, any errors parsing messages from [input] + /// are passed to it. Otherwise, they're ignored and the input is discarded. + factory TwoWayStream(String name, Stream input, String inputName, + StreamSink output, String outputName, + {void onInvalidInput(String message, FormatException error)}) { + if (output == null) { + if (input is! StreamSink) { + throw new ArgumentError("Either `$inputName` must be a StreamSink or " + "`$outputName` must be passed."); + } + output = input as StreamSink; + } + + var wrappedOutput = mapStreamSink(output, JSON.encode); + return new TwoWayStream.withoutJson(name, input.expand((message) { + var decodedMessage; + try { + decodedMessage = JSON.decode(message); + } on FormatException catch (error) { + if (onInvalidInput != null) onInvalidInput(message, error); + return []; + } + + return [decodedMessage]; + }), inputName, wrappedOutput, outputName); + } + + /// Creates a two-way stream that reads decoded input and writes decoded + /// responses. + /// + /// [input] and [output] should emit and take (respectively) decoded JSON + /// objects. + /// + /// [inputName] is used in error messages as the name of the input parameter. + /// [outputName] is likewise used as the name of the output parameter. + TwoWayStream.withoutJson(this._name, Stream input, String inputName, + StreamSink output, String outputName) + : _input = input, + _output = output == null && input is StreamSink ? input : output { + if (_output == null) { + throw new ArgumentError("Either `$inputName` must be a StreamSink or " + "`$outputName` must be passed."); + } + } + + /// Starts listening to the input stream. + /// + /// The returned Future will complete when the input stream is closed. If the + /// input stream emits an error, that will be piped to the returned Future. + Future listen(void handleInput(input)) { + if (_listenCompleter != null) { + throw new StateError("Can only call $_name.listen once."); + } + + _listenCompleter = new Completer(); + _inputSubscription = _input.listen(handleInput, + onError: (error, stackTrace) { + if (_listenCompleter.isCompleted) return; + _output.close(); + _listenCompleter.completeError(error, stackTrace); + }, onDone: () { + if (_listenCompleter.isCompleted) return; + _output.close(); + _listenCompleter.complete(); + }, cancelOnError: true); + + return _listenCompleter.future; + } + + /// Emit [event] on the output stream. + void add(event) => _output.add(event); + + /// Stops listening to the input stream and closes the output stream. + Future close() { + if (_listenCompleter == null) { + throw new StateError("Can't call $_name.close before $_name.listen."); + } + + if (!_listenCompleter.isCompleted) _listenCompleter.complete(); + + var inputFuture = _inputSubscription.cancel(); + // TODO(nweiz): include the output future in the return value when issue + // 19095 is fixed. + _output.close(); + return inputFuture == null ? new Future.value() : inputFuture; + } +} diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index df1a8a06a..e0fca72a7 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 0.1.0 +version: 0.1.1-dev author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://www.dartlang.org From b5b3db1510344089009824928a7e4c821d5a0a58 Mon Sep 17 00:00:00 2001 From: "lrn@google.com" Date: Mon, 14 Jul 2014 07:47:45 +0000 Subject: [PATCH 012/127] Add extra information to FormatException. R=floitsch@google.com Review URL: https://codereview.chromium.org//389603002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/json_rpc_2@38181 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/json_rpc_2/test/server/parameters_test.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pkgs/json_rpc_2/test/server/parameters_test.dart b/pkgs/json_rpc_2/test/server/parameters_test.dart index 64fe23260..3de03598c 100644 --- a/pkgs/json_rpc_2/test/server/parameters_test.dart +++ b/pkgs/json_rpc_2/test/server/parameters_test.dart @@ -229,7 +229,8 @@ void main() { test("[].asDateTime fails for invalid date/times", () { expect(() => parameters['string'].asDateTime, throwsInvalidParams('Parameter "string" for method "foo" must be a ' - 'valid date/time, but was "zap".')); + 'valid date/time, but was "zap".\n' + 'Invalid date format')); }); test("[].asUri returns URI parameters", () { @@ -262,9 +263,7 @@ void main() { expect(() => parameters['invalid-uri'].asUri, throwsInvalidParams('Parameter "invalid-uri" for method "foo" must ' 'be a valid URI, but was "http://[::1".\n' - 'Missing end `]` to match `[` in host at position 7.\n' - 'http://[::1\n' - ' ^')); + 'Missing end `]` to match `[` in host')); }); group("with a nested parameter map", () { From 5561e04406cf36ec5522fca8deb58cc769b1289a Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Thu, 24 Jul 2014 20:23:52 +0000 Subject: [PATCH 013/127] Remove documentation links for repo packages. pub.dartlang.org will now automatically link to dartdocs.org for packages with no documentation link. BUG= http://dartbug.com/20177, http://dartbug.com/20178, http://dartbug.com/20186 R=sigmund@google.com Review URL: https://codereview.chromium.org//416973003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/json_rpc_2@38551 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/json_rpc_2/pubspec.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index e0fca72a7..287bba786 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -3,7 +3,6 @@ version: 0.1.1-dev author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://www.dartlang.org -documentation: http://api.dartlang.org/docs/pkg/json_rpc_2 dependencies: stack_trace: '>=0.9.1 <2.0.0' dev_dependencies: From 5bfab2f05150bbc42067dd6bef6ad43a41cbe739 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Wed, 5 Nov 2014 23:37:42 +0000 Subject: [PATCH 014/127] Add a Client class to json_rpc_2. R=rnystrom@google.com Review URL: https://codereview.chromium.org//691053006 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/json_rpc_2@41535 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/json_rpc_2/CHANGELOG.md | 4 + pkgs/json_rpc_2/lib/error_code.dart | 15 ++ pkgs/json_rpc_2/lib/json_rpc_2.dart | 1 + pkgs/json_rpc_2/lib/src/client.dart | 198 +++++++++++++++ pkgs/json_rpc_2/lib/src/exception.dart | 7 + pkgs/json_rpc_2/lib/src/server.dart | 3 + pkgs/json_rpc_2/lib/src/utils.dart | 22 ++ pkgs/json_rpc_2/pubspec.yaml | 2 +- pkgs/json_rpc_2/test/client/client_test.dart | 239 +++++++++++++++++++ pkgs/json_rpc_2/test/client/stream_test.dart | 96 ++++++++ pkgs/json_rpc_2/test/client/utils.dart | 65 +++++ 11 files changed, 651 insertions(+), 1 deletion(-) create mode 100644 pkgs/json_rpc_2/lib/src/client.dart create mode 100644 pkgs/json_rpc_2/test/client/client_test.dart create mode 100644 pkgs/json_rpc_2/test/client/stream_test.dart create mode 100644 pkgs/json_rpc_2/test/client/utils.dart diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 34123f98d..15b5a3009 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.0 + +* Add a `Client` class for communicating with external JSON-RPC 2.0 servers. + ## 0.1.0 * Remove `Server.handleRequest()` and `Server.parseRequest()`. Instead, `new diff --git a/pkgs/json_rpc_2/lib/error_code.dart b/pkgs/json_rpc_2/lib/error_code.dart index 96cb909a3..14b77f2e0 100644 --- a/pkgs/json_rpc_2/lib/error_code.dart +++ b/pkgs/json_rpc_2/lib/error_code.dart @@ -34,3 +34,18 @@ const INTERNAL_ERROR = -32603; /// The spec reserves the range from -32000 to -32099 for implementation-defined /// server exceptions, but for now we only use one of those values. const SERVER_ERROR = -32000; + +/// 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; + } +} diff --git a/pkgs/json_rpc_2/lib/json_rpc_2.dart b/pkgs/json_rpc_2/lib/json_rpc_2.dart index 04e4a523e..3305eaee2 100644 --- a/pkgs/json_rpc_2/lib/json_rpc_2.dart +++ b/pkgs/json_rpc_2/lib/json_rpc_2.dart @@ -4,6 +4,7 @@ library json_rpc_2; +export 'src/client.dart'; export 'src/exception.dart'; export 'src/parameters.dart'; export 'src/server.dart'; diff --git a/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart new file mode 100644 index 000000000..2e1d2739b --- /dev/null +++ b/pkgs/json_rpc_2/lib/src/client.dart @@ -0,0 +1,198 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// 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. + +library json_rpc_2.client; + +import 'dart:async'; + +import 'package:stack_trace/stack_trace.dart'; + +import 'exception.dart'; +import 'two_way_stream.dart'; +import 'utils.dart'; + +/// A JSON-RPC 2.0 client. +/// +/// A client calls methods on a server and handles the server's responses to +/// those method calls. Methods can be called with [sendRequest], or with +/// [sendNotification] if no response is expected. +class Client { + final TwoWayStream _streams; + + /// The next request id. + var _id = 0; + + /// The current batch of requests to be sent together. + /// + /// Each element is a JSON-serializable object. + List _batch; + + /// The map of request ids for pending requests to [Completer]s that will be + /// completed with those requests' responses. + final _pendingRequests = new Map(); + + /// Creates a [Client] that writes requests to [requests] and reads responses + /// from [responses]. + /// + /// If [responses] is a [StreamSink] as well as a [Stream] (for example, a + /// `WebSocket`), [requests] may be omitted. + /// + /// Note that the client won't begin listening to [responses] until + /// [Client.listen] is called. + Client(Stream responses, [StreamSink requests]) + : _streams = new TwoWayStream( + "Client", responses, "responses", requests, "requests"); + + /// Creates a [Client] that writes decoded responses to [responses] and reads + /// decoded requests from [requests]. + /// + /// Unlike [new Client], this doesn't read or write JSON strings. Instead, it + /// reads and writes decoded maps or lists. + /// + /// If [responses] is a [StreamSink] as well as a [Stream], [requests] may be + /// omitted. + /// + /// Note that the client won't begin listening to [responses] until + /// [Client.listen] is called. + Client.withoutJson(Stream responses, [StreamSink requests]) + : _streams = new TwoWayStream.withoutJson( + "Client", responses, "responses", requests, "requests"); + + /// Users of the library should not use this constructor. + Client.internal(this._streams); + + /// Starts listening to the underlying stream. + /// + /// Returns a [Future] that will complete when the stream is closed or when it + /// has an error. + /// + /// [listen] may only be called once. + Future listen() => _streams.listen(_handleResponse); + + /// Closes the server's request sink and response subscription. + /// + /// Returns a [Future] that completes when all resources have been released. + /// + /// A client can't be closed before [listen] has been called. + Future close() => _streams.close(); + + /// Sends a JSON-RPC 2 request to invoke the given [method]. + /// + /// If passed, [parameters] is the parameters for the method. This must be + /// either an [Iterable] (to pass parameters by position) or a [Map] with + /// [String] keys (to pass parameters by name). Either way, it must be + /// JSON-serializable. + /// + /// If the request succeeds, this returns the response result as a decoded + /// JSON-serializable object. If it fails, it throws an [RpcException] + /// describing the failure. + Future sendRequest(String method, [parameters]) { + var id = _id++; + _send(method, parameters, id); + + var completer = new Completer.sync(); + _pendingRequests[id] = completer; + return completer.future; + } + + /// Sends a JSON-RPC 2 request to invoke the given [method] without expecting + /// a response. + /// + /// If passed, [parameters] is the parameters for the method. This must be + /// either an [Iterable] (to pass parameters by position) or a [Map] with + /// [String] keys (to pass parameters by name). Either way, it must be + /// JSON-serializable. + /// + /// Since this is just a notification to which the server isn't expected to + /// send a response, it has no return value. + void sendNotification(String method, [parameters]) => + _send(method, parameters); + + /// A helper method for [sendRequest] and [sendNotification]. + /// + /// Sends a request to invoke [method] with [parameters]. If [id] is given, + /// the request uses that id. + void _send(String method, parameters, [int id]) { + if (parameters is Iterable) parameters = parameters.toList(); + if (parameters is! Map && parameters is! List && parameters != null) { + throw new ArgumentError('Only maps and lists may be used as JSON-RPC ' + 'parameters, was "$parameters".'); + } + + var message = { + "jsonrpc": "2.0", + "method": method + }; + if (id != null) message["id"] = id; + if (parameters != null) message["params"] = parameters; + + if (_batch != null) { + _batch.add(message); + } else { + _streams.add(message); + } + } + + /// Runs [callback] and batches any requests sent until it returns. + /// + /// A batch of requests is sent in a single message on the underlying stream, + /// and the responses are likewise sent back in a single message. + /// + /// [callback] may be synchronous or asynchronous. If it returns a [Future], + /// requests will be batched until that Future returns; otherwise, requests + /// will only be batched while synchronously executing [callback]. + /// + /// 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. + withBatch(callback()) { + if (_batch != null) return callback(); + + _batch = []; + return tryFinally(callback, () { + _streams.add(_batch); + _batch = null; + }); + } + + /// Handles a decoded response from the server. + void _handleResponse(response) { + if (response is List) { + response.forEach(_handleSingleResponse); + } else { + _handleSingleResponse(response); + } + } + + /// Handles a decoded response from the server after batches have been + /// resolved. + void _handleSingleResponse(response) { + if (!_isResponseValid(response)) return; + var completer = _pendingRequests.remove(response["id"]); + if (response.containsKey("result")) { + completer.complete(response["result"]); + } else { + completer.completeError(new RpcException( + response["error"]["code"], + response["error"]["message"], + data: response["error"]["data"]), + new Chain.current()); + } + } + + /// Determines whether the server's response is valid per the spec. + bool _isResponseValid(response) { + if (response is! Map) return false; + if (response["jsonrpc"] != "2.0") return false; + if (!_pendingRequests.containsKey(response["id"])) return false; + if (response.containsKey("result")) return true; + + if (!response.containsKey("error")) return false; + var error = response["error"]; + if (error is! Map) return false; + if (error["code"] is! int) return false; + if (error["message"] is! String) return false; + return true; + } +} diff --git a/pkgs/json_rpc_2/lib/src/exception.dart b/pkgs/json_rpc_2/lib/src/exception.dart index be9c2d3e2..2fb071328 100644 --- a/pkgs/json_rpc_2/lib/src/exception.dart +++ b/pkgs/json_rpc_2/lib/src/exception.dart @@ -64,4 +64,11 @@ class RpcException implements Exception { 'id': id }; } + + String toString() { + var prefix = "JSON-RPC error $code"; + var errorName = error_code.name(code); + if (errorName != null) prefix += " ($errorName)"; + return "$prefix: $message"; + } } diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index bd243de73..1ba8fccc2 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -69,6 +69,9 @@ class Server { : _streams = new TwoWayStream.withoutJson( "Server", requests, "requests", responses, "responses"); + /// Users of the library should not use this constructor. + Server.internal(this._streams); + /// Starts listening to the underlying stream. /// /// Returns a [Future] that will complete when the stream is closed or when it diff --git a/pkgs/json_rpc_2/lib/src/utils.dart b/pkgs/json_rpc_2/lib/src/utils.dart index a212f58d8..f862cb784 100644 --- a/pkgs/json_rpc_2/lib/src/utils.dart +++ b/pkgs/json_rpc_2/lib/src/utils.dart @@ -44,6 +44,28 @@ final _exceptionPrefix = new RegExp(r'^([A-Z][a-zA-Z]*)?(Exception|Error): '); String getErrorMessage(error) => error.toString().replaceFirst(_exceptionPrefix, ''); +/// Like `try`/`finally`, run [body] and ensure that [whenComplete] runs +/// afterwards, regardless of whether [body] succeeded. +/// +/// This is synchronicity-agnostic relative to [body]. If [body] returns a +/// [Future], this wil run asynchronously; otherwise it will run synchronously. +tryFinally(body(), whenComplete()) { + var result; + try { + result = body(); + } catch (_) { + whenComplete(); + rethrow; + } + + if (result is! Future) { + whenComplete(); + return result; + } else { + return result.whenComplete(whenComplete); + } +} + /// Returns a [StreamSink] that wraps [sink] and maps each event added using /// [callback]. StreamSink mapStreamSink(StreamSink sink, callback(event)) => diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 287bba786..fed14a00a 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 0.1.1-dev +version: 1.0.0-dev author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://www.dartlang.org diff --git a/pkgs/json_rpc_2/test/client/client_test.dart b/pkgs/json_rpc_2/test/client/client_test.dart new file mode 100644 index 000000000..9fcf9e330 --- /dev/null +++ b/pkgs/json_rpc_2/test/client/client_test.dart @@ -0,0 +1,239 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// 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. + +library json_rpc_2.test.client.client_test; + +import 'dart:async'; +import 'dart:convert'; + +import 'package:unittest/unittest.dart'; +import 'package:json_rpc_2/error_code.dart' as error_code; +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; + +import 'utils.dart'; + +void main() { + var controller; + setUp(() => controller = new ClientController()); + + test("sends a message and returns the response", () { + controller.expectRequest((request) { + expect(request, allOf([ + containsPair('jsonrpc', '2.0'), + containsPair('method', 'foo'), + containsPair('params', {'param': 'value'}) + ])); + + return { + 'jsonrpc': '2.0', + 'result': 'bar', + 'id': request['id'] + }; + }); + + expect(controller.client.sendRequest("foo", {'param': 'value'}), + completion(equals('bar'))); + }); + + test("sends a notification and expects no response", () { + controller.expectRequest((request) { + expect(request, equals({ + 'jsonrpc': '2.0', + 'method': 'foo', + 'params': {'param': 'value'} + })); + }); + + controller.client.sendNotification("foo", {'param': 'value'}); + }); + + test("sends a notification with positional parameters", () { + controller.expectRequest((request) { + expect(request, equals({ + 'jsonrpc': '2.0', + 'method': 'foo', + 'params': ['value1', 'value2'] + })); + }); + + controller.client.sendNotification("foo", ['value1', 'value2']); + }); + + test("sends a notification with no parameters", () { + controller.expectRequest((request) { + expect(request, equals({ + 'jsonrpc': '2.0', + 'method': 'foo' + })); + }); + + controller.client.sendNotification("foo"); + }); + + test("sends a synchronous batch of requests", () { + controller.expectRequest((request) { + expect(request, new isInstanceOf()); + expect(request, hasLength(3)); + expect(request[0], equals({ + 'jsonrpc': '2.0', + 'method': 'foo' + })); + expect(request[1], allOf([ + containsPair('jsonrpc', '2.0'), + containsPair('method', 'bar'), + containsPair('params', {'param': 'value'}) + ])); + expect(request[2], allOf([ + containsPair('jsonrpc', '2.0'), + containsPair('method', 'baz') + ])); + + return [ + { + 'jsonrpc': '2.0', + 'result': 'baz response', + 'id': request[2]['id'] + }, + { + 'jsonrpc': '2.0', + 'result': 'bar response', + 'id': request[1]['id'] + } + ]; + }); + + controller.client.withBatch(() { + controller.client.sendNotification("foo"); + expect(controller.client.sendRequest("bar", {'param': 'value'}), + completion(equals("bar response"))); + expect(controller.client.sendRequest("baz"), + completion(equals("baz response"))); + }); + }); + + test("sends an asynchronous batch of requests", () { + controller.expectRequest((request) { + expect(request, new isInstanceOf()); + expect(request, hasLength(3)); + expect(request[0], equals({ + 'jsonrpc': '2.0', + 'method': 'foo' + })); + expect(request[1], allOf([ + containsPair('jsonrpc', '2.0'), + containsPair('method', 'bar'), + containsPair('params', {'param': 'value'}) + ])); + expect(request[2], allOf([ + containsPair('jsonrpc', '2.0'), + containsPair('method', 'baz') + ])); + + return [ + { + 'jsonrpc': '2.0', + 'result': 'baz response', + 'id': request[2]['id'] + }, + { + 'jsonrpc': '2.0', + 'result': 'bar response', + 'id': request[1]['id'] + } + ]; + }); + + controller.client.withBatch(() { + return new Future.value().then((_) { + controller.client.sendNotification("foo"); + return new Future.value(); + }).then((_) { + expect(controller.client.sendRequest("bar", {'param': 'value'}), + completion(equals("bar response"))); + return new Future.value(); + }).then((_) { + expect(controller.client.sendRequest("baz"), + completion(equals("baz response"))); + }); + }); + }); + + test("reports an error from the server", () { + controller.expectRequest((request) { + expect(request, allOf([ + containsPair('jsonrpc', '2.0'), + containsPair('method', 'foo') + ])); + + return { + 'jsonrpc': '2.0', + 'error': { + 'code': error_code.SERVER_ERROR, + 'message': 'you are bad at requests', + 'data': 'some junk' + }, + 'id': request['id'] + }; + }); + + expect(controller.client.sendRequest("foo", {'param': 'value'}), + throwsA(predicate((exception) { + expect(exception, new isInstanceOf()); + expect(exception.code, equals(error_code.SERVER_ERROR)); + expect(exception.message, equals('you are bad at requests')); + expect(exception.data, equals('some junk')); + return true; + }))); + }); + + test("ignores bogus responses", () { + // Make a request so we have something to respond to. + controller.expectRequest((request) { + controller.sendJsonResponse("{invalid"); + controller.sendResponse("not a map"); + controller.sendResponse({ + 'jsonrpc': 'wrong version', + 'result': 'wrong', + 'id': request['id'] + }); + controller.sendResponse({ + 'jsonrpc': '2.0', + 'result': 'wrong' + }); + controller.sendResponse({ + 'jsonrpc': '2.0', + 'id': request['id'] + }); + controller.sendResponse({ + 'jsonrpc': '2.0', + 'error': 'not a map', + 'id': request['id'] + }); + controller.sendResponse({ + 'jsonrpc': '2.0', + 'error': { + 'code': 'not an int', + 'message': 'dang yo' + }, + 'id': request['id'] + }); + controller.sendResponse({ + 'jsonrpc': '2.0', + 'error': { + 'code': 123, + 'message': 0xDEADBEEF + }, + 'id': request['id'] + }); + + return pumpEventQueue().then((_) => { + 'jsonrpc': '2.0', + 'result': 'right', + 'id': request['id'] + }); + }); + + expect(controller.client.sendRequest("foo"), completion(equals('right'))); + }); +} diff --git a/pkgs/json_rpc_2/test/client/stream_test.dart b/pkgs/json_rpc_2/test/client/stream_test.dart new file mode 100644 index 000000000..6942ff768 --- /dev/null +++ b/pkgs/json_rpc_2/test/client/stream_test.dart @@ -0,0 +1,96 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// 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. + +library json_rpc_2.test.client.stream_test; + +import 'dart:async'; + +import 'package:unittest/unittest.dart'; +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; + +import 'utils.dart'; + +void main() { + test(".withoutJson supports decoded stream and sink", () { + var responseController = new StreamController(); + var requestController = new StreamController(); + var client = new json_rpc.Client.withoutJson( + responseController.stream, requestController.sink); + client.listen(); + + expect(requestController.stream.first.then((request) { + expect(request, allOf([ + containsPair('jsonrpc', '2.0'), + containsPair('method', 'foo') + ])); + + responseController.add({ + 'jsonrpc': '2.0', + 'result': 'bar', + 'id': request['id'] + }); + }), completes); + + client.sendRequest('foo'); + }); + + test(".listen returns when the controller is closed", () { + var responseController = new StreamController(); + var requestController = new StreamController(); + var client = new json_rpc.Client.withoutJson( + responseController.stream, requestController.sink); + + var hasListenCompeted = false; + expect(client.listen().then((_) => hasListenCompeted = true), completes); + + return pumpEventQueue().then((_) { + expect(hasListenCompeted, isFalse); + + // This should cause listen to complete. + return responseController.close(); + }); + }); + + test(".listen returns a stream error", () { + var responseController = new StreamController(); + var requestController = new StreamController(); + var client = new json_rpc.Client( + responseController.stream, requestController.sink); + + expect(client.listen(), throwsA('oh no')); + responseController.addError('oh no'); + }); + + test(".listen can't be called twice", () { + var responseController = new StreamController(); + var requestController = new StreamController(); + var client = new json_rpc.Client( + responseController.stream, requestController.sink); + client.listen(); + + expect(() => client.listen(), throwsStateError); + }); + + test(".close cancels the stream subscription and closes the sink", () { + var responseController = new StreamController(); + var requestController = new StreamController(); + var client = new json_rpc.Client( + responseController.stream, requestController.sink); + + expect(client.listen(), completes); + expect(client.close(), completes); + + expect(() => responseController.stream.listen((_) {}), throwsStateError); + expect(requestController.isClosed, isTrue); + }); + + test(".close can't be called before .listen", () { + var responseController = new StreamController(); + var requestController = new StreamController(); + var client = new json_rpc.Client( + responseController.stream, requestController.sink); + + expect(() => client.close(), throwsStateError); + }); +} diff --git a/pkgs/json_rpc_2/test/client/utils.dart b/pkgs/json_rpc_2/test/client/utils.dart new file mode 100644 index 000000000..a164577c3 --- /dev/null +++ b/pkgs/json_rpc_2/test/client/utils.dart @@ -0,0 +1,65 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// 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. + +library json_rpc_2.test.client.utils; + +import 'dart:async'; +import 'dart:convert'; + +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; +import 'package:json_rpc_2/error_code.dart' as error_code; +import 'package:unittest/unittest.dart'; + +/// A controller used to test a [json_rpc.Client]. +class ClientController { + /// The controller for the client's response stream. + final _responseController = new StreamController(); + + /// The controller for the client's request sink. + final _requestController = new StreamController(); + + /// The client. + json_rpc.Client get client => _client; + json_rpc.Client _client; + + ClientController() { + _client = new json_rpc.Client( + _responseController.stream, _requestController.sink); + _client.listen(); + } + + /// Expects that the client will send a request. + /// + /// The request is passed to [callback], which can return a response. If it + /// 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(callback(request)) { + expect(_requestController.stream.first.then((request) { + return callback(JSON.decode(request)); + }).then((response) { + if (response == null) return; + if (response is! String) response = JSON.encode(response); + _responseController.add(response); + }), completes); + } + + /// Sends [response], a decoded response, to [client]. + Future sendResponse(response) => sendJsonResponse(JSON.encode(response)); + + /// Sends [response], a JSON-encoded response, to [client]. + Future sendJsonResponse(String request) => _responseController.add(request); +} + +/// Returns a [Future] that completes after pumping the event queue [times] +/// times. By default, this should pump the event queue enough times to allow +/// any code to run, as long as it's not waiting on some external event. +Future pumpEventQueue([int times = 20]) { + if (times == 0) return new Future.value(); + // We use a delayed future to allow microtask events to finish. The + // Future.value or Future() constructors use scheduleMicrotask themselves and + // would therefore not wait for microtask callbacks that are scheduled after + // invoking this method. + return new Future.delayed(Duration.ZERO, () => pumpEventQueue(times - 1)); +} From 713aee15359861f3780dac94987de219f3fd8d41 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Tue, 11 Nov 2014 23:10:39 +0000 Subject: [PATCH 015/127] Add a Peer class to json_rpc_2. R=rnystrom@google.com Review URL: https://codereview.chromium.org//707063002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/json_rpc_2@41680 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/json_rpc_2/CHANGELOG.md | 2 + pkgs/json_rpc_2/lib/json_rpc_2.dart | 1 + pkgs/json_rpc_2/lib/src/client.dart | 3 - pkgs/json_rpc_2/lib/src/peer.dart | 136 ++++++++++++++++++ pkgs/json_rpc_2/lib/src/server.dart | 3 - pkgs/json_rpc_2/pubspec.yaml | 2 +- pkgs/json_rpc_2/test/peer_test.dart | 205 ++++++++++++++++++++++++++++ 7 files changed, 345 insertions(+), 7 deletions(-) create mode 100644 pkgs/json_rpc_2/lib/src/peer.dart create mode 100644 pkgs/json_rpc_2/test/peer_test.dart diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 15b5a3009..a916c9ea5 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -2,6 +2,8 @@ * Add a `Client` class for communicating with external JSON-RPC 2.0 servers. +* Add a `Peer` class that's both a `Client` and a `Server`. + ## 0.1.0 * Remove `Server.handleRequest()` and `Server.parseRequest()`. Instead, `new diff --git a/pkgs/json_rpc_2/lib/json_rpc_2.dart b/pkgs/json_rpc_2/lib/json_rpc_2.dart index 3305eaee2..08e79a142 100644 --- a/pkgs/json_rpc_2/lib/json_rpc_2.dart +++ b/pkgs/json_rpc_2/lib/json_rpc_2.dart @@ -7,4 +7,5 @@ library json_rpc_2; export 'src/client.dart'; export 'src/exception.dart'; export 'src/parameters.dart'; +export 'src/peer.dart'; export 'src/server.dart'; diff --git a/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart index 2e1d2739b..f3c63ca97 100644 --- a/pkgs/json_rpc_2/lib/src/client.dart +++ b/pkgs/json_rpc_2/lib/src/client.dart @@ -59,9 +59,6 @@ class Client { : _streams = new TwoWayStream.withoutJson( "Client", responses, "responses", requests, "requests"); - /// Users of the library should not use this constructor. - Client.internal(this._streams); - /// Starts listening to the underlying stream. /// /// Returns a [Future] that will complete when the stream is closed or when it diff --git a/pkgs/json_rpc_2/lib/src/peer.dart b/pkgs/json_rpc_2/lib/src/peer.dart new file mode 100644 index 000000000..c83724d06 --- /dev/null +++ b/pkgs/json_rpc_2/lib/src/peer.dart @@ -0,0 +1,136 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// 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. + +library json_rpc_2.peer; + +import 'dart:async'; + +import '../error_code.dart' as error_code; +import 'client.dart'; +import 'exception.dart'; +import 'parameters.dart'; +import 'server.dart'; +import 'two_way_stream.dart'; + +/// A JSON-RPC 2.0 client *and* server. +/// +/// This supports bidirectional peer-to-peer communication with another JSON-RPC +/// 2.0 endpoint. It sends both requests and responses across the same +/// communication channel and expects to connect to a peer that does the same. +class Peer implements Client, Server { + TwoWayStream _streams; + + /// The underlying client that handles request-sending and response-receiving + /// logic. + Client _client; + + /// The underlying server that handles request-receiving and response-sending + /// logic. + Server _server; + + /// A stream controller that forwards incoming messages to [_server] if + /// they're requests. + final _serverIncomingForwarder = new StreamController(sync: true); + + /// A stream controller that forwards incoming messages to [_client] if + /// they're responses. + final _clientIncomingForwarder = new StreamController(sync: true); + + /// A stream controller that forwards outgoing messages from both [_server] + /// and [_client]. + final _outgoingForwarder = new StreamController(sync: true); + + /// Creates a [Peer] that reads incoming messages from [incoming] and writes + /// outgoing messages to [outgoing]. + /// + /// If [incoming] is a [StreamSink] as well as a [Stream] (for example, a + /// `WebSocket`), [outgoing] may be omitted. + /// + /// Note that the peer won't begin listening to [incoming] until [Peer.listen] + /// is called. + Peer(Stream incoming, [StreamSink outgoing]) { + _streams = new TwoWayStream("Peer", incoming, "incoming", + outgoing, "outgoing", onInvalidInput: (message, error) { + _streams.add(new RpcException(error_code.PARSE_ERROR, + 'Invalid JSON: ${error.message}').serialize(message)); + }); + + _outgoingForwarder.stream.listen(_streams.add); + _server = new Server.withoutJson( + _serverIncomingForwarder.stream, _outgoingForwarder); + _client = new Client.withoutJson( + _clientIncomingForwarder.stream, _outgoingForwarder); + } + + /// Creates a [Peer] that reads incoming decoded messages from [incoming] and + /// writes outgoing decoded messages to [outgoing]. + /// + /// Unlike [new Peer], this doesn't read or write JSON strings. Instead, it + /// reads and writes decoded maps or lists. + /// + /// If [incoming] is a [StreamSink] as well as a [Stream], [outgoing] may be + /// omitted. + /// + /// Note that the peer won't begin listening to [incoming] until + /// [Peer.listen] is called. + Peer.withoutJson(Stream incoming, [StreamSink outgoing]) { + _streams = new TwoWayStream.withoutJson("Peer", incoming, "incoming", + outgoing, "outgoing"); + + _outgoingForwarder.stream.listen(_streams.add); + _server = new Server.withoutJson( + _serverIncomingForwarder.stream, _outgoingForwarder); + _client = new Client.withoutJson( + _clientIncomingForwarder.stream, _outgoingForwarder); + } + + // Client methods. + + Future sendRequest(String method, [parameters]) => + _client.sendRequest(method, parameters); + + void sendNotification(String method, [parameters]) => + _client.sendNotification(method, parameters); + + withBatch(callback()) => _client.withBatch(callback); + + // Server methods. + + void registerMethod(String name, Function callback) => + _server.registerMethod(name, callback); + + void registerFallback(callback(Parameters parameters)) => + _server.registerFallback(callback); + + // Shared methods. + + Future listen() { + _client.listen(); + _server.listen(); + return _streams.listen((message) { + if (message is Map) { + if (message.containsKey('result') || message.containsKey('error')) { + _clientIncomingForwarder.add(message); + } else { + _serverIncomingForwarder.add(message); + } + } else if (message is List && message.isNotEmpty && + message.first is Map) { + if (message.first.containsKey('result') || + message.first.containsKey('error')) { + _clientIncomingForwarder.add(message); + } else { + _serverIncomingForwarder.add(message); + } + } else { + // Non-Map and -List messages are ill-formed, so we pass them to the + // server since it knows how to send error responses. + _serverIncomingForwarder.add(message); + } + }); + } + + Future close() => + Future.wait([_client.close(), _server.close(), _streams.close()]); +} diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index 1ba8fccc2..bd243de73 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -69,9 +69,6 @@ class Server { : _streams = new TwoWayStream.withoutJson( "Server", requests, "requests", responses, "responses"); - /// Users of the library should not use this constructor. - Server.internal(this._streams); - /// Starts listening to the underlying stream. /// /// Returns a [Future] that will complete when the stream is closed or when it diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index fed14a00a..4f6eb96ad 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 1.0.0-dev +version: 1.0.0 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://www.dartlang.org diff --git a/pkgs/json_rpc_2/test/peer_test.dart b/pkgs/json_rpc_2/test/peer_test.dart new file mode 100644 index 000000000..07bf79920 --- /dev/null +++ b/pkgs/json_rpc_2/test/peer_test.dart @@ -0,0 +1,205 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// 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. + +library json_rpc_2.test.client.client_test; + +import 'dart:async'; +import 'dart:convert'; + +import 'package:unittest/unittest.dart'; +import 'package:json_rpc_2/error_code.dart' as error_code; +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; + +void main() { + var incoming; + var outgoing; + var peer; + setUp(() { + var incomingController = new StreamController(); + incoming = incomingController.sink; + var outgoingController = new StreamController(); + outgoing = outgoingController.stream; + peer = new json_rpc.Peer.withoutJson( + incomingController.stream, outgoingController); + }); + + group("like a client,", () { + test("can send a message and receive a response", () { + expect(outgoing.first.then((request) { + expect(request, equals({ + "jsonrpc": "2.0", + "method": "foo", + "params": {"bar": "baz"}, + "id": 0 + })); + incoming.add({ + "jsonrpc": "2.0", + "result": "qux", + "id": 0 + }); + }), completes); + + peer.listen(); + expect(peer.sendRequest("foo", {"bar": "baz"}), + completion(equals("qux"))); + }); + + test("can send a batch of messages and receive a batch of responses", () { + expect(outgoing.first.then((request) { + expect(request, equals([ + { + "jsonrpc": "2.0", + "method": "foo", + "params": {"bar": "baz"}, + "id": 0 + }, + { + "jsonrpc": "2.0", + "method": "a", + "params": {"b": "c"}, + "id": 1 + }, + { + "jsonrpc": "2.0", + "method": "w", + "params": {"x": "y"}, + "id": 2 + } + ])); + + incoming.add([ + { + "jsonrpc": "2.0", + "result": "qux", + "id": 0 + }, + { + "jsonrpc": "2.0", + "result": "d", + "id": 1 + }, + { + "jsonrpc": "2.0", + "result": "z", + "id": 2 + } + ]); + }), completes); + + peer.listen(); + + peer.withBatch(() { + expect(peer.sendRequest("foo", {"bar": "baz"}), + completion(equals("qux"))); + expect(peer.sendRequest("a", {"b": "c"}), completion(equals("d"))); + expect(peer.sendRequest("w", {"x": "y"}), completion(equals("z"))); + }); + }); + }); + + group("like a server,", () { + test("can receive a call and return a response", () { + expect(outgoing.first, completion(equals({ + "jsonrpc": "2.0", + "result": "qux", + "id": 0 + }))); + + peer.registerMethod("foo", (_) => "qux"); + peer.listen(); + + incoming.add({ + "jsonrpc": "2.0", + "method": "foo", + "params": {"bar": "baz"}, + "id": 0 + }); + }); + + test("can receive a batch of calls and return a batch of responses", () { + expect(outgoing.first, completion(equals([ + { + "jsonrpc": "2.0", + "result": "qux", + "id": 0 + }, + { + "jsonrpc": "2.0", + "result": "d", + "id": 1 + }, + { + "jsonrpc": "2.0", + "result": "z", + "id": 2 + } + ]))); + + peer.registerMethod("foo", (_) => "qux"); + peer.registerMethod("a", (_) => "d"); + peer.registerMethod("w", (_) => "z"); + peer.listen(); + + incoming.add([ + { + "jsonrpc": "2.0", + "method": "foo", + "params": {"bar": "baz"}, + "id": 0 + }, + { + "jsonrpc": "2.0", + "method": "a", + "params": {"b": "c"}, + "id": 1 + }, + { + "jsonrpc": "2.0", + "method": "w", + "params": {"x": "y"}, + "id": 2 + } + ]); + }); + + test("returns a response for malformed JSON", () { + var incomingController = new StreamController(); + var outgoingController = new StreamController(); + var jsonPeer = new json_rpc.Peer( + incomingController.stream, outgoingController); + + expect(outgoingController.stream.first.then(JSON.decode), completion({ + "jsonrpc": "2.0", + "error": { + 'code': error_code.PARSE_ERROR, + "message": startsWith("Invalid JSON: "), + "data": {'request': '{invalid'} + }, + "id": null + })); + + jsonPeer.listen(); + + incomingController.add("{invalid"); + }); + + test("returns a response for incorrectly-structured JSON", () { + expect(outgoing.first, completion({ + "jsonrpc": "2.0", + "error": { + 'code': error_code.INVALID_REQUEST, + "message": 'Request must contain a "jsonrpc" key.', + "data": {'request': {'completely': 'wrong'}} + }, + "id": null + })); + + peer.listen(); + + incoming.add({ + "completely": "wrong" + }); + }); + }); +} From ed7cead7fa19d47b20302ef545cf1b9be4f3fdbf Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 17 Dec 2014 16:10:47 -0800 Subject: [PATCH 016/127] Add gitignore, status, and codereview files. --- pkgs/json_rpc_2/.gitignore | 14 ++++++++++++++ pkgs/json_rpc_2/.status | 3 +++ pkgs/json_rpc_2/codereview.settings | 3 +++ 3 files changed, 20 insertions(+) create mode 100644 pkgs/json_rpc_2/.gitignore create mode 100644 pkgs/json_rpc_2/.status create mode 100644 pkgs/json_rpc_2/codereview.settings diff --git a/pkgs/json_rpc_2/.gitignore b/pkgs/json_rpc_2/.gitignore new file mode 100644 index 000000000..388eff0ba --- /dev/null +++ b/pkgs/json_rpc_2/.gitignore @@ -0,0 +1,14 @@ +# Don’t commit the following directories created by pub. +.buildlog +.pub/ +build/ +packages + +# Or the files created by dart2js. +*.dart.js +*.js_ +*.js.deps +*.js.map + +# Include when developing application packages. +pubspec.lock \ No newline at end of file diff --git a/pkgs/json_rpc_2/.status b/pkgs/json_rpc_2/.status new file mode 100644 index 000000000..e9f2b0049 --- /dev/null +++ b/pkgs/json_rpc_2/.status @@ -0,0 +1,3 @@ +# Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +# 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. diff --git a/pkgs/json_rpc_2/codereview.settings b/pkgs/json_rpc_2/codereview.settings new file mode 100644 index 000000000..378f6eabb --- /dev/null +++ b/pkgs/json_rpc_2/codereview.settings @@ -0,0 +1,3 @@ +CODE_REVIEW_SERVER: http://codereview.chromium.org/ +VIEW_VC: https://github.com/dart-lang/json_rpc_2/commit/ +CC_LIST: reviews@dartlang.org \ No newline at end of file From 3b6991dc4e68fc0c15620bc3369f9d654939120a Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 17 Dec 2014 16:11:03 -0800 Subject: [PATCH 017/127] Update the pubspec's homepage link. --- pkgs/json_rpc_2/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 4f6eb96ad..09f54414f 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -2,7 +2,7 @@ name: json_rpc_2 version: 1.0.0 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. -homepage: http://www.dartlang.org +homepage: http://github.com/dart-lang/json_rpc_2 dependencies: stack_trace: '>=0.9.1 <2.0.0' dev_dependencies: From 5cf839884f92a822961b7fc1517da89d4de36ac4 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 20 Jan 2015 18:20:08 -0800 Subject: [PATCH 018/127] Add a done getter to Client, Server, and Peer. R=rnystrom@google.com Review URL: https://codereview.chromium.org//810333007 --- pkgs/json_rpc_2/CHANGELOG.md | 4 ++++ pkgs/json_rpc_2/lib/src/client.dart | 5 +++++ pkgs/json_rpc_2/lib/src/peer.dart | 2 ++ pkgs/json_rpc_2/lib/src/server.dart | 9 +++++++- pkgs/json_rpc_2/lib/src/two_way_stream.dart | 24 ++++++++++----------- pkgs/json_rpc_2/pubspec.yaml | 2 +- 6 files changed, 32 insertions(+), 14 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index a916c9ea5..1934fc040 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.1.0 + +* Add a `done` getter to `Client`, `Server`, and `Peer`. + ## 1.0.0 * Add a `Client` class for communicating with external JSON-RPC 2.0 servers. diff --git a/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart index f3c63ca97..f63fde6cb 100644 --- a/pkgs/json_rpc_2/lib/src/client.dart +++ b/pkgs/json_rpc_2/lib/src/client.dart @@ -32,6 +32,11 @@ class Client { /// completed with those requests' responses. final _pendingRequests = new Map(); + /// Returns a [Future] that completes when the connection is closed. + /// + /// This is the same future that's returned by [listen]. + Future get done => _streams.done; + /// Creates a [Client] that writes requests to [requests] and reads responses /// from [responses]. /// diff --git a/pkgs/json_rpc_2/lib/src/peer.dart b/pkgs/json_rpc_2/lib/src/peer.dart index c83724d06..54c38b7d0 100644 --- a/pkgs/json_rpc_2/lib/src/peer.dart +++ b/pkgs/json_rpc_2/lib/src/peer.dart @@ -41,6 +41,8 @@ class Peer implements Client, Server { /// and [_client]. final _outgoingForwarder = new StreamController(sync: true); + Future get done => _streams.done; + /// Creates a [Peer] that reads incoming messages from [incoming] and writes /// outgoing messages to [outgoing]. /// diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index bd243de73..4458e0e59 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -38,6 +38,11 @@ class Server { /// [RpcException.methodNotFound] exception. final _fallbacks = new Queue(); + /// Returns a [Future] that completes when the connection is closed. + /// + /// This is the same future that's returned by [listen]. + Future get done => _streams.done; + /// Creates a [Server] that reads requests from [requests] and writes /// responses to [responses]. /// @@ -135,7 +140,9 @@ class Server { var nonNull = results.where((result) => result != null); return nonNull.isEmpty ? null : nonNull.toList(); }); - }).then(_streams.add); + }).then((response) { + if (response != null) _streams.add(response); + }); } /// Handles an individual parsed request. diff --git a/pkgs/json_rpc_2/lib/src/two_way_stream.dart b/pkgs/json_rpc_2/lib/src/two_way_stream.dart index f470c2fce..591417387 100644 --- a/pkgs/json_rpc_2/lib/src/two_way_stream.dart +++ b/pkgs/json_rpc_2/lib/src/two_way_stream.dart @@ -32,10 +32,11 @@ class TwoWayStream { /// This takes decoded JSON objects. final StreamSink _output; - /// The completer for [listen]. + /// Returns a [Future] that completes when the connection is closed. /// - /// This is non-`null` after [listen] has been called. - Completer _listenCompleter; + /// This is the same future that's returned by [listen]. + Future get done => _doneCompleter.future; + final _doneCompleter = new Completer(); /// Creates a two-way stream. /// @@ -95,23 +96,22 @@ class TwoWayStream { /// The returned Future will complete when the input stream is closed. If the /// input stream emits an error, that will be piped to the returned Future. Future listen(void handleInput(input)) { - if (_listenCompleter != null) { + if (_inputSubscription != null) { throw new StateError("Can only call $_name.listen once."); } - _listenCompleter = new Completer(); _inputSubscription = _input.listen(handleInput, onError: (error, stackTrace) { - if (_listenCompleter.isCompleted) return; + if (_doneCompleter.isCompleted) return; _output.close(); - _listenCompleter.completeError(error, stackTrace); + _doneCompleter.completeError(error, stackTrace); }, onDone: () { - if (_listenCompleter.isCompleted) return; + if (_doneCompleter.isCompleted) return; _output.close(); - _listenCompleter.complete(); + _doneCompleter.complete(); }, cancelOnError: true); - return _listenCompleter.future; + return _doneCompleter.future; } /// Emit [event] on the output stream. @@ -119,11 +119,11 @@ class TwoWayStream { /// Stops listening to the input stream and closes the output stream. Future close() { - if (_listenCompleter == null) { + if (_inputSubscription == null) { throw new StateError("Can't call $_name.close before $_name.listen."); } - if (!_listenCompleter.isCompleted) _listenCompleter.complete(); + if (!_doneCompleter.isCompleted) _doneCompleter.complete(); var inputFuture = _inputSubscription.cancel(); // TODO(nweiz): include the output future in the return value when issue diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 09f54414f..abc05abbe 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 1.0.0 +version: 1.1.0 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://github.com/dart-lang/json_rpc_2 From 442328e63695a46ca6f5460275f1e9e86323e720 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 21 Jan 2015 16:16:20 -0800 Subject: [PATCH 019/127] Fix a couple test timeouts. R=rnystrom@google.com Review URL: https://codereview.chromium.org//864983002 --- pkgs/json_rpc_2/test/server/batch_test.dart | 2 +- pkgs/json_rpc_2/test/server/server_test.dart | 2 +- pkgs/json_rpc_2/test/server/utils.dart | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/test/server/batch_test.dart b/pkgs/json_rpc_2/test/server/batch_test.dart index 7dda84f34..f92a3cf04 100644 --- a/pkgs/json_rpc_2/test/server/batch_test.dart +++ b/pkgs/json_rpc_2/test/server/batch_test.dart @@ -70,7 +70,7 @@ void main() { {'jsonrpc': '2.0', 'method': 'foo'}, {'jsonrpc': '2.0', 'method': 'id', 'params': ['value']}, {'jsonrpc': '2.0', 'method': 'arg', 'params': {'arg': 'value'}} - ]), completion(isNull)); + ]), doesNotComplete); }); test('returns an error if the batch is empty', () { diff --git a/pkgs/json_rpc_2/test/server/server_test.dart b/pkgs/json_rpc_2/test/server/server_test.dart index a89a0d993..10498b781 100644 --- a/pkgs/json_rpc_2/test/server/server_test.dart +++ b/pkgs/json_rpc_2/test/server/server_test.dart @@ -89,7 +89,7 @@ void main() { 'jsonrpc': '2.0', 'method': 'foo', 'params': {} - }), completion(isNull)); + }), doesNotComplete); }); test("includes the error data in the response", () { diff --git a/pkgs/json_rpc_2/test/server/utils.dart b/pkgs/json_rpc_2/test/server/utils.dart index a4fd36af6..75dc9b247 100644 --- a/pkgs/json_rpc_2/test/server/utils.dart +++ b/pkgs/json_rpc_2/test/server/utils.dart @@ -61,6 +61,20 @@ void expectErrorResponse(ServerController controller, request, int errorCode, }))); } +/// Returns a matcher that matches [Future]s that never complete. +Matcher get doesNotComplete => predicate((future) { + future.then(expectAsync((_) { + // This will never be called. [expectAsync] with `count: 0` ensures that an + // error will be thrown when [future] completes. + }, count: 0)); + + // Make sure there's enough time in the test for [expectAsync] to fail if it's + // going to. + expect(pumpEventQueue(), completes); + + return true; +}); + /// Returns a matcher that matches a [json_rpc.RpcException] with an /// `invalid_params` error code. Matcher throwsInvalidParams(String message) { From c52ee94fb0c9237e1a5ea9e4bf2be6185ff43752 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 5 Feb 2015 16:26:17 -0800 Subject: [PATCH 020/127] Update the README to match the current API. R=rnystrom@google.com Closes dart-lang/json_rpc_2#2 Review URL: https://codereview.chromium.org//899093003 --- pkgs/json_rpc_2/CHANGELOG.md | 4 + pkgs/json_rpc_2/README.md | 171 ++++++++++++++++++++--------------- pkgs/json_rpc_2/pubspec.yaml | 2 +- 3 files changed, 105 insertions(+), 72 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 1934fc040..a1bd217b6 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.1.1 + +* Update the README to match the current API. + ## 1.1.0 * Add a `done` getter to `Client`, `Server`, and `Peer`. diff --git a/pkgs/json_rpc_2/README.md b/pkgs/json_rpc_2/README.md index 3d09d0f39..67afc3121 100644 --- a/pkgs/json_rpc_2/README.md +++ b/pkgs/json_rpc_2/README.md @@ -10,89 +10,118 @@ These methods can be registered using `Server.registerMethod`: ```dart import "package:json_rpc_2/json_rpc_2.dart" as json_rpc; -var server = new json_rpc.Server(); - -// Any string may be used as a method name. JSON-RPC 2.0 methods are -// case-sensitive. -var i = 0; -server.registerMethod("count", () { - // Just return the value to be sent as a response to the client. This can be - // anything JSON-serializable, or a Future that completes to something - // JSON-serializable. - return i++; -}); - -// Methods can take parameters. They're presented as a [Parameters] object which -// makes it easy to validate that the expected parameters exist. -server.registerMethod("echo", (params) { - // If the request doesn't have a "message" parameter, this will automatically - // send a response notifying the client that the request was invalid. - return params.getNamed("message"); -}); - -// [Parameters] has methods for verifying argument types. -server.registerMethod("subtract", (params) { - // If "minuend" or "subtrahend" aren't numbers, this will reject the request. - return params.getNum("minuend") - params.getNum("subtrahend"); -}); - -// [Parameters] also supports optional arguments. -server.registerMethod("sort", (params) { - var list = params.getList("list"); - list.sort(); - if (params.getBool("descending", orElse: () => false)) { - return params.list.reversed; - } else { - return params.list; - } -}); - -// A method can send an error response by throwing a `json_rpc.RpcException`. -// Any positive number may be used as an application-defined error code. -const DIVIDE_BY_ZERO = 1; -server.registerMethod("divide", (params) { - var divisor = params.getNum("divisor"); - if (divisor == 0) { - throw new json_rpc.RpcException(DIVIDE_BY_ZERO, "Cannot divide by zero."); - } - - return params.getNum("dividend") / divisor; -}); -``` +void main() { + WebSocket.connect('ws://localhost:4321').then((socket) { + // You can start the server with a Stream for requests and a StreamSink for + // responses, or with an object that's both, like a WebSocket. + var server = new json_rpc.Server(socket); + + // Any string may be used as a method name. JSON-RPC 2.0 methods are + // case-sensitive. + var i = 0; + server.registerMethod("count", () { + // Just return the value to be sent as a response to the client. This can + // be anything JSON-serializable, or a Future that completes to something + // JSON-serializable. + return i++; + }); -Once you've registered your methods, you can handle requests with -`Server.parseRequest`: + // Methods can take parameters. They're presented as a [Parameters] object + // which makes it easy to validate that the expected parameters exist. + server.registerMethod("echo", (params) { + // If the request doesn't have a "message" parameter, this will + // automatically send a response notifying the client that the request + // was invalid. + return params.getNamed("message"); + }); -```dart -import 'dart:io'; + // [Parameters] has methods for verifying argument types. + server.registerMethod("subtract", (params) { + // If "minuend" or "subtrahend" aren't numbers, this will reject the + // request. + return params.getNum("minuend") - params.getNum("subtrahend"); + }); + + // [Parameters] also supports optional arguments. + server.registerMethod("sort", (params) { + var list = params.getList("list"); + list.sort(); + if (params.getBool("descending", orElse: () => false)) { + return params.list.reversed; + } else { + return params.list; + } + }); -WebSocket.connect('ws://localhost:4321').then((socket) { - socket.listen((message) { - server.parseRequest(message).then((response) { - if (response != null) socket.add(response); + // A method can send an error response by throwing a + // `json_rpc.RpcException`. Any positive number may be used as an + // application- defined error code. + const DIVIDE_BY_ZERO = 1; + server.registerMethod("divide", (params) { + var divisor = params.getNum("divisor"); + if (divisor == 0) { + throw new json_rpc.RpcException( + DIVIDE_BY_ZERO, "Cannot divide by zero."); + } + + return params.getNum("dividend") / divisor; }); + + // To give you time to register all your methods, the server won't actually + // start listening for requests until you call `listen`. + server.listen(); }); -}); +} ``` -If you're communicating with objects that haven't been serialized to a string, -you can also call `Server.handleRequest` directly: +## Client + +A JSON-RPC 2.0 client calls methods on a server and handles the server's +responses to those method calls. These methods can be called using +`Client.sendRequest`: ```dart -import 'dart:isolate'; +import "package:json_rpc_2/json_rpc_2.dart" as json_rpc; -var receive = new ReceivePort(); -Isolate.spawnUri('path/to/client.dart', [], receive.sendPort).then((_) { - receive.listen((message) { - server.handleRequest(message['request']).then((response) { - if (response != null) message['respond'].send(response); +void main() { + WebSocket.connect('ws://localhost:4321').then((socket) { + // Just like the server, a client takes a Stream and a StreamSink or a + // single object that's both. + var client = new json_rpc.Client(socket); + + // This calls the "count" method on the server. A Future is returned that + // will complete to the value contained in the server's response. + client.sendRequest("count").then((result) => print("Count is $result.")); + + // Parameters are passed as a simple Map or, for positional parameters, an + // Iterable. Make sure they're JSON-serializable! + client.sendRequest("echo", {"message": "hello"}) + .then((echo) => print('Echo says "$echo"!')); + + // A notification is a way to call a method that tells the server that no + // result is expected. Its return type is `void`; even if it causes an + // error, you won't hear back. + client.sendNotification("count"); + + // If the server sends an error response, the returned Future will complete + // with an RpcException. You can catch this error and inspect its error + // code, message, and any data that the server sent along with it. + client.sendRequest("divide", {"dividend": 2, "divisor": 0}) + .catchError((error) { + print("RPC error ${error.code}: ${error.message}"); }); + + // The client won't subscribe to the input stream until you call `listen`. + client.listen(); }); -}) +} ``` -## Client - -Currently this package does not contain an implementation of a JSON-RPC 2.0 -client. +## Peer +Although JSON-RPC 2.0 only explicitly describes clients and servers, it also +mentions that two-way communication can be supported by making each endpoint +both a client and a server. This package supports this directly using the `Peer` +class, which implements both `Client` and `Server`. It supports the same methods +as those classes, and automatically makes sure that every message from the other +endpoint is routed and handled correctly. diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index abc05abbe..bf5939c94 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 1.1.0 +version: 1.1.1 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://github.com/dart-lang/json_rpc_2 From a6d3d2aa0efb0503508aeeb11e978b5d27f46a6f Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 16 Jul 2015 13:26:13 -0700 Subject: [PATCH 021/127] Upgrade to the new test runner. R=rnystrom@google.com Review URL: https://codereview.chromium.org//1234323004 . --- pkgs/json_rpc_2/.gitignore | 1 + pkgs/json_rpc_2/.status | 3 --- pkgs/json_rpc_2/.test_config | 3 +++ pkgs/json_rpc_2/pubspec.yaml | 4 ++-- pkgs/json_rpc_2/test/client/client_test.dart | 2 +- pkgs/json_rpc_2/test/client/stream_test.dart | 2 +- pkgs/json_rpc_2/test/client/utils.dart | 2 +- pkgs/json_rpc_2/test/peer_test.dart | 2 +- pkgs/json_rpc_2/test/server/batch_test.dart | 2 +- pkgs/json_rpc_2/test/server/invalid_request_test.dart | 2 +- pkgs/json_rpc_2/test/server/parameters_test.dart | 2 +- pkgs/json_rpc_2/test/server/server_test.dart | 2 +- pkgs/json_rpc_2/test/server/stream_test.dart | 2 +- pkgs/json_rpc_2/test/server/utils.dart | 2 +- 14 files changed, 16 insertions(+), 15 deletions(-) delete mode 100644 pkgs/json_rpc_2/.status create mode 100644 pkgs/json_rpc_2/.test_config diff --git a/pkgs/json_rpc_2/.gitignore b/pkgs/json_rpc_2/.gitignore index 388eff0ba..7dbf0350d 100644 --- a/pkgs/json_rpc_2/.gitignore +++ b/pkgs/json_rpc_2/.gitignore @@ -3,6 +3,7 @@ .pub/ build/ packages +.packages # Or the files created by dart2js. *.dart.js diff --git a/pkgs/json_rpc_2/.status b/pkgs/json_rpc_2/.status deleted file mode 100644 index e9f2b0049..000000000 --- a/pkgs/json_rpc_2/.status +++ /dev/null @@ -1,3 +0,0 @@ -# Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file -# 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. diff --git a/pkgs/json_rpc_2/.test_config b/pkgs/json_rpc_2/.test_config new file mode 100644 index 000000000..412fc5c5c --- /dev/null +++ b/pkgs/json_rpc_2/.test_config @@ -0,0 +1,3 @@ +{ + "test_package": true +} \ No newline at end of file diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index bf5939c94..d89569891 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,12 +1,12 @@ name: json_rpc_2 -version: 1.1.1 +version: 1.1.2-dev author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://github.com/dart-lang/json_rpc_2 dependencies: stack_trace: '>=0.9.1 <2.0.0' dev_dependencies: - unittest: ">=0.9.0 <0.12.0" + test: ">=0.12.0 <0.13.0" environment: sdk: ">=1.2.0 <2.0.0" diff --git a/pkgs/json_rpc_2/test/client/client_test.dart b/pkgs/json_rpc_2/test/client/client_test.dart index 9fcf9e330..3f50ab7f1 100644 --- a/pkgs/json_rpc_2/test/client/client_test.dart +++ b/pkgs/json_rpc_2/test/client/client_test.dart @@ -7,7 +7,7 @@ library json_rpc_2.test.client.client_test; import 'dart:async'; import 'dart:convert'; -import 'package:unittest/unittest.dart'; +import 'package:test/test.dart'; import 'package:json_rpc_2/error_code.dart' as error_code; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; diff --git a/pkgs/json_rpc_2/test/client/stream_test.dart b/pkgs/json_rpc_2/test/client/stream_test.dart index 6942ff768..20b21bde0 100644 --- a/pkgs/json_rpc_2/test/client/stream_test.dart +++ b/pkgs/json_rpc_2/test/client/stream_test.dart @@ -6,7 +6,7 @@ library json_rpc_2.test.client.stream_test; import 'dart:async'; -import 'package:unittest/unittest.dart'; +import 'package:test/test.dart'; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'utils.dart'; diff --git a/pkgs/json_rpc_2/test/client/utils.dart b/pkgs/json_rpc_2/test/client/utils.dart index a164577c3..e759fb1ad 100644 --- a/pkgs/json_rpc_2/test/client/utils.dart +++ b/pkgs/json_rpc_2/test/client/utils.dart @@ -9,7 +9,7 @@ import 'dart:convert'; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'package:json_rpc_2/error_code.dart' as error_code; -import 'package:unittest/unittest.dart'; +import 'package:test/test.dart'; /// A controller used to test a [json_rpc.Client]. class ClientController { diff --git a/pkgs/json_rpc_2/test/peer_test.dart b/pkgs/json_rpc_2/test/peer_test.dart index 07bf79920..c51987386 100644 --- a/pkgs/json_rpc_2/test/peer_test.dart +++ b/pkgs/json_rpc_2/test/peer_test.dart @@ -7,7 +7,7 @@ library json_rpc_2.test.client.client_test; import 'dart:async'; import 'dart:convert'; -import 'package:unittest/unittest.dart'; +import 'package:test/test.dart'; import 'package:json_rpc_2/error_code.dart' as error_code; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; diff --git a/pkgs/json_rpc_2/test/server/batch_test.dart b/pkgs/json_rpc_2/test/server/batch_test.dart index f92a3cf04..704ea6e59 100644 --- a/pkgs/json_rpc_2/test/server/batch_test.dart +++ b/pkgs/json_rpc_2/test/server/batch_test.dart @@ -6,7 +6,7 @@ library json_rpc_2.test.server.batch_test; import 'dart:convert'; -import 'package:unittest/unittest.dart'; +import 'package:test/test.dart'; import 'package:json_rpc_2/error_code.dart' as error_code; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; diff --git a/pkgs/json_rpc_2/test/server/invalid_request_test.dart b/pkgs/json_rpc_2/test/server/invalid_request_test.dart index 74cf86fec..aa00fb8e9 100644 --- a/pkgs/json_rpc_2/test/server/invalid_request_test.dart +++ b/pkgs/json_rpc_2/test/server/invalid_request_test.dart @@ -6,7 +6,7 @@ library json_rpc_2.test.server.invalid_request_test; import 'dart:convert'; -import 'package:unittest/unittest.dart'; +import 'package:test/test.dart'; import 'package:json_rpc_2/error_code.dart' as error_code; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; diff --git a/pkgs/json_rpc_2/test/server/parameters_test.dart b/pkgs/json_rpc_2/test/server/parameters_test.dart index 3de03598c..a5186980d 100644 --- a/pkgs/json_rpc_2/test/server/parameters_test.dart +++ b/pkgs/json_rpc_2/test/server/parameters_test.dart @@ -4,7 +4,7 @@ library json_rpc_2.test.server.parameters_test; -import 'package:unittest/unittest.dart'; +import 'package:test/test.dart'; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'utils.dart'; diff --git a/pkgs/json_rpc_2/test/server/server_test.dart b/pkgs/json_rpc_2/test/server/server_test.dart index 10498b781..357333222 100644 --- a/pkgs/json_rpc_2/test/server/server_test.dart +++ b/pkgs/json_rpc_2/test/server/server_test.dart @@ -6,7 +6,7 @@ library json_rpc_2.test.server.server_test; import 'dart:convert'; -import 'package:unittest/unittest.dart'; +import 'package:test/test.dart'; import 'package:json_rpc_2/error_code.dart' as error_code; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; diff --git a/pkgs/json_rpc_2/test/server/stream_test.dart b/pkgs/json_rpc_2/test/server/stream_test.dart index 5459e3e6a..a7a2d8652 100644 --- a/pkgs/json_rpc_2/test/server/stream_test.dart +++ b/pkgs/json_rpc_2/test/server/stream_test.dart @@ -6,7 +6,7 @@ library json_rpc_2.test.server.stream_test; import 'dart:async'; -import 'package:unittest/unittest.dart'; +import 'package:test/test.dart'; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'utils.dart'; diff --git a/pkgs/json_rpc_2/test/server/utils.dart b/pkgs/json_rpc_2/test/server/utils.dart index 75dc9b247..fabef1f9f 100644 --- a/pkgs/json_rpc_2/test/server/utils.dart +++ b/pkgs/json_rpc_2/test/server/utils.dart @@ -9,7 +9,7 @@ import 'dart:convert'; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'package:json_rpc_2/error_code.dart' as error_code; -import 'package:unittest/unittest.dart'; +import 'package:test/test.dart'; /// A controller used to test a [json_rpc.Server]. class ServerController { From 8960a39a1ade2fd58785618cd5ccf7c5959baf4b Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Mon, 14 Sep 2015 17:27:46 -0700 Subject: [PATCH 022/127] Add Server.isClosed and Client.isClosed. R=rnystrom@google.com Review URL: https://codereview.chromium.org//1328503003 . --- pkgs/json_rpc_2/CHANGELOG.md | 10 ++++++++++ pkgs/json_rpc_2/lib/src/client.dart | 3 +++ pkgs/json_rpc_2/lib/src/peer.dart | 1 + pkgs/json_rpc_2/lib/src/server.dart | 5 ++++- pkgs/json_rpc_2/lib/src/two_way_stream.dart | 7 +++++-- pkgs/json_rpc_2/pubspec.yaml | 2 +- pkgs/json_rpc_2/test/client/stream_test.dart | 3 +++ pkgs/json_rpc_2/test/server/stream_test.dart | 3 +++ 8 files changed, 30 insertions(+), 4 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index a1bd217b6..7de0e876b 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,13 @@ +## 1.2.0 + +* Add `Client.isClosed` and `Server.isClosed`, which make it possible to + synchronously determine whether the connection is open. In particular, this + makes it possible to reliably tell whether it's safe to call + `Client.sendRequest`. + +* Fix a race condition in `Server` where a `StateError` could be thrown if the + connection was closed in the middle of handling a request. + ## 1.1.1 * Update the README to match the current API. diff --git a/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart index f63fde6cb..763a9871a 100644 --- a/pkgs/json_rpc_2/lib/src/client.dart +++ b/pkgs/json_rpc_2/lib/src/client.dart @@ -37,6 +37,9 @@ class Client { /// This is the same future that's returned by [listen]. Future get done => _streams.done; + /// Whether the connection is closed. + bool get isClosed => _streams.isClosed; + /// Creates a [Client] that writes requests to [requests] and reads responses /// from [responses]. /// diff --git a/pkgs/json_rpc_2/lib/src/peer.dart b/pkgs/json_rpc_2/lib/src/peer.dart index 54c38b7d0..7821d9fe9 100644 --- a/pkgs/json_rpc_2/lib/src/peer.dart +++ b/pkgs/json_rpc_2/lib/src/peer.dart @@ -42,6 +42,7 @@ class Peer implements Client, Server { final _outgoingForwarder = new StreamController(sync: true); Future get done => _streams.done; + bool get isClosed => _streams.isClosed; /// Creates a [Peer] that reads incoming messages from [incoming] and writes /// outgoing messages to [outgoing]. diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index 4458e0e59..4188e78cd 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -43,6 +43,9 @@ class Server { /// This is the same future that's returned by [listen]. Future get done => _streams.done; + /// Whether the connection is closed. + bool get isClosed => _streams.isClosed; + /// Creates a [Server] that reads requests from [requests] and writes /// responses to [responses]. /// @@ -141,7 +144,7 @@ class Server { return nonNull.isEmpty ? null : nonNull.toList(); }); }).then((response) { - if (response != null) _streams.add(response); + if (!_streams.isClosed && response != null) _streams.add(response); }); } diff --git a/pkgs/json_rpc_2/lib/src/two_way_stream.dart b/pkgs/json_rpc_2/lib/src/two_way_stream.dart index 591417387..88a8dcc24 100644 --- a/pkgs/json_rpc_2/lib/src/two_way_stream.dart +++ b/pkgs/json_rpc_2/lib/src/two_way_stream.dart @@ -38,6 +38,9 @@ class TwoWayStream { Future get done => _doneCompleter.future; final _doneCompleter = new Completer(); + /// Whether the stream has been closed. + bool get isClosed => _doneCompleter.isCompleted; + /// Creates a two-way stream. /// /// [input] and [output] should emit and take (respectively) JSON-encoded @@ -103,12 +106,12 @@ class TwoWayStream { _inputSubscription = _input.listen(handleInput, onError: (error, stackTrace) { if (_doneCompleter.isCompleted) return; - _output.close(); _doneCompleter.completeError(error, stackTrace); + _output.close(); }, onDone: () { if (_doneCompleter.isCompleted) return; - _output.close(); _doneCompleter.complete(); + _output.close(); }, cancelOnError: true); return _doneCompleter.future; diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index d89569891..348dc35b1 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 1.1.2-dev +version: 1.2.0 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://github.com/dart-lang/json_rpc_2 diff --git a/pkgs/json_rpc_2/test/client/stream_test.dart b/pkgs/json_rpc_2/test/client/stream_test.dart index 20b21bde0..ba5db5bc3 100644 --- a/pkgs/json_rpc_2/test/client/stream_test.dart +++ b/pkgs/json_rpc_2/test/client/stream_test.dart @@ -79,7 +79,10 @@ void main() { responseController.stream, requestController.sink); expect(client.listen(), completes); + + expect(client.isClosed, isFalse); expect(client.close(), completes); + expect(client.isClosed, isTrue); expect(() => responseController.stream.listen((_) {}), throwsStateError); expect(requestController.isClosed, isTrue); diff --git a/pkgs/json_rpc_2/test/server/stream_test.dart b/pkgs/json_rpc_2/test/server/stream_test.dart index a7a2d8652..8fd12396f 100644 --- a/pkgs/json_rpc_2/test/server/stream_test.dart +++ b/pkgs/json_rpc_2/test/server/stream_test.dart @@ -81,7 +81,10 @@ void main() { requestController.stream, responseController.sink); expect(server.listen(), completes); + + expect(server.isClosed, isFalse); expect(server.close(), completes); + expect(server.isClosed, isTrue); expect(() => requestController.stream.listen((_) {}), throwsStateError); expect(responseController.isClosed, isTrue); From bd32c69460e65469c24bfe662bd920f87d5455cc Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Mon, 14 Sep 2015 17:28:50 -0700 Subject: [PATCH 023/127] Improve stack traces for error responses. This tracks the stack chain when the request is made and uses it if the response fails. R=rnystrom@google.com Review URL: https://codereview.chromium.org//1341933002 . --- pkgs/json_rpc_2/CHANGELOG.md | 2 ++ pkgs/json_rpc_2/lib/src/client.dart | 26 ++++++++++++++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 7de0e876b..524cbad6a 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -8,6 +8,8 @@ * Fix a race condition in `Server` where a `StateError` could be thrown if the connection was closed in the middle of handling a request. +* Improve stack traces for error responses. + ## 1.1.1 * Update the README to match the current API. diff --git a/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart index 763a9871a..951f92732 100644 --- a/pkgs/json_rpc_2/lib/src/client.dart +++ b/pkgs/json_rpc_2/lib/src/client.dart @@ -28,9 +28,8 @@ class Client { /// Each element is a JSON-serializable object. List _batch; - /// The map of request ids for pending requests to [Completer]s that will be - /// completed with those requests' responses. - final _pendingRequests = new Map(); + /// The map of request ids to pending requests. + final _pendingRequests = new Map(); /// Returns a [Future] that completes when the connection is closed. /// @@ -97,7 +96,7 @@ class Client { _send(method, parameters, id); var completer = new Completer.sync(); - _pendingRequests[id] = completer; + _pendingRequests[id] = new _Request(completer, new Chain.current()); return completer.future; } @@ -174,15 +173,15 @@ class Client { /// resolved. void _handleSingleResponse(response) { if (!_isResponseValid(response)) return; - var completer = _pendingRequests.remove(response["id"]); + var request = _pendingRequests.remove(response["id"]); if (response.containsKey("result")) { - completer.complete(response["result"]); + request.completer.complete(response["result"]); } else { - completer.completeError(new RpcException( + request.completer.completeError(new RpcException( response["error"]["code"], response["error"]["message"], data: response["error"]["data"]), - new Chain.current()); + request.chain); } } @@ -201,3 +200,14 @@ class Client { return true; } } + +/// A pending request to the server. +class _Request { + /// The completer to use to complete the response future. + final Completer completer; + + /// The stack chain from where the request was made. + final Chain chain; + + _Request(this.completer, this.chain); +} From 9f5ca499441dbfa68a92611ef21acd4ef1fd6b5a Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 12 Jan 2016 17:21:31 -0800 Subject: [PATCH 024/127] Get rid of all the library tags. R=rnystrom@google.com Review URL: https://codereview.chromium.org//1577363003 . --- pkgs/json_rpc_2/lib/error_code.dart | 2 -- pkgs/json_rpc_2/lib/json_rpc_2.dart | 2 -- pkgs/json_rpc_2/lib/src/client.dart | 2 -- pkgs/json_rpc_2/lib/src/exception.dart | 2 -- pkgs/json_rpc_2/lib/src/parameters.dart | 2 -- pkgs/json_rpc_2/lib/src/peer.dart | 2 -- pkgs/json_rpc_2/lib/src/server.dart | 2 -- pkgs/json_rpc_2/lib/src/two_way_stream.dart | 2 -- pkgs/json_rpc_2/lib/src/utils.dart | 2 -- pkgs/json_rpc_2/test/client/client_test.dart | 2 -- pkgs/json_rpc_2/test/client/stream_test.dart | 2 -- pkgs/json_rpc_2/test/client/utils.dart | 2 -- pkgs/json_rpc_2/test/peer_test.dart | 2 -- pkgs/json_rpc_2/test/server/batch_test.dart | 2 -- pkgs/json_rpc_2/test/server/invalid_request_test.dart | 2 -- pkgs/json_rpc_2/test/server/parameters_test.dart | 2 -- pkgs/json_rpc_2/test/server/server_test.dart | 2 -- pkgs/json_rpc_2/test/server/stream_test.dart | 2 -- pkgs/json_rpc_2/test/server/utils.dart | 2 -- 19 files changed, 38 deletions(-) diff --git a/pkgs/json_rpc_2/lib/error_code.dart b/pkgs/json_rpc_2/lib/error_code.dart index 14b77f2e0..7dd80798b 100644 --- a/pkgs/json_rpc_2/lib/error_code.dart +++ b/pkgs/json_rpc_2/lib/error_code.dart @@ -9,8 +9,6 @@ /// convenience constructors in [RpcException]. /// /// [spec]: http://www.jsonrpc.org/specification#error_object -library json_rpc_2.error_code; - /// An error code indicating that invalid JSON was received by the server. const PARSE_ERROR = -32700; diff --git a/pkgs/json_rpc_2/lib/json_rpc_2.dart b/pkgs/json_rpc_2/lib/json_rpc_2.dart index 08e79a142..33e5f4987 100644 --- a/pkgs/json_rpc_2/lib/json_rpc_2.dart +++ b/pkgs/json_rpc_2/lib/json_rpc_2.dart @@ -2,8 +2,6 @@ // 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. -library json_rpc_2; - export 'src/client.dart'; export 'src/exception.dart'; export 'src/parameters.dart'; diff --git a/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart index 951f92732..e75e7f38c 100644 --- a/pkgs/json_rpc_2/lib/src/client.dart +++ b/pkgs/json_rpc_2/lib/src/client.dart @@ -2,8 +2,6 @@ // 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. -library json_rpc_2.client; - import 'dart:async'; import 'package:stack_trace/stack_trace.dart'; diff --git a/pkgs/json_rpc_2/lib/src/exception.dart b/pkgs/json_rpc_2/lib/src/exception.dart index 2fb071328..b359155b9 100644 --- a/pkgs/json_rpc_2/lib/src/exception.dart +++ b/pkgs/json_rpc_2/lib/src/exception.dart @@ -2,8 +2,6 @@ // 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. -library json_rpc_2.exception; - import '../error_code.dart' as error_code; /// An exception from a JSON-RPC server that can be translated into an error diff --git a/pkgs/json_rpc_2/lib/src/parameters.dart b/pkgs/json_rpc_2/lib/src/parameters.dart index 1e2522013..91ad1b581 100644 --- a/pkgs/json_rpc_2/lib/src/parameters.dart +++ b/pkgs/json_rpc_2/lib/src/parameters.dart @@ -2,8 +2,6 @@ // 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. -library json_rpc_2.parameters; - import 'dart:convert'; import 'exception.dart'; diff --git a/pkgs/json_rpc_2/lib/src/peer.dart b/pkgs/json_rpc_2/lib/src/peer.dart index 7821d9fe9..a6707e255 100644 --- a/pkgs/json_rpc_2/lib/src/peer.dart +++ b/pkgs/json_rpc_2/lib/src/peer.dart @@ -2,8 +2,6 @@ // 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. -library json_rpc_2.peer; - import 'dart:async'; import '../error_code.dart' as error_code; diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index 4188e78cd..06dd0b9e3 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -2,8 +2,6 @@ // 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. -library json_rpc_2.server; - import 'dart:async'; import 'dart:collection'; import 'dart:convert'; diff --git a/pkgs/json_rpc_2/lib/src/two_way_stream.dart b/pkgs/json_rpc_2/lib/src/two_way_stream.dart index 88a8dcc24..4f20686b5 100644 --- a/pkgs/json_rpc_2/lib/src/two_way_stream.dart +++ b/pkgs/json_rpc_2/lib/src/two_way_stream.dart @@ -2,8 +2,6 @@ // 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. -library json_rpc_2.two_way_stream; - import 'dart:async'; import 'dart:convert'; diff --git a/pkgs/json_rpc_2/lib/src/utils.dart b/pkgs/json_rpc_2/lib/src/utils.dart index f862cb784..dde6b4af9 100644 --- a/pkgs/json_rpc_2/lib/src/utils.dart +++ b/pkgs/json_rpc_2/lib/src/utils.dart @@ -2,8 +2,6 @@ // 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. -library json_rpc_2.utils; - import 'dart:async'; import 'package:stack_trace/stack_trace.dart'; diff --git a/pkgs/json_rpc_2/test/client/client_test.dart b/pkgs/json_rpc_2/test/client/client_test.dart index 3f50ab7f1..1553787ce 100644 --- a/pkgs/json_rpc_2/test/client/client_test.dart +++ b/pkgs/json_rpc_2/test/client/client_test.dart @@ -2,8 +2,6 @@ // 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. -library json_rpc_2.test.client.client_test; - import 'dart:async'; import 'dart:convert'; diff --git a/pkgs/json_rpc_2/test/client/stream_test.dart b/pkgs/json_rpc_2/test/client/stream_test.dart index ba5db5bc3..146e75359 100644 --- a/pkgs/json_rpc_2/test/client/stream_test.dart +++ b/pkgs/json_rpc_2/test/client/stream_test.dart @@ -2,8 +2,6 @@ // 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. -library json_rpc_2.test.client.stream_test; - import 'dart:async'; import 'package:test/test.dart'; diff --git a/pkgs/json_rpc_2/test/client/utils.dart b/pkgs/json_rpc_2/test/client/utils.dart index e759fb1ad..868489235 100644 --- a/pkgs/json_rpc_2/test/client/utils.dart +++ b/pkgs/json_rpc_2/test/client/utils.dart @@ -2,8 +2,6 @@ // 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. -library json_rpc_2.test.client.utils; - import 'dart:async'; import 'dart:convert'; diff --git a/pkgs/json_rpc_2/test/peer_test.dart b/pkgs/json_rpc_2/test/peer_test.dart index c51987386..7008b724a 100644 --- a/pkgs/json_rpc_2/test/peer_test.dart +++ b/pkgs/json_rpc_2/test/peer_test.dart @@ -2,8 +2,6 @@ // 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. -library json_rpc_2.test.client.client_test; - import 'dart:async'; import 'dart:convert'; diff --git a/pkgs/json_rpc_2/test/server/batch_test.dart b/pkgs/json_rpc_2/test/server/batch_test.dart index 704ea6e59..d7bf79d4f 100644 --- a/pkgs/json_rpc_2/test/server/batch_test.dart +++ b/pkgs/json_rpc_2/test/server/batch_test.dart @@ -2,8 +2,6 @@ // 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. -library json_rpc_2.test.server.batch_test; - import 'dart:convert'; import 'package:test/test.dart'; diff --git a/pkgs/json_rpc_2/test/server/invalid_request_test.dart b/pkgs/json_rpc_2/test/server/invalid_request_test.dart index aa00fb8e9..cd6f014cc 100644 --- a/pkgs/json_rpc_2/test/server/invalid_request_test.dart +++ b/pkgs/json_rpc_2/test/server/invalid_request_test.dart @@ -2,8 +2,6 @@ // 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. -library json_rpc_2.test.server.invalid_request_test; - import 'dart:convert'; import 'package:test/test.dart'; diff --git a/pkgs/json_rpc_2/test/server/parameters_test.dart b/pkgs/json_rpc_2/test/server/parameters_test.dart index a5186980d..e7cb9f265 100644 --- a/pkgs/json_rpc_2/test/server/parameters_test.dart +++ b/pkgs/json_rpc_2/test/server/parameters_test.dart @@ -2,8 +2,6 @@ // 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. -library json_rpc_2.test.server.parameters_test; - import 'package:test/test.dart'; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; diff --git a/pkgs/json_rpc_2/test/server/server_test.dart b/pkgs/json_rpc_2/test/server/server_test.dart index 357333222..8cc9c5a29 100644 --- a/pkgs/json_rpc_2/test/server/server_test.dart +++ b/pkgs/json_rpc_2/test/server/server_test.dart @@ -2,8 +2,6 @@ // 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. -library json_rpc_2.test.server.server_test; - import 'dart:convert'; import 'package:test/test.dart'; diff --git a/pkgs/json_rpc_2/test/server/stream_test.dart b/pkgs/json_rpc_2/test/server/stream_test.dart index 8fd12396f..58c5e62ac 100644 --- a/pkgs/json_rpc_2/test/server/stream_test.dart +++ b/pkgs/json_rpc_2/test/server/stream_test.dart @@ -2,8 +2,6 @@ // 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. -library json_rpc_2.test.server.stream_test; - import 'dart:async'; import 'package:test/test.dart'; diff --git a/pkgs/json_rpc_2/test/server/utils.dart b/pkgs/json_rpc_2/test/server/utils.dart index fabef1f9f..9b4b020c5 100644 --- a/pkgs/json_rpc_2/test/server/utils.dart +++ b/pkgs/json_rpc_2/test/server/utils.dart @@ -2,8 +2,6 @@ // 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. -library json_rpc_2.test.server.util; - import 'dart:async'; import 'dart:convert'; From 0fc2fc448ed10081f4ec3a162d1ad40a5c6410d0 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 2 Feb 2016 12:21:41 -0800 Subject: [PATCH 025/127] Use StreamChannel. This converts the constructors to take StreamChannels, and changes some edge-case semantics to be more familiar to StreamChannel (and WebSocket) users. R=rnystrom@google.com Review URL: https://codereview.chromium.org//1652413002 . --- pkgs/json_rpc_2/CHANGELOG.md | 18 ++ pkgs/json_rpc_2/README.md | 178 +++++++++---------- pkgs/json_rpc_2/lib/src/channel_manager.dart | 79 ++++++++ pkgs/json_rpc_2/lib/src/client.dart | 63 ++++--- pkgs/json_rpc_2/lib/src/peer.dart | 69 +++---- pkgs/json_rpc_2/lib/src/server.dart | 94 +++++----- pkgs/json_rpc_2/lib/src/two_way_stream.dart | 135 -------------- pkgs/json_rpc_2/lib/src/utils.dart | 33 ++++ pkgs/json_rpc_2/pubspec.yaml | 6 +- pkgs/json_rpc_2/test/client/stream_test.dart | 46 ++--- pkgs/json_rpc_2/test/client/utils.dart | 6 +- pkgs/json_rpc_2/test/peer_test.dart | 6 +- pkgs/json_rpc_2/test/server/stream_test.dart | 45 ++--- pkgs/json_rpc_2/test/server/utils.dart | 6 +- 14 files changed, 360 insertions(+), 424 deletions(-) create mode 100644 pkgs/json_rpc_2/lib/src/channel_manager.dart delete mode 100644 pkgs/json_rpc_2/lib/src/two_way_stream.dart diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 524cbad6a..649c738b2 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,21 @@ +## 2.0.0 + +* **Breaking change:** all constructors now take a `StreamChannel` rather than a + `Stream`/`StreamSink` pair. + +* `Client.sendRequest()` and `Client.sendNotification()` no longer throw + `StateError`s after the connection has been closed but before `Client.close()` + has been called. + +* The various `close()` methods may now be called before their corresponding + `listen()` methods. + +* The various `close()` methods now wait on the result of closing the underlying + `StreamSink`. Be aware that [in some circumstances][issue 19095] + `StreamController`s' `Sink.close()` futures may never complete. + +[issue 19095]: https://github.com/dart-lang/sdk/issues/19095 + ## 1.2.0 * Add `Client.isClosed` and `Server.isClosed`, which make it possible to diff --git a/pkgs/json_rpc_2/README.md b/pkgs/json_rpc_2/README.md index 67afc3121..8078fa273 100644 --- a/pkgs/json_rpc_2/README.md +++ b/pkgs/json_rpc_2/README.md @@ -9,68 +9,66 @@ These methods can be registered using `Server.registerMethod`: ```dart import "package:json_rpc_2/json_rpc_2.dart" as json_rpc; +import "package:stream_channel/stream_channel.dart"; + +main() async { + var socket = await WebSocket.connect('ws://localhost:4321'); + var server = new json_rpc.Server(new StreamChannel(socket, socket)); + + // Any string may be used as a method name. JSON-RPC 2.0 methods are + // case-sensitive. + var i = 0; + server.registerMethod("count", () { + // Just return the value to be sent as a response to the client. This can + // be anything JSON-serializable, or a Future that completes to something + // JSON-serializable. + return i++; + }); + + // Methods can take parameters. They're presented as a [Parameters] object + // which makes it easy to validate that the expected parameters exist. + server.registerMethod("echo", (params) { + // If the request doesn't have a "message" parameter, this will + // automatically send a response notifying the client that the request + // was invalid. + return params.getNamed("message"); + }); + + // [Parameters] has methods for verifying argument types. + server.registerMethod("subtract", (params) { + // If "minuend" or "subtrahend" aren't numbers, this will reject the + // request. + return params.getNum("minuend") - params.getNum("subtrahend"); + }); -void main() { - WebSocket.connect('ws://localhost:4321').then((socket) { - // You can start the server with a Stream for requests and a StreamSink for - // responses, or with an object that's both, like a WebSocket. - var server = new json_rpc.Server(socket); - - // Any string may be used as a method name. JSON-RPC 2.0 methods are - // case-sensitive. - var i = 0; - server.registerMethod("count", () { - // Just return the value to be sent as a response to the client. This can - // be anything JSON-serializable, or a Future that completes to something - // JSON-serializable. - return i++; - }); - - // Methods can take parameters. They're presented as a [Parameters] object - // which makes it easy to validate that the expected parameters exist. - server.registerMethod("echo", (params) { - // If the request doesn't have a "message" parameter, this will - // automatically send a response notifying the client that the request - // was invalid. - return params.getNamed("message"); - }); - - // [Parameters] has methods for verifying argument types. - server.registerMethod("subtract", (params) { - // If "minuend" or "subtrahend" aren't numbers, this will reject the - // request. - return params.getNum("minuend") - params.getNum("subtrahend"); - }); - - // [Parameters] also supports optional arguments. - server.registerMethod("sort", (params) { - var list = params.getList("list"); - list.sort(); - if (params.getBool("descending", orElse: () => false)) { - return params.list.reversed; - } else { - return params.list; - } - }); - - // A method can send an error response by throwing a - // `json_rpc.RpcException`. Any positive number may be used as an - // application- defined error code. - const DIVIDE_BY_ZERO = 1; - server.registerMethod("divide", (params) { - var divisor = params.getNum("divisor"); - if (divisor == 0) { - throw new json_rpc.RpcException( - DIVIDE_BY_ZERO, "Cannot divide by zero."); - } - - return params.getNum("dividend") / divisor; - }); - - // To give you time to register all your methods, the server won't actually - // start listening for requests until you call `listen`. - server.listen(); + // [Parameters] also supports optional arguments. + server.registerMethod("sort", (params) { + var list = params.getList("list"); + list.sort(); + if (params.getBool("descending", orElse: () => false)) { + return params.list.reversed; + } else { + return params.list; + } }); + + // A method can send an error response by throwing a + // `json_rpc.RpcException`. Any positive number may be used as an + // application- defined error code. + const DIVIDE_BY_ZERO = 1; + server.registerMethod("divide", (params) { + var divisor = params.getNum("divisor"); + if (divisor == 0) { + throw new json_rpc.RpcException( + DIVIDE_BY_ZERO, "Cannot divide by zero."); + } + + return params.getNum("dividend") / divisor; + }); + + // To give you time to register all your methods, the server won't actually + // start listening for requests until you call `listen`. + server.listen(); } ``` @@ -82,38 +80,36 @@ responses to those method calls. These methods can be called using ```dart import "package:json_rpc_2/json_rpc_2.dart" as json_rpc; - -void main() { - WebSocket.connect('ws://localhost:4321').then((socket) { - // Just like the server, a client takes a Stream and a StreamSink or a - // single object that's both. - var client = new json_rpc.Client(socket); - - // This calls the "count" method on the server. A Future is returned that - // will complete to the value contained in the server's response. - client.sendRequest("count").then((result) => print("Count is $result.")); - - // Parameters are passed as a simple Map or, for positional parameters, an - // Iterable. Make sure they're JSON-serializable! - client.sendRequest("echo", {"message": "hello"}) - .then((echo) => print('Echo says "$echo"!')); - - // A notification is a way to call a method that tells the server that no - // result is expected. Its return type is `void`; even if it causes an - // error, you won't hear back. - client.sendNotification("count"); - - // If the server sends an error response, the returned Future will complete - // with an RpcException. You can catch this error and inspect its error - // code, message, and any data that the server sent along with it. - client.sendRequest("divide", {"dividend": 2, "divisor": 0}) - .catchError((error) { - print("RPC error ${error.code}: ${error.message}"); - }); - - // The client won't subscribe to the input stream until you call `listen`. - client.listen(); +import "package:stream_channel/stream_channel.dart"; + +main() async { + var socket = await WebSocket.connect('ws://localhost:4321'); + var client = new json_rpc.Client(new StreamChannel(socket, socket)); + + // This calls the "count" method on the server. A Future is returned that + // will complete to the value contained in the server's response. + client.sendRequest("count").then((result) => print("Count is $result.")); + + // Parameters are passed as a simple Map or, for positional parameters, an + // Iterable. Make sure they're JSON-serializable! + client.sendRequest("echo", {"message": "hello"}) + .then((echo) => print('Echo says "$echo"!')); + + // A notification is a way to call a method that tells the server that no + // result is expected. Its return type is `void`; even if it causes an + // error, you won't hear back. + client.sendNotification("count"); + + // If the server sends an error response, the returned Future will complete + // with an RpcException. You can catch this error and inspect its error + // code, message, and any data that the server sent along with it. + client.sendRequest("divide", {"dividend": 2, "divisor": 0}) + .catchError((error) { + print("RPC error ${error.code}: ${error.message}"); }); + + // The client won't subscribe to the input stream until you call `listen`. + client.listen(); } ``` diff --git a/pkgs/json_rpc_2/lib/src/channel_manager.dart b/pkgs/json_rpc_2/lib/src/channel_manager.dart new file mode 100644 index 000000000..35d75a608 --- /dev/null +++ b/pkgs/json_rpc_2/lib/src/channel_manager.dart @@ -0,0 +1,79 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// 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. + +import 'dart:async'; + +import 'package:stream_channel/stream_channel.dart'; + +/// Wraps a [StreamChannel] and handles logic that's shared between [Server], +/// [Client], and [Peer]. +/// +/// These classes don't provide the user direct access to a +/// [StreamSubscription]. Instead, they use the future returned by [listen] to +/// notify the user of the remote endpoint closing or producing an error. +class ChannelManager { + /// The name of the component whose channel is wrapped (e.g. "Server"). + /// + /// Used for error reporting. + final String _name; + + /// The underlying channel. + final StreamChannel _channel; + + /// Returns a [Future] that completes when the connection is closed. + /// + /// This is the same future that's returned by [listen]. + Future get done => _doneCompleter.future; + final _doneCompleter = new Completer(); + + /// Whether the underlying communication channel is closed. + bool get isClosed => _doneCompleter.isCompleted; + + /// Whether [listen] has been called. + bool _listenCalled = false; + + /// Whether [close] has been called. + /// + /// Note that [isClosed] tracks whether the underlying connection is closed, + /// whereas this tracks only whether it was explicitly closed from this end. + bool _closeCalled = false; + + ChannelManager(this._name, this._channel); + + /// Starts listening to the channel. + /// + /// The returned Future will complete when the input stream is closed. If the + /// input stream emits an error, that will be piped to the returned Future. + Future listen(void handleInput(input)) { + if (_listenCalled) { + throw new StateError("Can only call $_name.listen() once."); + } + _listenCalled = true; + + _channel.stream.listen(handleInput, + onError: (error, stackTrace) { + _doneCompleter.completeError(error, stackTrace); + _channel.sink.close(); + }, + onDone: _doneCompleter.complete, + cancelOnError: true); + + return done; + } + + /// Emit [event]. + void add(event) { + if (isClosed && !_closeCalled) return; + _channel.sink.add(event); + } + + /// Closes the channel. + Future close() { + _closeCalled = true; + if (!_doneCompleter.isCompleted) { + _doneCompleter.complete(_channel.sink.close()); + } + return done; + } +} diff --git a/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart index e75e7f38c..5df21e438 100644 --- a/pkgs/json_rpc_2/lib/src/client.dart +++ b/pkgs/json_rpc_2/lib/src/client.dart @@ -5,9 +5,10 @@ import 'dart:async'; import 'package:stack_trace/stack_trace.dart'; +import 'package:stream_channel/stream_channel.dart'; +import 'channel_manager.dart'; import 'exception.dart'; -import 'two_way_stream.dart'; import 'utils.dart'; /// A JSON-RPC 2.0 client. @@ -16,7 +17,7 @@ import 'utils.dart'; /// those method calls. Methods can be called with [sendRequest], or with /// [sendNotification] if no response is expected. class Client { - final TwoWayStream _streams; + final ChannelManager _manager; /// The next request id. var _id = 0; @@ -29,55 +30,53 @@ class Client { /// The map of request ids to pending requests. final _pendingRequests = new Map(); - /// Returns a [Future] that completes when the connection is closed. + /// Returns a [Future] that completes when the underlying connection is + /// closed. /// - /// This is the same future that's returned by [listen]. - Future get done => _streams.done; + /// This is the same future that's returned by [listen] and [close]. It may + /// complete before [close] is called if the remote endpoint closes the + /// connection. + Future get done => _manager.done; - /// Whether the connection is closed. - bool get isClosed => _streams.isClosed; - - /// Creates a [Client] that writes requests to [requests] and reads responses - /// from [responses]. + /// Whether the underlying connection is closed. /// - /// If [responses] is a [StreamSink] as well as a [Stream] (for example, a - /// `WebSocket`), [requests] may be omitted. + /// Note that this will be `true` before [close] is called if the remote + /// endpoint closes the connection. + bool get isClosed => _manager.isClosed; + + /// Creates a [Client] that communicates over [channel]. /// /// Note that the client won't begin listening to [responses] until /// [Client.listen] is called. - Client(Stream responses, [StreamSink requests]) - : _streams = new TwoWayStream( - "Client", responses, "responses", requests, "requests"); + Client(StreamChannel channel) + : this.withoutJson(channel + .transform(jsonDocument) + .transformStream(ignoreFormatExceptions)); - /// Creates a [Client] that writes decoded responses to [responses] and reads - /// decoded requests from [requests]. + /// Creates a [Client] that communicates using decoded messages over + /// [channel]. /// /// Unlike [new Client], this doesn't read or write JSON strings. Instead, it /// reads and writes decoded maps or lists. /// - /// If [responses] is a [StreamSink] as well as a [Stream], [requests] may be - /// omitted. - /// /// Note that the client won't begin listening to [responses] until /// [Client.listen] is called. - Client.withoutJson(Stream responses, [StreamSink requests]) - : _streams = new TwoWayStream.withoutJson( - "Client", responses, "responses", requests, "requests"); + Client.withoutJson(StreamChannel channel) + : _manager = new ChannelManager("Client", channel); /// Starts listening to the underlying stream. /// - /// Returns a [Future] that will complete when the stream is closed or when it - /// has an error. + /// Returns a [Future] that will complete when the connection is closed or + /// when it has an error. This is the same as [done]. /// /// [listen] may only be called once. - Future listen() => _streams.listen(_handleResponse); + Future listen() => _manager.listen(_handleResponse); - /// Closes the server's request sink and response subscription. + /// Closes the underlying connection. /// /// Returns a [Future] that completes when all resources have been released. - /// - /// A client can't be closed before [listen] has been called. - Future close() => _streams.close(); + /// This is the same as [done]. + Future close() => _manager.close(); /// Sends a JSON-RPC 2 request to invoke the given [method]. /// @@ -132,7 +131,7 @@ class Client { if (_batch != null) { _batch.add(message); } else { - _streams.add(message); + _manager.add(message); } } @@ -153,7 +152,7 @@ class Client { _batch = []; return tryFinally(callback, () { - _streams.add(_batch); + _manager.add(_batch); _batch = null; }); } diff --git a/pkgs/json_rpc_2/lib/src/peer.dart b/pkgs/json_rpc_2/lib/src/peer.dart index a6707e255..810d173b2 100644 --- a/pkgs/json_rpc_2/lib/src/peer.dart +++ b/pkgs/json_rpc_2/lib/src/peer.dart @@ -4,12 +4,13 @@ import 'dart:async'; -import '../error_code.dart' as error_code; +import 'package:stream_channel/stream_channel.dart'; + +import 'channel_manager.dart'; import 'client.dart'; -import 'exception.dart'; import 'parameters.dart'; import 'server.dart'; -import 'two_way_stream.dart'; +import 'utils.dart'; /// A JSON-RPC 2.0 client *and* server. /// @@ -17,7 +18,7 @@ import 'two_way_stream.dart'; /// 2.0 endpoint. It sends both requests and responses across the same /// communication channel and expects to connect to a peer that does the same. class Peer implements Client, Server { - TwoWayStream _streams; + final ChannelManager _manager; /// The underlying client that handles request-sending and response-receiving /// logic. @@ -35,55 +36,31 @@ class Peer implements Client, Server { /// they're responses. final _clientIncomingForwarder = new StreamController(sync: true); - /// A stream controller that forwards outgoing messages from both [_server] - /// and [_client]. - final _outgoingForwarder = new StreamController(sync: true); - - Future get done => _streams.done; - bool get isClosed => _streams.isClosed; + Future get done => _manager.done; + bool get isClosed => _manager.isClosed; - /// Creates a [Peer] that reads incoming messages from [incoming] and writes - /// outgoing messages to [outgoing]. + /// Creates a [Peer] that communicates over [channel]. /// - /// If [incoming] is a [StreamSink] as well as a [Stream] (for example, a - /// `WebSocket`), [outgoing] may be omitted. - /// - /// Note that the peer won't begin listening to [incoming] until [Peer.listen] + /// Note that the peer won't begin listening to [channel] until [Peer.listen] /// is called. - Peer(Stream incoming, [StreamSink outgoing]) { - _streams = new TwoWayStream("Peer", incoming, "incoming", - outgoing, "outgoing", onInvalidInput: (message, error) { - _streams.add(new RpcException(error_code.PARSE_ERROR, - 'Invalid JSON: ${error.message}').serialize(message)); - }); + Peer(StreamChannel channel) + : this.withoutJson(channel + .transform(jsonDocument) + .transform(respondToFormatExceptions)); - _outgoingForwarder.stream.listen(_streams.add); - _server = new Server.withoutJson( - _serverIncomingForwarder.stream, _outgoingForwarder); - _client = new Client.withoutJson( - _clientIncomingForwarder.stream, _outgoingForwarder); - } - - /// Creates a [Peer] that reads incoming decoded messages from [incoming] and - /// writes outgoing decoded messages to [outgoing]. + /// Creates a [Peer] that communicates using decoded messages over [channel]. /// /// Unlike [new Peer], this doesn't read or write JSON strings. Instead, it /// reads and writes decoded maps or lists. /// - /// If [incoming] is a [StreamSink] as well as a [Stream], [outgoing] may be - /// omitted. - /// - /// Note that the peer won't begin listening to [incoming] until + /// Note that the peer won't begin listening to [channel] until /// [Peer.listen] is called. - Peer.withoutJson(Stream incoming, [StreamSink outgoing]) { - _streams = new TwoWayStream.withoutJson("Peer", incoming, "incoming", - outgoing, "outgoing"); - - _outgoingForwarder.stream.listen(_streams.add); - _server = new Server.withoutJson( - _serverIncomingForwarder.stream, _outgoingForwarder); - _client = new Client.withoutJson( - _clientIncomingForwarder.stream, _outgoingForwarder); + Peer.withoutJson(StreamChannel channel) + : _manager = new ChannelManager("Peer", channel) { + _server = new Server.withoutJson(new StreamChannel( + _serverIncomingForwarder.stream, channel.sink)); + _client = new Client.withoutJson(new StreamChannel( + _clientIncomingForwarder.stream, channel.sink)); } // Client methods. @@ -109,7 +86,7 @@ class Peer implements Client, Server { Future listen() { _client.listen(); _server.listen(); - return _streams.listen((message) { + return _manager.listen((message) { if (message is Map) { if (message.containsKey('result') || message.containsKey('error')) { _clientIncomingForwarder.add(message); @@ -133,5 +110,5 @@ class Peer implements Client, Server { } Future close() => - Future.wait([_client.close(), _server.close(), _streams.close()]); + Future.wait([_client.close(), _server.close(), _manager.close()]); } diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index 06dd0b9e3..3ef59ed0f 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -7,11 +7,12 @@ import 'dart:collection'; import 'dart:convert'; import 'package:stack_trace/stack_trace.dart'; +import 'package:stream_channel/stream_channel.dart'; import '../error_code.dart' as error_code; +import 'channel_manager.dart'; import 'exception.dart'; import 'parameters.dart'; -import 'two_way_stream.dart'; import 'utils.dart'; /// A JSON-RPC 2.0 server. @@ -25,7 +26,7 @@ import 'utils.dart'; /// asynchronously, it's possible for multiple methods to be invoked at the same /// time, or even for a single method to be invoked multiple times at once. class Server { - TwoWayStream _streams; + final ChannelManager _manager; /// The methods registered for this server. final _methods = new Map(); @@ -36,59 +37,53 @@ class Server { /// [RpcException.methodNotFound] exception. final _fallbacks = new Queue(); - /// Returns a [Future] that completes when the connection is closed. + /// Returns a [Future] that completes when the underlying connection is + /// closed. /// - /// This is the same future that's returned by [listen]. - Future get done => _streams.done; + /// This is the same future that's returned by [listen] and [close]. It may + /// complete before [close] is called if the remote endpoint closes the + /// connection. + Future get done => _manager.done; - /// Whether the connection is closed. - bool get isClosed => _streams.isClosed; - - /// Creates a [Server] that reads requests from [requests] and writes - /// responses to [responses]. + /// Whether the underlying connection is closed. /// - /// If [requests] is a [StreamSink] as well as a [Stream] (for example, a - /// `WebSocket`), [responses] may be omitted. + /// Note that this will be `true` before [close] is called if the remote + /// endpoint closes the connection. + bool get isClosed => _manager.isClosed; + + /// Creates a [Server] that communicates over [channel]. /// /// Note that the server won't begin listening to [requests] until /// [Server.listen] is called. - Server(Stream requests, [StreamSink responses]) { - _streams = new TwoWayStream("Server", requests, "requests", - responses, "responses", onInvalidInput: (message, error) { - _streams.add(new RpcException(error_code.PARSE_ERROR, - 'Invalid JSON: ${error.message}').serialize(message)); - }); - } + Server(StreamChannel channel) + : this.withoutJson(channel + .transform(jsonDocument) + .transform(respondToFormatExceptions)); - /// Creates a [Server] that reads decoded requests from [requests] and writes - /// decoded responses to [responses]. + /// Creates a [Server] that communicates using decoded messages over + /// [channel]. /// /// Unlike [new Server], this doesn't read or write JSON strings. Instead, it /// reads and writes decoded maps or lists. /// - /// If [requests] is a [StreamSink] as well as a [Stream], [responses] may be - /// omitted. - /// /// Note that the server won't begin listening to [requests] until /// [Server.listen] is called. - Server.withoutJson(Stream requests, [StreamSink responses]) - : _streams = new TwoWayStream.withoutJson( - "Server", requests, "requests", responses, "responses"); + Server.withoutJson(StreamChannel channel) + : _manager = new ChannelManager("Server", channel); /// Starts listening to the underlying stream. /// - /// Returns a [Future] that will complete when the stream is closed or when it - /// has an error. + /// Returns a [Future] that will complete when the connection is closed or + /// when it has an error. This is the same as [done]. /// /// [listen] may only be called once. - Future listen() => _streams.listen(_handleRequest); + Future listen() => _manager.listen(_handleRequest); - /// Closes the server's request subscription and response sink. + /// Closes the underlying connection. /// /// Returns a [Future] that completes when all resources have been released. - /// - /// A server can't be closed before [listen] has been called. - Future close() => _streams.close(); + /// This is the same as [done]. + Future close() => _manager.close(); /// Registers a method named [name] on this server. /// @@ -129,21 +124,24 @@ class Server { /// 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) { - return syncFuture(() { - if (request is! List) return _handleSingleRequest(request); - if (request.isEmpty) { - return new RpcException(error_code.INVALID_REQUEST, 'A batch must ' - 'contain at least one request.').serialize(request); - } + Future _handleRequest(request) async { + var response; + if (request is! List) { + response = await _handleSingleRequest(request); + if (response == null) return; + } else if (request.isEmpty) { + response = new RpcException( + error_code.INVALID_REQUEST, + 'A batch must contain at least one request.') + .serialize(request); + } else { + var results = await Future.wait(request.map(_handleSingleRequest)); + var nonNull = results.where((result) => result != null); + if (nonNull.isEmpty) return; + response = nonNull.toList(); + } - return Future.wait(request.map(_handleSingleRequest)).then((results) { - var nonNull = results.where((result) => result != null); - return nonNull.isEmpty ? null : nonNull.toList(); - }); - }).then((response) { - if (!_streams.isClosed && response != null) _streams.add(response); - }); + if (!isClosed) _manager.add(response); } /// Handles an individual parsed request. diff --git a/pkgs/json_rpc_2/lib/src/two_way_stream.dart b/pkgs/json_rpc_2/lib/src/two_way_stream.dart deleted file mode 100644 index 4f20686b5..000000000 --- a/pkgs/json_rpc_2/lib/src/two_way_stream.dart +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file -// 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. - -import 'dart:async'; -import 'dart:convert'; - -import 'utils.dart'; - -/// A class for managing a stream of input messages and a sink for output -/// messages. -/// -/// This contains stream logic that's shared between [Server] and [Client]. -class TwoWayStream { - /// The name of the component whose streams are being managed (e.g. "Server"). - /// - /// Used for error reporting. - final String _name; - - /// The input stream. - /// - /// This is a stream of decoded JSON objects. - final Stream _input; - - /// The subscription to [_input]. - StreamSubscription _inputSubscription; - - /// The output sink. - /// - /// This takes decoded JSON objects. - final StreamSink _output; - - /// Returns a [Future] that completes when the connection is closed. - /// - /// This is the same future that's returned by [listen]. - Future get done => _doneCompleter.future; - final _doneCompleter = new Completer(); - - /// Whether the stream has been closed. - bool get isClosed => _doneCompleter.isCompleted; - - /// Creates a two-way stream. - /// - /// [input] and [output] should emit and take (respectively) JSON-encoded - /// strings. - /// - /// [inputName] is used in error messages as the name of the input parameter. - /// [outputName] is likewise used as the name of the output parameter. - /// - /// If [onInvalidInput] is passed, any errors parsing messages from [input] - /// are passed to it. Otherwise, they're ignored and the input is discarded. - factory TwoWayStream(String name, Stream input, String inputName, - StreamSink output, String outputName, - {void onInvalidInput(String message, FormatException error)}) { - if (output == null) { - if (input is! StreamSink) { - throw new ArgumentError("Either `$inputName` must be a StreamSink or " - "`$outputName` must be passed."); - } - output = input as StreamSink; - } - - var wrappedOutput = mapStreamSink(output, JSON.encode); - return new TwoWayStream.withoutJson(name, input.expand((message) { - var decodedMessage; - try { - decodedMessage = JSON.decode(message); - } on FormatException catch (error) { - if (onInvalidInput != null) onInvalidInput(message, error); - return []; - } - - return [decodedMessage]; - }), inputName, wrappedOutput, outputName); - } - - /// Creates a two-way stream that reads decoded input and writes decoded - /// responses. - /// - /// [input] and [output] should emit and take (respectively) decoded JSON - /// objects. - /// - /// [inputName] is used in error messages as the name of the input parameter. - /// [outputName] is likewise used as the name of the output parameter. - TwoWayStream.withoutJson(this._name, Stream input, String inputName, - StreamSink output, String outputName) - : _input = input, - _output = output == null && input is StreamSink ? input : output { - if (_output == null) { - throw new ArgumentError("Either `$inputName` must be a StreamSink or " - "`$outputName` must be passed."); - } - } - - /// Starts listening to the input stream. - /// - /// The returned Future will complete when the input stream is closed. If the - /// input stream emits an error, that will be piped to the returned Future. - Future listen(void handleInput(input)) { - if (_inputSubscription != null) { - throw new StateError("Can only call $_name.listen once."); - } - - _inputSubscription = _input.listen(handleInput, - onError: (error, stackTrace) { - if (_doneCompleter.isCompleted) return; - _doneCompleter.completeError(error, stackTrace); - _output.close(); - }, onDone: () { - if (_doneCompleter.isCompleted) return; - _doneCompleter.complete(); - _output.close(); - }, cancelOnError: true); - - return _doneCompleter.future; - } - - /// Emit [event] on the output stream. - void add(event) => _output.add(event); - - /// Stops listening to the input stream and closes the output stream. - Future close() { - if (_inputSubscription == null) { - throw new StateError("Can't call $_name.close before $_name.listen."); - } - - if (!_doneCompleter.isCompleted) _doneCompleter.complete(); - - var inputFuture = _inputSubscription.cancel(); - // TODO(nweiz): include the output future in the return value when issue - // 19095 is fixed. - _output.close(); - return inputFuture == null ? new Future.value() : inputFuture; - } -} diff --git a/pkgs/json_rpc_2/lib/src/utils.dart b/pkgs/json_rpc_2/lib/src/utils.dart index dde6b4af9..8871b56c2 100644 --- a/pkgs/json_rpc_2/lib/src/utils.dart +++ b/pkgs/json_rpc_2/lib/src/utils.dart @@ -5,6 +5,10 @@ import 'dart:async'; import 'package:stack_trace/stack_trace.dart'; +import 'package:stream_channel/stream_channel.dart'; + +import '../error_code.dart' as error_code; +import 'exception.dart'; typedef ZeroArgumentFunction(); @@ -64,6 +68,35 @@ tryFinally(body(), whenComplete()) { } } +/// A transformer that silently drops [FormatException]s. +final ignoreFormatExceptions = new StreamTransformer.fromHandlers( + handleError: (error, stackTrace, sink) { + if (error is FormatException) return; + sink.addError(error, stackTrace); +}); + +/// A transformer that sends error responses on [FormatException]s. +final StreamChannelTransformer respondToFormatExceptions = + new _RespondToFormatExceptionsTransformer(); + +/// The implementation of [respondToFormatExceptions]. +class _RespondToFormatExceptionsTransformer + implements StreamChannelTransformer { + StreamChannel bind(StreamChannel channel) { + var transformed; + transformed = channel.changeStream((stream) { + return stream.handleError((error) { + if (error is! FormatException) throw error; + + var exception = new RpcException( + error_code.PARSE_ERROR, 'Invalid JSON: ${error.message}'); + transformed.sink.add(exception.serialize(error.source)); + }); + }); + return transformed; + } +} + /// Returns a [StreamSink] that wraps [sink] and maps each event added using /// [callback]. StreamSink mapStreamSink(StreamSink sink, callback(event)) => diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 348dc35b1..630b9ccee 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,12 +1,12 @@ name: json_rpc_2 -version: 1.2.0 +version: 2.0.0 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://github.com/dart-lang/json_rpc_2 dependencies: stack_trace: '>=0.9.1 <2.0.0' + stream_channel: '^1.1.0' dev_dependencies: test: ">=0.12.0 <0.13.0" environment: - sdk: ">=1.2.0 <2.0.0" - + sdk: ">=1.8.0 <2.0.0" diff --git a/pkgs/json_rpc_2/test/client/stream_test.dart b/pkgs/json_rpc_2/test/client/stream_test.dart index 146e75359..b9a31c679 100644 --- a/pkgs/json_rpc_2/test/client/stream_test.dart +++ b/pkgs/json_rpc_2/test/client/stream_test.dart @@ -4,17 +4,25 @@ import 'dart:async'; +import 'package:stream_channel/stream_channel.dart'; import 'package:test/test.dart'; + import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'utils.dart'; void main() { + var responseController; + var requestController; + var client; + setUp(() { + responseController = new StreamController(); + requestController = new StreamController(); + client = new json_rpc.Client.withoutJson( + new StreamChannel(responseController.stream, requestController.sink)); + }); + test(".withoutJson supports decoded stream and sink", () { - var responseController = new StreamController(); - var requestController = new StreamController(); - var client = new json_rpc.Client.withoutJson( - responseController.stream, requestController.sink); client.listen(); expect(requestController.stream.first.then((request) { @@ -34,11 +42,6 @@ void main() { }); test(".listen returns when the controller is closed", () { - var responseController = new StreamController(); - var requestController = new StreamController(); - var client = new json_rpc.Client.withoutJson( - responseController.stream, requestController.sink); - var hasListenCompeted = false; expect(client.listen().then((_) => hasListenCompeted = true), completes); @@ -51,30 +54,18 @@ void main() { }); test(".listen returns a stream error", () { - var responseController = new StreamController(); - var requestController = new StreamController(); - var client = new json_rpc.Client( - responseController.stream, requestController.sink); - expect(client.listen(), throwsA('oh no')); responseController.addError('oh no'); }); test(".listen can't be called twice", () { - var responseController = new StreamController(); - var requestController = new StreamController(); - var client = new json_rpc.Client( - responseController.stream, requestController.sink); client.listen(); - expect(() => client.listen(), throwsStateError); }); test(".close cancels the stream subscription and closes the sink", () { - var responseController = new StreamController(); - var requestController = new StreamController(); - var client = new json_rpc.Client( - responseController.stream, requestController.sink); + // Work around sdk#19095. + requestController.stream.listen(null); expect(client.listen(), completes); @@ -85,13 +76,4 @@ void main() { expect(() => responseController.stream.listen((_) {}), throwsStateError); expect(requestController.isClosed, isTrue); }); - - test(".close can't be called before .listen", () { - var responseController = new StreamController(); - var requestController = new StreamController(); - var client = new json_rpc.Client( - responseController.stream, requestController.sink); - - expect(() => client.close(), throwsStateError); - }); } diff --git a/pkgs/json_rpc_2/test/client/utils.dart b/pkgs/json_rpc_2/test/client/utils.dart index 868489235..5eb0b6047 100644 --- a/pkgs/json_rpc_2/test/client/utils.dart +++ b/pkgs/json_rpc_2/test/client/utils.dart @@ -5,9 +5,11 @@ import 'dart:async'; import 'dart:convert'; +import 'package:stream_channel/stream_channel.dart'; +import 'package:test/test.dart'; + import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'package:json_rpc_2/error_code.dart' as error_code; -import 'package:test/test.dart'; /// A controller used to test a [json_rpc.Client]. class ClientController { @@ -23,7 +25,7 @@ class ClientController { ClientController() { _client = new json_rpc.Client( - _responseController.stream, _requestController.sink); + new StreamChannel(_responseController.stream, _requestController.sink)); _client.listen(); } diff --git a/pkgs/json_rpc_2/test/peer_test.dart b/pkgs/json_rpc_2/test/peer_test.dart index 7008b724a..89ab2d611 100644 --- a/pkgs/json_rpc_2/test/peer_test.dart +++ b/pkgs/json_rpc_2/test/peer_test.dart @@ -5,7 +5,9 @@ import 'dart:async'; import 'dart:convert'; +import 'package:stream_channel/stream_channel.dart'; import 'package:test/test.dart'; + import 'package:json_rpc_2/error_code.dart' as error_code; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; @@ -19,7 +21,7 @@ void main() { var outgoingController = new StreamController(); outgoing = outgoingController.stream; peer = new json_rpc.Peer.withoutJson( - incomingController.stream, outgoingController); + new StreamChannel(incomingController.stream, outgoingController)); }); group("like a client,", () { @@ -165,7 +167,7 @@ void main() { var incomingController = new StreamController(); var outgoingController = new StreamController(); var jsonPeer = new json_rpc.Peer( - incomingController.stream, outgoingController); + new StreamChannel(incomingController.stream, outgoingController)); expect(outgoingController.stream.first.then(JSON.decode), completion({ "jsonrpc": "2.0", diff --git a/pkgs/json_rpc_2/test/server/stream_test.dart b/pkgs/json_rpc_2/test/server/stream_test.dart index 58c5e62ac..7be00011b 100644 --- a/pkgs/json_rpc_2/test/server/stream_test.dart +++ b/pkgs/json_rpc_2/test/server/stream_test.dart @@ -4,17 +4,25 @@ import 'dart:async'; +import 'package:stream_channel/stream_channel.dart'; import 'package:test/test.dart'; + import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'utils.dart'; void main() { + var requestController; + var responseController; + var server; + setUp(() { + requestController = new StreamController(); + responseController = new StreamController(); + server = new json_rpc.Server.withoutJson( + new StreamChannel(requestController.stream, responseController.sink)); + }); + test(".withoutJson supports decoded stream and sink", () { - var requestController = new StreamController(); - var responseController = new StreamController(); - var server = new json_rpc.Server.withoutJson( - requestController.stream, responseController.sink); server.listen(); server.registerMethod('foo', (params) { @@ -36,11 +44,6 @@ void main() { }); test(".listen returns when the controller is closed", () { - var requestController = new StreamController(); - var responseController = new StreamController(); - var server = new json_rpc.Server( - requestController.stream, responseController.sink); - var hasListenCompeted = false; expect(server.listen().then((_) => hasListenCompeted = true), completes); @@ -53,30 +56,19 @@ void main() { }); test(".listen returns a stream error", () { - var requestController = new StreamController(); - var responseController = new StreamController(); - var server = new json_rpc.Server( - requestController.stream, responseController.sink); - expect(server.listen(), throwsA('oh no')); requestController.addError('oh no'); }); test(".listen can't be called twice", () { - var requestController = new StreamController(); - var responseController = new StreamController(); - var server = new json_rpc.Server( - requestController.stream, responseController.sink); server.listen(); expect(() => server.listen(), throwsStateError); }); test(".close cancels the stream subscription and closes the sink", () { - var requestController = new StreamController(); - var responseController = new StreamController(); - var server = new json_rpc.Server( - requestController.stream, responseController.sink); + // Work around sdk#19095. + responseController.stream.listen(null); expect(server.listen(), completes); @@ -87,13 +79,4 @@ void main() { expect(() => requestController.stream.listen((_) {}), throwsStateError); expect(responseController.isClosed, isTrue); }); - - test(".close can't be called before .listen", () { - var requestController = new StreamController(); - var responseController = new StreamController(); - var server = new json_rpc.Server( - requestController.stream, responseController.sink); - - expect(() => server.close(), throwsStateError); - }); } diff --git a/pkgs/json_rpc_2/test/server/utils.dart b/pkgs/json_rpc_2/test/server/utils.dart index 9b4b020c5..f33cb1398 100644 --- a/pkgs/json_rpc_2/test/server/utils.dart +++ b/pkgs/json_rpc_2/test/server/utils.dart @@ -5,9 +5,11 @@ import 'dart:async'; import 'dart:convert'; +import 'package:stream_channel/stream_channel.dart'; +import 'package:test/test.dart'; + import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'package:json_rpc_2/error_code.dart' as error_code; -import 'package:test/test.dart'; /// A controller used to test a [json_rpc.Server]. class ServerController { @@ -23,7 +25,7 @@ class ServerController { ServerController() { _server = new json_rpc.Server( - _requestController.stream, _responseController.sink); + new StreamChannel(_requestController.stream, _responseController.sink)); _server.listen(); } From 0c69288da661b7763ab316d018939a3cc63da8f6 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 2 Feb 2016 15:37:09 -0800 Subject: [PATCH 026/127] Fix the tests. It seems we tickled a core library inconsistency (dart-lang/sdkdart-lang/json_rpc_2#25655): although on the VM `FormatException.source` is set for JSON parse errors, it is not set using dart2js. R=rnystrom@google.com Review URL: https://codereview.chromium.org//1659313002 . --- pkgs/json_rpc_2/pubspec.yaml | 2 +- pkgs/json_rpc_2/test/peer_test.dart | 3 ++- pkgs/json_rpc_2/test/server/server_test.dart | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 630b9ccee..0d905468a 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.0.0 +version: 2.0.1-dev author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://github.com/dart-lang/json_rpc_2 diff --git a/pkgs/json_rpc_2/test/peer_test.dart b/pkgs/json_rpc_2/test/peer_test.dart index 89ab2d611..ad7ad22e0 100644 --- a/pkgs/json_rpc_2/test/peer_test.dart +++ b/pkgs/json_rpc_2/test/peer_test.dart @@ -174,7 +174,8 @@ void main() { "error": { 'code': error_code.PARSE_ERROR, "message": startsWith("Invalid JSON: "), - "data": {'request': '{invalid'} + // TODO(nweiz): Always expect the source when sdk#25655 is fixed. + "data": {'request': anyOf([isNull, '{invalid'])} }, "id": null })); diff --git a/pkgs/json_rpc_2/test/server/server_test.dart b/pkgs/json_rpc_2/test/server/server_test.dart index 8cc9c5a29..ef2e45bba 100644 --- a/pkgs/json_rpc_2/test/server/server_test.dart +++ b/pkgs/json_rpc_2/test/server/server_test.dart @@ -113,7 +113,8 @@ void main() { 'error': { 'code': error_code.PARSE_ERROR, 'message': startsWith("Invalid JSON: "), - 'data': {'request': 'invalid json {'} + // TODO(nweiz): Always expect the source when sdk#25655 is fixed. + 'data': {'request': anyOf([isNull, 'invalid json {'])} }, 'id': null }); From 26f649e5cb8cf565bf41e32407459470e49ae62b Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 8 Mar 2016 16:09:42 -0800 Subject: [PATCH 027/127] Use web_socket_channel in the README. Closes dart-lang/json_rpc_2#8 R=tjblasi@google.com Review URL: https://codereview.chromium.org//1780583002 . --- pkgs/json_rpc_2/README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkgs/json_rpc_2/README.md b/pkgs/json_rpc_2/README.md index 8078fa273..e49526404 100644 --- a/pkgs/json_rpc_2/README.md +++ b/pkgs/json_rpc_2/README.md @@ -10,10 +10,11 @@ These methods can be registered using `Server.registerMethod`: ```dart import "package:json_rpc_2/json_rpc_2.dart" as json_rpc; import "package:stream_channel/stream_channel.dart"; +import "package:web_socket_channel/io.dart"; main() async { - var socket = await WebSocket.connect('ws://localhost:4321'); - var server = new json_rpc.Server(new StreamChannel(socket, socket)); + var socket = IOWebSocketChannel.connect('ws://localhost:4321'); + var server = new json_rpc.Server(socket); // Any string may be used as a method name. JSON-RPC 2.0 methods are // case-sensitive. @@ -81,10 +82,11 @@ responses to those method calls. These methods can be called using ```dart import "package:json_rpc_2/json_rpc_2.dart" as json_rpc; import "package:stream_channel/stream_channel.dart"; +import "package:web_socket_channel/html.dart"; main() async { - var socket = await WebSocket.connect('ws://localhost:4321'); - var client = new json_rpc.Client(new StreamChannel(socket, socket)); + var socket = HtmlWebSocketChannel.connect('ws://localhost:4321'); + var client = new json_rpc.Client(socket); // This calls the "count" method on the server. A Future is returned that // will complete to the value contained in the server's response. From 6a37fb02e9e6d59e4275cae2ea0efb69a442ff6b Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 10 Mar 2016 07:01:16 -0800 Subject: [PATCH 028/127] Remove unused imports in the test files --- pkgs/json_rpc_2/test/client/client_test.dart | 1 - pkgs/json_rpc_2/test/client/utils.dart | 1 - pkgs/json_rpc_2/test/server/batch_test.dart | 5 +---- pkgs/json_rpc_2/test/server/invalid_request_test.dart | 3 --- 4 files changed, 1 insertion(+), 9 deletions(-) diff --git a/pkgs/json_rpc_2/test/client/client_test.dart b/pkgs/json_rpc_2/test/client/client_test.dart index 1553787ce..5601daac6 100644 --- a/pkgs/json_rpc_2/test/client/client_test.dart +++ b/pkgs/json_rpc_2/test/client/client_test.dart @@ -3,7 +3,6 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; -import 'dart:convert'; import 'package:test/test.dart'; import 'package:json_rpc_2/error_code.dart' as error_code; diff --git a/pkgs/json_rpc_2/test/client/utils.dart b/pkgs/json_rpc_2/test/client/utils.dart index 5eb0b6047..09a200c1d 100644 --- a/pkgs/json_rpc_2/test/client/utils.dart +++ b/pkgs/json_rpc_2/test/client/utils.dart @@ -9,7 +9,6 @@ import 'package:stream_channel/stream_channel.dart'; import 'package:test/test.dart'; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; -import 'package:json_rpc_2/error_code.dart' as error_code; /// A controller used to test a [json_rpc.Client]. class ClientController { diff --git a/pkgs/json_rpc_2/test/server/batch_test.dart b/pkgs/json_rpc_2/test/server/batch_test.dart index d7bf79d4f..11ec66e57 100644 --- a/pkgs/json_rpc_2/test/server/batch_test.dart +++ b/pkgs/json_rpc_2/test/server/batch_test.dart @@ -2,11 +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. -import 'dart:convert'; - import 'package:test/test.dart'; import 'package:json_rpc_2/error_code.dart' as error_code; -import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'utils.dart'; @@ -89,4 +86,4 @@ void main() { } }]))); }); -} \ No newline at end of file +} diff --git a/pkgs/json_rpc_2/test/server/invalid_request_test.dart b/pkgs/json_rpc_2/test/server/invalid_request_test.dart index cd6f014cc..cd54b6f36 100644 --- a/pkgs/json_rpc_2/test/server/invalid_request_test.dart +++ b/pkgs/json_rpc_2/test/server/invalid_request_test.dart @@ -2,11 +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. -import 'dart:convert'; - import 'package:test/test.dart'; import 'package:json_rpc_2/error_code.dart' as error_code; -import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'utils.dart'; From 16f0cbe78954615f1d3d706d79c0702fd150db58 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 10 Mar 2016 12:28:12 -0800 Subject: [PATCH 029/127] Revert "Merge pull request dart-lang/json_rpc_2#9 from srawlins/remove-unused-imports" This reverts commit e0459c8c981dd62425add930d329a501c5c52286, reversing changes made to 26f649e5cb8cf565bf41e32407459470e49ae62b. This commit seemed to cause the bots to go red under mysterious circumstances. Reverting to determine why. --- pkgs/json_rpc_2/test/client/client_test.dart | 1 + pkgs/json_rpc_2/test/client/utils.dart | 1 + pkgs/json_rpc_2/test/server/batch_test.dart | 5 ++++- pkgs/json_rpc_2/test/server/invalid_request_test.dart | 3 +++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pkgs/json_rpc_2/test/client/client_test.dart b/pkgs/json_rpc_2/test/client/client_test.dart index 5601daac6..1553787ce 100644 --- a/pkgs/json_rpc_2/test/client/client_test.dart +++ b/pkgs/json_rpc_2/test/client/client_test.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; import 'package:test/test.dart'; import 'package:json_rpc_2/error_code.dart' as error_code; diff --git a/pkgs/json_rpc_2/test/client/utils.dart b/pkgs/json_rpc_2/test/client/utils.dart index 09a200c1d..5eb0b6047 100644 --- a/pkgs/json_rpc_2/test/client/utils.dart +++ b/pkgs/json_rpc_2/test/client/utils.dart @@ -9,6 +9,7 @@ import 'package:stream_channel/stream_channel.dart'; import 'package:test/test.dart'; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; +import 'package:json_rpc_2/error_code.dart' as error_code; /// A controller used to test a [json_rpc.Client]. class ClientController { diff --git a/pkgs/json_rpc_2/test/server/batch_test.dart b/pkgs/json_rpc_2/test/server/batch_test.dart index 11ec66e57..d7bf79d4f 100644 --- a/pkgs/json_rpc_2/test/server/batch_test.dart +++ b/pkgs/json_rpc_2/test/server/batch_test.dart @@ -2,8 +2,11 @@ // 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. +import 'dart:convert'; + import 'package:test/test.dart'; import 'package:json_rpc_2/error_code.dart' as error_code; +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'utils.dart'; @@ -86,4 +89,4 @@ void main() { } }]))); }); -} +} \ No newline at end of file diff --git a/pkgs/json_rpc_2/test/server/invalid_request_test.dart b/pkgs/json_rpc_2/test/server/invalid_request_test.dart index cd54b6f36..cd6f014cc 100644 --- a/pkgs/json_rpc_2/test/server/invalid_request_test.dart +++ b/pkgs/json_rpc_2/test/server/invalid_request_test.dart @@ -2,8 +2,11 @@ // 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. +import 'dart:convert'; + import 'package:test/test.dart'; import 'package:json_rpc_2/error_code.dart' as error_code; +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'utils.dart'; From 9d8241200fe92eb4cebfe6f13a9748a3d0cc8a58 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 10 Mar 2016 12:37:39 -0800 Subject: [PATCH 030/127] Re-apply "Merge pull request dart-lang/json_rpc_2#9 from srawlins/remove-unused-imports" This reverts commit 16f0cbe78954615f1d3d706d79c0702fd150db58 and re-applies commit e0459c8c981dd62425add930d329a501c5c52286. It was determined that the build failure was caused by an upgraded SDK version. --- pkgs/json_rpc_2/test/client/client_test.dart | 1 - pkgs/json_rpc_2/test/client/utils.dart | 1 - pkgs/json_rpc_2/test/server/batch_test.dart | 5 +---- pkgs/json_rpc_2/test/server/invalid_request_test.dart | 3 --- 4 files changed, 1 insertion(+), 9 deletions(-) diff --git a/pkgs/json_rpc_2/test/client/client_test.dart b/pkgs/json_rpc_2/test/client/client_test.dart index 1553787ce..5601daac6 100644 --- a/pkgs/json_rpc_2/test/client/client_test.dart +++ b/pkgs/json_rpc_2/test/client/client_test.dart @@ -3,7 +3,6 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; -import 'dart:convert'; import 'package:test/test.dart'; import 'package:json_rpc_2/error_code.dart' as error_code; diff --git a/pkgs/json_rpc_2/test/client/utils.dart b/pkgs/json_rpc_2/test/client/utils.dart index 5eb0b6047..09a200c1d 100644 --- a/pkgs/json_rpc_2/test/client/utils.dart +++ b/pkgs/json_rpc_2/test/client/utils.dart @@ -9,7 +9,6 @@ import 'package:stream_channel/stream_channel.dart'; import 'package:test/test.dart'; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; -import 'package:json_rpc_2/error_code.dart' as error_code; /// A controller used to test a [json_rpc.Client]. class ClientController { diff --git a/pkgs/json_rpc_2/test/server/batch_test.dart b/pkgs/json_rpc_2/test/server/batch_test.dart index d7bf79d4f..11ec66e57 100644 --- a/pkgs/json_rpc_2/test/server/batch_test.dart +++ b/pkgs/json_rpc_2/test/server/batch_test.dart @@ -2,11 +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. -import 'dart:convert'; - import 'package:test/test.dart'; import 'package:json_rpc_2/error_code.dart' as error_code; -import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'utils.dart'; @@ -89,4 +86,4 @@ void main() { } }]))); }); -} \ No newline at end of file +} diff --git a/pkgs/json_rpc_2/test/server/invalid_request_test.dart b/pkgs/json_rpc_2/test/server/invalid_request_test.dart index cd6f014cc..cd54b6f36 100644 --- a/pkgs/json_rpc_2/test/server/invalid_request_test.dart +++ b/pkgs/json_rpc_2/test/server/invalid_request_test.dart @@ -2,11 +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. -import 'dart:convert'; - import 'package:test/test.dart'; import 'package:json_rpc_2/error_code.dart' as error_code; -import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'utils.dart'; From d2b6da781f68a6f066eaa737ea33cb59fa91d217 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 14 Apr 2016 15:18:34 -0700 Subject: [PATCH 031/127] Fix a race condition. If the peer was closed and then the channel closed, we would double-complete a completer. R=jmesserly@google.com Review URL: https://codereview.chromium.org//1890003002 . --- pkgs/json_rpc_2/CHANGELOG.md | 5 +++++ pkgs/json_rpc_2/lib/src/channel_manager.dart | 4 +++- pkgs/json_rpc_2/pubspec.yaml | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 649c738b2..feb1001e9 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.0.1 + +* Fix a race condition in which a `StateError` could be top-leveled if + `Peer.close()` was called before the underlying channel closed. + ## 2.0.0 * **Breaking change:** all constructors now take a `StreamChannel` rather than a diff --git a/pkgs/json_rpc_2/lib/src/channel_manager.dart b/pkgs/json_rpc_2/lib/src/channel_manager.dart index 35d75a608..8f6687073 100644 --- a/pkgs/json_rpc_2/lib/src/channel_manager.dart +++ b/pkgs/json_rpc_2/lib/src/channel_manager.dart @@ -56,7 +56,9 @@ class ChannelManager { _doneCompleter.completeError(error, stackTrace); _channel.sink.close(); }, - onDone: _doneCompleter.complete, + onDone: () { + if (!_doneCompleter.isCompleted) _doneCompleter.complete(); + }, cancelOnError: true); return done; diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 0d905468a..8cb0dc887 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.0.1-dev +version: 2.0.1 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://github.com/dart-lang/json_rpc_2 From 0d6facd51100ad754ca92bae902fc3b688b6c4fa Mon Sep 17 00:00:00 2001 From: dramos07 Date: Fri, 5 Aug 2016 13:19:47 -0400 Subject: [PATCH 032/127] Fix strong mode issues --- pkgs/json_rpc_2/lib/src/client.dart | 2 +- pkgs/json_rpc_2/lib/src/parameters.dart | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart index 5df21e438..82baa6a65 100644 --- a/pkgs/json_rpc_2/lib/src/client.dart +++ b/pkgs/json_rpc_2/lib/src/client.dart @@ -121,7 +121,7 @@ class Client { 'parameters, was "$parameters".'); } - var message = { + var message = { "jsonrpc": "2.0", "method": method }; diff --git a/pkgs/json_rpc_2/lib/src/parameters.dart b/pkgs/json_rpc_2/lib/src/parameters.dart index 91ad1b581..1d2f86ff5 100644 --- a/pkgs/json_rpc_2/lib/src/parameters.dart +++ b/pkgs/json_rpc_2/lib/src/parameters.dart @@ -26,9 +26,10 @@ class Parameters { /// /// If this is accessed for a [Parameter] that was not passed, the request /// will be automatically rejected. To avoid this, use [Parameter.valueOr]. - final value; + final _value; + get value => _value; - Parameters(this.method, this.value); + Parameters(this.method, this._value); /// Returns a single parameter. /// @@ -141,15 +142,16 @@ class Parameter extends Parameters { } var path = computePath(params._parent); - return params._key is int ? - "$path[${params._key}]" : "$path.${quoteKey(params._key)}"; + return params._key is int + ? "$path[${params._key}]" + : "$path.${quoteKey(params._key)}"; } return computePath(this); } /// Whether this parameter exists. - final exists = true; + bool get exists => true; Parameter._(String method, value, this._parent, this._key) : super(method, value); @@ -307,7 +309,7 @@ class _MissingParameter extends Parameter { 'missing required parameter $_path.'); } - final exists = false; + bool get exists => false; _MissingParameter(String method, Parameters parent, key) : super._(method, null, parent, key); From da015df513f377b0c3a19a05817720c7fc9524e6 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 9 Aug 2016 14:49:17 -0700 Subject: [PATCH 033/127] Bump the version. --- pkgs/json_rpc_2/CHANGELOG.md | 4 ++++ pkgs/json_rpc_2/lib/src/parameters.dart | 2 +- pkgs/json_rpc_2/pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index feb1001e9..cb13d7b26 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.2 + +* Fix all strong-mode warnings. + ## 2.0.1 * Fix a race condition in which a `StateError` could be top-leveled if diff --git a/pkgs/json_rpc_2/lib/src/parameters.dart b/pkgs/json_rpc_2/lib/src/parameters.dart index 1d2f86ff5..c6c653bd0 100644 --- a/pkgs/json_rpc_2/lib/src/parameters.dart +++ b/pkgs/json_rpc_2/lib/src/parameters.dart @@ -26,8 +26,8 @@ class Parameters { /// /// If this is accessed for a [Parameter] that was not passed, the request /// will be automatically rejected. To avoid this, use [Parameter.valueOr]. - final _value; get value => _value; + final _value; Parameters(this.method, this._value); diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 8cb0dc887..01cb52867 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.0.1 +version: 2.0.2 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://github.com/dart-lang/json_rpc_2 From dfa309da1c6ae0afeab9c354dbf3ce175843a977 Mon Sep 17 00:00:00 2001 From: Harry Cameron Date: Fri, 28 Oct 2016 21:33:36 +0100 Subject: [PATCH 034/127] Fix strong mode errors, warnings, and hints. (dart-lang/json_rpc_2#14) --- pkgs/json_rpc_2/lib/src/client.dart | 5 +- pkgs/json_rpc_2/lib/src/peer.dart | 5 +- pkgs/json_rpc_2/lib/src/server.dart | 87 ++++++++++++++------------ pkgs/json_rpc_2/lib/src/utils.dart | 10 +-- pkgs/json_rpc_2/test/client/utils.dart | 8 ++- pkgs/json_rpc_2/test/peer_test.dart | 4 +- 6 files changed, 61 insertions(+), 58 deletions(-) diff --git a/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart index 82baa6a65..0568da0dd 100644 --- a/pkgs/json_rpc_2/lib/src/client.dart +++ b/pkgs/json_rpc_2/lib/src/client.dart @@ -49,9 +49,8 @@ class Client { /// Note that the client won't begin listening to [responses] until /// [Client.listen] is called. Client(StreamChannel channel) - : this.withoutJson(channel - .transform(jsonDocument) - .transformStream(ignoreFormatExceptions)); + : this.withoutJson( + jsonDocument.bind(channel).transformStream(ignoreFormatExceptions)); /// Creates a [Client] that communicates using decoded messages over /// [channel]. diff --git a/pkgs/json_rpc_2/lib/src/peer.dart b/pkgs/json_rpc_2/lib/src/peer.dart index 810d173b2..c663da2c2 100644 --- a/pkgs/json_rpc_2/lib/src/peer.dart +++ b/pkgs/json_rpc_2/lib/src/peer.dart @@ -44,9 +44,8 @@ class Peer implements Client, Server { /// Note that the peer won't begin listening to [channel] until [Peer.listen] /// is called. Peer(StreamChannel channel) - : this.withoutJson(channel - .transform(jsonDocument) - .transform(respondToFormatExceptions)); + : this.withoutJson( + jsonDocument.bind(channel).transform(respondToFormatExceptions)); /// Creates a [Peer] that communicates using decoded messages over [channel]. /// diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index 3ef59ed0f..b85a46fd6 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -56,9 +56,8 @@ class Server { /// Note that the server won't begin listening to [requests] until /// [Server.listen] is called. Server(StreamChannel channel) - : this.withoutJson(channel - .transform(jsonDocument) - .transform(respondToFormatExceptions)); + : this.withoutJson( + jsonDocument.bind(channel).transform(respondToFormatExceptions)); /// Creates a [Server] that communicates using decoded messages over /// [channel]. @@ -126,41 +125,45 @@ class Server { /// errors by throwing an [RpcException]. Future _handleRequest(request) async { var response; - if (request is! List) { + if (request is List) { + if (request.isEmpty) { + response = new RpcException(error_code.INVALID_REQUEST, + 'A batch must contain at least one request.') + .serialize(request); + } else { + var results = await Future.wait(request.map(_handleSingleRequest)); + var nonNull = results.where((result) => result != null); + if (nonNull.isEmpty) return; + response = nonNull.toList(); + } + } else { response = await _handleSingleRequest(request); if (response == null) return; - } else if (request.isEmpty) { - response = new RpcException( - error_code.INVALID_REQUEST, - 'A batch must contain at least one request.') - .serialize(request); - } else { - var results = await Future.wait(request.map(_handleSingleRequest)); - var nonNull = results.where((result) => result != null); - if (nonNull.isEmpty) return; - response = nonNull.toList(); } if (!isClosed) _manager.add(response); } /// Handles an individual parsed request. - Future _handleSingleRequest(request) { - return syncFuture(() { + Future _handleSingleRequest(request) async { + try { _validateRequest(request); var name = request['method']; var method = _methods[name]; if (method == null) method = _tryFallbacks; + Object result; if (method is ZeroArgumentFunction) { - if (!request.containsKey('params')) return method(); - throw new RpcException.invalidParams('No parameters are allowed for ' - 'method "$name".'); + if (request.containsKey('params')) { + throw new RpcException.invalidParams('No parameters are allowed for ' + 'method "$name".'); + } + result = await method(); + } else { + result = await method(new Parameters(name, request['params'])); } - return method(new Parameters(name, request['params'])); - }).then((result) { // A request without an id is a notification, which should not be sent a // response, even if one is generated on the server. if (!request.containsKey('id')) return null; @@ -170,22 +173,24 @@ class Server { 'result': result, 'id': request['id'] }; - }).catchError((error, stackTrace) { - if (error is! RpcException) { - error = new RpcException( - error_code.SERVER_ERROR, getErrorMessage(error), data: { - 'full': error.toString(), - 'stack': new Chain.forTrace(stackTrace).toString() - }); - } - - if (error.code != error_code.INVALID_REQUEST && - !request.containsKey('id')) { + } catch (error, stackTrace) { + if (error is RpcException) { + if (error.code == error_code.INVALID_REQUEST || + request.containsKey('id')) { + return error.serialize(request); + } else { + return null; + } + } else if (!request.containsKey('id')) { return null; - } else { - return error.serialize(request); } - }); + final chain = new Chain.forTrace(stackTrace); + return new RpcException(error_code.SERVER_ERROR, getErrorMessage(error), + data: { + 'full': '$error', + 'stack': '$chain', + }).serialize(request); + } } /// Validates that [request] matches the JSON-RPC spec. @@ -233,18 +238,18 @@ class Server { Future _tryFallbacks(Parameters params) { var iterator = _fallbacks.toList().iterator; - _tryNext() { + _tryNext() async { if (!iterator.moveNext()) { - return new Future.error( - new RpcException.methodNotFound(params.method), - new Chain.current()); + throw new RpcException.methodNotFound(params.method); } - return syncFuture(() => iterator.current(params)).catchError((error) { + try { + return iterator.current(params); + } on RpcException catch (error) { if (error is! RpcException) throw error; if (error.code != error_code.METHOD_NOT_FOUND) throw error; return _tryNext(); - }); + } } return _tryNext(); diff --git a/pkgs/json_rpc_2/lib/src/utils.dart b/pkgs/json_rpc_2/lib/src/utils.dart index 8871b56c2..1e2d6b1c7 100644 --- a/pkgs/json_rpc_2/lib/src/utils.dart +++ b/pkgs/json_rpc_2/lib/src/utils.dart @@ -4,7 +4,6 @@ import 'dart:async'; -import 'package:stack_trace/stack_trace.dart'; import 'package:stream_channel/stream_channel.dart'; import '../error_code.dart' as error_code; @@ -12,10 +11,6 @@ import 'exception.dart'; typedef ZeroArgumentFunction(); -/// Like [new Future.sync], but automatically wraps the future in a -/// [Chain.track] call. -Future syncFuture(callback()) => Chain.track(new Future.sync(callback)); - /// Returns a sentence fragment listing the elements of [iter]. /// /// This converts each element of [iter] to a string and separates them with @@ -69,8 +64,9 @@ tryFinally(body(), whenComplete()) { } /// A transformer that silently drops [FormatException]s. -final ignoreFormatExceptions = new StreamTransformer.fromHandlers( - handleError: (error, stackTrace, sink) { +final ignoreFormatExceptions = + new StreamTransformer.fromHandlers( + handleError: (error, stackTrace, sink) { if (error is FormatException) return; sink.addError(error, stackTrace); }); diff --git a/pkgs/json_rpc_2/test/client/utils.dart b/pkgs/json_rpc_2/test/client/utils.dart index 09a200c1d..9b4dd7901 100644 --- a/pkgs/json_rpc_2/test/client/utils.dart +++ b/pkgs/json_rpc_2/test/client/utils.dart @@ -45,10 +45,14 @@ class ClientController { } /// Sends [response], a decoded response, to [client]. - Future sendResponse(response) => sendJsonResponse(JSON.encode(response)); + void sendResponse(response) { + sendJsonResponse(JSON.encode(response)); + } /// Sends [response], a JSON-encoded response, to [client]. - Future sendJsonResponse(String request) => _responseController.add(request); + void sendJsonResponse(String request) { + _responseController.add(request); + } } /// Returns a [Future] that completes after pumping the event queue [times] diff --git a/pkgs/json_rpc_2/test/peer_test.dart b/pkgs/json_rpc_2/test/peer_test.dart index ad7ad22e0..5241e1589 100644 --- a/pkgs/json_rpc_2/test/peer_test.dart +++ b/pkgs/json_rpc_2/test/peer_test.dart @@ -164,8 +164,8 @@ void main() { }); test("returns a response for malformed JSON", () { - var incomingController = new StreamController(); - var outgoingController = new StreamController(); + var incomingController = new StreamController(); + var outgoingController = new StreamController(); var jsonPeer = new json_rpc.Peer( new StreamChannel(incomingController.stream, outgoingController)); From 95e5efbe531c0527e5607ffc273daf1638de3b87 Mon Sep 17 00:00:00 2001 From: Harry Cameron Date: Tue, 1 Nov 2016 21:35:48 +0000 Subject: [PATCH 035/127] Bump the version to 2.0.3. (dart-lang/json_rpc_2#15) --- pkgs/json_rpc_2/CHANGELOG.md | 4 ++++ pkgs/json_rpc_2/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index cb13d7b26..9375a9c4e 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.3 + +* Fix new strong-mode warnings. + ## 2.0.2 * Fix all strong-mode warnings. diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 01cb52867..c2b9f55a2 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.0.2 +version: 2.0.3 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://github.com/dart-lang/json_rpc_2 From 362d86dd16fab00ade34b64e300c56868030b1ad Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 15 Feb 2017 11:42:51 -0800 Subject: [PATCH 036/127] Don't leave dangling request futures. (dart-lang/json_rpc_2#19) Closes dart-lang/json_rpc_2#18 --- pkgs/json_rpc_2/CHANGELOG.md | 9 ++++++++ pkgs/json_rpc_2/lib/src/channel_manager.dart | 2 +- pkgs/json_rpc_2/lib/src/client.dart | 19 +++++++++++++++- pkgs/json_rpc_2/pubspec.yaml | 2 +- pkgs/json_rpc_2/test/client/client_test.dart | 6 +++++ pkgs/json_rpc_2/test/client/stream_test.dart | 23 ++++++++++++++++++++ 6 files changed, 58 insertions(+), 3 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 9375a9c4e..0b6fdd1be 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,12 @@ +## 2.0.4 + +* `Client.sendRequest()` now throws a `StateError` if the client is closed while + the request is in-flight. This avoids dangling `Future`s that will never be + completed. + +* Both `Client.sendRequest()` and `Client.sendNotification()` now throw + `StateError`s if they're called after the client is closed. + ## 2.0.3 * Fix new strong-mode warnings. diff --git a/pkgs/json_rpc_2/lib/src/channel_manager.dart b/pkgs/json_rpc_2/lib/src/channel_manager.dart index 8f6687073..8981d6817 100644 --- a/pkgs/json_rpc_2/lib/src/channel_manager.dart +++ b/pkgs/json_rpc_2/lib/src/channel_manager.dart @@ -25,7 +25,7 @@ class ChannelManager { /// /// This is the same future that's returned by [listen]. Future get done => _doneCompleter.future; - final _doneCompleter = new Completer(); + final _doneCompleter = new Completer.sync(); /// Whether the underlying communication channel is closed. bool get isClosed => _doneCompleter.isCompleted; diff --git a/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart index 0568da0dd..7cd428239 100644 --- a/pkgs/json_rpc_2/lib/src/client.dart +++ b/pkgs/json_rpc_2/lib/src/client.dart @@ -61,7 +61,18 @@ class Client { /// Note that the client won't begin listening to [responses] until /// [Client.listen] is called. Client.withoutJson(StreamChannel channel) - : _manager = new ChannelManager("Client", channel); + : _manager = new ChannelManager("Client", channel) { + _manager.done.whenComplete(() { + for (var request in _pendingRequests.values) { + request.completer.completeError( + new StateError("The client closed with pending requests."), + StackTrace.current); + } + _pendingRequests.clear(); + }).catchError((_) { + // Avoid an unhandled error. + }); + } /// Starts listening to the underlying stream. /// @@ -87,6 +98,9 @@ class Client { /// If the request succeeds, this returns the response result as a decoded /// JSON-serializable object. If it fails, it throws an [RpcException] /// describing the failure. + /// + /// 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, [parameters]) { var id = _id++; _send(method, parameters, id); @@ -106,6 +120,8 @@ class Client { /// /// Since this is just a notification to which the server isn't expected to /// send a response, it has no return value. + /// + /// Throws a [StateError] if the client is closed when this method is called. void sendNotification(String method, [parameters]) => _send(method, parameters); @@ -119,6 +135,7 @@ class Client { throw new ArgumentError('Only maps and lists may be used as JSON-RPC ' 'parameters, was "$parameters".'); } + if (isClosed) throw new StateError("The client is closed."); var message = { "jsonrpc": "2.0", diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index c2b9f55a2..57a1e37a3 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.0.3 +version: 2.0.4 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://github.com/dart-lang/json_rpc_2 diff --git a/pkgs/json_rpc_2/test/client/client_test.dart b/pkgs/json_rpc_2/test/client/client_test.dart index 5601daac6..5147a0b8e 100644 --- a/pkgs/json_rpc_2/test/client/client_test.dart +++ b/pkgs/json_rpc_2/test/client/client_test.dart @@ -184,6 +184,12 @@ void main() { }))); }); + test("requests throw StateErrors if the client is closed", () { + controller.client.close(); + expect(() => controller.client.sendRequest("foo"), throwsStateError); + expect(() => controller.client.sendNotification("foo"), throwsStateError); + }); + test("ignores bogus responses", () { // Make a request so we have something to respond to. controller.expectRequest((request) { diff --git a/pkgs/json_rpc_2/test/client/stream_test.dart b/pkgs/json_rpc_2/test/client/stream_test.dart index b9a31c679..031d38dac 100644 --- a/pkgs/json_rpc_2/test/client/stream_test.dart +++ b/pkgs/json_rpc_2/test/client/stream_test.dart @@ -76,4 +76,27 @@ void main() { expect(() => responseController.stream.listen((_) {}), throwsStateError); expect(requestController.isClosed, isTrue); }); + + group("a stream error", () { + test("is reported through .done", () { + expect(client.listen(), throwsA("oh no!")); + expect(client.done, throwsA("oh no!")); + responseController.addError("oh no!"); + }); + + test("cause a pending request to throw a StateError", () { + expect(client.listen(), throwsA("oh no!")); + expect(client.sendRequest('foo'), throwsStateError); + responseController.addError("oh no!"); + }); + + test("causes future requests to throw StateErrors", () async { + expect(client.listen(), throwsA("oh no!")); + responseController.addError("oh no!"); + await pumpEventQueue(); + + expect(() => client.sendRequest('foo'), throwsStateError); + expect(() => client.sendNotification('foo'), throwsStateError); + }); + }); } From 33cedf9a3eecb8916919456b8dc7db4626ddc823 Mon Sep 17 00:00:00 2001 From: Gary Roumanis Date: Mon, 5 Jun 2017 14:09:52 -0700 Subject: [PATCH 037/127] fix tests due to doesNotComplete matcher in package:test --- pkgs/json_rpc_2/pubspec.yaml | 2 +- pkgs/json_rpc_2/test/server/utils.dart | 14 -------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 57a1e37a3..e1a2a92ae 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -7,6 +7,6 @@ dependencies: stack_trace: '>=0.9.1 <2.0.0' stream_channel: '^1.1.0' dev_dependencies: - test: ">=0.12.0 <0.13.0" + test: "^0.12.21" environment: sdk: ">=1.8.0 <2.0.0" diff --git a/pkgs/json_rpc_2/test/server/utils.dart b/pkgs/json_rpc_2/test/server/utils.dart index f33cb1398..02d9f347d 100644 --- a/pkgs/json_rpc_2/test/server/utils.dart +++ b/pkgs/json_rpc_2/test/server/utils.dart @@ -61,20 +61,6 @@ void expectErrorResponse(ServerController controller, request, int errorCode, }))); } -/// Returns a matcher that matches [Future]s that never complete. -Matcher get doesNotComplete => predicate((future) { - future.then(expectAsync((_) { - // This will never be called. [expectAsync] with `count: 0` ensures that an - // error will be thrown when [future] completes. - }, count: 0)); - - // Make sure there's enough time in the test for [expectAsync] to fail if it's - // going to. - expect(pumpEventQueue(), completes); - - return true; -}); - /// Returns a matcher that matches a [json_rpc.RpcException] with an /// `invalid_params` error code. Matcher throwsInvalidParams(String message) { From 5913a0539ed244b7ea2a18c7cd1135b72541384e Mon Sep 17 00:00:00 2001 From: Gary Roumanis Date: Mon, 5 Jun 2017 14:11:27 -0700 Subject: [PATCH 038/127] create new version --- pkgs/json_rpc_2/CHANGELOG.md | 4 ++++ pkgs/json_rpc_2/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 0b6fdd1be..cc62e3564 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.5 + +* Fix tests due to new `doesNotComplete` matcher in package:test + ## 2.0.4 * `Client.sendRequest()` now throws a `StateError` if the client is closed while diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index e1a2a92ae..9e84f3b33 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.0.4 +version: 2.0.5 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://github.com/dart-lang/json_rpc_2 From bcd817c51a7d5c041d7a8774840c8c328ed749a2 Mon Sep 17 00:00:00 2001 From: Gary Roumanis Date: Mon, 5 Jun 2017 14:25:34 -0700 Subject: [PATCH 039/127] change version to dev --- pkgs/json_rpc_2/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 9e84f3b33..724e4d731 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.0.5 +version: 2.0.5-dev author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://github.com/dart-lang/json_rpc_2 From 0c48ce25df4d81000e0cb5ae115e08ae4554d72d Mon Sep 17 00:00:00 2001 From: Gary Roumanis Date: Mon, 5 Jun 2017 14:30:55 -0700 Subject: [PATCH 040/127] remove changelog entry --- pkgs/json_rpc_2/CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index cc62e3564..0b6fdd1be 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,7 +1,3 @@ -## 2.0.5 - -* Fix tests due to new `doesNotComplete` matcher in package:test - ## 2.0.4 * `Client.sendRequest()` now throws a `StateError` if the client is closed while From 110e1689a65be02be495683eca5943daae8b1fbe Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 14 Nov 2017 14:25:22 -0800 Subject: [PATCH 041/127] Use pumpEventQueue() from test (dart-lang/json_rpc_2#22) --- pkgs/json_rpc_2/pubspec.yaml | 2 +- pkgs/json_rpc_2/test/client/stream_test.dart | 2 -- pkgs/json_rpc_2/test/client/utils.dart | 12 ------------ pkgs/json_rpc_2/test/server/stream_test.dart | 2 -- pkgs/json_rpc_2/test/server/utils.dart | 12 ------------ 5 files changed, 1 insertion(+), 29 deletions(-) diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 724e4d731..f80caa348 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -7,6 +7,6 @@ dependencies: stack_trace: '>=0.9.1 <2.0.0' stream_channel: '^1.1.0' dev_dependencies: - test: "^0.12.21" + test: "^0.12.28" environment: sdk: ">=1.8.0 <2.0.0" diff --git a/pkgs/json_rpc_2/test/client/stream_test.dart b/pkgs/json_rpc_2/test/client/stream_test.dart index 031d38dac..fb71f176c 100644 --- a/pkgs/json_rpc_2/test/client/stream_test.dart +++ b/pkgs/json_rpc_2/test/client/stream_test.dart @@ -9,8 +9,6 @@ import 'package:test/test.dart'; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; -import 'utils.dart'; - void main() { var responseController; var requestController; diff --git a/pkgs/json_rpc_2/test/client/utils.dart b/pkgs/json_rpc_2/test/client/utils.dart index 9b4dd7901..9b35da53c 100644 --- a/pkgs/json_rpc_2/test/client/utils.dart +++ b/pkgs/json_rpc_2/test/client/utils.dart @@ -54,15 +54,3 @@ class ClientController { _responseController.add(request); } } - -/// Returns a [Future] that completes after pumping the event queue [times] -/// times. By default, this should pump the event queue enough times to allow -/// any code to run, as long as it's not waiting on some external event. -Future pumpEventQueue([int times = 20]) { - if (times == 0) return new Future.value(); - // We use a delayed future to allow microtask events to finish. The - // Future.value or Future() constructors use scheduleMicrotask themselves and - // would therefore not wait for microtask callbacks that are scheduled after - // invoking this method. - return new Future.delayed(Duration.ZERO, () => pumpEventQueue(times - 1)); -} diff --git a/pkgs/json_rpc_2/test/server/stream_test.dart b/pkgs/json_rpc_2/test/server/stream_test.dart index 7be00011b..bc320edf5 100644 --- a/pkgs/json_rpc_2/test/server/stream_test.dart +++ b/pkgs/json_rpc_2/test/server/stream_test.dart @@ -9,8 +9,6 @@ import 'package:test/test.dart'; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; -import 'utils.dart'; - void main() { var requestController; var responseController; diff --git a/pkgs/json_rpc_2/test/server/utils.dart b/pkgs/json_rpc_2/test/server/utils.dart index 02d9f347d..f2345fc8f 100644 --- a/pkgs/json_rpc_2/test/server/utils.dart +++ b/pkgs/json_rpc_2/test/server/utils.dart @@ -71,15 +71,3 @@ Matcher throwsInvalidParams(String message) { return true; })); } - -/// Returns a [Future] that completes after pumping the event queue [times] -/// times. By default, this should pump the event queue enough times to allow -/// any code to run, as long as it's not waiting on some external event. -Future pumpEventQueue([int times = 20]) { - if (times == 0) return new Future.value(); - // We use a delayed future to allow microtask events to finish. The - // Future.value or Future() constructors use scheduleMicrotask themselves and - // would therefore not wait for microtask callbacks that are scheduled after - // invoking this method. - return new Future.delayed(Duration.ZERO, () => pumpEventQueue(times - 1)); -} From 70e30683bce8d54b7a9b3a915b16d3918643132d Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Wed, 15 Nov 2017 13:35:27 -0800 Subject: [PATCH 042/127] enable Travis-CI (dart-lang/json_rpc_2#23) * enable Travis-CI * dartfmt * Add analysis options --- pkgs/json_rpc_2/.travis.yml | 22 ++ pkgs/json_rpc_2/analysis_options.yaml | 2 + pkgs/json_rpc_2/lib/error_code.dart | 18 +- pkgs/json_rpc_2/lib/src/channel_manager.dart | 15 +- pkgs/json_rpc_2/lib/src/client.dart | 13 +- pkgs/json_rpc_2/lib/src/peer.dart | 13 +- pkgs/json_rpc_2/lib/src/server.dart | 34 +-- pkgs/json_rpc_2/test/client/client_test.dart | 172 ++++++--------- pkgs/json_rpc_2/test/client/stream_test.dart | 17 +- pkgs/json_rpc_2/test/client/utils.dart | 16 +- pkgs/json_rpc_2/test/peer_test.dart | 169 +++++++-------- pkgs/json_rpc_2/test/server/batch_test.dart | 162 +++++++++----- .../test/server/invalid_request_test.dart | 74 +++---- .../test/server/parameters_test.dart | 120 +++++++---- pkgs/json_rpc_2/test/server/server_test.dart | 201 +++++++++--------- pkgs/json_rpc_2/test/server/stream_test.dart | 14 +- pkgs/json_rpc_2/test/server/utils.dart | 21 +- 17 files changed, 567 insertions(+), 516 deletions(-) create mode 100644 pkgs/json_rpc_2/.travis.yml create mode 100644 pkgs/json_rpc_2/analysis_options.yaml diff --git a/pkgs/json_rpc_2/.travis.yml b/pkgs/json_rpc_2/.travis.yml new file mode 100644 index 000000000..63f5e5eec --- /dev/null +++ b/pkgs/json_rpc_2/.travis.yml @@ -0,0 +1,22 @@ +language: dart + +dart: + - dev + - stable + +dart_task: + - test + - dartanalyzer + +matrix: + include: + - dart: dev + dart_task: dartfmt + +# Only building master means that we don't run two builds for each pull request. +branches: + only: [master] + +cache: + directories: + - $HOME/.pub-cache diff --git a/pkgs/json_rpc_2/analysis_options.yaml b/pkgs/json_rpc_2/analysis_options.yaml new file mode 100644 index 000000000..a10d4c5a0 --- /dev/null +++ b/pkgs/json_rpc_2/analysis_options.yaml @@ -0,0 +1,2 @@ +analyzer: + strong-mode: true diff --git a/pkgs/json_rpc_2/lib/error_code.dart b/pkgs/json_rpc_2/lib/error_code.dart index 7dd80798b..eb2d9be95 100644 --- a/pkgs/json_rpc_2/lib/error_code.dart +++ b/pkgs/json_rpc_2/lib/error_code.dart @@ -39,11 +39,17 @@ const SERVER_ERROR = -32000; /// 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; + 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; } } diff --git a/pkgs/json_rpc_2/lib/src/channel_manager.dart b/pkgs/json_rpc_2/lib/src/channel_manager.dart index 8981d6817..162c9ef38 100644 --- a/pkgs/json_rpc_2/lib/src/channel_manager.dart +++ b/pkgs/json_rpc_2/lib/src/channel_manager.dart @@ -51,15 +51,12 @@ class ChannelManager { } _listenCalled = true; - _channel.stream.listen(handleInput, - onError: (error, stackTrace) { - _doneCompleter.completeError(error, stackTrace); - _channel.sink.close(); - }, - onDone: () { - if (!_doneCompleter.isCompleted) _doneCompleter.complete(); - }, - cancelOnError: true); + _channel.stream.listen(handleInput, onError: (error, stackTrace) { + _doneCompleter.completeError(error, stackTrace); + _channel.sink.close(); + }, onDone: () { + if (!_doneCompleter.isCompleted) _doneCompleter.complete(); + }, cancelOnError: true); return done; } diff --git a/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart index 7cd428239..3607c4a18 100644 --- a/pkgs/json_rpc_2/lib/src/client.dart +++ b/pkgs/json_rpc_2/lib/src/client.dart @@ -137,10 +137,7 @@ class Client { } if (isClosed) throw new StateError("The client is closed."); - var message = { - "jsonrpc": "2.0", - "method": method - }; + var message = {"jsonrpc": "2.0", "method": method}; if (id != null) message["id"] = id; if (parameters != null) message["params"] = parameters; @@ -190,10 +187,10 @@ class Client { if (response.containsKey("result")) { request.completer.complete(response["result"]); } else { - request.completer.completeError(new RpcException( - response["error"]["code"], - response["error"]["message"], - data: response["error"]["data"]), + request.completer.completeError( + new RpcException( + response["error"]["code"], response["error"]["message"], + data: response["error"]["data"]), request.chain); } } diff --git a/pkgs/json_rpc_2/lib/src/peer.dart b/pkgs/json_rpc_2/lib/src/peer.dart index c663da2c2..18cac075a 100644 --- a/pkgs/json_rpc_2/lib/src/peer.dart +++ b/pkgs/json_rpc_2/lib/src/peer.dart @@ -56,10 +56,10 @@ class Peer implements Client, Server { /// [Peer.listen] is called. Peer.withoutJson(StreamChannel channel) : _manager = new ChannelManager("Peer", channel) { - _server = new Server.withoutJson(new StreamChannel( - _serverIncomingForwarder.stream, channel.sink)); - _client = new Client.withoutJson(new StreamChannel( - _clientIncomingForwarder.stream, channel.sink)); + _server = new Server.withoutJson( + new StreamChannel(_serverIncomingForwarder.stream, channel.sink)); + _client = new Client.withoutJson( + new StreamChannel(_clientIncomingForwarder.stream, channel.sink)); } // Client methods. @@ -92,8 +92,9 @@ class Peer implements Client, Server { } else { _serverIncomingForwarder.add(message); } - } else if (message is List && message.isNotEmpty && - message.first is Map) { + } else if (message is List && + message.isNotEmpty && + message.first is Map) { if (message.first.containsKey('result') || message.first.containsKey('error')) { _clientIncomingForwarder.add(message); diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index b85a46fd6..98f472b62 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -168,11 +168,7 @@ class Server { // response, even if one is generated on the server. if (!request.containsKey('id')) return null; - return { - 'jsonrpc': '2.0', - 'result': result, - 'id': request['id'] - }; + return {'jsonrpc': '2.0', 'result': result, 'id': request['id']}; } catch (error, stackTrace) { if (error is RpcException) { if (error.code == error_code.INVALID_REQUEST || @@ -196,40 +192,54 @@ class Server { /// Validates that [request] matches the JSON-RPC spec. void _validateRequest(request) { if (request is! Map) { - throw new RpcException(error_code.INVALID_REQUEST, 'Request must be ' + throw new RpcException( + error_code.INVALID_REQUEST, + 'Request must be ' 'an Array or an Object.'); } if (!request.containsKey('jsonrpc')) { - throw new RpcException(error_code.INVALID_REQUEST, 'Request must ' + throw new RpcException( + error_code.INVALID_REQUEST, + 'Request must ' 'contain a "jsonrpc" key.'); } if (request['jsonrpc'] != '2.0') { - throw new RpcException(error_code.INVALID_REQUEST, 'Invalid JSON-RPC ' + throw new RpcException( + error_code.INVALID_REQUEST, + 'Invalid JSON-RPC ' 'version ${JSON.encode(request['jsonrpc'])}, expected "2.0".'); } if (!request.containsKey('method')) { - throw new RpcException(error_code.INVALID_REQUEST, 'Request must ' + throw new RpcException( + error_code.INVALID_REQUEST, + 'Request must ' 'contain a "method" key.'); } var method = request['method']; if (request['method'] is! String) { - throw new RpcException(error_code.INVALID_REQUEST, 'Request method must ' + throw new RpcException( + error_code.INVALID_REQUEST, + 'Request method must ' 'be a string, but was ${JSON.encode(method)}.'); } var params = request['params']; if (request.containsKey('params') && params is! List && params is! Map) { - throw new RpcException(error_code.INVALID_REQUEST, 'Request params must ' + throw new RpcException( + error_code.INVALID_REQUEST, + 'Request params must ' 'be an Array or an Object, but was ${JSON.encode(params)}.'); } var id = request['id']; if (id != null && id is! String && id is! num) { - throw new RpcException(error_code.INVALID_REQUEST, 'Request id must be a ' + throw new RpcException( + error_code.INVALID_REQUEST, + 'Request id must be a ' 'string, number, or null, but was ${JSON.encode(id)}.'); } } diff --git a/pkgs/json_rpc_2/test/client/client_test.dart b/pkgs/json_rpc_2/test/client/client_test.dart index 5147a0b8e..1648df048 100644 --- a/pkgs/json_rpc_2/test/client/client_test.dart +++ b/pkgs/json_rpc_2/test/client/client_test.dart @@ -16,17 +16,15 @@ void main() { test("sends a message and returns the response", () { controller.expectRequest((request) { - expect(request, allOf([ - containsPair('jsonrpc', '2.0'), - containsPair('method', 'foo'), - containsPair('params', {'param': 'value'}) - ])); - - return { - 'jsonrpc': '2.0', - 'result': 'bar', - 'id': request['id'] - }; + expect( + request, + allOf([ + containsPair('jsonrpc', '2.0'), + containsPair('method', 'foo'), + containsPair('params', {'param': 'value'}) + ])); + + return {'jsonrpc': '2.0', 'result': 'bar', 'id': request['id']}; }); expect(controller.client.sendRequest("foo", {'param': 'value'}), @@ -35,11 +33,13 @@ void main() { test("sends a notification and expects no response", () { controller.expectRequest((request) { - expect(request, equals({ - 'jsonrpc': '2.0', - 'method': 'foo', - 'params': {'param': 'value'} - })); + expect( + request, + equals({ + 'jsonrpc': '2.0', + 'method': 'foo', + 'params': {'param': 'value'} + })); }); controller.client.sendNotification("foo", {'param': 'value'}); @@ -47,11 +47,13 @@ void main() { test("sends a notification with positional parameters", () { controller.expectRequest((request) { - expect(request, equals({ - 'jsonrpc': '2.0', - 'method': 'foo', - 'params': ['value1', 'value2'] - })); + expect( + request, + equals({ + 'jsonrpc': '2.0', + 'method': 'foo', + 'params': ['value1', 'value2'] + })); }); controller.client.sendNotification("foo", ['value1', 'value2']); @@ -59,10 +61,7 @@ void main() { test("sends a notification with no parameters", () { controller.expectRequest((request) { - expect(request, equals({ - 'jsonrpc': '2.0', - 'method': 'foo' - })); + expect(request, equals({'jsonrpc': '2.0', 'method': 'foo'})); }); controller.client.sendNotification("foo"); @@ -72,31 +71,22 @@ void main() { controller.expectRequest((request) { expect(request, new isInstanceOf()); expect(request, hasLength(3)); - expect(request[0], equals({ - 'jsonrpc': '2.0', - 'method': 'foo' - })); - expect(request[1], allOf([ - containsPair('jsonrpc', '2.0'), - containsPair('method', 'bar'), - containsPair('params', {'param': 'value'}) - ])); - expect(request[2], allOf([ - containsPair('jsonrpc', '2.0'), - containsPair('method', 'baz') - ])); + expect(request[0], equals({'jsonrpc': '2.0', 'method': 'foo'})); + expect( + request[1], + allOf([ + containsPair('jsonrpc', '2.0'), + containsPair('method', 'bar'), + containsPair('params', {'param': 'value'}) + ])); + expect( + request[2], + allOf( + [containsPair('jsonrpc', '2.0'), containsPair('method', 'baz')])); return [ - { - 'jsonrpc': '2.0', - 'result': 'baz response', - 'id': request[2]['id'] - }, - { - 'jsonrpc': '2.0', - 'result': 'bar response', - 'id': request[1]['id'] - } + {'jsonrpc': '2.0', 'result': 'baz response', 'id': request[2]['id']}, + {'jsonrpc': '2.0', 'result': 'bar response', 'id': request[1]['id']} ]; }); @@ -113,31 +103,22 @@ void main() { controller.expectRequest((request) { expect(request, new isInstanceOf()); expect(request, hasLength(3)); - expect(request[0], equals({ - 'jsonrpc': '2.0', - 'method': 'foo' - })); - expect(request[1], allOf([ - containsPair('jsonrpc', '2.0'), - containsPair('method', 'bar'), - containsPair('params', {'param': 'value'}) - ])); - expect(request[2], allOf([ - containsPair('jsonrpc', '2.0'), - containsPair('method', 'baz') - ])); + expect(request[0], equals({'jsonrpc': '2.0', 'method': 'foo'})); + expect( + request[1], + allOf([ + containsPair('jsonrpc', '2.0'), + containsPair('method', 'bar'), + containsPair('params', {'param': 'value'}) + ])); + expect( + request[2], + allOf( + [containsPair('jsonrpc', '2.0'), containsPair('method', 'baz')])); return [ - { - 'jsonrpc': '2.0', - 'result': 'baz response', - 'id': request[2]['id'] - }, - { - 'jsonrpc': '2.0', - 'result': 'bar response', - 'id': request[1]['id'] - } + {'jsonrpc': '2.0', 'result': 'baz response', 'id': request[2]['id']}, + {'jsonrpc': '2.0', 'result': 'bar response', 'id': request[1]['id']} ]; }); @@ -158,10 +139,10 @@ void main() { test("reports an error from the server", () { controller.expectRequest((request) { - expect(request, allOf([ - containsPair('jsonrpc', '2.0'), - containsPair('method', 'foo') - ])); + expect( + request, + allOf( + [containsPair('jsonrpc', '2.0'), containsPair('method', 'foo')])); return { 'jsonrpc': '2.0', @@ -195,46 +176,25 @@ void main() { controller.expectRequest((request) { controller.sendJsonResponse("{invalid"); controller.sendResponse("not a map"); - controller.sendResponse({ - 'jsonrpc': 'wrong version', - 'result': 'wrong', - 'id': request['id'] - }); + controller.sendResponse( + {'jsonrpc': 'wrong version', 'result': 'wrong', 'id': request['id']}); + controller.sendResponse({'jsonrpc': '2.0', 'result': 'wrong'}); + controller.sendResponse({'jsonrpc': '2.0', 'id': request['id']}); + controller.sendResponse( + {'jsonrpc': '2.0', 'error': 'not a map', 'id': request['id']}); controller.sendResponse({ 'jsonrpc': '2.0', - 'result': 'wrong' - }); - controller.sendResponse({ - 'jsonrpc': '2.0', - 'id': request['id'] - }); - controller.sendResponse({ - 'jsonrpc': '2.0', - 'error': 'not a map', - 'id': request['id'] - }); - controller.sendResponse({ - 'jsonrpc': '2.0', - 'error': { - 'code': 'not an int', - 'message': 'dang yo' - }, + 'error': {'code': 'not an int', 'message': 'dang yo'}, 'id': request['id'] }); controller.sendResponse({ 'jsonrpc': '2.0', - 'error': { - 'code': 123, - 'message': 0xDEADBEEF - }, + 'error': {'code': 123, 'message': 0xDEADBEEF}, 'id': request['id'] }); - return pumpEventQueue().then((_) => { - 'jsonrpc': '2.0', - 'result': 'right', - 'id': request['id'] - }); + return pumpEventQueue().then( + (_) => {'jsonrpc': '2.0', 'result': 'right', 'id': request['id']}); }); expect(controller.client.sendRequest("foo"), completion(equals('right'))); diff --git a/pkgs/json_rpc_2/test/client/stream_test.dart b/pkgs/json_rpc_2/test/client/stream_test.dart index fb71f176c..94a060458 100644 --- a/pkgs/json_rpc_2/test/client/stream_test.dart +++ b/pkgs/json_rpc_2/test/client/stream_test.dart @@ -24,16 +24,13 @@ void main() { client.listen(); expect(requestController.stream.first.then((request) { - expect(request, allOf([ - containsPair('jsonrpc', '2.0'), - containsPair('method', 'foo') - ])); - - responseController.add({ - 'jsonrpc': '2.0', - 'result': 'bar', - 'id': request['id'] - }); + expect( + request, + allOf( + [containsPair('jsonrpc', '2.0'), containsPair('method', 'foo')])); + + responseController + .add({'jsonrpc': '2.0', 'result': 'bar', 'id': request['id']}); }), completes); client.sendRequest('foo'); diff --git a/pkgs/json_rpc_2/test/client/utils.dart b/pkgs/json_rpc_2/test/client/utils.dart index 9b35da53c..cd534934f 100644 --- a/pkgs/json_rpc_2/test/client/utils.dart +++ b/pkgs/json_rpc_2/test/client/utils.dart @@ -35,13 +35,15 @@ class ClientController { /// null, no response is sent. Otherwise, the return value is encoded and sent /// as the response. void expectRequest(callback(request)) { - expect(_requestController.stream.first.then((request) { - return callback(JSON.decode(request)); - }).then((response) { - if (response == null) return; - if (response is! String) response = JSON.encode(response); - _responseController.add(response); - }), completes); + expect( + _requestController.stream.first.then((request) { + return callback(JSON.decode(request)); + }).then((response) { + if (response == null) return; + if (response is! String) response = JSON.encode(response); + _responseController.add(response); + }), + completes); } /// Sends [response], a decoded response, to [client]. diff --git a/pkgs/json_rpc_2/test/peer_test.dart b/pkgs/json_rpc_2/test/peer_test.dart index 5241e1589..f2ab5b156 100644 --- a/pkgs/json_rpc_2/test/peer_test.dart +++ b/pkgs/json_rpc_2/test/peer_test.dart @@ -27,71 +27,59 @@ void main() { group("like a client,", () { test("can send a message and receive a response", () { expect(outgoing.first.then((request) { - expect(request, equals({ - "jsonrpc": "2.0", - "method": "foo", - "params": {"bar": "baz"}, - "id": 0 - })); - incoming.add({ - "jsonrpc": "2.0", - "result": "qux", - "id": 0 - }); + expect( + request, + equals({ + "jsonrpc": "2.0", + "method": "foo", + "params": {"bar": "baz"}, + "id": 0 + })); + incoming.add({"jsonrpc": "2.0", "result": "qux", "id": 0}); }), completes); peer.listen(); - expect(peer.sendRequest("foo", {"bar": "baz"}), - completion(equals("qux"))); + expect( + peer.sendRequest("foo", {"bar": "baz"}), completion(equals("qux"))); }); test("can send a batch of messages and receive a batch of responses", () { expect(outgoing.first.then((request) { - expect(request, equals([ - { - "jsonrpc": "2.0", - "method": "foo", - "params": {"bar": "baz"}, - "id": 0 - }, - { - "jsonrpc": "2.0", - "method": "a", - "params": {"b": "c"}, - "id": 1 - }, - { - "jsonrpc": "2.0", - "method": "w", - "params": {"x": "y"}, - "id": 2 - } - ])); + expect( + request, + equals([ + { + "jsonrpc": "2.0", + "method": "foo", + "params": {"bar": "baz"}, + "id": 0 + }, + { + "jsonrpc": "2.0", + "method": "a", + "params": {"b": "c"}, + "id": 1 + }, + { + "jsonrpc": "2.0", + "method": "w", + "params": {"x": "y"}, + "id": 2 + } + ])); incoming.add([ - { - "jsonrpc": "2.0", - "result": "qux", - "id": 0 - }, - { - "jsonrpc": "2.0", - "result": "d", - "id": 1 - }, - { - "jsonrpc": "2.0", - "result": "z", - "id": 2 - } + {"jsonrpc": "2.0", "result": "qux", "id": 0}, + {"jsonrpc": "2.0", "result": "d", "id": 1}, + {"jsonrpc": "2.0", "result": "z", "id": 2} ]); }), completes); peer.listen(); peer.withBatch(() { - expect(peer.sendRequest("foo", {"bar": "baz"}), - completion(equals("qux"))); + expect( + peer.sendRequest("foo", {"bar": "baz"}), completion(equals("qux"))); expect(peer.sendRequest("a", {"b": "c"}), completion(equals("d"))); expect(peer.sendRequest("w", {"x": "y"}), completion(equals("z"))); }); @@ -100,11 +88,8 @@ void main() { group("like a server,", () { test("can receive a call and return a response", () { - expect(outgoing.first, completion(equals({ - "jsonrpc": "2.0", - "result": "qux", - "id": 0 - }))); + expect(outgoing.first, + completion(equals({"jsonrpc": "2.0", "result": "qux", "id": 0}))); peer.registerMethod("foo", (_) => "qux"); peer.listen(); @@ -118,23 +103,13 @@ void main() { }); test("can receive a batch of calls and return a batch of responses", () { - expect(outgoing.first, completion(equals([ - { - "jsonrpc": "2.0", - "result": "qux", - "id": 0 - }, - { - "jsonrpc": "2.0", - "result": "d", - "id": 1 - }, - { - "jsonrpc": "2.0", - "result": "z", - "id": 2 - } - ]))); + expect( + outgoing.first, + completion(equals([ + {"jsonrpc": "2.0", "result": "qux", "id": 0}, + {"jsonrpc": "2.0", "result": "d", "id": 1}, + {"jsonrpc": "2.0", "result": "z", "id": 2} + ]))); peer.registerMethod("foo", (_) => "qux"); peer.registerMethod("a", (_) => "d"); @@ -169,16 +144,20 @@ void main() { var jsonPeer = new json_rpc.Peer( new StreamChannel(incomingController.stream, outgoingController)); - expect(outgoingController.stream.first.then(JSON.decode), completion({ - "jsonrpc": "2.0", - "error": { - 'code': error_code.PARSE_ERROR, - "message": startsWith("Invalid JSON: "), - // TODO(nweiz): Always expect the source when sdk#25655 is fixed. - "data": {'request': anyOf([isNull, '{invalid'])} - }, - "id": null - })); + expect( + outgoingController.stream.first.then(JSON.decode), + completion({ + "jsonrpc": "2.0", + "error": { + 'code': error_code.PARSE_ERROR, + "message": startsWith("Invalid JSON: "), + // TODO(nweiz): Always expect the source when sdk#25655 is fixed. + "data": { + 'request': anyOf([isNull, '{invalid']) + } + }, + "id": null + })); jsonPeer.listen(); @@ -186,21 +165,23 @@ void main() { }); test("returns a response for incorrectly-structured JSON", () { - expect(outgoing.first, completion({ - "jsonrpc": "2.0", - "error": { - 'code': error_code.INVALID_REQUEST, - "message": 'Request must contain a "jsonrpc" key.', - "data": {'request': {'completely': 'wrong'}} - }, - "id": null - })); + expect( + outgoing.first, + completion({ + "jsonrpc": "2.0", + "error": { + 'code': error_code.INVALID_REQUEST, + "message": 'Request must contain a "jsonrpc" key.', + "data": { + 'request': {'completely': 'wrong'} + } + }, + "id": null + })); peer.listen(); - incoming.add({ - "completely": "wrong" - }); + incoming.add({"completely": "wrong"}); }); }); } diff --git a/pkgs/json_rpc_2/test/server/batch_test.dart b/pkgs/json_rpc_2/test/server/batch_test.dart index 11ec66e57..3257256a6 100644 --- a/pkgs/json_rpc_2/test/server/batch_test.dart +++ b/pkgs/json_rpc_2/test/server/batch_test.dart @@ -12,60 +12,106 @@ void main() { setUp(() { controller = new ServerController(); controller.server - ..registerMethod('foo', () => 'foo') - ..registerMethod('id', (params) => params.value) - ..registerMethod('arg', (params) => params['arg'].value); + ..registerMethod('foo', () => 'foo') + ..registerMethod('id', (params) => params.value) + ..registerMethod('arg', (params) => params['arg'].value); }); test('handles a batch of requests', () { - expect(controller.handleRequest([ - {'jsonrpc': '2.0', 'method': 'foo', 'id': 1}, - {'jsonrpc': '2.0', 'method': 'id', 'params': ['value'], 'id': 2}, - {'jsonrpc': '2.0', 'method': 'arg', 'params': {'arg': 'value'}, 'id': 3} - ]), completion(equals([ - {'jsonrpc': '2.0', 'result': 'foo', 'id': 1}, - {'jsonrpc': '2.0', 'result': ['value'], 'id': 2}, - {'jsonrpc': '2.0', 'result': 'value', 'id': 3} - ]))); + expect( + controller.handleRequest([ + {'jsonrpc': '2.0', 'method': 'foo', 'id': 1}, + { + 'jsonrpc': '2.0', + 'method': 'id', + 'params': ['value'], + 'id': 2 + }, + { + 'jsonrpc': '2.0', + 'method': 'arg', + 'params': {'arg': 'value'}, + 'id': 3 + } + ]), + completion(equals([ + {'jsonrpc': '2.0', 'result': 'foo', 'id': 1}, + { + 'jsonrpc': '2.0', + 'result': ['value'], + 'id': 2 + }, + {'jsonrpc': '2.0', 'result': 'value', 'id': 3} + ]))); }); test('handles errors individually', () { - expect(controller.handleRequest([ - {'jsonrpc': '2.0', 'method': 'foo', 'id': 1}, - {'jsonrpc': '2.0', 'method': 'zap', 'id': 2}, - {'jsonrpc': '2.0', 'method': 'arg', 'params': {'arg': 'value'}, 'id': 3} - ]), completion(equals([ - {'jsonrpc': '2.0', 'result': 'foo', 'id': 1}, - { - 'jsonrpc': '2.0', - 'id': 2, - 'error': { - 'code': error_code.METHOD_NOT_FOUND, - 'message': 'Unknown method "zap".', - 'data': {'request': {'jsonrpc': '2.0', 'method': 'zap', 'id': 2}}, - } - }, - {'jsonrpc': '2.0', 'result': 'value', 'id': 3} - ]))); + expect( + controller.handleRequest([ + {'jsonrpc': '2.0', 'method': 'foo', 'id': 1}, + {'jsonrpc': '2.0', 'method': 'zap', 'id': 2}, + { + 'jsonrpc': '2.0', + 'method': 'arg', + 'params': {'arg': 'value'}, + 'id': 3 + } + ]), + completion(equals([ + {'jsonrpc': '2.0', 'result': 'foo', 'id': 1}, + { + 'jsonrpc': '2.0', + 'id': 2, + 'error': { + 'code': error_code.METHOD_NOT_FOUND, + 'message': 'Unknown method "zap".', + 'data': { + 'request': {'jsonrpc': '2.0', 'method': 'zap', 'id': 2} + }, + } + }, + {'jsonrpc': '2.0', 'result': 'value', 'id': 3} + ]))); }); test('handles notifications individually', () { - expect(controller.handleRequest([ - {'jsonrpc': '2.0', 'method': 'foo', 'id': 1}, - {'jsonrpc': '2.0', 'method': 'id', 'params': ['value']}, - {'jsonrpc': '2.0', 'method': 'arg', 'params': {'arg': 'value'}, 'id': 3} - ]), completion(equals([ - {'jsonrpc': '2.0', 'result': 'foo', 'id': 1}, - {'jsonrpc': '2.0', 'result': 'value', 'id': 3} - ]))); + expect( + controller.handleRequest([ + {'jsonrpc': '2.0', 'method': 'foo', 'id': 1}, + { + 'jsonrpc': '2.0', + 'method': 'id', + 'params': ['value'] + }, + { + 'jsonrpc': '2.0', + 'method': 'arg', + 'params': {'arg': 'value'}, + 'id': 3 + } + ]), + completion(equals([ + {'jsonrpc': '2.0', 'result': 'foo', 'id': 1}, + {'jsonrpc': '2.0', 'result': 'value', 'id': 3} + ]))); }); test('returns nothing if every request is a notification', () { - expect(controller.handleRequest([ - {'jsonrpc': '2.0', 'method': 'foo'}, - {'jsonrpc': '2.0', 'method': 'id', 'params': ['value']}, - {'jsonrpc': '2.0', 'method': 'arg', 'params': {'arg': 'value'}} - ]), doesNotComplete); + expect( + controller.handleRequest([ + {'jsonrpc': '2.0', 'method': 'foo'}, + { + 'jsonrpc': '2.0', + 'method': 'id', + 'params': ['value'] + }, + { + 'jsonrpc': '2.0', + 'method': 'arg', + 'params': {'arg': 'value'} + } + ]), + doesNotComplete); }); test('returns an error if the batch is empty', () { @@ -74,16 +120,26 @@ void main() { }); test('disallows nested batches', () { - expect(controller.handleRequest([ - [{'jsonrpc': '2.0', 'method': 'foo', 'id': 1}] - ]), completion(equals([{ - 'jsonrpc': '2.0', - 'id': null, - 'error': { - 'code': error_code.INVALID_REQUEST, - 'message': 'Request must be an Array or an Object.', - 'data': {'request': [{'jsonrpc': '2.0', 'method': 'foo', 'id': 1}]} - } - }]))); + expect( + controller.handleRequest([ + [ + {'jsonrpc': '2.0', 'method': 'foo', 'id': 1} + ] + ]), + completion(equals([ + { + 'jsonrpc': '2.0', + 'id': null, + 'error': { + 'code': error_code.INVALID_REQUEST, + 'message': 'Request must be an Array or an Object.', + 'data': { + 'request': [ + {'jsonrpc': '2.0', 'method': 'foo', 'id': 1} + ] + } + } + } + ]))); }); } diff --git a/pkgs/json_rpc_2/test/server/invalid_request_test.dart b/pkgs/json_rpc_2/test/server/invalid_request_test.dart index cd54b6f36..e62558f5c 100644 --- a/pkgs/json_rpc_2/test/server/invalid_request_test.dart +++ b/pkgs/json_rpc_2/test/server/invalid_request_test.dart @@ -17,65 +17,61 @@ void main() { }); test("requests must have a jsonrpc key", () { - expectErrorResponse(controller, { - 'method': 'foo', - 'id': 1234 - }, error_code.INVALID_REQUEST, 'Request must contain a "jsonrpc" key.'); + expectErrorResponse(controller, {'method': 'foo', 'id': 1234}, + error_code.INVALID_REQUEST, 'Request must contain a "jsonrpc" key.'); }); test("the jsonrpc version must be 2.0", () { - expectErrorResponse(controller, { - 'jsonrpc': '1.0', - 'method': 'foo', - 'id': 1234 - }, error_code.INVALID_REQUEST, + expectErrorResponse( + controller, + {'jsonrpc': '1.0', 'method': 'foo', 'id': 1234}, + error_code.INVALID_REQUEST, 'Invalid JSON-RPC version "1.0", expected "2.0".'); }); test("requests must have a method key", () { - expectErrorResponse(controller, { - 'jsonrpc': '2.0', - 'id': 1234 - }, error_code.INVALID_REQUEST, 'Request must contain a "method" key.'); + expectErrorResponse(controller, {'jsonrpc': '2.0', 'id': 1234}, + error_code.INVALID_REQUEST, 'Request must contain a "method" key.'); }); test("request method must be a string", () { - expectErrorResponse(controller, { - 'jsonrpc': '2.0', - 'method': 1234, - 'id': 1234 - }, error_code.INVALID_REQUEST, + expectErrorResponse( + controller, + {'jsonrpc': '2.0', 'method': 1234, 'id': 1234}, + error_code.INVALID_REQUEST, 'Request method must be a string, but was 1234.'); }); test("request params must be an Array or Object", () { - expectErrorResponse(controller, { - 'jsonrpc': '2.0', - 'method': 'foo', - 'params': 1234, - 'id': 1234 - }, error_code.INVALID_REQUEST, + expectErrorResponse( + controller, + {'jsonrpc': '2.0', 'method': 'foo', 'params': 1234, 'id': 1234}, + error_code.INVALID_REQUEST, 'Request params must be an Array or an Object, but was 1234.'); }); test("request id may not be an Array or Object", () { - expect(controller.handleRequest({ - 'jsonrpc': '2.0', - 'method': 'foo', - 'id': {'bad': 'id'} - }), completion(equals({ - 'jsonrpc': '2.0', - 'id': null, - 'error': { - 'code': error_code.INVALID_REQUEST, - 'message': 'Request id must be a string, number, or null, but was ' - '{"bad":"id"}.', - 'data': {'request': { + expect( + controller.handleRequest({ 'jsonrpc': '2.0', 'method': 'foo', 'id': {'bad': 'id'} - }} - } - }))); + }), + completion(equals({ + 'jsonrpc': '2.0', + 'id': null, + 'error': { + 'code': error_code.INVALID_REQUEST, + 'message': 'Request id must be a string, number, or null, but was ' + '{"bad":"id"}.', + 'data': { + 'request': { + 'jsonrpc': '2.0', + 'method': 'foo', + 'id': {'bad': 'id'} + } + } + } + }))); }); } diff --git a/pkgs/json_rpc_2/test/server/parameters_test.dart b/pkgs/json_rpc_2/test/server/parameters_test.dart index e7cb9f265..c970befe0 100644 --- a/pkgs/json_rpc_2/test/server/parameters_test.dart +++ b/pkgs/json_rpc_2/test/server/parameters_test.dart @@ -20,32 +20,29 @@ void main() { "date-time": "1990-01-01 00:00:00.000", "uri": "http://dartlang.org", "invalid-uri": "http://[::1", - "map": { - "num": 4.2, - "bool": false - } + "map": {"num": 4.2, "bool": false} }); }); test("value returns the wrapped value", () { - expect(parameters.value, equals({ - "num": 1.5, - "int": 1, - "bool": true, - "string": "zap", - "list": [1, 2, 3], - "date-time": "1990-01-01 00:00:00.000", - "uri": "http://dartlang.org", - "invalid-uri": "http://[::1", - "map": { - "num": 4.2, - "bool": false - } - })); + expect( + parameters.value, + equals({ + "num": 1.5, + "int": 1, + "bool": true, + "string": "zap", + "list": [1, 2, 3], + "date-time": "1990-01-01 00:00:00.000", + "uri": "http://dartlang.org", + "invalid-uri": "http://[::1", + "map": {"num": 4.2, "bool": false} + })); }); test("[int] throws a parameter error", () { - expect(() => parameters[0], + expect( + () => parameters[0], throwsInvalidParams('Parameters for method "foo" must be passed by ' 'position.')); }); @@ -59,7 +56,8 @@ void main() { }); test("[].value fails for absent parameters", () { - expect(() => parameters['fblthp'].value, + expect( + () => parameters['fblthp'].value, throwsInvalidParams('Request for method "foo" is missing required ' 'parameter "fblthp".')); }); @@ -86,19 +84,22 @@ void main() { }); test("[].asNum fails for non-numeric parameters", () { - expect(() => parameters['bool'].asNum, + expect( + () => parameters['bool'].asNum, throwsInvalidParams('Parameter "bool" for method "foo" must be a ' 'number, but was true.')); }); test("[].asNumOr fails for non-numeric parameters", () { - expect(() => parameters['bool'].asNumOr(7), + expect( + () => parameters['bool'].asNumOr(7), throwsInvalidParams('Parameter "bool" for method "foo" must be a ' 'number, but was true.')); }); test("[].asNum fails for absent parameters", () { - expect(() => parameters['fblthp'].asNum, + expect( + () => parameters['fblthp'].asNum, throwsInvalidParams('Request for method "foo" is missing required ' 'parameter "fblthp".')); }); @@ -116,7 +117,8 @@ void main() { }); test("[].asInt fails for non-integer parameters", () { - expect(() => parameters['bool'].asInt, + expect( + () => parameters['bool'].asInt, throwsInvalidParams('Parameter "bool" for method "foo" must be an ' 'integer, but was true.')); }); @@ -134,7 +136,8 @@ void main() { }); test("[].asBoolOr fails for non-boolean parameters", () { - expect(() => parameters['int'].asBool, + expect( + () => parameters['int'].asBool, throwsInvalidParams('Parameter "int" for method "foo" must be a ' 'boolean, but was 1.')); }); @@ -152,7 +155,8 @@ void main() { }); test("[].asString fails for non-string parameters", () { - expect(() => parameters['int'].asString, + expect( + () => parameters['int'].asString, throwsInvalidParams('Parameter "int" for method "foo" must be a ' 'string, but was 1.')); }); @@ -170,7 +174,8 @@ void main() { }); test("[].asList fails for non-list parameters", () { - expect(() => parameters['int'].asList, + expect( + () => parameters['int'].asList, throwsInvalidParams('Parameter "int" for method "foo" must be an ' 'Array, but was 1.')); }); @@ -184,12 +189,13 @@ void main() { }); test("[].asMapOr returns map parameters", () { - expect(parameters['map'].asMapOr({}), - equals({"num": 4.2, "bool": false})); + expect( + parameters['map'].asMapOr({}), equals({"num": 4.2, "bool": false})); }); test("[].asMap fails for non-map parameters", () { - expect(() => parameters['int'].asMap, + expect( + () => parameters['int'].asMap, throwsInvalidParams('Parameter "int" for method "foo" must be an ' 'Object, but was 1.')); }); @@ -208,7 +214,8 @@ void main() { }); test("[].asDateTime fails for non-date/time parameters", () { - expect(() => parameters['int'].asDateTime, + expect( + () => parameters['int'].asDateTime, throwsInvalidParams('Parameter "int" for method "foo" must be a ' 'string, but was 1.')); }); @@ -219,13 +226,15 @@ void main() { }); test("[].asDateTime fails for non-date/time parameters", () { - expect(() => parameters['int'].asDateTime, + expect( + () => parameters['int'].asDateTime, throwsInvalidParams('Parameter "int" for method "foo" must be a ' 'string, but was 1.')); }); test("[].asDateTime fails for invalid date/times", () { - expect(() => parameters['string'].asDateTime, + expect( + () => parameters['string'].asDateTime, throwsInvalidParams('Parameter "string" for method "foo" must be a ' 'valid date/time, but was "zap".\n' 'Invalid date format')); @@ -241,7 +250,8 @@ void main() { }); test("[].asUri fails for non-URI parameters", () { - expect(() => parameters['int'].asUri, + expect( + () => parameters['int'].asUri, throwsInvalidParams('Parameter "int" for method "foo" must be a ' 'string, but was 1.')); }); @@ -252,13 +262,15 @@ void main() { }); test("[].asUri fails for non-URI parameters", () { - expect(() => parameters['int'].asUri, + expect( + () => parameters['int'].asUri, throwsInvalidParams('Parameter "int" for method "foo" must be a ' 'string, but was 1.')); }); test("[].asUri fails for invalid URIs", () { - expect(() => parameters['invalid-uri'].asUri, + expect( + () => parameters['invalid-uri'].asUri, throwsInvalidParams('Parameter "invalid-uri" for method "foo" must ' 'be a valid URI, but was "http://[::1".\n' 'Missing end `]` to match `[` in host')); @@ -269,7 +281,8 @@ void main() { setUp(() => nested = parameters['map']); test("[int] fails with a type error", () { - expect(() => nested[0], + expect( + () => nested[0], throwsInvalidParams('Parameter "map" for method "foo" must be an ' 'Array, but was {"num":4.2,"bool":false}.')); }); @@ -280,7 +293,8 @@ void main() { }); test("[].value fails for absent parameters", () { - expect(() => nested['fblthp'].value, + expect( + () => nested['fblthp'].value, throwsInvalidParams('Request for method "foo" is missing required ' 'parameter map.fblthp.')); }); @@ -290,7 +304,8 @@ void main() { }); test("typed getters fail for incorrectly-typed parameters", () { - expect(() => nested['bool'].asNum, + expect( + () => nested['bool'].asNum, throwsInvalidParams('Parameter map.bool for method "foo" must be ' 'a number, but was false.')); }); @@ -301,7 +316,8 @@ void main() { setUp(() => nested = parameters['list']); test("[string] fails with a type error", () { - expect(() => nested['foo'], + expect( + () => nested['foo'], throwsInvalidParams('Parameter "list" for method "foo" must be an ' 'Object, but was [1,2,3].')); }); @@ -312,7 +328,8 @@ void main() { }); test("[].value fails for absent parameters", () { - expect(() => nested[5].value, + expect( + () => nested[5].value, throwsInvalidParams('Request for method "foo" is missing required ' 'parameter list[5].')); }); @@ -322,7 +339,8 @@ void main() { }); test("typed getters fail for incorrectly-typed parameters", () { - expect(() => nested[0].asBool, + expect( + () => nested[0].asBool, throwsInvalidParams('Parameter list[0] for method "foo" must be ' 'a boolean, but was 1.')); }); @@ -338,7 +356,8 @@ void main() { }); test("[string] throws a parameter error", () { - expect(() => parameters['foo'], + expect( + () => parameters['foo'], throwsInvalidParams('Parameters for method "foo" must be passed by ' 'name.')); }); @@ -348,7 +367,8 @@ void main() { }); test("[].value fails for out-of-range parameters", () { - expect(() => parameters[10].value, + expect( + () => parameters[10].value, throwsInvalidParams('Request for method "foo" is missing required ' 'parameter 11.')); }); @@ -364,10 +384,18 @@ void main() { test("with a complex parameter path", () { var parameters = new json_rpc.Parameters("foo", { - 'bar baz': [0, 1, 2, {'bang.zap': {'\n': 'qux'}}] + 'bar baz': [ + 0, + 1, + 2, + { + 'bang.zap': {'\n': 'qux'} + } + ] }); - expect(() => parameters['bar baz'][3]['bang.zap']['\n']['bip'], + expect( + () => parameters['bar baz'][3]['bang.zap']['\n']['bip'], throwsInvalidParams('Parameter "bar baz"[3]."bang.zap"."\\n" for ' 'method "foo" must be an Object, but was "qux".')); }); diff --git a/pkgs/json_rpc_2/test/server/server_test.dart b/pkgs/json_rpc_2/test/server/server_test.dart index ef2e45bba..cfe8b2f4a 100644 --- a/pkgs/json_rpc_2/test/server/server_test.dart +++ b/pkgs/json_rpc_2/test/server/server_test.dart @@ -19,75 +19,70 @@ void main() { return {'params': params.value}; }); - expect(controller.handleRequest({ - 'jsonrpc': '2.0', - 'method': 'foo', - 'params': {'param': 'value'}, - 'id': 1234 - }), completion(equals({ - 'jsonrpc': '2.0', - 'result': {'params': {'param': 'value'}}, - 'id': 1234 - }))); + expect( + controller.handleRequest({ + 'jsonrpc': '2.0', + 'method': 'foo', + 'params': {'param': 'value'}, + 'id': 1234 + }), + completion(equals({ + 'jsonrpc': '2.0', + 'result': { + 'params': {'param': 'value'} + }, + 'id': 1234 + }))); }); test("calls a method that takes no parameters", () { controller.server.registerMethod('foo', () => 'foo'); - expect(controller.handleRequest({ - 'jsonrpc': '2.0', - 'method': 'foo', - 'id': 1234 - }), completion(equals({ - 'jsonrpc': '2.0', - 'result': 'foo', - 'id': 1234 - }))); + expect( + controller + .handleRequest({'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}), + completion(equals({'jsonrpc': '2.0', 'result': 'foo', 'id': 1234}))); }); test("a method that takes no parameters rejects parameters", () { controller.server.registerMethod('foo', () => 'foo'); - expectErrorResponse(controller, { - 'jsonrpc': '2.0', - 'method': 'foo', - 'params': {}, - 'id': 1234 - }, + expectErrorResponse( + controller, + {'jsonrpc': '2.0', 'method': 'foo', 'params': {}, '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 new FormatException('bad format')); - - expect(controller.handleRequest({ - 'jsonrpc': '2.0', - 'method': 'foo', - 'id': 1234 - }), completion({ - 'jsonrpc': '2.0', - 'id': 1234, - 'error': { - 'code': error_code.SERVER_ERROR, - 'message': 'bad format', - 'data': { - 'request': {'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}, - 'full': 'FormatException: bad format', - 'stack': new isInstanceOf() - } - } - })); + controller.server + .registerMethod('foo', () => throw new FormatException('bad format')); + + expect( + controller + .handleRequest({'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}), + completion({ + 'jsonrpc': '2.0', + 'id': 1234, + 'error': { + 'code': error_code.SERVER_ERROR, + 'message': 'bad format', + 'data': { + 'request': {'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}, + 'full': 'FormatException: bad format', + 'stack': new isInstanceOf() + } + } + })); }); test("doesn't return a result for a notification", () { controller.server.registerMethod('foo', (args) => 'result'); - expect(controller.handleRequest({ - 'jsonrpc': '2.0', - 'method': 'foo', - 'params': {} - }), doesNotComplete); + expect( + controller + .handleRequest({'jsonrpc': '2.0', 'method': 'foo', 'params': {}}), + doesNotComplete); }); test("includes the error data in the response", () { @@ -95,12 +90,9 @@ void main() { throw new json_rpc.RpcException(5, 'Error message.', data: 'data value'); }); - expectErrorResponse(controller, { - 'jsonrpc': '2.0', - 'method': 'foo', - 'params': {}, - 'id': 1234 - }, + expectErrorResponse( + controller, + {'jsonrpc': '2.0', 'method': 'foo', 'params': {}, 'id': 1234}, 5, 'Error message.', data: 'data value'); @@ -114,7 +106,9 @@ void main() { 'code': error_code.PARSE_ERROR, 'message': startsWith("Invalid JSON: "), // TODO(nweiz): Always expect the source when sdk#25655 is fixed. - 'data': {'request': anyOf([isNull, 'invalid json {'])} + 'data': { + 'request': anyOf([isNull, 'invalid json {']) + } }, 'id': null }); @@ -124,61 +118,60 @@ void main() { group("fallbacks", () { test("calls a fallback if no method matches", () { controller.server - ..registerMethod('foo', () => 'foo') - ..registerMethod('bar', () => 'foo') - ..registerFallback((params) => {'fallback': params.value}); - - expect(controller.handleRequest({ - 'jsonrpc': '2.0', - 'method': 'baz', - 'params': {'param': 'value'}, - 'id': 1234 - }), completion(equals({ - 'jsonrpc': '2.0', - 'result': {'fallback': {'param': 'value'}}, - 'id': 1234 - }))); + ..registerMethod('foo', () => 'foo') + ..registerMethod('bar', () => 'foo') + ..registerFallback((params) => {'fallback': params.value}); + + expect( + controller.handleRequest({ + 'jsonrpc': '2.0', + 'method': 'baz', + 'params': {'param': 'value'}, + 'id': 1234 + }), + completion(equals({ + 'jsonrpc': '2.0', + 'result': { + 'fallback': {'param': 'value'} + }, + 'id': 1234 + }))); }); test("calls the first matching fallback", () { controller.server - ..registerFallback((params) => - throw new json_rpc.RpcException.methodNotFound(params.method)) - ..registerFallback((params) => 'fallback 2') - ..registerFallback((params) => 'fallback 3'); - - expect(controller.handleRequest({ - 'jsonrpc': '2.0', - 'method': 'fallback 2', - 'id': 1234 - }), completion(equals({ - 'jsonrpc': '2.0', - 'result': 'fallback 2', - 'id': 1234 - }))); + ..registerFallback((params) => + throw new json_rpc.RpcException.methodNotFound(params.method)) + ..registerFallback((params) => 'fallback 2') + ..registerFallback((params) => 'fallback 3'); + + expect( + controller.handleRequest( + {'jsonrpc': '2.0', 'method': 'fallback 2', 'id': 1234}), + completion( + equals({'jsonrpc': '2.0', 'result': 'fallback 2', 'id': 1234}))); }); test("an unexpected error in a fallback is captured", () { - controller.server.registerFallback((_) => - throw new FormatException('bad format')); - - expect(controller.handleRequest({ - 'jsonrpc': '2.0', - 'method': 'foo', - 'id': 1234 - }), completion({ - 'jsonrpc': '2.0', - 'id': 1234, - 'error': { - 'code': error_code.SERVER_ERROR, - 'message': 'bad format', - 'data': { - 'request': {'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}, - 'full': 'FormatException: bad format', - 'stack': new isInstanceOf() - } - } - })); + controller.server + .registerFallback((_) => throw new FormatException('bad format')); + + expect( + controller + .handleRequest({'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}), + completion({ + 'jsonrpc': '2.0', + 'id': 1234, + 'error': { + 'code': error_code.SERVER_ERROR, + 'message': 'bad format', + 'data': { + 'request': {'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}, + 'full': 'FormatException: bad format', + 'stack': new isInstanceOf() + } + } + })); }); }); diff --git a/pkgs/json_rpc_2/test/server/stream_test.dart b/pkgs/json_rpc_2/test/server/stream_test.dart index bc320edf5..19629e755 100644 --- a/pkgs/json_rpc_2/test/server/stream_test.dart +++ b/pkgs/json_rpc_2/test/server/stream_test.dart @@ -34,11 +34,15 @@ void main() { 'id': 1234 }); - expect(responseController.stream.first, completion(equals({ - 'jsonrpc': '2.0', - 'result': {'params': {'param': 'value'}}, - 'id': 1234 - }))); + expect( + responseController.stream.first, + completion(equals({ + 'jsonrpc': '2.0', + 'result': { + 'params': {'param': 'value'} + }, + 'id': 1234 + }))); }); test(".listen returns when the controller is closed", () { diff --git a/pkgs/json_rpc_2/test/server/utils.dart b/pkgs/json_rpc_2/test/server/utils.dart index f2345fc8f..ca2b27796 100644 --- a/pkgs/json_rpc_2/test/server/utils.dart +++ b/pkgs/json_rpc_2/test/server/utils.dart @@ -44,21 +44,20 @@ class ServerController { /// Expects that [controller]'s server will return an error response to /// [request] with the given [errorCode], [message], and [data]. -void expectErrorResponse(ServerController controller, request, int errorCode, - String message, {data}) { +void expectErrorResponse( + ServerController controller, request, int errorCode, String message, + {data}) { var id; if (request is Map) id = request['id']; if (data == null) data = {'request': request}; - expect(controller.handleRequest(request), completion(equals({ - 'jsonrpc': '2.0', - 'id': id, - 'error': { - 'code': errorCode, - 'message': message, - 'data': data - } - }))); + expect( + controller.handleRequest(request), + completion(equals({ + 'jsonrpc': '2.0', + 'id': id, + 'error': {'code': errorCode, 'message': message, 'data': data} + }))); } /// Returns a matcher that matches a [json_rpc.RpcException] with an From 7001221670210dc28f5452c5daddc990299e6c3a Mon Sep 17 00:00:00 2001 From: Gary Roumanis Date: Fri, 5 Jan 2018 16:34:35 -0800 Subject: [PATCH 043/127] prep for release (dart-lang/json_rpc_2#24) --- pkgs/json_rpc_2/CHANGELOG.md | 4 ++++ pkgs/json_rpc_2/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 0b6fdd1be..a221c811d 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.5 + +* Internal changes only. + ## 2.0.4 * `Client.sendRequest()` now throws a `StateError` if the client is closed while diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index f80caa348..64c7ce71f 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.0.5-dev +version: 2.0.5 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://github.com/dart-lang/json_rpc_2 From a68b6e3f498934d9a871fb3a0b4e08d2ab827c0f Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 11 Jan 2018 10:04:02 -0800 Subject: [PATCH 044/127] Fix uses_dynamic_as_bottom error (dart-lang/json_rpc_2#25) Fix uses_dynamic_as_bottom error --- pkgs/json_rpc_2/CHANGELOG.md | 4 ++++ pkgs/json_rpc_2/pubspec.yaml | 2 +- pkgs/json_rpc_2/test/server/utils.dart | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index a221c811d..627965388 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.6 + +* Internal changes only. + ## 2.0.5 * Internal changes only. diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 64c7ce71f..b841c4d42 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.0.5 +version: 2.0.6 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://github.com/dart-lang/json_rpc_2 diff --git a/pkgs/json_rpc_2/test/server/utils.dart b/pkgs/json_rpc_2/test/server/utils.dart index ca2b27796..dffb384c4 100644 --- a/pkgs/json_rpc_2/test/server/utils.dart +++ b/pkgs/json_rpc_2/test/server/utils.dart @@ -36,7 +36,7 @@ class ServerController { /// Passes [request], a JSON-encoded request, to [server] and returns its /// encoded response. - Future handleJsonRequest(String request) { + Future handleJsonRequest(String request) { _requestController.add(request); return _responseController.stream.first; } From 594400f1f0b33497408161f826d8c547812e761f Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 17 Jan 2018 12:53:22 -0800 Subject: [PATCH 045/127] Add the method name to pending request errors (dart-lang/json_rpc_2#26) This makes it easier to debug pending request errors. --- pkgs/json_rpc_2/CHANGELOG.md | 5 +++++ pkgs/json_rpc_2/lib/src/client.dart | 10 +++++++--- pkgs/json_rpc_2/pubspec.yaml | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 627965388..ac97f0784 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.0.7 + +* When a `Client` is closed before a request completes, the error sent to that + request's `Future` now includes the request method to aid in debugging. + ## 2.0.6 * Internal changes only. diff --git a/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart index 3607c4a18..ff5c803ef 100644 --- a/pkgs/json_rpc_2/lib/src/client.dart +++ b/pkgs/json_rpc_2/lib/src/client.dart @@ -65,7 +65,8 @@ class Client { _manager.done.whenComplete(() { for (var request in _pendingRequests.values) { request.completer.completeError( - new StateError("The client closed with pending requests."), + new StateError( + 'The client closed with pending request "${request.method}".'), StackTrace.current); } _pendingRequests.clear(); @@ -106,7 +107,7 @@ class Client { _send(method, parameters, id); var completer = new Completer.sync(); - _pendingRequests[id] = new _Request(completer, new Chain.current()); + _pendingRequests[id] = new _Request(method, completer, new Chain.current()); return completer.future; } @@ -213,11 +214,14 @@ class Client { /// A pending request to the server. class _Request { + /// THe method that was sent. + final String method; + /// The completer to use to complete the response future. final Completer completer; /// The stack chain from where the request was made. final Chain chain; - _Request(this.completer, this.chain); + _Request(this.method, this.completer, this.chain); } diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index b841c4d42..3b4f993b8 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.0.6 +version: 2.0.7 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://github.com/dart-lang/json_rpc_2 From c7f7a6e820b15f8eaa85acfb11815bea7495e62e Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Tue, 10 Apr 2018 13:59:42 +0200 Subject: [PATCH 046/127] Remove usage of upper-case constants. --- pkgs/json_rpc_2/lib/src/parameters.dart | 8 ++++---- pkgs/json_rpc_2/lib/src/server.dart | 8 ++++---- pkgs/json_rpc_2/pubspec.yaml | 2 +- pkgs/json_rpc_2/test/client/utils.dart | 6 +++--- pkgs/json_rpc_2/test/peer_test.dart | 2 +- pkgs/json_rpc_2/test/server/server_test.dart | 2 +- pkgs/json_rpc_2/test/server/utils.dart | 2 +- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pkgs/json_rpc_2/lib/src/parameters.dart b/pkgs/json_rpc_2/lib/src/parameters.dart index c6c653bd0..8b39d4633 100644 --- a/pkgs/json_rpc_2/lib/src/parameters.dart +++ b/pkgs/json_rpc_2/lib/src/parameters.dart @@ -128,11 +128,11 @@ class Parameter extends Parameters { /// parameter is represented by that name in quotes. String get _path { if (_parent is! Parameter) { - return _key is int ? (_key + 1).toString() : JSON.encode(_key); + return _key is int ? (_key + 1).toString() : jsonEncode(_key); } quoteKey(key) { - if (key.contains(new RegExp(r'[^a-zA-Z0-9_-]'))) return JSON.encode(key); + if (key.contains(new RegExp(r'[^a-zA-Z0-9_-]'))) return jsonEncode(key); return key; } @@ -267,7 +267,7 @@ class Parameter extends Parameters { _getTyped(String type, bool test(value)) { if (test(value)) return value; throw new RpcException.invalidParams('Parameter $_path for method ' - '"$method" must be $type, but was ${JSON.encode(value)}.'); + '"$method" must be $type, but was ${jsonEncode(value)}.'); } _getParsed(String description, parse(String value)) { @@ -287,7 +287,7 @@ class Parameter extends Parameters { throw new RpcException.invalidParams('Parameter $_path for method ' '"$method" must be a valid $description, but was ' - '${JSON.encode(string)}.$message'); + '${jsonEncode(string)}.$message'); } } diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index 98f472b62..0aa4c2f4a 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -209,7 +209,7 @@ class Server { throw new RpcException( error_code.INVALID_REQUEST, 'Invalid JSON-RPC ' - 'version ${JSON.encode(request['jsonrpc'])}, expected "2.0".'); + 'version ${jsonEncode(request['jsonrpc'])}, expected "2.0".'); } if (!request.containsKey('method')) { @@ -224,7 +224,7 @@ class Server { throw new RpcException( error_code.INVALID_REQUEST, 'Request method must ' - 'be a string, but was ${JSON.encode(method)}.'); + 'be a string, but was ${jsonEncode(method)}.'); } var params = request['params']; @@ -232,7 +232,7 @@ class Server { throw new RpcException( error_code.INVALID_REQUEST, 'Request params must ' - 'be an Array or an Object, but was ${JSON.encode(params)}.'); + 'be an Array or an Object, but was ${jsonEncode(params)}.'); } var id = request['id']; @@ -240,7 +240,7 @@ class Server { throw new RpcException( error_code.INVALID_REQUEST, 'Request id must be a ' - 'string, number, or null, but was ${JSON.encode(id)}.'); + 'string, number, or null, but was ${jsonEncode(id)}.'); } } diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 3b4f993b8..4f53cdc9b 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.0.7 +version: 2.0.8 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://github.com/dart-lang/json_rpc_2 diff --git a/pkgs/json_rpc_2/test/client/utils.dart b/pkgs/json_rpc_2/test/client/utils.dart index cd534934f..8a0a9a326 100644 --- a/pkgs/json_rpc_2/test/client/utils.dart +++ b/pkgs/json_rpc_2/test/client/utils.dart @@ -37,10 +37,10 @@ class ClientController { void expectRequest(callback(request)) { expect( _requestController.stream.first.then((request) { - return callback(JSON.decode(request)); + return callback(jsonDecode(request)); }).then((response) { if (response == null) return; - if (response is! String) response = JSON.encode(response); + if (response is! String) response = jsonEncode(response); _responseController.add(response); }), completes); @@ -48,7 +48,7 @@ class ClientController { /// Sends [response], a decoded response, to [client]. void sendResponse(response) { - sendJsonResponse(JSON.encode(response)); + sendJsonResponse(jsonEncode(response)); } /// Sends [response], a JSON-encoded response, to [client]. diff --git a/pkgs/json_rpc_2/test/peer_test.dart b/pkgs/json_rpc_2/test/peer_test.dart index f2ab5b156..94c49e6bb 100644 --- a/pkgs/json_rpc_2/test/peer_test.dart +++ b/pkgs/json_rpc_2/test/peer_test.dart @@ -145,7 +145,7 @@ void main() { new StreamChannel(incomingController.stream, outgoingController)); expect( - outgoingController.stream.first.then(JSON.decode), + outgoingController.stream.first.then(jsonDecode), completion({ "jsonrpc": "2.0", "error": { diff --git a/pkgs/json_rpc_2/test/server/server_test.dart b/pkgs/json_rpc_2/test/server/server_test.dart index cfe8b2f4a..c2f87ab0e 100644 --- a/pkgs/json_rpc_2/test/server/server_test.dart +++ b/pkgs/json_rpc_2/test/server/server_test.dart @@ -100,7 +100,7 @@ void main() { test("a JSON parse error is rejected", () { return controller.handleJsonRequest('invalid json {').then((result) { - expect(JSON.decode(result), { + expect(jsonDecode(result), { 'jsonrpc': '2.0', 'error': { 'code': error_code.PARSE_ERROR, diff --git a/pkgs/json_rpc_2/test/server/utils.dart b/pkgs/json_rpc_2/test/server/utils.dart index dffb384c4..f383f8936 100644 --- a/pkgs/json_rpc_2/test/server/utils.dart +++ b/pkgs/json_rpc_2/test/server/utils.dart @@ -32,7 +32,7 @@ class ServerController { /// Passes [request], a decoded request, to [server] and returns its decoded /// response. Future handleRequest(request) => - handleJsonRequest(JSON.encode(request)).then(JSON.decode); + handleJsonRequest(jsonEncode(request)).then(jsonDecode); /// Passes [request], a JSON-encoded request, to [server] and returns its /// encoded response. From 52df8e64d1d7c42055c7ccffeca9e1754d428d76 Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Tue, 10 Apr 2018 15:24:17 +0200 Subject: [PATCH 047/127] update SDK version --- pkgs/json_rpc_2/pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 4f53cdc9b..afe3cadae 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.0.8 +version: 2.0.7 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://github.com/dart-lang/json_rpc_2 @@ -9,4 +9,4 @@ dependencies: dev_dependencies: test: "^0.12.28" environment: - sdk: ">=1.8.0 <2.0.0" + sdk: ">=2.0.0-dev.17.0 <2.0.0" From 6aad85be51775e825ac8614f9766d49ce747ec25 Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Tue, 10 Apr 2018 15:37:40 +0200 Subject: [PATCH 048/127] update version number --- pkgs/json_rpc_2/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index afe3cadae..77b52c0b8 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.0.7 +version: 2.0.8 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://github.com/dart-lang/json_rpc_2 From fc908734899c2d40e3a56ce7bfcfe1b5482283a6 Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Thu, 12 Apr 2018 15:01:48 +0200 Subject: [PATCH 049/127] Update changelog --- pkgs/json_rpc_2/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index ac97f0784..d10f4e406 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.8 + +- Updated SDK version to 2.0.0-dev.17.0 + ## 2.0.7 * When a `Client` is closed before a request completes, the error sent to that From 0de0d0b8956dc891c653dc47503703bc3f4c1a20 Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Tue, 17 Apr 2018 13:48:05 +0200 Subject: [PATCH 050/127] fix changelog bullet --- pkgs/json_rpc_2/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index d10f4e406..3e2d662dc 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,6 +1,6 @@ ## 2.0.8 -- Updated SDK version to 2.0.0-dev.17.0 +* Updated SDK version to 2.0.0-dev.17.0 ## 2.0.7 From 1bbb9da2ddfaa0150adb6780431a411033bfc23c Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Fri, 4 May 2018 11:10:37 +0200 Subject: [PATCH 051/127] remove stable from Travis config --- pkgs/json_rpc_2/.travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkgs/json_rpc_2/.travis.yml b/pkgs/json_rpc_2/.travis.yml index 63f5e5eec..7dee3ccdc 100644 --- a/pkgs/json_rpc_2/.travis.yml +++ b/pkgs/json_rpc_2/.travis.yml @@ -2,8 +2,6 @@ language: dart dart: - dev - - stable - dart_task: - test - dartanalyzer From b1fdee823aa665a23e8ef791a69d4a6f55ce883c Mon Sep 17 00:00:00 2001 From: BC Ko Date: Thu, 24 May 2018 03:18:11 -0700 Subject: [PATCH 052/127] Update .gitignore to new `dart_tool` pub cache dart-lang/sdkdart-lang/json_rpc_2#32030 --- pkgs/json_rpc_2/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkgs/json_rpc_2/.gitignore b/pkgs/json_rpc_2/.gitignore index 7dbf0350d..ab3cb76e6 100644 --- a/pkgs/json_rpc_2/.gitignore +++ b/pkgs/json_rpc_2/.gitignore @@ -1,5 +1,6 @@ # Don’t commit the following directories created by pub. .buildlog +.dart_tool/ .pub/ build/ packages @@ -12,4 +13,4 @@ packages *.js.map # Include when developing application packages. -pubspec.lock \ No newline at end of file +pubspec.lock From 3f041c872c021f29fe358abe51940decdb74f8be Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 12 Jun 2018 12:49:08 -0700 Subject: [PATCH 053/127] Fix a Dart 2 runtime error in the README (dart-lang/json_rpc_2#31) Closes dart-lang/json_rpc_2#30 --- pkgs/json_rpc_2/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkgs/json_rpc_2/README.md b/pkgs/json_rpc_2/README.md index e49526404..0567d20f8 100644 --- a/pkgs/json_rpc_2/README.md +++ b/pkgs/json_rpc_2/README.md @@ -14,7 +14,11 @@ import "package:web_socket_channel/io.dart"; main() async { var socket = IOWebSocketChannel.connect('ws://localhost:4321'); - var server = new json_rpc.Server(socket); + + // The socket is a StreamChannel because it might emit binary + // Lists, but JSON RPC 2 only works with Strings so we assert it only + // emits those by casting it. + var server = new json_rpc.Server(socket.cast()); // Any string may be used as a method name. JSON-RPC 2.0 methods are // case-sensitive. From fea51b4d00a8fda61c9e7fbf44bc74b42d27961a Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Fri, 20 Jul 2018 14:55:55 -0700 Subject: [PATCH 054/127] misc: support Dart 2 stable (dart-lang/json_rpc_2#34) Also fix deprecations in tests --- pkgs/json_rpc_2/analysis_options.yaml | 2 -- pkgs/json_rpc_2/pubspec.yaml | 11 +++++++---- pkgs/json_rpc_2/test/client/client_test.dart | 6 +++--- pkgs/json_rpc_2/test/server/server_test.dart | 4 ++-- pkgs/json_rpc_2/test/server/utils.dart | 2 +- 5 files changed, 13 insertions(+), 12 deletions(-) delete mode 100644 pkgs/json_rpc_2/analysis_options.yaml diff --git a/pkgs/json_rpc_2/analysis_options.yaml b/pkgs/json_rpc_2/analysis_options.yaml deleted file mode 100644 index a10d4c5a0..000000000 --- a/pkgs/json_rpc_2/analysis_options.yaml +++ /dev/null @@ -1,2 +0,0 @@ -analyzer: - strong-mode: true diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 77b52c0b8..8c7773e8a 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,12 +1,15 @@ name: json_rpc_2 -version: 2.0.8 +version: 2.0.9 author: Dart Team description: An implementation of the JSON-RPC 2.0 spec. homepage: http://github.com/dart-lang/json_rpc_2 + +environment: + sdk: ">=2.0.0-dev.17.0 <3.0.0" + dependencies: stack_trace: '>=0.9.1 <2.0.0' stream_channel: '^1.1.0' + dev_dependencies: - test: "^0.12.28" -environment: - sdk: ">=2.0.0-dev.17.0 <2.0.0" + test: ^1.0.0 diff --git a/pkgs/json_rpc_2/test/client/client_test.dart b/pkgs/json_rpc_2/test/client/client_test.dart index 1648df048..358247d27 100644 --- a/pkgs/json_rpc_2/test/client/client_test.dart +++ b/pkgs/json_rpc_2/test/client/client_test.dart @@ -69,7 +69,7 @@ void main() { test("sends a synchronous batch of requests", () { controller.expectRequest((request) { - expect(request, new isInstanceOf()); + expect(request, new TypeMatcher()); expect(request, hasLength(3)); expect(request[0], equals({'jsonrpc': '2.0', 'method': 'foo'})); expect( @@ -101,7 +101,7 @@ void main() { test("sends an asynchronous batch of requests", () { controller.expectRequest((request) { - expect(request, new isInstanceOf()); + expect(request, new TypeMatcher()); expect(request, hasLength(3)); expect(request[0], equals({'jsonrpc': '2.0', 'method': 'foo'})); expect( @@ -157,7 +157,7 @@ void main() { expect(controller.client.sendRequest("foo", {'param': 'value'}), throwsA(predicate((exception) { - expect(exception, new isInstanceOf()); + expect(exception, new TypeMatcher()); expect(exception.code, equals(error_code.SERVER_ERROR)); expect(exception.message, equals('you are bad at requests')); expect(exception.data, equals('some junk')); diff --git a/pkgs/json_rpc_2/test/server/server_test.dart b/pkgs/json_rpc_2/test/server/server_test.dart index c2f87ab0e..b9ba504d3 100644 --- a/pkgs/json_rpc_2/test/server/server_test.dart +++ b/pkgs/json_rpc_2/test/server/server_test.dart @@ -70,7 +70,7 @@ void main() { 'data': { 'request': {'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}, 'full': 'FormatException: bad format', - 'stack': new isInstanceOf() + 'stack': new TypeMatcher() } } })); @@ -168,7 +168,7 @@ void main() { 'data': { 'request': {'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}, 'full': 'FormatException: bad format', - 'stack': new isInstanceOf() + 'stack': new TypeMatcher() } } })); diff --git a/pkgs/json_rpc_2/test/server/utils.dart b/pkgs/json_rpc_2/test/server/utils.dart index f383f8936..079201a2b 100644 --- a/pkgs/json_rpc_2/test/server/utils.dart +++ b/pkgs/json_rpc_2/test/server/utils.dart @@ -64,7 +64,7 @@ void expectErrorResponse( /// `invalid_params` error code. Matcher throwsInvalidParams(String message) { return throwsA(predicate((error) { - expect(error, new isInstanceOf()); + expect(error, new TypeMatcher()); expect(error.code, equals(error_code.INVALID_PARAMS)); expect(error.message, equals(message)); return true; From 1d52c4325857b3897be2c593b55925051012e0f1 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Wed, 27 Mar 2019 14:49:50 -0700 Subject: [PATCH 055/127] Allow stream_channel version 2.x (dart-lang/json_rpc_2#38) None of the planned breaking changes impact this package. - Fix constraint style. - Remove lower bound on `stack_trace` since versions before `1.0.0` didn't support the Dart 2 SDK anyway. - Expand description. --- pkgs/json_rpc_2/CHANGELOG.md | 4 ++++ pkgs/json_rpc_2/pubspec.yaml | 11 ++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 3e2d662dc..24a90935c 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.9 + +* Allow `stream_channel` version 2.x + ## 2.0.8 * Updated SDK version to 2.0.0-dev.17.0 diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 8c7773e8a..2708dd488 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,15 +1,16 @@ name: json_rpc_2 version: 2.0.9 author: Dart Team -description: An implementation of the JSON-RPC 2.0 spec. -homepage: http://github.com/dart-lang/json_rpc_2 +description: >- + Utilities to write a client or server using the JSON-RPC 2.0 spec. +homepage: https://github.com/dart-lang/json_rpc_2 environment: - sdk: ">=2.0.0-dev.17.0 <3.0.0" + sdk: ">=2.0.0 <3.0.0" dependencies: - stack_trace: '>=0.9.1 <2.0.0' - stream_channel: '^1.1.0' + stack_trace: ^1.0.0 + stream_channel: ">=1.1.0 <3.0.0" dev_dependencies: test: ^1.0.0 From 766f864392d17e15d3f79f848b18e2eca637c13b Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Wed, 27 Mar 2019 14:59:13 -0700 Subject: [PATCH 056/127] Bump to 2.0.10 (dart-lang/json_rpc_2#39) Version 2.0.9 was already published with no changelog. --- pkgs/json_rpc_2/CHANGELOG.md | 2 +- pkgs/json_rpc_2/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 24a90935c..c5f9542ac 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.0.9 +## 2.0.10 * Allow `stream_channel` version 2.x diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 2708dd488..799fb8898 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.0.9 +version: 2.0.10 author: Dart Team description: >- Utilities to write a client or server using the JSON-RPC 2.0 spec. From 7b10b8ab78d82e731115567b66b5815f288fb7ed Mon Sep 17 00:00:00 2001 From: Dan Field Date: Tue, 23 Apr 2019 10:35:43 -0700 Subject: [PATCH 057/127] Add an unhandledError callback (dart-lang/json_rpc_2#37) Allows clients to handle or rethrow errors instead of silently swallowing them. --- pkgs/json_rpc_2/CHANGELOG.md | 5 +++++ pkgs/json_rpc_2/lib/src/peer.dart | 19 +++++++++++++++---- pkgs/json_rpc_2/lib/src/server.dart | 25 ++++++++++++++++++++++--- pkgs/json_rpc_2/pubspec.yaml | 2 +- pkgs/json_rpc_2/test/peer_test.dart | 20 ++++++++++++++++++++ pkgs/json_rpc_2/test/server/utils.dart | 5 +++-- 6 files changed, 66 insertions(+), 10 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index c5f9542ac..878e1f078 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -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 diff --git a/pkgs/json_rpc_2/lib/src/peer.dart b/pkgs/json_rpc_2/lib/src/peer.dart index 18cac075a..71767638d 100644 --- a/pkgs/json_rpc_2/lib/src/peer.dart +++ b/pkgs/json_rpc_2/lib/src/peer.dart @@ -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 channel) + /// + /// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError]. + /// If this is not provided, unhandled exceptions will be swallowed. + Peer(StreamChannel 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]. /// @@ -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)); } diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index 0aa4c2f4a..5c3b1321c 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -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 @@ -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 channel) + /// + /// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError]. + /// If this is not provided, unhandled exceptions will be swallowed. + Server(StreamChannel 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]. @@ -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. @@ -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); diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 799fb8898..40c0293eb 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.0.10 +version: 2.1.0 author: Dart Team description: >- Utilities to write a client or server using the JSON-RPC 2.0 spec. diff --git a/pkgs/json_rpc_2/test/peer_test.dart b/pkgs/json_rpc_2/test/peer_test.dart index 94c49e6bb..491526fa0 100644 --- a/pkgs/json_rpc_2/test/peer_test.dart +++ b/pkgs/json_rpc_2/test/peer_test.dart @@ -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 completer = Completer(); + 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)); + }); } diff --git a/pkgs/json_rpc_2/test/server/utils.dart b/pkgs/json_rpc_2/test/server/utils.dart index 079201a2b..ef089ad2a 100644 --- a/pkgs/json_rpc_2/test/server/utils.dart +++ b/pkgs/json_rpc_2/test/server/utils.dart @@ -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(); } From 97530173b6fd02bc9379d2053507a450f0110f43 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Wed, 31 Jul 2019 21:13:08 -0700 Subject: [PATCH 058/127] Delete codereview.settings --- pkgs/json_rpc_2/codereview.settings | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 pkgs/json_rpc_2/codereview.settings diff --git a/pkgs/json_rpc_2/codereview.settings b/pkgs/json_rpc_2/codereview.settings deleted file mode 100644 index 378f6eabb..000000000 --- a/pkgs/json_rpc_2/codereview.settings +++ /dev/null @@ -1,3 +0,0 @@ -CODE_REVIEW_SERVER: http://codereview.chromium.org/ -VIEW_VC: https://github.com/dart-lang/json_rpc_2/commit/ -CC_LIST: reviews@dartlang.org \ No newline at end of file From 559c0b2b318f2aea87795c687403cbd6977d22d5 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Mon, 27 Jan 2020 16:48:46 +0100 Subject: [PATCH 059/127] Enforce package:pedantic lints (dart-lang/json_rpc_2#41) --- pkgs/json_rpc_2/analysis_options.yaml | 5 + pkgs/json_rpc_2/lib/error_code.dart | 10 +- pkgs/json_rpc_2/lib/src/channel_manager.dart | 6 +- pkgs/json_rpc_2/lib/src/client.dart | 47 ++--- pkgs/json_rpc_2/lib/src/exception.dart | 11 +- pkgs/json_rpc_2/lib/src/parameters.dart | 59 ++++-- pkgs/json_rpc_2/lib/src/peer.dart | 27 ++- pkgs/json_rpc_2/lib/src/server.dart | 44 ++-- pkgs/json_rpc_2/lib/src/utils.dart | 27 ++- pkgs/json_rpc_2/pubspec.yaml | 1 + pkgs/json_rpc_2/test/client/client_test.dart | 72 +++---- pkgs/json_rpc_2/test/client/stream_test.dart | 40 ++-- pkgs/json_rpc_2/test/client/utils.dart | 10 +- pkgs/json_rpc_2/test/peer_test.dart | 168 +++++++-------- pkgs/json_rpc_2/test/server/batch_test.dart | 2 +- .../test/server/invalid_request_test.dart | 16 +- .../test/server/parameters_test.dart | 196 +++++++++--------- pkgs/json_rpc_2/test/server/server_test.dart | 40 ++-- pkgs/json_rpc_2/test/server/stream_test.dart | 18 +- pkgs/json_rpc_2/test/server/utils.dart | 12 +- 20 files changed, 423 insertions(+), 388 deletions(-) create mode 100644 pkgs/json_rpc_2/analysis_options.yaml diff --git a/pkgs/json_rpc_2/analysis_options.yaml b/pkgs/json_rpc_2/analysis_options.yaml new file mode 100644 index 000000000..bff1a7444 --- /dev/null +++ b/pkgs/json_rpc_2/analysis_options.yaml @@ -0,0 +1,5 @@ +# Defines a default set of lint rules enforced for +# projects at Google. For details and rationale, +# see https://github.com/dart-lang/pedantic#enabled-lints. +include: package:pedantic/analysis_options.yaml + diff --git a/pkgs/json_rpc_2/lib/error_code.dart b/pkgs/json_rpc_2/lib/error_code.dart index eb2d9be95..4bb080883 100644 --- a/pkgs/json_rpc_2/lib/error_code.dart +++ b/pkgs/json_rpc_2/lib/error_code.dart @@ -40,15 +40,15 @@ const SERVER_ERROR = -32000; String name(int errorCode) { switch (errorCode) { case PARSE_ERROR: - return "parse error"; + return 'parse error'; case INVALID_REQUEST: - return "invalid request"; + return 'invalid request'; case METHOD_NOT_FOUND: - return "method not found"; + return 'method not found'; case INVALID_PARAMS: - return "invalid parameters"; + return 'invalid parameters'; case INTERNAL_ERROR: - return "internal error"; + return 'internal error'; default: return null; } diff --git a/pkgs/json_rpc_2/lib/src/channel_manager.dart b/pkgs/json_rpc_2/lib/src/channel_manager.dart index 162c9ef38..5be11dd65 100644 --- a/pkgs/json_rpc_2/lib/src/channel_manager.dart +++ b/pkgs/json_rpc_2/lib/src/channel_manager.dart @@ -25,7 +25,7 @@ class ChannelManager { /// /// This is the same future that's returned by [listen]. Future get done => _doneCompleter.future; - final _doneCompleter = new Completer.sync(); + final _doneCompleter = Completer.sync(); /// Whether the underlying communication channel is closed. bool get isClosed => _doneCompleter.isCompleted; @@ -45,9 +45,9 @@ class ChannelManager { /// /// The returned Future will complete when the input stream is closed. If the /// input stream emits an error, that will be piped to the returned Future. - Future listen(void handleInput(input)) { + Future listen(void Function(dynamic) handleInput) { if (_listenCalled) { - throw new StateError("Can only call $_name.listen() once."); + throw StateError('Can only call $_name.listen() once.'); } _listenCalled = true; diff --git a/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart index ff5c803ef..1e421edc4 100644 --- a/pkgs/json_rpc_2/lib/src/client.dart +++ b/pkgs/json_rpc_2/lib/src/client.dart @@ -28,7 +28,7 @@ class Client { List _batch; /// The map of request ids to pending requests. - final _pendingRequests = new Map(); + final _pendingRequests = {}; /// Returns a [Future] that completes when the underlying connection is /// closed. @@ -61,11 +61,11 @@ class Client { /// Note that the client won't begin listening to [responses] until /// [Client.listen] is called. Client.withoutJson(StreamChannel channel) - : _manager = new ChannelManager("Client", channel) { + : _manager = ChannelManager('Client', channel) { _manager.done.whenComplete(() { for (var request in _pendingRequests.values) { request.completer.completeError( - new StateError( + StateError( 'The client closed with pending request "${request.method}".'), StackTrace.current); } @@ -106,8 +106,8 @@ class Client { var id = _id++; _send(method, parameters, id); - var completer = new Completer.sync(); - _pendingRequests[id] = new _Request(method, completer, new Chain.current()); + var completer = Completer.sync(); + _pendingRequests[id] = _Request(method, completer, Chain.current()); return completer.future; } @@ -133,14 +133,14 @@ class Client { void _send(String method, parameters, [int id]) { if (parameters is Iterable) parameters = parameters.toList(); if (parameters is! Map && parameters is! List && parameters != null) { - throw new ArgumentError('Only maps and lists may be used as JSON-RPC ' + throw ArgumentError('Only maps and lists may be used as JSON-RPC ' 'parameters, was "$parameters".'); } - if (isClosed) throw new StateError("The client is closed."); + if (isClosed) throw StateError('The client is closed.'); - var message = {"jsonrpc": "2.0", "method": method}; - if (id != null) message["id"] = id; - if (parameters != null) message["params"] = parameters; + var message = {'jsonrpc': '2.0', 'method': method}; + if (id != null) message['id'] = id; + if (parameters != null) message['params'] = parameters; if (_batch != null) { _batch.add(message); @@ -161,7 +161,7 @@ class Client { /// 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. - withBatch(callback()) { + void withBatch(Function() callback) { if (_batch != null) return callback(); _batch = []; @@ -184,14 +184,13 @@ class Client { /// resolved. void _handleSingleResponse(response) { if (!_isResponseValid(response)) return; - var request = _pendingRequests.remove(response["id"]); - if (response.containsKey("result")) { - request.completer.complete(response["result"]); + var request = _pendingRequests.remove(response['id']); + if (response.containsKey('result')) { + request.completer.complete(response['result']); } else { request.completer.completeError( - new RpcException( - response["error"]["code"], response["error"]["message"], - data: response["error"]["data"]), + RpcException(response['error']['code'], response['error']['message'], + data: response['error']['data']), request.chain); } } @@ -199,15 +198,15 @@ class Client { /// Determines whether the server's response is valid per the spec. bool _isResponseValid(response) { if (response is! Map) return false; - if (response["jsonrpc"] != "2.0") return false; - if (!_pendingRequests.containsKey(response["id"])) return false; - if (response.containsKey("result")) return true; + if (response['jsonrpc'] != '2.0') return false; + if (!_pendingRequests.containsKey(response['id'])) return false; + if (response.containsKey('result')) return true; - if (!response.containsKey("error")) return false; - var error = response["error"]; + if (!response.containsKey('error')) return false; + var error = response['error']; if (error is! Map) return false; - if (error["code"] is! int) return false; - if (error["message"] is! String) return false; + if (error['code'] is! int) return false; + if (error['message'] is! String) return false; return true; } } diff --git a/pkgs/json_rpc_2/lib/src/exception.dart b/pkgs/json_rpc_2/lib/src/exception.dart index b359155b9..64c97d07f 100644 --- a/pkgs/json_rpc_2/lib/src/exception.dart +++ b/pkgs/json_rpc_2/lib/src/exception.dart @@ -43,10 +43,10 @@ class RpcException implements Exception { /// Converts this exception into a JSON-serializable object that's a valid /// JSON-RPC 2.0 error response. - serialize(request) { + Map serialize(request) { var modifiedData; if (data is Map && !data.containsKey('request')) { - modifiedData = new Map.from(data); + modifiedData = Map.from(data); modifiedData['request'] = request; } else if (data == null) { modifiedData = {'request': request}; @@ -63,10 +63,11 @@ class RpcException implements Exception { }; } + @override String toString() { - var prefix = "JSON-RPC error $code"; + var prefix = 'JSON-RPC error $code'; var errorName = error_code.name(code); - if (errorName != null) prefix += " ($errorName)"; - return "$prefix: $message"; + if (errorName != null) prefix += ' ($errorName)'; + return '$prefix: $message'; } } diff --git a/pkgs/json_rpc_2/lib/src/parameters.dart b/pkgs/json_rpc_2/lib/src/parameters.dart index 8b39d4633..665765bb2 100644 --- a/pkgs/json_rpc_2/lib/src/parameters.dart +++ b/pkgs/json_rpc_2/lib/src/parameters.dart @@ -26,7 +26,7 @@ class Parameters { /// /// If this is accessed for a [Parameter] that was not passed, the request /// will be automatically rejected. To avoid this, use [Parameter.valueOr]. - get value => _value; + dynamic get value => _value; final _value; Parameters(this.method, this._value); @@ -48,19 +48,19 @@ class Parameters { if (key is int) { _assertPositional(); if (key < value.length) { - return new Parameter._(method, value[key], this, key); + return Parameter._(method, value[key], this, key); } else { - return new _MissingParameter(method, this, key); + return _MissingParameter(method, this, key); } } else if (key is String) { _assertNamed(); if (value.containsKey(key)) { - return new Parameter._(method, value[key], this, key); + return Parameter._(method, value[key], this, key); } else { - return new _MissingParameter(method, this, key); + return _MissingParameter(method, this, key); } } else { - throw new ArgumentError('Parameters[] only takes an int or a string, was ' + throw ArgumentError('Parameters[] only takes an int or a string, was ' '"$key".'); } } @@ -80,14 +80,14 @@ class Parameters { /// Asserts that [value] is a positional argument list. void _assertPositional() { if (value is List) return; - throw new RpcException.invalidParams('Parameters for method "$method" ' + throw RpcException.invalidParams('Parameters for method "$method" ' 'must be passed by position.'); } /// Asserts that [value] is a named argument map. void _assertNamed() { if (value is Map) return; - throw new RpcException.invalidParams('Parameters for method "$method" ' + throw RpcException.invalidParams('Parameters for method "$method" ' 'must be passed by name.'); } } @@ -131,20 +131,20 @@ class Parameter extends Parameters { return _key is int ? (_key + 1).toString() : jsonEncode(_key); } - quoteKey(key) { - if (key.contains(new RegExp(r'[^a-zA-Z0-9_-]'))) return jsonEncode(key); + String quoteKey(key) { + if (key.contains(RegExp(r'[^a-zA-Z0-9_-]'))) return jsonEncode(key); return key; } - computePath(params) { + String computePath(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); } var path = computePath(params._parent); return params._key is int - ? "$path[${params._key}]" - : "$path.${quoteKey(params._key)}"; + ? '$path[${params._key}]' + : '$path.${quoteKey(params._key)}'; } return computePath(this); @@ -157,7 +157,7 @@ class Parameter extends Parameters { : super(method, value); /// Returns [value], or [defaultValue] if this parameter wasn't passed. - valueOr(defaultValue) => value; + dynamic valueOr(defaultValue) => value; /// Asserts that [value] exists and is a number and returns it. /// @@ -215,6 +215,7 @@ class Parameter extends Parameters { /// /// [asListOr] may be used to provide a default value instead of rejecting the /// request if [value] doesn't exist. + @override List get asList => _getTyped('an Array', (value) => value is List); /// Asserts that [value] is a [List] and returns it. @@ -226,6 +227,7 @@ class Parameter extends Parameters { /// /// [asMapOr] may be used to provide a default value instead of rejecting the /// request if [value] doesn't exist. + @override Map get asMap => _getTyped('an Object', (value) => value is Map); /// Asserts that [value] is a [Map] and returns it. @@ -264,13 +266,13 @@ class Parameter extends Parameters { /// /// [type] is used for the error message. It should begin with an indefinite /// article. - _getTyped(String type, bool test(value)) { + dynamic _getTyped(String type, bool Function(dynamic) test) { if (test(value)) return value; - throw new RpcException.invalidParams('Parameter $_path for method ' + throw RpcException.invalidParams('Parameter $_path for method ' '"$method" must be $type, but was ${jsonEncode(value)}.'); } - _getParsed(String description, parse(String value)) { + dynamic _getParsed(String description, Function(String) parse) { var string = asString; try { return parse(string); @@ -285,17 +287,19 @@ class Parameter extends Parameters { message = '\n$message'; } - throw new RpcException.invalidParams('Parameter $_path for method ' + throw RpcException.invalidParams('Parameter $_path for method ' '"$method" must be a valid $description, but was ' '${jsonEncode(string)}.$message'); } } + @override void _assertPositional() { // Throw the standard exception for a mis-typed list. asList; } + @override void _assertNamed() { // Throw the standard exception for a mis-typed map. asMap; @@ -304,31 +308,42 @@ class Parameter extends Parameters { /// A subclass of [Parameter] representing a missing parameter. class _MissingParameter extends Parameter { - get value { - throw new RpcException.invalidParams('Request for method "$method" is ' + @override + dynamic get value { + throw RpcException.invalidParams('Request for method "$method" is ' 'missing required parameter $_path.'); } + @override bool get exists => false; _MissingParameter(String method, Parameters parent, key) : super._(method, null, parent, key); - valueOr(defaultValue) => defaultValue; + @override + dynamic valueOr(defaultValue) => defaultValue; + @override num asNumOr(num defaultValue) => defaultValue; + @override int asIntOr(int defaultValue) => defaultValue; + @override bool asBoolOr(bool defaultValue) => defaultValue; + @override String asStringOr(String defaultValue) => defaultValue; + @override List asListOr(List defaultValue) => defaultValue; + @override Map asMapOr(Map defaultValue) => defaultValue; + @override DateTime asDateTimeOr(DateTime defaultValue) => defaultValue; + @override Uri asUriOr(Uri defaultValue) => defaultValue; } diff --git a/pkgs/json_rpc_2/lib/src/peer.dart b/pkgs/json_rpc_2/lib/src/peer.dart index 71767638d..cd51a7c66 100644 --- a/pkgs/json_rpc_2/lib/src/peer.dart +++ b/pkgs/json_rpc_2/lib/src/peer.dart @@ -30,13 +30,15 @@ class Peer implements Client, Server { /// A stream controller that forwards incoming messages to [_server] if /// they're requests. - final _serverIncomingForwarder = new StreamController(sync: true); + final _serverIncomingForwarder = StreamController(sync: true); /// A stream controller that forwards incoming messages to [_client] if /// they're responses. - final _clientIncomingForwarder = new StreamController(sync: true); + final _clientIncomingForwarder = StreamController(sync: true); + @override Future get done => _manager.done; + @override bool get isClosed => _manager.isClosed; @override @@ -65,34 +67,40 @@ class Peer implements Client, Server { /// 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), + : _manager = ChannelManager('Peer', channel) { + _server = Server.withoutJson( + StreamChannel(_serverIncomingForwarder.stream, channel.sink), onUnhandledError: onUnhandledError); - _client = new Client.withoutJson( - new StreamChannel(_clientIncomingForwarder.stream, channel.sink)); + _client = Client.withoutJson( + StreamChannel(_clientIncomingForwarder.stream, channel.sink)); } // Client methods. + @override Future sendRequest(String method, [parameters]) => _client.sendRequest(method, parameters); + @override void sendNotification(String method, [parameters]) => _client.sendNotification(method, parameters); - withBatch(callback()) => _client.withBatch(callback); + @override + void withBatch(Function() callback) => _client.withBatch(callback); // Server methods. + @override void registerMethod(String name, Function callback) => _server.registerMethod(name, callback); - void registerFallback(callback(Parameters parameters)) => + @override + void registerFallback(Function(Parameters parameters) callback) => _server.registerFallback(callback); // Shared methods. + @override Future listen() { _client.listen(); _server.listen(); @@ -120,6 +128,7 @@ class Peer implements Client, Server { }); } + @override Future close() => Future.wait([_client.close(), _server.close(), _manager.close()]); } diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index 5c3b1321c..30da924db 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -32,13 +32,13 @@ class Server { final ChannelManager _manager; /// The methods registered for this server. - final _methods = new Map(); + final _methods = {}; /// The fallback methods for this server. /// /// These are tried in order until one of them doesn't throw a /// [RpcException.methodNotFound] exception. - final _fallbacks = new Queue(); + final _fallbacks = Queue(); /// Returns a [Future] that completes when the underlying connection is /// closed. @@ -85,7 +85,7 @@ class Server { /// 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); + : _manager = ChannelManager('Server', channel); /// Starts listening to the underlying stream. /// @@ -112,7 +112,7 @@ class Server { /// reported to the client as JSON-RPC 2.0 errors. void registerMethod(String name, Function callback) { if (_methods.containsKey(name)) { - throw new ArgumentError('There\'s already a method named "$name".'); + throw ArgumentError('There\'s already a method named "$name".'); } _methods[name] = callback; @@ -129,7 +129,7 @@ class Server { /// 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(callback(Parameters parameters)) { + void registerFallback(Function(Parameters parameters) callback) { _fallbacks.add(callback); } @@ -144,7 +144,7 @@ class Server { var response; if (request is List) { if (request.isEmpty) { - response = new RpcException(error_code.INVALID_REQUEST, + response = RpcException(error_code.INVALID_REQUEST, 'A batch must contain at least one request.') .serialize(request); } else { @@ -168,17 +168,17 @@ class Server { var name = request['method']; var method = _methods[name]; - if (method == null) method = _tryFallbacks; + method ??= _tryFallbacks; Object result; if (method is ZeroArgumentFunction) { if (request.containsKey('params')) { - throw new RpcException.invalidParams('No parameters are allowed for ' + throw RpcException.invalidParams('No parameters are allowed for ' 'method "$name".'); } result = await method(); } else { - result = await method(new Parameters(name, request['params'])); + result = await method(Parameters(name, request['params'])); } // A request without an id is a notification, which should not be sent a @@ -199,8 +199,8 @@ class Server { onUnhandledError?.call(error, stackTrace); return null; } - final chain = new Chain.forTrace(stackTrace); - return new RpcException(error_code.SERVER_ERROR, getErrorMessage(error), + final chain = Chain.forTrace(stackTrace); + return RpcException(error_code.SERVER_ERROR, getErrorMessage(error), data: { 'full': '$error', 'stack': '$chain', @@ -211,28 +211,28 @@ class Server { /// Validates that [request] matches the JSON-RPC spec. void _validateRequest(request) { if (request is! Map) { - throw new RpcException( + throw RpcException( error_code.INVALID_REQUEST, 'Request must be ' 'an Array or an Object.'); } if (!request.containsKey('jsonrpc')) { - throw new RpcException( + throw RpcException( error_code.INVALID_REQUEST, 'Request must ' 'contain a "jsonrpc" key.'); } if (request['jsonrpc'] != '2.0') { - throw new RpcException( + throw RpcException( error_code.INVALID_REQUEST, 'Invalid JSON-RPC ' 'version ${jsonEncode(request['jsonrpc'])}, expected "2.0".'); } if (!request.containsKey('method')) { - throw new RpcException( + throw RpcException( error_code.INVALID_REQUEST, 'Request must ' 'contain a "method" key.'); @@ -240,7 +240,7 @@ class Server { var method = request['method']; if (request['method'] is! String) { - throw new RpcException( + throw RpcException( error_code.INVALID_REQUEST, 'Request method must ' 'be a string, but was ${jsonEncode(method)}.'); @@ -248,7 +248,7 @@ class Server { var params = request['params']; if (request.containsKey('params') && params is! List && params is! Map) { - throw new RpcException( + throw RpcException( error_code.INVALID_REQUEST, 'Request params must ' 'be an Array or an Object, but was ${jsonEncode(params)}.'); @@ -256,7 +256,7 @@ class Server { var id = request['id']; if (id != null && id is! String && id is! num) { - throw new RpcException( + throw RpcException( error_code.INVALID_REQUEST, 'Request id must be a ' 'string, number, or null, but was ${jsonEncode(id)}.'); @@ -267,16 +267,16 @@ class Server { Future _tryFallbacks(Parameters params) { var iterator = _fallbacks.toList().iterator; - _tryNext() async { + Future _tryNext() async { if (!iterator.moveNext()) { - throw new RpcException.methodNotFound(params.method); + throw RpcException.methodNotFound(params.method); } try { return iterator.current(params); } on RpcException catch (error) { - if (error is! RpcException) throw error; - if (error.code != error_code.METHOD_NOT_FOUND) throw error; + if (error is! RpcException) rethrow; + if (error.code != error_code.METHOD_NOT_FOUND) rethrow; return _tryNext(); } } diff --git a/pkgs/json_rpc_2/lib/src/utils.dart b/pkgs/json_rpc_2/lib/src/utils.dart index 1e2d6b1c7..f1989a798 100644 --- a/pkgs/json_rpc_2/lib/src/utils.dart +++ b/pkgs/json_rpc_2/lib/src/utils.dart @@ -9,7 +9,7 @@ import 'package:stream_channel/stream_channel.dart'; import '../error_code.dart' as error_code; import 'exception.dart'; -typedef ZeroArgumentFunction(); +typedef ZeroArgumentFunction = Function(); /// Returns a sentence fragment listing the elements of [iter]. /// @@ -17,7 +17,7 @@ typedef ZeroArgumentFunction(); /// commas and/or "and" where appropriate. String toSentence(Iterable iter) { if (iter.length == 1) return iter.first.toString(); - return iter.take(iter.length - 1).join(", ") + " and ${iter.last}"; + return iter.take(iter.length - 1).join(', ') + ' and ${iter.last}'; } /// Returns [name] if [number] is 1, or the plural of [name] otherwise. @@ -32,7 +32,7 @@ String pluralize(String name, int number, {String plural}) { /// A regular expression to match the exception prefix that some exceptions' /// [Object.toString] values contain. -final _exceptionPrefix = new RegExp(r'^([A-Z][a-zA-Z]*)?(Exception|Error): '); +final _exceptionPrefix = RegExp(r'^([A-Z][a-zA-Z]*)?(Exception|Error): '); /// Get a string description of an exception. /// @@ -46,7 +46,7 @@ String getErrorMessage(error) => /// /// This is synchronicity-agnostic relative to [body]. If [body] returns a /// [Future], this wil run asynchronously; otherwise it will run synchronously. -tryFinally(body(), whenComplete()) { +void tryFinally(Function() body, Function() whenComplete) { var result; try { result = body(); @@ -64,27 +64,27 @@ tryFinally(body(), whenComplete()) { } /// A transformer that silently drops [FormatException]s. -final ignoreFormatExceptions = - new StreamTransformer.fromHandlers( - handleError: (error, stackTrace, sink) { +final ignoreFormatExceptions = StreamTransformer.fromHandlers( + handleError: (error, stackTrace, sink) { if (error is FormatException) return; sink.addError(error, stackTrace); }); /// A transformer that sends error responses on [FormatException]s. final StreamChannelTransformer respondToFormatExceptions = - new _RespondToFormatExceptionsTransformer(); + _RespondToFormatExceptionsTransformer(); /// The implementation of [respondToFormatExceptions]. class _RespondToFormatExceptionsTransformer implements StreamChannelTransformer { + @override StreamChannel bind(StreamChannel channel) { var transformed; transformed = channel.changeStream((stream) { return stream.handleError((error) { if (error is! FormatException) throw error; - var exception = new RpcException( + var exception = RpcException( error_code.PARSE_ERROR, 'Invalid JSON: ${error.message}'); transformed.sink.add(exception.serialize(error.source)); }); @@ -95,21 +95,26 @@ class _RespondToFormatExceptionsTransformer /// Returns a [StreamSink] that wraps [sink] and maps each event added using /// [callback]. -StreamSink mapStreamSink(StreamSink sink, callback(event)) => - new _MappedStreamSink(sink, callback); +StreamSink mapStreamSink(StreamSink sink, Function(dynamic) callback) => + _MappedStreamSink(sink, callback); /// A [StreamSink] wrapper that maps each event added to the sink. class _MappedStreamSink implements StreamSink { final StreamSink _inner; final Function _callback; + @override Future get done => _inner.done; _MappedStreamSink(this._inner, this._callback); + @override void add(event) => _inner.add(_callback(event)); + @override void addError(error, [StackTrace stackTrace]) => _inner.addError(error, stackTrace); + @override Future addStream(Stream stream) => _inner.addStream(stream.map(_callback)); + @override Future close() => _inner.close(); } diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 40c0293eb..5e8e29c01 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -13,4 +13,5 @@ dependencies: stream_channel: ">=1.1.0 <3.0.0" dev_dependencies: + pedantic: ^1.8.0 test: ^1.0.0 diff --git a/pkgs/json_rpc_2/test/client/client_test.dart b/pkgs/json_rpc_2/test/client/client_test.dart index 358247d27..c1edbe4c7 100644 --- a/pkgs/json_rpc_2/test/client/client_test.dart +++ b/pkgs/json_rpc_2/test/client/client_test.dart @@ -12,9 +12,9 @@ import 'utils.dart'; void main() { var controller; - setUp(() => controller = new ClientController()); + setUp(() => controller = ClientController()); - test("sends a message and returns the response", () { + test('sends a message and returns the response', () { controller.expectRequest((request) { expect( request, @@ -27,11 +27,11 @@ void main() { return {'jsonrpc': '2.0', 'result': 'bar', 'id': request['id']}; }); - expect(controller.client.sendRequest("foo", {'param': 'value'}), + expect(controller.client.sendRequest('foo', {'param': 'value'}), completion(equals('bar'))); }); - test("sends a notification and expects no response", () { + test('sends a notification and expects no response', () { controller.expectRequest((request) { expect( request, @@ -42,10 +42,10 @@ void main() { })); }); - controller.client.sendNotification("foo", {'param': 'value'}); + controller.client.sendNotification('foo', {'param': 'value'}); }); - test("sends a notification with positional parameters", () { + test('sends a notification with positional parameters', () { controller.expectRequest((request) { expect( request, @@ -56,20 +56,20 @@ void main() { })); }); - controller.client.sendNotification("foo", ['value1', 'value2']); + controller.client.sendNotification('foo', ['value1', 'value2']); }); - test("sends a notification with no parameters", () { + test('sends a notification with no parameters', () { controller.expectRequest((request) { expect(request, equals({'jsonrpc': '2.0', 'method': 'foo'})); }); - controller.client.sendNotification("foo"); + controller.client.sendNotification('foo'); }); - test("sends a synchronous batch of requests", () { + test('sends a synchronous batch of requests', () { controller.expectRequest((request) { - expect(request, new TypeMatcher()); + expect(request, TypeMatcher()); expect(request, hasLength(3)); expect(request[0], equals({'jsonrpc': '2.0', 'method': 'foo'})); expect( @@ -91,17 +91,17 @@ void main() { }); controller.client.withBatch(() { - controller.client.sendNotification("foo"); - expect(controller.client.sendRequest("bar", {'param': 'value'}), - completion(equals("bar response"))); - expect(controller.client.sendRequest("baz"), - completion(equals("baz response"))); + controller.client.sendNotification('foo'); + expect(controller.client.sendRequest('bar', {'param': 'value'}), + completion(equals('bar response'))); + expect(controller.client.sendRequest('baz'), + completion(equals('baz response'))); }); }); - test("sends an asynchronous batch of requests", () { + test('sends an asynchronous batch of requests', () { controller.expectRequest((request) { - expect(request, new TypeMatcher()); + expect(request, TypeMatcher()); expect(request, hasLength(3)); expect(request[0], equals({'jsonrpc': '2.0', 'method': 'foo'})); expect( @@ -123,21 +123,21 @@ void main() { }); controller.client.withBatch(() { - return new Future.value().then((_) { - controller.client.sendNotification("foo"); - return new Future.value(); + return Future.value().then((_) { + controller.client.sendNotification('foo'); + return Future.value(); }).then((_) { - expect(controller.client.sendRequest("bar", {'param': 'value'}), - completion(equals("bar response"))); - return new Future.value(); + expect(controller.client.sendRequest('bar', {'param': 'value'}), + completion(equals('bar response'))); + return Future.value(); }).then((_) { - expect(controller.client.sendRequest("baz"), - completion(equals("baz response"))); + expect(controller.client.sendRequest('baz'), + completion(equals('baz response'))); }); }); }); - test("reports an error from the server", () { + test('reports an error from the server', () { controller.expectRequest((request) { expect( request, @@ -155,9 +155,9 @@ void main() { }; }); - expect(controller.client.sendRequest("foo", {'param': 'value'}), + expect(controller.client.sendRequest('foo', {'param': 'value'}), throwsA(predicate((exception) { - expect(exception, new TypeMatcher()); + expect(exception, TypeMatcher()); expect(exception.code, equals(error_code.SERVER_ERROR)); expect(exception.message, equals('you are bad at requests')); expect(exception.data, equals('some junk')); @@ -165,17 +165,17 @@ void main() { }))); }); - test("requests throw StateErrors if the client is closed", () { + test('requests throw StateErrors if the client is closed', () { controller.client.close(); - expect(() => controller.client.sendRequest("foo"), throwsStateError); - expect(() => controller.client.sendNotification("foo"), throwsStateError); + expect(() => controller.client.sendRequest('foo'), throwsStateError); + expect(() => controller.client.sendNotification('foo'), throwsStateError); }); - test("ignores bogus responses", () { + test('ignores bogus responses', () { // Make a request so we have something to respond to. controller.expectRequest((request) { - controller.sendJsonResponse("{invalid"); - controller.sendResponse("not a map"); + controller.sendJsonResponse('{invalid'); + controller.sendResponse('not a map'); controller.sendResponse( {'jsonrpc': 'wrong version', 'result': 'wrong', 'id': request['id']}); controller.sendResponse({'jsonrpc': '2.0', 'result': 'wrong'}); @@ -197,6 +197,6 @@ void main() { (_) => {'jsonrpc': '2.0', 'result': 'right', 'id': request['id']}); }); - expect(controller.client.sendRequest("foo"), completion(equals('right'))); + expect(controller.client.sendRequest('foo'), completion(equals('right'))); }); } diff --git a/pkgs/json_rpc_2/test/client/stream_test.dart b/pkgs/json_rpc_2/test/client/stream_test.dart index 94a060458..4080064de 100644 --- a/pkgs/json_rpc_2/test/client/stream_test.dart +++ b/pkgs/json_rpc_2/test/client/stream_test.dart @@ -14,13 +14,13 @@ void main() { var requestController; var client; setUp(() { - responseController = new StreamController(); - requestController = new StreamController(); - client = new json_rpc.Client.withoutJson( - new StreamChannel(responseController.stream, requestController.sink)); + responseController = StreamController(); + requestController = StreamController(); + client = json_rpc.Client.withoutJson( + StreamChannel(responseController.stream, requestController.sink)); }); - test(".withoutJson supports decoded stream and sink", () { + test('.withoutJson supports decoded stream and sink', () { client.listen(); expect(requestController.stream.first.then((request) { @@ -36,7 +36,7 @@ void main() { client.sendRequest('foo'); }); - test(".listen returns when the controller is closed", () { + test('.listen returns when the controller is closed', () { var hasListenCompeted = false; expect(client.listen().then((_) => hasListenCompeted = true), completes); @@ -48,17 +48,17 @@ void main() { }); }); - test(".listen returns a stream error", () { + test('.listen returns a stream error', () { expect(client.listen(), throwsA('oh no')); responseController.addError('oh no'); }); - test(".listen can't be called twice", () { + test('.listen can\'t be called twice', () { client.listen(); expect(() => client.listen(), throwsStateError); }); - test(".close cancels the stream subscription and closes the sink", () { + test('.close cancels the stream subscription and closes the sink', () { // Work around sdk#19095. requestController.stream.listen(null); @@ -72,22 +72,22 @@ void main() { expect(requestController.isClosed, isTrue); }); - group("a stream error", () { - test("is reported through .done", () { - expect(client.listen(), throwsA("oh no!")); - expect(client.done, throwsA("oh no!")); - responseController.addError("oh no!"); + group('a stream error', () { + test('is reported through .done', () { + expect(client.listen(), throwsA('oh no!')); + expect(client.done, throwsA('oh no!')); + responseController.addError('oh no!'); }); - test("cause a pending request to throw a StateError", () { - expect(client.listen(), throwsA("oh no!")); + test('cause a pending request to throw a StateError', () { + expect(client.listen(), throwsA('oh no!')); expect(client.sendRequest('foo'), throwsStateError); - responseController.addError("oh no!"); + responseController.addError('oh no!'); }); - test("causes future requests to throw StateErrors", () async { - expect(client.listen(), throwsA("oh no!")); - responseController.addError("oh no!"); + test('causes future requests to throw StateErrors', () async { + expect(client.listen(), throwsA('oh no!')); + responseController.addError('oh no!'); await pumpEventQueue(); expect(() => client.sendRequest('foo'), throwsStateError); diff --git a/pkgs/json_rpc_2/test/client/utils.dart b/pkgs/json_rpc_2/test/client/utils.dart index 8a0a9a326..1684b3705 100644 --- a/pkgs/json_rpc_2/test/client/utils.dart +++ b/pkgs/json_rpc_2/test/client/utils.dart @@ -13,18 +13,18 @@ import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; /// A controller used to test a [json_rpc.Client]. class ClientController { /// The controller for the client's response stream. - final _responseController = new StreamController(); + final _responseController = StreamController(); /// The controller for the client's request sink. - final _requestController = new StreamController(); + final _requestController = StreamController(); /// The client. json_rpc.Client get client => _client; json_rpc.Client _client; ClientController() { - _client = new json_rpc.Client( - new StreamChannel(_responseController.stream, _requestController.sink)); + _client = json_rpc.Client( + StreamChannel(_responseController.stream, _requestController.sink)); _client.listen(); } @@ -34,7 +34,7 @@ class ClientController { /// 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(callback(request)) { + void expectRequest(Function(dynamic) callback) { expect( _requestController.stream.first.then((request) { return callback(jsonDecode(request)); diff --git a/pkgs/json_rpc_2/test/peer_test.dart b/pkgs/json_rpc_2/test/peer_test.dart index 491526fa0..33184b0ab 100644 --- a/pkgs/json_rpc_2/test/peer_test.dart +++ b/pkgs/json_rpc_2/test/peer_test.dart @@ -16,62 +16,62 @@ void main() { var outgoing; var peer; setUp(() { - var incomingController = new StreamController(); + var incomingController = StreamController(); incoming = incomingController.sink; - var outgoingController = new StreamController(); + var outgoingController = StreamController(); outgoing = outgoingController.stream; - peer = new json_rpc.Peer.withoutJson( - new StreamChannel(incomingController.stream, outgoingController)); + peer = json_rpc.Peer.withoutJson( + StreamChannel(incomingController.stream, outgoingController)); }); - group("like a client,", () { - test("can send a message and receive a response", () { + group('like a client,', () { + test('can send a message and receive a response', () { expect(outgoing.first.then((request) { expect( request, equals({ - "jsonrpc": "2.0", - "method": "foo", - "params": {"bar": "baz"}, - "id": 0 + 'jsonrpc': '2.0', + 'method': 'foo', + 'params': {'bar': 'baz'}, + 'id': 0 })); - incoming.add({"jsonrpc": "2.0", "result": "qux", "id": 0}); + incoming.add({'jsonrpc': '2.0', 'result': 'qux', 'id': 0}); }), completes); peer.listen(); expect( - peer.sendRequest("foo", {"bar": "baz"}), completion(equals("qux"))); + peer.sendRequest('foo', {'bar': 'baz'}), completion(equals('qux'))); }); - test("can send a batch of messages and receive a batch of responses", () { + test('can send a batch of messages and receive a batch of responses', () { expect(outgoing.first.then((request) { expect( request, equals([ { - "jsonrpc": "2.0", - "method": "foo", - "params": {"bar": "baz"}, - "id": 0 + 'jsonrpc': '2.0', + 'method': 'foo', + 'params': {'bar': 'baz'}, + 'id': 0 }, { - "jsonrpc": "2.0", - "method": "a", - "params": {"b": "c"}, - "id": 1 + 'jsonrpc': '2.0', + 'method': 'a', + 'params': {'b': 'c'}, + 'id': 1 }, { - "jsonrpc": "2.0", - "method": "w", - "params": {"x": "y"}, - "id": 2 + 'jsonrpc': '2.0', + 'method': 'w', + 'params': {'x': 'y'}, + 'id': 2 } ])); incoming.add([ - {"jsonrpc": "2.0", "result": "qux", "id": 0}, - {"jsonrpc": "2.0", "result": "d", "id": 1}, - {"jsonrpc": "2.0", "result": "z", "id": 2} + {'jsonrpc': '2.0', 'result': 'qux', 'id': 0}, + {'jsonrpc': '2.0', 'result': 'd', 'id': 1}, + {'jsonrpc': '2.0', 'result': 'z', 'id': 2} ]); }), completes); @@ -79,119 +79,119 @@ void main() { peer.withBatch(() { expect( - peer.sendRequest("foo", {"bar": "baz"}), completion(equals("qux"))); - expect(peer.sendRequest("a", {"b": "c"}), completion(equals("d"))); - expect(peer.sendRequest("w", {"x": "y"}), completion(equals("z"))); + peer.sendRequest('foo', {'bar': 'baz'}), completion(equals('qux'))); + expect(peer.sendRequest('a', {'b': 'c'}), completion(equals('d'))); + expect(peer.sendRequest('w', {'x': 'y'}), completion(equals('z'))); }); }); }); - group("like a server,", () { - test("can receive a call and return a response", () { + group('like a server,', () { + test('can receive a call and return a response', () { expect(outgoing.first, - completion(equals({"jsonrpc": "2.0", "result": "qux", "id": 0}))); + completion(equals({'jsonrpc': '2.0', 'result': 'qux', 'id': 0}))); - peer.registerMethod("foo", (_) => "qux"); + peer.registerMethod('foo', (_) => 'qux'); peer.listen(); incoming.add({ - "jsonrpc": "2.0", - "method": "foo", - "params": {"bar": "baz"}, - "id": 0 + 'jsonrpc': '2.0', + 'method': 'foo', + 'params': {'bar': 'baz'}, + 'id': 0 }); }); - test("can receive a batch of calls and return a batch of responses", () { + test('can receive a batch of calls and return a batch of responses', () { expect( outgoing.first, completion(equals([ - {"jsonrpc": "2.0", "result": "qux", "id": 0}, - {"jsonrpc": "2.0", "result": "d", "id": 1}, - {"jsonrpc": "2.0", "result": "z", "id": 2} + {'jsonrpc': '2.0', 'result': 'qux', 'id': 0}, + {'jsonrpc': '2.0', 'result': 'd', 'id': 1}, + {'jsonrpc': '2.0', 'result': 'z', 'id': 2} ]))); - peer.registerMethod("foo", (_) => "qux"); - peer.registerMethod("a", (_) => "d"); - peer.registerMethod("w", (_) => "z"); + peer.registerMethod('foo', (_) => 'qux'); + peer.registerMethod('a', (_) => 'd'); + peer.registerMethod('w', (_) => 'z'); peer.listen(); incoming.add([ { - "jsonrpc": "2.0", - "method": "foo", - "params": {"bar": "baz"}, - "id": 0 + 'jsonrpc': '2.0', + 'method': 'foo', + 'params': {'bar': 'baz'}, + 'id': 0 }, { - "jsonrpc": "2.0", - "method": "a", - "params": {"b": "c"}, - "id": 1 + 'jsonrpc': '2.0', + 'method': 'a', + 'params': {'b': 'c'}, + 'id': 1 }, { - "jsonrpc": "2.0", - "method": "w", - "params": {"x": "y"}, - "id": 2 + 'jsonrpc': '2.0', + 'method': 'w', + 'params': {'x': 'y'}, + 'id': 2 } ]); }); - test("returns a response for malformed JSON", () { - var incomingController = new StreamController(); - var outgoingController = new StreamController(); - var jsonPeer = new json_rpc.Peer( - new StreamChannel(incomingController.stream, outgoingController)); + test('returns a response for malformed JSON', () { + var incomingController = StreamController(); + var outgoingController = StreamController(); + var jsonPeer = json_rpc.Peer( + StreamChannel(incomingController.stream, outgoingController)); expect( outgoingController.stream.first.then(jsonDecode), completion({ - "jsonrpc": "2.0", - "error": { + 'jsonrpc': '2.0', + 'error': { 'code': error_code.PARSE_ERROR, - "message": startsWith("Invalid JSON: "), + 'message': startsWith('Invalid JSON: '), // TODO(nweiz): Always expect the source when sdk#25655 is fixed. - "data": { + 'data': { 'request': anyOf([isNull, '{invalid']) } }, - "id": null + 'id': null })); jsonPeer.listen(); - incomingController.add("{invalid"); + incomingController.add('{invalid'); }); - test("returns a response for incorrectly-structured JSON", () { + test('returns a response for incorrectly-structured JSON', () { expect( outgoing.first, completion({ - "jsonrpc": "2.0", - "error": { + 'jsonrpc': '2.0', + 'error': { 'code': error_code.INVALID_REQUEST, - "message": 'Request must contain a "jsonrpc" key.', - "data": { + 'message': 'Request must contain a "jsonrpc" key.', + 'data': { 'request': {'completely': 'wrong'} } }, - "id": null + 'id': null })); peer.listen(); - incoming.add({"completely": "wrong"}); + 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 completer = Completer(); - peer = new json_rpc.Peer.withoutJson( - new StreamChannel(incomingController.stream, outgoingController), + test('can notify on unhandled errors for if the method throws', () async { + var exception = Exception('test exception'); + var incomingController = StreamController(); + var outgoingController = StreamController(); + final completer = Completer(); + peer = json_rpc.Peer.withoutJson( + StreamChannel(incomingController.stream, outgoingController), onUnhandledError: (error, stack) { completer.complete(error); }, @@ -201,7 +201,7 @@ void main() { ..listen(); incomingController.add({'jsonrpc': '2.0', 'method': 'foo'}); - Exception receivedException = await completer.future; + var receivedException = await completer.future; expect(receivedException, equals(exception)); }); } diff --git a/pkgs/json_rpc_2/test/server/batch_test.dart b/pkgs/json_rpc_2/test/server/batch_test.dart index 3257256a6..c9411b361 100644 --- a/pkgs/json_rpc_2/test/server/batch_test.dart +++ b/pkgs/json_rpc_2/test/server/batch_test.dart @@ -10,7 +10,7 @@ import 'utils.dart'; void main() { var controller; setUp(() { - controller = new ServerController(); + controller = ServerController(); controller.server ..registerMethod('foo', () => 'foo') ..registerMethod('id', (params) => params.value) diff --git a/pkgs/json_rpc_2/test/server/invalid_request_test.dart b/pkgs/json_rpc_2/test/server/invalid_request_test.dart index e62558f5c..4dbca0c0b 100644 --- a/pkgs/json_rpc_2/test/server/invalid_request_test.dart +++ b/pkgs/json_rpc_2/test/server/invalid_request_test.dart @@ -9,19 +9,19 @@ import 'utils.dart'; void main() { var controller; - setUp(() => controller = new ServerController()); + setUp(() => controller = ServerController()); - test("a non-Array/Object request is invalid", () { + test('a non-Array/Object request is invalid', () { expectErrorResponse(controller, 'foo', error_code.INVALID_REQUEST, 'Request must be an Array or an Object.'); }); - test("requests must have a jsonrpc key", () { + test('requests must have a jsonrpc key', () { expectErrorResponse(controller, {'method': 'foo', 'id': 1234}, error_code.INVALID_REQUEST, 'Request must contain a "jsonrpc" key.'); }); - test("the jsonrpc version must be 2.0", () { + test('the jsonrpc version must be 2.0', () { expectErrorResponse( controller, {'jsonrpc': '1.0', 'method': 'foo', 'id': 1234}, @@ -29,12 +29,12 @@ void main() { 'Invalid JSON-RPC version "1.0", expected "2.0".'); }); - test("requests must have a method key", () { + test('requests must have a method key', () { expectErrorResponse(controller, {'jsonrpc': '2.0', 'id': 1234}, error_code.INVALID_REQUEST, 'Request must contain a "method" key.'); }); - test("request method must be a string", () { + test('request method must be a string', () { expectErrorResponse( controller, {'jsonrpc': '2.0', 'method': 1234, 'id': 1234}, @@ -42,7 +42,7 @@ void main() { 'Request method must be a string, but was 1234.'); }); - test("request params must be an Array or Object", () { + test('request params must be an Array or Object', () { expectErrorResponse( controller, {'jsonrpc': '2.0', 'method': 'foo', 'params': 1234, 'id': 1234}, @@ -50,7 +50,7 @@ void main() { 'Request params must be an Array or an Object, but was 1234.'); }); - test("request id may not be an Array or Object", () { + test('request id may not be an Array or Object', () { expect( controller.handleRequest({ 'jsonrpc': '2.0', diff --git a/pkgs/json_rpc_2/test/server/parameters_test.dart b/pkgs/json_rpc_2/test/server/parameters_test.dart index c970befe0..8b8d07f0f 100644 --- a/pkgs/json_rpc_2/test/server/parameters_test.dart +++ b/pkgs/json_rpc_2/test/server/parameters_test.dart @@ -8,231 +8,231 @@ import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'utils.dart'; void main() { - group("with named parameters", () { + group('with named parameters', () { var parameters; setUp(() { - parameters = new json_rpc.Parameters("foo", { - "num": 1.5, - "int": 1, - "bool": true, - "string": "zap", - "list": [1, 2, 3], - "date-time": "1990-01-01 00:00:00.000", - "uri": "http://dartlang.org", - "invalid-uri": "http://[::1", - "map": {"num": 4.2, "bool": false} + parameters = json_rpc.Parameters('foo', { + 'num': 1.5, + 'int': 1, + 'bool': true, + 'string': 'zap', + 'list': [1, 2, 3], + 'date-time': '1990-01-01 00:00:00.000', + 'uri': 'http://dartlang.org', + 'invalid-uri': 'http://[::1', + 'map': {'num': 4.2, 'bool': false} }); }); - test("value returns the wrapped value", () { + test('value returns the wrapped value', () { expect( parameters.value, equals({ - "num": 1.5, - "int": 1, - "bool": true, - "string": "zap", - "list": [1, 2, 3], - "date-time": "1990-01-01 00:00:00.000", - "uri": "http://dartlang.org", - "invalid-uri": "http://[::1", - "map": {"num": 4.2, "bool": false} + 'num': 1.5, + 'int': 1, + 'bool': true, + 'string': 'zap', + 'list': [1, 2, 3], + 'date-time': '1990-01-01 00:00:00.000', + 'uri': 'http://dartlang.org', + 'invalid-uri': 'http://[::1', + 'map': {'num': 4.2, 'bool': false} })); }); - test("[int] throws a parameter error", () { + test('[int] throws a parameter error', () { expect( () => parameters[0], throwsInvalidParams('Parameters for method "foo" must be passed by ' 'position.')); }); - test("[].value returns existing parameters", () { + test('[].value returns existing parameters', () { expect(parameters['num'].value, equals(1.5)); }); - test("[].valueOr returns existing parameters", () { + test('[].valueOr returns existing parameters', () { expect(parameters['num'].valueOr(7), equals(1.5)); }); - test("[].value fails for absent parameters", () { + test('[].value fails for absent parameters', () { expect( () => parameters['fblthp'].value, throwsInvalidParams('Request for method "foo" is missing required ' 'parameter "fblthp".')); }); - test("[].valueOr succeeds for absent parameters", () { + test('[].valueOr succeeds for absent parameters', () { expect(parameters['fblthp'].valueOr(7), equals(7)); }); - test("[].exists returns true for existing parameters", () { + test('[].exists returns true for existing parameters', () { expect(parameters['num'].exists, isTrue); }); - test("[].exists returns false for missing parameters", () { + test('[].exists returns false for missing parameters', () { expect(parameters['fblthp'].exists, isFalse); }); - test("[].asNum returns numeric parameters", () { + test('[].asNum returns numeric parameters', () { expect(parameters['num'].asNum, equals(1.5)); expect(parameters['int'].asNum, equals(1)); }); - test("[].asNumOr returns numeric parameters", () { + test('[].asNumOr returns numeric parameters', () { expect(parameters['num'].asNumOr(7), equals(1.5)); }); - test("[].asNum fails for non-numeric parameters", () { + test('[].asNum fails for non-numeric parameters', () { expect( () => parameters['bool'].asNum, throwsInvalidParams('Parameter "bool" for method "foo" must be a ' 'number, but was true.')); }); - test("[].asNumOr fails for non-numeric parameters", () { + test('[].asNumOr fails for non-numeric parameters', () { expect( () => parameters['bool'].asNumOr(7), throwsInvalidParams('Parameter "bool" for method "foo" must be a ' 'number, but was true.')); }); - test("[].asNum fails for absent parameters", () { + test('[].asNum fails for absent parameters', () { expect( () => parameters['fblthp'].asNum, throwsInvalidParams('Request for method "foo" is missing required ' 'parameter "fblthp".')); }); - test("[].asNumOr succeeds for absent parameters", () { + test('[].asNumOr succeeds for absent parameters', () { expect(parameters['fblthp'].asNumOr(7), equals(7)); }); - test("[].asInt returns integer parameters", () { + test('[].asInt returns integer parameters', () { expect(parameters['int'].asInt, equals(1)); }); - test("[].asIntOr returns integer parameters", () { + test('[].asIntOr returns integer parameters', () { expect(parameters['int'].asIntOr(7), equals(1)); }); - test("[].asInt fails for non-integer parameters", () { + test('[].asInt fails for non-integer parameters', () { expect( () => parameters['bool'].asInt, throwsInvalidParams('Parameter "bool" for method "foo" must be an ' 'integer, but was true.')); }); - test("[].asIntOr succeeds for absent parameters", () { + test('[].asIntOr succeeds for absent parameters', () { expect(parameters['fblthp'].asIntOr(7), equals(7)); }); - test("[].asBool returns boolean parameters", () { + test('[].asBool returns boolean parameters', () { expect(parameters['bool'].asBool, isTrue); }); - test("[].asBoolOr returns boolean parameters", () { + test('[].asBoolOr returns boolean parameters', () { expect(parameters['bool'].asBoolOr(false), isTrue); }); - test("[].asBoolOr fails for non-boolean parameters", () { + test('[].asBoolOr fails for non-boolean parameters', () { expect( () => parameters['int'].asBool, throwsInvalidParams('Parameter "int" for method "foo" must be a ' 'boolean, but was 1.')); }); - test("[].asBoolOr succeeds for absent parameters", () { + test('[].asBoolOr succeeds for absent parameters', () { expect(parameters['fblthp'].asBoolOr(false), isFalse); }); - test("[].asString returns string parameters", () { - expect(parameters['string'].asString, equals("zap")); + test('[].asString returns string parameters', () { + expect(parameters['string'].asString, equals('zap')); }); - test("[].asStringOr returns string parameters", () { - expect(parameters['string'].asStringOr("bap"), equals("zap")); + test('[].asStringOr returns string parameters', () { + expect(parameters['string'].asStringOr('bap'), equals('zap')); }); - test("[].asString fails for non-string parameters", () { + test('[].asString fails for non-string parameters', () { expect( () => parameters['int'].asString, throwsInvalidParams('Parameter "int" for method "foo" must be a ' 'string, but was 1.')); }); - test("[].asStringOr succeeds for absent parameters", () { - expect(parameters['fblthp'].asStringOr("bap"), equals("bap")); + test('[].asStringOr succeeds for absent parameters', () { + expect(parameters['fblthp'].asStringOr('bap'), equals('bap')); }); - test("[].asList returns list parameters", () { + test('[].asList returns list parameters', () { expect(parameters['list'].asList, equals([1, 2, 3])); }); - test("[].asListOr returns list parameters", () { + test('[].asListOr returns list parameters', () { expect(parameters['list'].asListOr([5, 6, 7]), equals([1, 2, 3])); }); - test("[].asList fails for non-list parameters", () { + test('[].asList fails for non-list parameters', () { expect( () => parameters['int'].asList, throwsInvalidParams('Parameter "int" for method "foo" must be an ' 'Array, but was 1.')); }); - test("[].asListOr succeeds for absent parameters", () { + test('[].asListOr succeeds for absent parameters', () { expect(parameters['fblthp'].asListOr([5, 6, 7]), equals([5, 6, 7])); }); - test("[].asMap returns map parameters", () { - expect(parameters['map'].asMap, equals({"num": 4.2, "bool": false})); + test('[].asMap returns map parameters', () { + expect(parameters['map'].asMap, equals({'num': 4.2, 'bool': false})); }); - test("[].asMapOr returns map parameters", () { + test('[].asMapOr returns map parameters', () { expect( - parameters['map'].asMapOr({}), equals({"num": 4.2, "bool": false})); + parameters['map'].asMapOr({}), equals({'num': 4.2, 'bool': false})); }); - test("[].asMap fails for non-map parameters", () { + test('[].asMap fails for non-map parameters', () { expect( () => parameters['int'].asMap, throwsInvalidParams('Parameter "int" for method "foo" must be an ' 'Object, but was 1.')); }); - test("[].asMapOr succeeds for absent parameters", () { + test('[].asMapOr succeeds for absent parameters', () { expect(parameters['fblthp'].asMapOr({}), equals({})); }); - test("[].asDateTime returns date/time parameters", () { - expect(parameters['date-time'].asDateTime, equals(new DateTime(1990))); + test('[].asDateTime returns date/time parameters', () { + expect(parameters['date-time'].asDateTime, equals(DateTime(1990))); }); - test("[].asDateTimeOr returns date/time parameters", () { - expect(parameters['date-time'].asDateTimeOr(new DateTime(2014)), - equals(new DateTime(1990))); + test('[].asDateTimeOr returns date/time parameters', () { + expect(parameters['date-time'].asDateTimeOr(DateTime(2014)), + equals(DateTime(1990))); }); - test("[].asDateTime fails for non-date/time parameters", () { + test('[].asDateTime fails for non-date/time parameters', () { expect( () => parameters['int'].asDateTime, throwsInvalidParams('Parameter "int" for method "foo" must be a ' 'string, but was 1.')); }); - test("[].asDateTimeOr succeeds for absent parameters", () { - expect(parameters['fblthp'].asDateTimeOr(new DateTime(2014)), - equals(new DateTime(2014))); + test('[].asDateTimeOr succeeds for absent parameters', () { + expect(parameters['fblthp'].asDateTimeOr(DateTime(2014)), + equals(DateTime(2014))); }); - test("[].asDateTime fails for non-date/time parameters", () { + test('[].asDateTime fails for non-date/time parameters', () { expect( () => parameters['int'].asDateTime, throwsInvalidParams('Parameter "int" for method "foo" must be a ' 'string, but was 1.')); }); - test("[].asDateTime fails for invalid date/times", () { + test('[].asDateTime fails for invalid date/times', () { expect( () => parameters['string'].asDateTime, throwsInvalidParams('Parameter "string" for method "foo" must be a ' @@ -240,35 +240,35 @@ void main() { 'Invalid date format')); }); - test("[].asUri returns URI parameters", () { + test('[].asUri returns URI parameters', () { expect(parameters['uri'].asUri, equals(Uri.parse('http://dartlang.org'))); }); - test("[].asUriOr returns URI parameters", () { + test('[].asUriOr returns URI parameters', () { expect(parameters['uri'].asUriOr(Uri.parse('http://google.com')), equals(Uri.parse('http://dartlang.org'))); }); - test("[].asUri fails for non-URI parameters", () { + test('[].asUri fails for non-URI parameters', () { expect( () => parameters['int'].asUri, throwsInvalidParams('Parameter "int" for method "foo" must be a ' 'string, but was 1.')); }); - test("[].asUriOr succeeds for absent parameters", () { + test('[].asUriOr succeeds for absent parameters', () { expect(parameters['fblthp'].asUriOr(Uri.parse('http://google.com')), equals(Uri.parse('http://google.com'))); }); - test("[].asUri fails for non-URI parameters", () { + test('[].asUri fails for non-URI parameters', () { expect( () => parameters['int'].asUri, throwsInvalidParams('Parameter "int" for method "foo" must be a ' 'string, but was 1.')); }); - test("[].asUri fails for invalid URIs", () { + test('[].asUri fails for invalid URIs', () { expect( () => parameters['invalid-uri'].asUri, throwsInvalidParams('Parameter "invalid-uri" for method "foo" must ' @@ -276,34 +276,34 @@ void main() { 'Missing end `]` to match `[` in host')); }); - group("with a nested parameter map", () { + group('with a nested parameter map', () { var nested; setUp(() => nested = parameters['map']); - test("[int] fails with a type error", () { + test('[int] fails with a type error', () { expect( () => nested[0], throwsInvalidParams('Parameter "map" for method "foo" must be an ' 'Array, but was {"num":4.2,"bool":false}.')); }); - test("[].value returns existing parameters", () { + test('[].value returns existing parameters', () { expect(nested['num'].value, equals(4.2)); expect(nested['bool'].value, isFalse); }); - test("[].value fails for absent parameters", () { + test('[].value fails for absent parameters', () { expect( () => nested['fblthp'].value, throwsInvalidParams('Request for method "foo" is missing required ' 'parameter map.fblthp.')); }); - test("typed getters return correctly-typed parameters", () { + test('typed getters return correctly-typed parameters', () { expect(nested['num'].asNum, equals(4.2)); }); - test("typed getters fail for incorrectly-typed parameters", () { + test('typed getters fail for incorrectly-typed parameters', () { expect( () => nested['bool'].asNum, throwsInvalidParams('Parameter map.bool for method "foo" must be ' @@ -311,34 +311,34 @@ void main() { }); }); - group("with a nested parameter list", () { + group('with a nested parameter list', () { var nested; setUp(() => nested = parameters['list']); - test("[string] fails with a type error", () { + test('[string] fails with a type error', () { expect( () => nested['foo'], throwsInvalidParams('Parameter "list" for method "foo" must be an ' 'Object, but was [1,2,3].')); }); - test("[].value returns existing parameters", () { + test('[].value returns existing parameters', () { expect(nested[0].value, equals(1)); expect(nested[1].value, equals(2)); }); - test("[].value fails for absent parameters", () { + test('[].value fails for absent parameters', () { expect( () => nested[5].value, throwsInvalidParams('Request for method "foo" is missing required ' 'parameter list[5].')); }); - test("typed getters return correctly-typed parameters", () { + test('typed getters return correctly-typed parameters', () { expect(nested[0].asInt, equals(1)); }); - test("typed getters fail for incorrectly-typed parameters", () { + test('typed getters fail for incorrectly-typed parameters', () { expect( () => nested[0].asBool, throwsInvalidParams('Parameter list[0] for method "foo" must be ' @@ -347,43 +347,43 @@ void main() { }); }); - group("with positional parameters", () { + group('with positional parameters', () { var parameters; - setUp(() => parameters = new json_rpc.Parameters("foo", [1, 2, 3, 4, 5])); + setUp(() => parameters = json_rpc.Parameters('foo', [1, 2, 3, 4, 5])); - test("value returns the wrapped value", () { + test('value returns the wrapped value', () { expect(parameters.value, equals([1, 2, 3, 4, 5])); }); - test("[string] throws a parameter error", () { + test('[string] throws a parameter error', () { expect( () => parameters['foo'], throwsInvalidParams('Parameters for method "foo" must be passed by ' 'name.')); }); - test("[].value returns existing parameters", () { + test('[].value returns existing parameters', () { expect(parameters[2].value, equals(3)); }); - test("[].value fails for out-of-range parameters", () { + test('[].value fails for out-of-range parameters', () { expect( () => parameters[10].value, throwsInvalidParams('Request for method "foo" is missing required ' 'parameter 11.')); }); - test("[].exists returns true for existing parameters", () { + test('[].exists returns true for existing parameters', () { expect(parameters[0].exists, isTrue); }); - test("[].exists returns false for missing parameters", () { + test('[].exists returns false for missing parameters', () { expect(parameters[10].exists, isFalse); }); }); - test("with a complex parameter path", () { - var parameters = new json_rpc.Parameters("foo", { + test('with a complex parameter path', () { + var parameters = json_rpc.Parameters('foo', { 'bar baz': [ 0, 1, diff --git a/pkgs/json_rpc_2/test/server/server_test.dart b/pkgs/json_rpc_2/test/server/server_test.dart index b9ba504d3..14ae1e495 100644 --- a/pkgs/json_rpc_2/test/server/server_test.dart +++ b/pkgs/json_rpc_2/test/server/server_test.dart @@ -12,9 +12,9 @@ import 'utils.dart'; void main() { var controller; - setUp(() => controller = new ServerController()); + setUp(() => controller = ServerController()); - test("calls a registered method with the given name", () { + test('calls a registered method with the given name', () { controller.server.registerMethod('foo', (params) { return {'params': params.value}; }); @@ -35,7 +35,7 @@ void main() { }))); }); - test("calls a method that takes no parameters", () { + test('calls a method that takes no parameters', () { controller.server.registerMethod('foo', () => 'foo'); expect( @@ -44,7 +44,7 @@ void main() { completion(equals({'jsonrpc': '2.0', 'result': 'foo', 'id': 1234}))); }); - test("a method that takes no parameters rejects parameters", () { + test('a method that takes no parameters rejects parameters', () { controller.server.registerMethod('foo', () => 'foo'); expectErrorResponse( @@ -54,9 +54,9 @@ void main() { 'No parameters are allowed for method "foo".'); }); - test("an unexpected error in a method is captured", () { + test('an unexpected error in a method is captured', () { controller.server - .registerMethod('foo', () => throw new FormatException('bad format')); + .registerMethod('foo', () => throw FormatException('bad format')); expect( controller @@ -70,13 +70,13 @@ void main() { 'data': { 'request': {'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}, 'full': 'FormatException: bad format', - 'stack': new TypeMatcher() + 'stack': TypeMatcher() } } })); }); - test("doesn't return a result for a notification", () { + test('doesn\'t return a result for a notification', () { controller.server.registerMethod('foo', (args) => 'result'); expect( @@ -85,9 +85,9 @@ void main() { doesNotComplete); }); - test("includes the error data in the response", () { + test('includes the error data in the response', () { controller.server.registerMethod('foo', (params) { - throw new json_rpc.RpcException(5, 'Error message.', data: 'data value'); + throw json_rpc.RpcException(5, 'Error message.', data: 'data value'); }); expectErrorResponse( @@ -98,13 +98,13 @@ void main() { data: 'data value'); }); - test("a JSON parse error is rejected", () { + test('a JSON parse error is rejected', () { return controller.handleJsonRequest('invalid json {').then((result) { expect(jsonDecode(result), { 'jsonrpc': '2.0', 'error': { 'code': error_code.PARSE_ERROR, - 'message': startsWith("Invalid JSON: "), + 'message': startsWith('Invalid JSON: '), // TODO(nweiz): Always expect the source when sdk#25655 is fixed. 'data': { 'request': anyOf([isNull, 'invalid json {']) @@ -115,8 +115,8 @@ void main() { }); }); - group("fallbacks", () { - test("calls a fallback if no method matches", () { + group('fallbacks', () { + test('calls a fallback if no method matches', () { controller.server ..registerMethod('foo', () => 'foo') ..registerMethod('bar', () => 'foo') @@ -138,10 +138,10 @@ void main() { }))); }); - test("calls the first matching fallback", () { + test('calls the first matching fallback', () { controller.server ..registerFallback((params) => - throw new json_rpc.RpcException.methodNotFound(params.method)) + throw json_rpc.RpcException.methodNotFound(params.method)) ..registerFallback((params) => 'fallback 2') ..registerFallback((params) => 'fallback 3'); @@ -152,9 +152,9 @@ void main() { equals({'jsonrpc': '2.0', 'result': 'fallback 2', 'id': 1234}))); }); - test("an unexpected error in a fallback is captured", () { + test('an unexpected error in a fallback is captured', () { controller.server - .registerFallback((_) => throw new FormatException('bad format')); + .registerFallback((_) => throw FormatException('bad format')); expect( controller @@ -168,14 +168,14 @@ void main() { 'data': { 'request': {'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}, 'full': 'FormatException: bad format', - 'stack': new TypeMatcher() + 'stack': TypeMatcher() } } })); }); }); - test("disallows multiple methods with the same name", () { + test('disallows multiple methods with the same name', () { controller.server.registerMethod('foo', () => null); expect(() => controller.server.registerMethod('foo', () => null), throwsArgumentError); diff --git a/pkgs/json_rpc_2/test/server/stream_test.dart b/pkgs/json_rpc_2/test/server/stream_test.dart index 19629e755..7105cd601 100644 --- a/pkgs/json_rpc_2/test/server/stream_test.dart +++ b/pkgs/json_rpc_2/test/server/stream_test.dart @@ -14,13 +14,13 @@ void main() { var responseController; var server; setUp(() { - requestController = new StreamController(); - responseController = new StreamController(); - server = new json_rpc.Server.withoutJson( - new StreamChannel(requestController.stream, responseController.sink)); + requestController = StreamController(); + responseController = StreamController(); + server = json_rpc.Server.withoutJson( + StreamChannel(requestController.stream, responseController.sink)); }); - test(".withoutJson supports decoded stream and sink", () { + test('.withoutJson supports decoded stream and sink', () { server.listen(); server.registerMethod('foo', (params) { @@ -45,7 +45,7 @@ void main() { }))); }); - test(".listen returns when the controller is closed", () { + test('.listen returns when the controller is closed', () { var hasListenCompeted = false; expect(server.listen().then((_) => hasListenCompeted = true), completes); @@ -57,18 +57,18 @@ void main() { }); }); - test(".listen returns a stream error", () { + test('.listen returns a stream error', () { expect(server.listen(), throwsA('oh no')); requestController.addError('oh no'); }); - test(".listen can't be called twice", () { + test('.listen can\'t be called twice', () { server.listen(); expect(() => server.listen(), throwsStateError); }); - test(".close cancels the stream subscription and closes the sink", () { + test('.close cancels the stream subscription and closes the sink', () { // Work around sdk#19095. responseController.stream.listen(null); diff --git a/pkgs/json_rpc_2/test/server/utils.dart b/pkgs/json_rpc_2/test/server/utils.dart index ef089ad2a..bf1db6fc4 100644 --- a/pkgs/json_rpc_2/test/server/utils.dart +++ b/pkgs/json_rpc_2/test/server/utils.dart @@ -14,18 +14,18 @@ import 'package:json_rpc_2/error_code.dart' as error_code; /// A controller used to test a [json_rpc.Server]. class ServerController { /// The controller for the server's request stream. - final _requestController = new StreamController(); + final _requestController = StreamController(); /// The controller for the server's response sink. - final _responseController = new StreamController(); + final _responseController = StreamController(); /// The server. json_rpc.Server get server => _server; json_rpc.Server _server; ServerController({json_rpc.ErrorCallback onUnhandledError}) { - _server = new json_rpc.Server( - new StreamChannel(_requestController.stream, _responseController.sink), + _server = json_rpc.Server( + StreamChannel(_requestController.stream, _responseController.sink), onUnhandledError: onUnhandledError); _server.listen(); } @@ -50,7 +50,7 @@ void expectErrorResponse( {data}) { var id; if (request is Map) id = request['id']; - if (data == null) data = {'request': request}; + data ??= {'request': request}; expect( controller.handleRequest(request), @@ -65,7 +65,7 @@ void expectErrorResponse( /// `invalid_params` error code. Matcher throwsInvalidParams(String message) { return throwsA(predicate((error) { - expect(error, new TypeMatcher()); + expect(error, TypeMatcher()); expect(error.code, equals(error_code.INVALID_PARAMS)); expect(error.message, equals(message)); return true; From 1b9d2c656dda2e6549de117129ad0a33955bccc7 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Tue, 21 Apr 2020 13:01:09 -0700 Subject: [PATCH 060/127] Fixed issue where throwing from within an asynchronous handler (dart-lang/json_rpc_2#47) [2.1.1] Fixed issue where throwing `RpcException.methodNotFound` from within an asynchronous fallback handler would not result in the next fallback handler being executed. --- pkgs/json_rpc_2/CHANGELOG.md | 5 +++++ pkgs/json_rpc_2/lib/src/server.dart | 2 +- pkgs/json_rpc_2/pubspec.yaml | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 878e1f078..fca454ba2 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.1.1 + +* Fixed issue where throwing `RpcException.methodNotFound` in an asynchronous + fallback handler would not result in the next fallback being executed. + ## 2.1.0 * `Server` and related classes can now take an `onUnhandledError` callback to diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index 30da924db..127a6d5e6 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -273,7 +273,7 @@ class Server { } try { - return iterator.current(params); + return await iterator.current(params); } on RpcException catch (error) { if (error is! RpcException) rethrow; if (error.code != error_code.METHOD_NOT_FOUND) rethrow; diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 5e8e29c01..16fd74be8 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.1.0 +version: 2.1.1 author: Dart Team description: >- Utilities to write a client or server using the JSON-RPC 2.0 spec. From 1e32ccd8938e7c26254da206831ee29ea21c332d Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 21 Apr 2020 13:05:53 -0700 Subject: [PATCH 061/127] remove author from pubspec --- pkgs/json_rpc_2/pubspec.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 16fd74be8..89fb7b830 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,6 +1,5 @@ name: json_rpc_2 version: 2.1.1 -author: Dart Team description: >- Utilities to write a client or server using the JSON-RPC 2.0 spec. homepage: https://github.com/dart-lang/json_rpc_2 From 1503cf104685ba036a59912479b9ebffa0285ef3 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 21 Apr 2020 13:30:55 -0700 Subject: [PATCH 062/127] test on oldest supported SDK (dart-lang/json_rpc_2#48) * update min sdk * Fix references to dartlang.org --- pkgs/json_rpc_2/.travis.yml | 4 +++- pkgs/json_rpc_2/CHANGELOG.md | 1 + pkgs/json_rpc_2/pubspec.yaml | 2 +- pkgs/json_rpc_2/test/server/parameters_test.dart | 8 ++++---- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/pkgs/json_rpc_2/.travis.yml b/pkgs/json_rpc_2/.travis.yml index 7dee3ccdc..3537f6541 100644 --- a/pkgs/json_rpc_2/.travis.yml +++ b/pkgs/json_rpc_2/.travis.yml @@ -2,9 +2,11 @@ language: dart dart: - dev + - 2.2.0 + dart_task: - test - - dartanalyzer + - dartanalyzer: --fatal-infos --fatal-warnings . matrix: include: diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index fca454ba2..c95f7f132 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -2,6 +2,7 @@ * Fixed issue where throwing `RpcException.methodNotFound` in an asynchronous fallback handler would not result in the next fallback being executed. +* Updated minimum SDK to Dart `2.2.0`. ## 2.1.0 diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 89fb7b830..0f55fb768 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -5,7 +5,7 @@ description: >- homepage: https://github.com/dart-lang/json_rpc_2 environment: - sdk: ">=2.0.0 <3.0.0" + sdk: ">=2.2.0 <3.0.0" dependencies: stack_trace: ^1.0.0 diff --git a/pkgs/json_rpc_2/test/server/parameters_test.dart b/pkgs/json_rpc_2/test/server/parameters_test.dart index 8b8d07f0f..069daf3a7 100644 --- a/pkgs/json_rpc_2/test/server/parameters_test.dart +++ b/pkgs/json_rpc_2/test/server/parameters_test.dart @@ -18,7 +18,7 @@ void main() { 'string': 'zap', 'list': [1, 2, 3], 'date-time': '1990-01-01 00:00:00.000', - 'uri': 'http://dartlang.org', + 'uri': 'https://dart.dev', 'invalid-uri': 'http://[::1', 'map': {'num': 4.2, 'bool': false} }); @@ -34,7 +34,7 @@ void main() { 'string': 'zap', 'list': [1, 2, 3], 'date-time': '1990-01-01 00:00:00.000', - 'uri': 'http://dartlang.org', + 'uri': 'https://dart.dev', 'invalid-uri': 'http://[::1', 'map': {'num': 4.2, 'bool': false} })); @@ -241,12 +241,12 @@ void main() { }); test('[].asUri returns URI parameters', () { - expect(parameters['uri'].asUri, equals(Uri.parse('http://dartlang.org'))); + expect(parameters['uri'].asUri, equals(Uri.parse('https://dart.dev'))); }); test('[].asUriOr returns URI parameters', () { expect(parameters['uri'].asUriOr(Uri.parse('http://google.com')), - equals(Uri.parse('http://dartlang.org'))); + equals(Uri.parse('https://dart.dev'))); }); test('[].asUri fails for non-URI parameters', () { From 34def9aed107ba7e6dcfc63102797e0e06a68354 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Mon, 18 May 2020 14:01:38 -0700 Subject: [PATCH 063/127] Added `strictProtocolChecks` named parameter to `Peer` and `Server` constructors (dart-lang/json_rpc_2#51) Allows creating servers which are lenient towards misbehaving clients. --- pkgs/json_rpc_2/CHANGELOG.md | 6 +++ pkgs/json_rpc_2/lib/src/peer.dart | 25 +++++++++-- pkgs/json_rpc_2/lib/src/server.dart | 43 ++++++++++++++----- pkgs/json_rpc_2/pubspec.yaml | 2 +- .../test/server/invalid_request_test.dart | 17 ++++++++ pkgs/json_rpc_2/test/server/utils.dart | 7 ++- 6 files changed, 82 insertions(+), 18 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index c95f7f132..c3eec9aab 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.2.0 + +* Added `strictProtocolChecks` named parameter to `Server` and `Peer` + constructors. Setting this parameter to false will result in the server not + rejecting requests missing the `jsonrpc` parameter. + ## 2.1.1 * Fixed issue where throwing `RpcException.methodNotFound` in an asynchronous diff --git a/pkgs/json_rpc_2/lib/src/peer.dart b/pkgs/json_rpc_2/lib/src/peer.dart index cd51a7c66..eeb7cd9a6 100644 --- a/pkgs/json_rpc_2/lib/src/peer.dart +++ b/pkgs/json_rpc_2/lib/src/peer.dart @@ -44,6 +44,9 @@ class Peer implements Client, Server { @override ErrorCallback get onUnhandledError => _server?.onUnhandledError; + @override + bool get strictProtocolChecks => _server.strictProtocolChecks; + /// Creates a [Peer] that communicates over [channel]. /// /// Note that the peer won't begin listening to [channel] until [Peer.listen] @@ -51,10 +54,17 @@ class Peer implements Client, Server { /// /// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError]. /// If this is not provided, unhandled exceptions will be swallowed. - Peer(StreamChannel channel, {ErrorCallback onUnhandledError}) + /// + /// If [strictProtocolChecks] is false, the underlying [Server] will accept + /// some requests which are not conformant with the JSON-RPC 2.0 + /// specification. In particular, requests missing the `jsonrpc` parameter + /// will be accepted. + Peer(StreamChannel channel, + {ErrorCallback onUnhandledError, bool strictProtocolChecks = true}) : this.withoutJson( jsonDocument.bind(channel).transform(respondToFormatExceptions), - onUnhandledError: onUnhandledError); + onUnhandledError: onUnhandledError, + strictProtocolChecks: strictProtocolChecks); /// Creates a [Peer] that communicates using decoded messages over [channel]. /// @@ -66,11 +76,18 @@ class Peer implements Client, Server { /// /// 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}) + /// + /// If [strictProtocolChecks] is false, the underlying [Server] will accept + /// some requests which are not conformant with the JSON-RPC 2.0 + /// specification. In particular, requests missing the `jsonrpc` parameter + /// will be accepted. + Peer.withoutJson(StreamChannel channel, + {ErrorCallback onUnhandledError, bool strictProtocolChecks = true}) : _manager = ChannelManager('Peer', channel) { _server = Server.withoutJson( StreamChannel(_serverIncomingForwarder.stream, channel.sink), - onUnhandledError: onUnhandledError); + onUnhandledError: onUnhandledError, + strictProtocolChecks: strictProtocolChecks); _client = Client.withoutJson( StreamChannel(_clientIncomingForwarder.stream, channel.sink)); } diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index 127a6d5e6..83129ea72 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -61,6 +61,13 @@ class Server { /// invoked. If it is not set, the exception will be swallowed. final ErrorCallback onUnhandledError; + /// Whether to strictly enforce the JSON-RPC 2.0 specification for received messages. + /// + /// If `false`, this [Server] will accept some requests which are not conformant + /// with the JSON-RPC 2.0 specification. In particular, requests missing the + /// `jsonrpc` parameter will be accepted. + final bool strictProtocolChecks; + /// Creates a [Server] that communicates over [channel]. /// /// Note that the server won't begin listening to [requests] until @@ -68,10 +75,16 @@ class Server { /// /// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError]. /// If this is not provided, unhandled exceptions will be swallowed. - Server(StreamChannel channel, {ErrorCallback onUnhandledError}) + /// + /// If [strictProtocolChecks] is false, this [Server] will accept some + /// requests which are not conformant with the JSON-RPC 2.0 specification. In + /// particular, requests missing the `jsonrpc` parameter will be accepted. + Server(StreamChannel channel, + {ErrorCallback onUnhandledError, bool strictProtocolChecks = true}) : this.withoutJson( jsonDocument.bind(channel).transform(respondToFormatExceptions), - onUnhandledError: onUnhandledError); + onUnhandledError: onUnhandledError, + strictProtocolChecks: strictProtocolChecks); /// Creates a [Server] that communicates using decoded messages over /// [channel]. @@ -84,7 +97,12 @@ class Server { /// /// 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}) + /// + /// If [strictProtocolChecks] is false, this [Server] will accept some + /// requests which are not conformant with the JSON-RPC 2.0 specification. In + /// particular, requests missing the `jsonrpc` parameter will be accepted. + Server.withoutJson(StreamChannel channel, + {this.onUnhandledError, this.strictProtocolChecks = true}) : _manager = ChannelManager('Server', channel); /// Starts listening to the underlying stream. @@ -217,14 +235,15 @@ class Server { 'an Array or an Object.'); } - if (!request.containsKey('jsonrpc')) { + if (strictProtocolChecks && !request.containsKey('jsonrpc')) { throw RpcException( error_code.INVALID_REQUEST, 'Request must ' 'contain a "jsonrpc" key.'); } - if (request['jsonrpc'] != '2.0') { + if ((strictProtocolChecks || request.containsKey('jsonrpc')) && + request['jsonrpc'] != '2.0') { throw RpcException( error_code.INVALID_REQUEST, 'Invalid JSON-RPC ' @@ -246,12 +265,14 @@ class Server { 'be a string, but was ${jsonEncode(method)}.'); } - var params = request['params']; - if (request.containsKey('params') && params is! List && params is! Map) { - throw RpcException( - error_code.INVALID_REQUEST, - 'Request params must ' - 'be an Array or an Object, but was ${jsonEncode(params)}.'); + if (request.containsKey('params')) { + var params = request['params']; + if (params is! List && params is! Map) { + throw RpcException( + error_code.INVALID_REQUEST, + 'Request params must ' + 'be an Array or an Object, but was ${jsonEncode(params)}.'); + } } var id = request['id']; diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 0f55fb768..abd2520c9 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.1.1 +version: 2.2.0 description: >- Utilities to write a client or server using the JSON-RPC 2.0 spec. homepage: https://github.com/dart-lang/json_rpc_2 diff --git a/pkgs/json_rpc_2/test/server/invalid_request_test.dart b/pkgs/json_rpc_2/test/server/invalid_request_test.dart index 4dbca0c0b..a67afec1f 100644 --- a/pkgs/json_rpc_2/test/server/invalid_request_test.dart +++ b/pkgs/json_rpc_2/test/server/invalid_request_test.dart @@ -74,4 +74,21 @@ void main() { } }))); }); + + group('strict protocol checks disabled', () { + setUp(() => controller = ServerController(strictProtocolChecks: false)); + + test('and no jsonrpc param', () { + expectErrorResponse(controller, {'method': 'foo', 'id': 1234}, + error_code.METHOD_NOT_FOUND, 'Unknown method "foo".'); + }); + + test('the jsonrpc version must be 2.0', () { + expectErrorResponse( + controller, + {'jsonrpc': '1.0', 'method': 'foo', 'id': 1234}, + error_code.INVALID_REQUEST, + 'Invalid JSON-RPC version "1.0", expected "2.0".'); + }); + }); } diff --git a/pkgs/json_rpc_2/test/server/utils.dart b/pkgs/json_rpc_2/test/server/utils.dart index bf1db6fc4..7ff491c59 100644 --- a/pkgs/json_rpc_2/test/server/utils.dart +++ b/pkgs/json_rpc_2/test/server/utils.dart @@ -23,10 +23,13 @@ class ServerController { json_rpc.Server get server => _server; json_rpc.Server _server; - ServerController({json_rpc.ErrorCallback onUnhandledError}) { + ServerController( + {json_rpc.ErrorCallback onUnhandledError, + bool strictProtocolChecks = true}) { _server = json_rpc.Server( StreamChannel(_requestController.stream, _responseController.sink), - onUnhandledError: onUnhandledError); + onUnhandledError: onUnhandledError, + strictProtocolChecks: strictProtocolChecks); _server.listen(); } From 0c05f886465018753312050ee6bc09485112a6cf Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Mon, 1 Jun 2020 14:12:18 -0700 Subject: [PATCH 064/127] Fix README for style (dart-lang/json_rpc_2#53) Add an example with content from the readme. This improves the score on pub and lets me run the analyzer over the example. - Remove the import prefix for the example, it is especially unwieldy for the `Parameters` type annotation. - Add argument types to the callbacks to avoid implicit `dynamic` which makes it harder to use inside the method body. - Change `[]` inside comments to backticks. `[]` is only used for doc comments. - Drop the `getNamed` API that has not been present since the very first code review for this package. - Use lower camel case for a constant. - Use single quotes consistently. There was a mix before, this is especially important for imports which are idiomatically written with single quotes. - Add a note about message buffering before `listen`. - Use `WebSocketChannel.connect` for platform agnostic imports. - Use `async/await` in the client example. --- pkgs/json_rpc_2/CHANGELOG.md | 2 + pkgs/json_rpc_2/README.md | 100 ++++++++++++++-------------- pkgs/json_rpc_2/example/client.dart | 40 +++++++++++ pkgs/json_rpc_2/example/main.dart | 70 +++++++++++++++++++ pkgs/json_rpc_2/pubspec.yaml | 3 +- 5 files changed, 165 insertions(+), 50 deletions(-) create mode 100644 pkgs/json_rpc_2/example/client.dart create mode 100644 pkgs/json_rpc_2/example/main.dart diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index c3eec9aab..11e0cc269 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.2.1-dev + ## 2.2.0 * Added `strictProtocolChecks` named parameter to `Server` and `Peer` diff --git a/pkgs/json_rpc_2/README.md b/pkgs/json_rpc_2/README.md index 0567d20f8..64562ace7 100644 --- a/pkgs/json_rpc_2/README.md +++ b/pkgs/json_rpc_2/README.md @@ -8,71 +8,70 @@ A JSON-RPC 2.0 server exposes a set of methods that can be called by clients. These methods can be registered using `Server.registerMethod`: ```dart -import "package:json_rpc_2/json_rpc_2.dart" as json_rpc; -import "package:stream_channel/stream_channel.dart"; -import "package:web_socket_channel/io.dart"; +import 'package:json_rpc_2/json_rpc_2.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; -main() async { - var socket = IOWebSocketChannel.connect('ws://localhost:4321'); +void main() { + var socket = WebSocketChannel.connect(Uri.parse('ws://localhost:4321')); - // The socket is a StreamChannel because it might emit binary - // Lists, but JSON RPC 2 only works with Strings so we assert it only + // The socket is a `StreamChannel` because it might emit binary + // `List`, but JSON RPC 2 only works with Strings so we assert it only // emits those by casting it. - var server = new json_rpc.Server(socket.cast()); + var server = Server(socket.cast()); // Any string may be used as a method name. JSON-RPC 2.0 methods are // case-sensitive. var i = 0; - server.registerMethod("count", () { + server.registerMethod('count', () { // Just return the value to be sent as a response to the client. This can // be anything JSON-serializable, or a Future that completes to something // JSON-serializable. return i++; }); - // Methods can take parameters. They're presented as a [Parameters] object + // Methods can take parameters. They're presented as a `Parameters` object // which makes it easy to validate that the expected parameters exist. - server.registerMethod("echo", (params) { - // If the request doesn't have a "message" parameter, this will + server.registerMethod('echo', (Parameters params) { + // If the request doesn't have a "message" parameter this will // automatically send a response notifying the client that the request // was invalid. - return params.getNamed("message"); + return params['message'].value; }); - // [Parameters] has methods for verifying argument types. - server.registerMethod("subtract", (params) { + // `Parameters` has methods for verifying argument types. + server.registerMethod('subtract', (Parameters params) { // If "minuend" or "subtrahend" aren't numbers, this will reject the // request. - return params.getNum("minuend") - params.getNum("subtrahend"); + return params['minuend'].asNum - params['subtrahend'].asNum; }); // [Parameters] also supports optional arguments. - server.registerMethod("sort", (params) { - var list = params.getList("list"); + server.registerMethod('sort', (Parameters params) { + var list = params['list'].asList; list.sort(); - if (params.getBool("descending", orElse: () => false)) { - return params.list.reversed; + if (params['descendint'].asBoolOr(false)) { + return list.reversed; } else { - return params.list; + return list; } }); - // A method can send an error response by throwing a - // `json_rpc.RpcException`. Any positive number may be used as an - // application- defined error code. - const DIVIDE_BY_ZERO = 1; - server.registerMethod("divide", (params) { - var divisor = params.getNum("divisor"); + // A method can send an error response by throwing a `RpcException`. + // Any positive number may be used as an application- defined error code. + const dividByZero = 1; + server.registerMethod('divide', (Parameters params) { + var divisor = params['divisor'].asNum; if (divisor == 0) { - throw new json_rpc.RpcException( - DIVIDE_BY_ZERO, "Cannot divide by zero."); + throw RpcException(dividByZero, 'Cannot divide by zero.'); } - return params.getNum("dividend") / divisor; + return params['dividend'].asNum / divisor; }); - // To give you time to register all your methods, the server won't actually - // start listening for requests until you call `listen`. + // To give you time to register all your methods, the server won't start + // listening for requests until you call `listen`. Messages are buffered until + // listen is called. The returned Future won't complete until the connection + // is closed. server.listen(); } ``` @@ -84,38 +83,41 @@ responses to those method calls. These methods can be called using `Client.sendRequest`: ```dart -import "package:json_rpc_2/json_rpc_2.dart" as json_rpc; -import "package:stream_channel/stream_channel.dart"; -import "package:web_socket_channel/html.dart"; +import 'package:json_rpc_2/json_rpc_2.dart'; +import 'package:pedantic/pedantic.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; -main() async { - var socket = HtmlWebSocketChannel.connect('ws://localhost:4321'); - var client = new json_rpc.Client(socket); +void main() async { + var socket = WebSocketChannel.connect(Uri.parse('ws://localhost:4321')); + var client = Client(socket.cast()); + + // The client won't subscribe to the input stream until you call `listen`. + // The returned Future won't complete until the connection is closed. + unawaited(client.listen()); // This calls the "count" method on the server. A Future is returned that // will complete to the value contained in the server's response. - client.sendRequest("count").then((result) => print("Count is $result.")); + var count = await client.sendRequest('count'); + print('Count is $count'); // Parameters are passed as a simple Map or, for positional parameters, an // Iterable. Make sure they're JSON-serializable! - client.sendRequest("echo", {"message": "hello"}) - .then((echo) => print('Echo says "$echo"!')); + var echo = await client.sendRequest('echo', {'message': 'hello'}); + print('Echo says "$echo"!'); // A notification is a way to call a method that tells the server that no // result is expected. Its return type is `void`; even if it causes an // error, you won't hear back. - client.sendNotification("count"); + client.sendNotification('count'); // If the server sends an error response, the returned Future will complete // with an RpcException. You can catch this error and inspect its error // code, message, and any data that the server sent along with it. - client.sendRequest("divide", {"dividend": 2, "divisor": 0}) - .catchError((error) { - print("RPC error ${error.code}: ${error.message}"); - }); - - // The client won't subscribe to the input stream until you call `listen`. - client.listen(); + try { + await client.sendRequest('divide', {'dividend': 2, 'divisor': 0}); + } on RpcException catch (error) { + print('RPC error ${error.code}: ${error.message}'); + } } ``` diff --git a/pkgs/json_rpc_2/example/client.dart b/pkgs/json_rpc_2/example/client.dart new file mode 100644 index 000000000..243b83e2a --- /dev/null +++ b/pkgs/json_rpc_2/example/client.dart @@ -0,0 +1,40 @@ +// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file +// 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. + +import 'package:json_rpc_2/json_rpc_2.dart'; +import 'package:pedantic/pedantic.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; + +void main() async { + var socket = WebSocketChannel.connect(Uri.parse('ws://localhost:4321')); + var client = Client(socket.cast()); + + // The client won't subscribe to the input stream until you call `listen`. + // The returned Future won't complete until the connection is closed. + unawaited(client.listen()); + + // This calls the "count" method on the server. A Future is returned that + // will complete to the value contained in the server's response. + var count = await client.sendRequest('count'); + print('Count is $count'); + + // Parameters are passed as a simple Map or, for positional parameters, an + // Iterable. Make sure they're JSON-serializable! + var echo = await client.sendRequest('echo', {'message': 'hello'}); + print('Echo says "$echo"!'); + + // A notification is a way to call a method that tells the server that no + // result is expected. Its return type is `void`; even if it causes an + // error, you won't hear back. + client.sendNotification('count'); + + // If the server sends an error response, the returned Future will complete + // with an RpcException. You can catch this error and inspect its error + // code, message, and any data that the server sent along with it. + try { + await client.sendRequest('divide', {'dividend': 2, 'divisor': 0}); + } on RpcException catch (error) { + print('RPC error ${error.code}: ${error.message}'); + } +} diff --git a/pkgs/json_rpc_2/example/main.dart b/pkgs/json_rpc_2/example/main.dart new file mode 100644 index 000000000..fc6042e71 --- /dev/null +++ b/pkgs/json_rpc_2/example/main.dart @@ -0,0 +1,70 @@ +// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file +// 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. + +import 'package:json_rpc_2/json_rpc_2.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; + +void main() { + var socket = WebSocketChannel.connect(Uri.parse('ws://localhost:4321')); + + // The socket is a `StreamChannel` because it might emit binary + // `List`, but JSON RPC 2 only works with Strings so we assert it only + // emits those by casting it. + var server = Server(socket.cast()); + + // Any string may be used as a method name. JSON-RPC 2.0 methods are + // case-sensitive. + var i = 0; + server.registerMethod('count', () { + // Just return the value to be sent as a response to the client. This can + // be anything JSON-serializable, or a Future that completes to something + // JSON-serializable. + return i++; + }); + + // Methods can take parameters. They're presented as a `Parameters` object + // which makes it easy to validate that the expected parameters exist. + server.registerMethod('echo', (Parameters params) { + // If the request doesn't have a "message" parameter this will + // automatically send a response notifying the client that the request + // was invalid. + return params['message'].value; + }); + + // `Parameters` has methods for verifying argument types. + server.registerMethod('subtract', (Parameters params) { + // If "minuend" or "subtrahend" aren't numbers, this will reject the + // request. + return params['minuend'].asNum - params['subtrahend'].asNum; + }); + + // [Parameters] also supports optional arguments. + server.registerMethod('sort', (Parameters params) { + var list = params['list'].asList; + list.sort(); + if (params['descendint'].asBoolOr(false)) { + return list.reversed; + } else { + return list; + } + }); + + // A method can send an error response by throwing a `RpcException`. + // Any positive number may be used as an application- defined error code. + const dividByZero = 1; + server.registerMethod('divide', (Parameters params) { + var divisor = params['divisor'].asNum; + if (divisor == 0) { + throw RpcException(dividByZero, 'Cannot divide by zero.'); + } + + return params['dividend'].asNum / divisor; + }); + + // To give you time to register all your methods, the server won't start + // listening for requests until you call `listen`. Messages are buffered until + // listen is called. The returned Future won't complete until the connection + // is closed. + server.listen(); +} diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index abd2520c9..d030b6b41 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.2.0 +version: 2.2.1-dev description: >- Utilities to write a client or server using the JSON-RPC 2.0 spec. homepage: https://github.com/dart-lang/json_rpc_2 @@ -14,3 +14,4 @@ dependencies: dev_dependencies: pedantic: ^1.8.0 test: ^1.0.0 + web_socket_channel: ^1.1.0 From 0413c152d8f21b90bc57f39a5d3f871a9a10e158 Mon Sep 17 00:00:00 2001 From: Jia Hao Date: Tue, 9 Jun 2020 00:04:20 +0800 Subject: [PATCH 065/127] Fix `Peer` requests not terminating when the channel closes (dart-lang/json_rpc_2#52) The `listen()` method of `Peer` never propagates close events from its manager to the `client` field. This causes in-flight requests to never terminate as the clean up handler in `client.dart` is never called. --- pkgs/json_rpc_2/CHANGELOG.md | 2 ++ pkgs/json_rpc_2/lib/src/peer.dart | 2 +- pkgs/json_rpc_2/test/peer_test.dart | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 11e0cc269..9e5672376 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,5 +1,7 @@ ## 2.2.1-dev +* Fix `Peer` requests not terminating when the underlying channel is closed. + ## 2.2.0 * Added `strictProtocolChecks` named parameter to `Server` and `Peer` diff --git a/pkgs/json_rpc_2/lib/src/peer.dart b/pkgs/json_rpc_2/lib/src/peer.dart index eeb7cd9a6..7f89dd2cb 100644 --- a/pkgs/json_rpc_2/lib/src/peer.dart +++ b/pkgs/json_rpc_2/lib/src/peer.dart @@ -142,7 +142,7 @@ class Peer implements Client, Server { // server since it knows how to send error responses. _serverIncomingForwarder.add(message); } - }); + }).whenComplete(close); } @override diff --git a/pkgs/json_rpc_2/test/peer_test.dart b/pkgs/json_rpc_2/test/peer_test.dart index 33184b0ab..a9c295a0f 100644 --- a/pkgs/json_rpc_2/test/peer_test.dart +++ b/pkgs/json_rpc_2/test/peer_test.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:convert'; +import 'package:pedantic/pedantic.dart'; import 'package:stream_channel/stream_channel.dart'; import 'package:test/test.dart'; @@ -84,6 +85,21 @@ void main() { expect(peer.sendRequest('w', {'x': 'y'}), completion(equals('z'))); }); }); + + test('requests terminates when the channel is closed', () async { + var incomingController = StreamController(); + var channel = StreamChannel.withGuarantees( + incomingController.stream, + StreamController(), + ); + var peer = json_rpc.Peer.withoutJson(channel); + unawaited(peer.listen()); + + var response = peer.sendRequest('foo'); + await incomingController.close(); + + expect(response, throwsStateError); + }); }); group('like a server,', () { From ed40306880e22486eac00bafd8aeafd790f6c495 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Mon, 8 Jun 2020 14:51:52 -0700 Subject: [PATCH 066/127] Prepare to publish (dart-lang/json_rpc_2#54) --- pkgs/json_rpc_2/CHANGELOG.md | 2 +- pkgs/json_rpc_2/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 9e5672376..611aa71a9 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.2.1-dev +## 2.2.1 * Fix `Peer` requests not terminating when the underlying channel is closed. diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index d030b6b41..08c435a7b 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.2.1-dev +version: 2.2.1 description: >- Utilities to write a client or server using the JSON-RPC 2.0 spec. homepage: https://github.com/dart-lang/json_rpc_2 From 6938a4168e4649446e1a1fcc079c1c0857f3b700 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Tue, 9 Jun 2020 14:05:49 -0700 Subject: [PATCH 067/127] Add a test showing failure to close peer (dart-lang/json_rpc_2#56) Example for dart-lang/json_rpc_2#55 --- pkgs/json_rpc_2/CHANGELOG.md | 2 ++ pkgs/json_rpc_2/pubspec.yaml | 2 +- pkgs/json_rpc_2/test/peer_test.dart | 11 +++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 611aa71a9..93dc007bd 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.2.2-dev + ## 2.2.1 * Fix `Peer` requests not terminating when the underlying channel is closed. diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 08c435a7b..806383b6c 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.2.1 +version: 2.2.2-dev description: >- Utilities to write a client or server using the JSON-RPC 2.0 spec. homepage: https://github.com/dart-lang/json_rpc_2 diff --git a/pkgs/json_rpc_2/test/peer_test.dart b/pkgs/json_rpc_2/test/peer_test.dart index a9c295a0f..e2c46f8a3 100644 --- a/pkgs/json_rpc_2/test/peer_test.dart +++ b/pkgs/json_rpc_2/test/peer_test.dart @@ -102,6 +102,17 @@ void main() { }); }); + test('can be closed', () async { + var incomingController = StreamController(); + var channel = StreamChannel.withGuarantees( + incomingController.stream, + StreamController(), + ); + var peer = json_rpc.Peer.withoutJson(channel); + unawaited(peer.listen()); + await peer.close(); + }, skip: 'https://github.com/dart-lang/json_rpc_2/issues/55'); + group('like a server,', () { test('can receive a call and return a response', () { expect(outgoing.first, From b8ef462ff2a75544ad53f188521031b528de03c7 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Tue, 9 Jun 2020 19:56:49 -0700 Subject: [PATCH 068/127] Remove some unused utils (dart-lang/json_rpc_2#59) --- pkgs/json_rpc_2/lib/src/utils.dart | 45 ------------------------------ 1 file changed, 45 deletions(-) diff --git a/pkgs/json_rpc_2/lib/src/utils.dart b/pkgs/json_rpc_2/lib/src/utils.dart index f1989a798..f8f776f80 100644 --- a/pkgs/json_rpc_2/lib/src/utils.dart +++ b/pkgs/json_rpc_2/lib/src/utils.dart @@ -11,25 +11,6 @@ import 'exception.dart'; typedef ZeroArgumentFunction = Function(); -/// Returns a sentence fragment listing the elements of [iter]. -/// -/// This converts each element of [iter] to a string and separates them with -/// commas and/or "and" where appropriate. -String toSentence(Iterable iter) { - if (iter.length == 1) return iter.first.toString(); - return iter.take(iter.length - 1).join(', ') + ' and ${iter.last}'; -} - -/// Returns [name] if [number] is 1, or the plural of [name] otherwise. -/// -/// By default, this just adds "s" to the end of [name] to get the plural. If -/// [plural] is passed, that's used instead. -String pluralize(String name, int number, {String plural}) { - if (number == 1) return name; - if (plural != null) return plural; - return '${name}s'; -} - /// A regular expression to match the exception prefix that some exceptions' /// [Object.toString] values contain. final _exceptionPrefix = RegExp(r'^([A-Z][a-zA-Z]*)?(Exception|Error): '); @@ -92,29 +73,3 @@ class _RespondToFormatExceptionsTransformer return transformed; } } - -/// Returns a [StreamSink] that wraps [sink] and maps each event added using -/// [callback]. -StreamSink mapStreamSink(StreamSink sink, Function(dynamic) callback) => - _MappedStreamSink(sink, callback); - -/// A [StreamSink] wrapper that maps each event added to the sink. -class _MappedStreamSink implements StreamSink { - final StreamSink _inner; - final Function _callback; - - @override - Future get done => _inner.done; - - _MappedStreamSink(this._inner, this._callback); - - @override - void add(event) => _inner.add(_callback(event)); - @override - void addError(error, [StackTrace stackTrace]) => - _inner.addError(error, stackTrace); - @override - Future addStream(Stream stream) => _inner.addStream(stream.map(_callback)); - @override - Future close() => _inner.close(); -} From 7623e2d5c1d38455c22982ad1f8ff8ebaeb2559f Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Tue, 9 Jun 2020 19:57:15 -0700 Subject: [PATCH 069/127] Reflow a doc comment to 80 characters (dart-lang/json_rpc_2#61) --- pkgs/json_rpc_2/lib/src/server.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index 83129ea72..107544a84 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -61,11 +61,12 @@ class Server { /// invoked. If it is not set, the exception will be swallowed. final ErrorCallback onUnhandledError; - /// Whether to strictly enforce the JSON-RPC 2.0 specification for received messages. + /// Whether to strictly enforce the JSON-RPC 2.0 specification for received + /// messages. /// - /// If `false`, this [Server] will accept some requests which are not conformant - /// with the JSON-RPC 2.0 specification. In particular, requests missing the - /// `jsonrpc` parameter will be accepted. + /// If `false`, this [Server] will accept some requests which are not + /// conformant with the JSON-RPC 2.0 specification. In particular, requests + /// missing the `jsonrpc` parameter will be accepted. final bool strictProtocolChecks; /// Creates a [Server] that communicates over [channel]. From 17c480b94f32938f43845f2cc86fa9bbc155ce1b Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Wed, 10 Jun 2020 09:03:27 -0700 Subject: [PATCH 070/127] Refactor RespondToFormatExceptionsTransformer (dart-lang/json_rpc_2#60) Remove some implicit dynamic. - Add a generic type on the transformer. - Use the original `channel` argument to access the `sink` rather than assign to an intermediate transformed channel variable. - Add a `test` argument to the `handleError` call to avoid having to rethrow and change stack traces on other error types. - Read the `message` and `source` fields from a variable which has a static type. - Add an explicit `dynamic` on one argument type which must be dynamic. --- pkgs/json_rpc_2/lib/src/utils.dart | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/pkgs/json_rpc_2/lib/src/utils.dart b/pkgs/json_rpc_2/lib/src/utils.dart index f8f776f80..3f86f0134 100644 --- a/pkgs/json_rpc_2/lib/src/utils.dart +++ b/pkgs/json_rpc_2/lib/src/utils.dart @@ -52,24 +52,20 @@ final ignoreFormatExceptions = StreamTransformer.fromHandlers( }); /// A transformer that sends error responses on [FormatException]s. -final StreamChannelTransformer respondToFormatExceptions = +final StreamChannelTransformer respondToFormatExceptions = _RespondToFormatExceptionsTransformer(); -/// The implementation of [respondToFormatExceptions]. class _RespondToFormatExceptionsTransformer - implements StreamChannelTransformer { + implements StreamChannelTransformer { @override - StreamChannel bind(StreamChannel channel) { - var transformed; - transformed = channel.changeStream((stream) { - return stream.handleError((error) { - if (error is! FormatException) throw error; - + StreamChannel bind(StreamChannel channel) { + return channel.changeStream((stream) { + return stream.handleError((dynamic error) { + final formatException = error as FormatException; var exception = RpcException( - error_code.PARSE_ERROR, 'Invalid JSON: ${error.message}'); - transformed.sink.add(exception.serialize(error.source)); - }); + error_code.PARSE_ERROR, 'Invalid JSON: ${formatException.message}'); + channel.sink.add(exception.serialize(formatException.source)); + }, test: (error) => error is FormatException); }); - return transformed; } } From 754590dc1d8250b169838a268af040fa78c75014 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Wed, 10 Jun 2020 09:03:50 -0700 Subject: [PATCH 071/127] Give a type to the _batch list (dart-lang/json_rpc_2#62) The comment and lack of type signal that this would be a list of objects matching the `parameters` argument to `sendRequest`, but what is added to this map is the full request which is always a `Map`. --- pkgs/json_rpc_2/lib/src/client.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart index 1e421edc4..f52f630b8 100644 --- a/pkgs/json_rpc_2/lib/src/client.dart +++ b/pkgs/json_rpc_2/lib/src/client.dart @@ -24,8 +24,8 @@ class Client { /// The current batch of requests to be sent together. /// - /// Each element is a JSON-serializable object. - List _batch; + /// Each element is a JSON RPC spec compliant message. + List> _batch; /// The map of request ids to pending requests. final _pendingRequests = {}; From 4cb5c3b6b4377efccb74210e5780c0da9b42ea65 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Mon, 10 Aug 2020 09:01:12 -0700 Subject: [PATCH 072/127] Remove ChannelManager class (dart-lang/json_rpc_2#64) Fixes dart-lang/json_rpc_2#55 This class was intended to manage lifecycle around a `StreamChannel`, but in the case of a `Peer` there is a conflict having 3 instances of a `ChannelManager` attempting to manage lifecycle constraints which results in completing the same `Completer` instance twice. Since `Peer` needs a different behavior than `Server` or `Client` the `ChannelManager` abstraction isn't helpful. - Inline the important behavior around completing `done` appropriately for the end of the channel `stream`, or for errors, into `Peer`, `Server`, and `Client`. - Inline the important behavior around closing the channel `sink` and completing `done` when a `Server` or `Client` is closed. `Peer` has different behavior and only forwards to it's `Server` and `Client`. The returned future from `channel.sink.close` is ignored, since this future does not complete in the case where a done even can't be delivered to listeners. --- pkgs/json_rpc_2/CHANGELOG.md | 4 +- pkgs/json_rpc_2/lib/src/channel_manager.dart | 78 -------------------- pkgs/json_rpc_2/lib/src/client.dart | 35 ++++++--- pkgs/json_rpc_2/lib/src/peer.dart | 31 ++++---- pkgs/json_rpc_2/lib/src/server.dart | 32 +++++--- pkgs/json_rpc_2/pubspec.yaml | 2 +- pkgs/json_rpc_2/test/peer_test.dart | 2 +- 7 files changed, 69 insertions(+), 115 deletions(-) delete mode 100644 pkgs/json_rpc_2/lib/src/channel_manager.dart diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 93dc007bd..c7f1652e6 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,4 +1,6 @@ -## 2.2.2-dev +## 2.2.2 + +* Fix `Peer.close()` throwing `Bad state: Future already completed`. ## 2.2.1 diff --git a/pkgs/json_rpc_2/lib/src/channel_manager.dart b/pkgs/json_rpc_2/lib/src/channel_manager.dart deleted file mode 100644 index 5be11dd65..000000000 --- a/pkgs/json_rpc_2/lib/src/channel_manager.dart +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file -// 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. - -import 'dart:async'; - -import 'package:stream_channel/stream_channel.dart'; - -/// Wraps a [StreamChannel] and handles logic that's shared between [Server], -/// [Client], and [Peer]. -/// -/// These classes don't provide the user direct access to a -/// [StreamSubscription]. Instead, they use the future returned by [listen] to -/// notify the user of the remote endpoint closing or producing an error. -class ChannelManager { - /// The name of the component whose channel is wrapped (e.g. "Server"). - /// - /// Used for error reporting. - final String _name; - - /// The underlying channel. - final StreamChannel _channel; - - /// Returns a [Future] that completes when the connection is closed. - /// - /// This is the same future that's returned by [listen]. - Future get done => _doneCompleter.future; - final _doneCompleter = Completer.sync(); - - /// Whether the underlying communication channel is closed. - bool get isClosed => _doneCompleter.isCompleted; - - /// Whether [listen] has been called. - bool _listenCalled = false; - - /// Whether [close] has been called. - /// - /// Note that [isClosed] tracks whether the underlying connection is closed, - /// whereas this tracks only whether it was explicitly closed from this end. - bool _closeCalled = false; - - ChannelManager(this._name, this._channel); - - /// Starts listening to the channel. - /// - /// The returned Future will complete when the input stream is closed. If the - /// input stream emits an error, that will be piped to the returned Future. - Future listen(void Function(dynamic) handleInput) { - if (_listenCalled) { - throw StateError('Can only call $_name.listen() once.'); - } - _listenCalled = true; - - _channel.stream.listen(handleInput, onError: (error, stackTrace) { - _doneCompleter.completeError(error, stackTrace); - _channel.sink.close(); - }, onDone: () { - if (!_doneCompleter.isCompleted) _doneCompleter.complete(); - }, cancelOnError: true); - - return done; - } - - /// Emit [event]. - void add(event) { - if (isClosed && !_closeCalled) return; - _channel.sink.add(event); - } - - /// Closes the channel. - Future close() { - _closeCalled = true; - if (!_doneCompleter.isCompleted) { - _doneCompleter.complete(_channel.sink.close()); - } - return done; - } -} diff --git a/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart index f52f630b8..33a529c6e 100644 --- a/pkgs/json_rpc_2/lib/src/client.dart +++ b/pkgs/json_rpc_2/lib/src/client.dart @@ -7,7 +7,6 @@ import 'dart:async'; import 'package:stack_trace/stack_trace.dart'; import 'package:stream_channel/stream_channel.dart'; -import 'channel_manager.dart'; import 'exception.dart'; import 'utils.dart'; @@ -17,7 +16,7 @@ import 'utils.dart'; /// those method calls. Methods can be called with [sendRequest], or with /// [sendNotification] if no response is expected. class Client { - final ChannelManager _manager; + final StreamChannel _channel; /// The next request id. var _id = 0; @@ -30,19 +29,21 @@ class Client { /// The map of request ids to pending requests. final _pendingRequests = {}; + final _done = Completer(); + /// Returns a [Future] that completes when the underlying connection is /// closed. /// /// This is the same future that's returned by [listen] and [close]. It may /// complete before [close] is called if the remote endpoint closes the /// connection. - Future get done => _manager.done; + Future get done => _done.future; /// Whether the underlying connection is closed. /// /// Note that this will be `true` before [close] is called if the remote /// endpoint closes the connection. - bool get isClosed => _manager.isClosed; + bool get isClosed => _done.isCompleted; /// Creates a [Client] that communicates over [channel]. /// @@ -60,9 +61,8 @@ class Client { /// /// Note that the client won't begin listening to [responses] until /// [Client.listen] is called. - Client.withoutJson(StreamChannel channel) - : _manager = ChannelManager('Client', channel) { - _manager.done.whenComplete(() { + Client.withoutJson(this._channel) { + done.whenComplete(() { for (var request in _pendingRequests.values) { request.completer.completeError( StateError( @@ -81,13 +81,26 @@ class Client { /// when it has an error. This is the same as [done]. /// /// [listen] may only be called once. - Future listen() => _manager.listen(_handleResponse); + Future listen() { + _channel.stream.listen(_handleResponse, onError: (error, stackTrace) { + _done.completeError(error, stackTrace); + _channel.sink.close(); + }, onDone: () { + if (!_done.isCompleted) _done.complete(); + close(); + }); + return done; + } /// Closes the underlying connection. /// /// Returns a [Future] that completes when all resources have been released. /// This is the same as [done]. - Future close() => _manager.close(); + Future close() { + _channel.sink.close(); + if (!_done.isCompleted) _done.complete(); + return done; + } /// Sends a JSON-RPC 2 request to invoke the given [method]. /// @@ -145,7 +158,7 @@ class Client { if (_batch != null) { _batch.add(message); } else { - _manager.add(message); + _channel.sink.add(message); } } @@ -166,7 +179,7 @@ class Client { _batch = []; return tryFinally(callback, () { - _manager.add(_batch); + _channel.sink.add(_batch); _batch = null; }); } diff --git a/pkgs/json_rpc_2/lib/src/peer.dart b/pkgs/json_rpc_2/lib/src/peer.dart index 7f89dd2cb..31e893651 100644 --- a/pkgs/json_rpc_2/lib/src/peer.dart +++ b/pkgs/json_rpc_2/lib/src/peer.dart @@ -6,7 +6,6 @@ import 'dart:async'; import 'package:stream_channel/stream_channel.dart'; -import 'channel_manager.dart'; import 'client.dart'; import 'parameters.dart'; import 'server.dart'; @@ -18,7 +17,7 @@ import 'utils.dart'; /// 2.0 endpoint. It sends both requests and responses across the same /// communication channel and expects to connect to a peer that does the same. class Peer implements Client, Server { - final ChannelManager _manager; + final StreamChannel _channel; /// The underlying client that handles request-sending and response-receiving /// logic. @@ -36,10 +35,11 @@ class Peer implements Client, Server { /// they're responses. final _clientIncomingForwarder = StreamController(sync: true); + final _done = Completer(); @override - Future get done => _manager.done; + Future get done => _done.future; @override - bool get isClosed => _manager.isClosed; + bool get isClosed => _done.isCompleted; @override ErrorCallback get onUnhandledError => _server?.onUnhandledError; @@ -81,15 +81,14 @@ class Peer implements Client, Server { /// some requests which are not conformant with the JSON-RPC 2.0 /// specification. In particular, requests missing the `jsonrpc` parameter /// will be accepted. - Peer.withoutJson(StreamChannel channel, - {ErrorCallback onUnhandledError, bool strictProtocolChecks = true}) - : _manager = ChannelManager('Peer', channel) { + Peer.withoutJson(this._channel, + {ErrorCallback onUnhandledError, bool strictProtocolChecks = true}) { _server = Server.withoutJson( - StreamChannel(_serverIncomingForwarder.stream, channel.sink), + StreamChannel(_serverIncomingForwarder.stream, _channel.sink), onUnhandledError: onUnhandledError, strictProtocolChecks: strictProtocolChecks); _client = Client.withoutJson( - StreamChannel(_clientIncomingForwarder.stream, channel.sink)); + StreamChannel(_clientIncomingForwarder.stream, _channel.sink)); } // Client methods. @@ -121,7 +120,7 @@ class Peer implements Client, Server { Future listen() { _client.listen(); _server.listen(); - return _manager.listen((message) { + _channel.stream.listen((message) { if (message is Map) { if (message.containsKey('result') || message.containsKey('error')) { _clientIncomingForwarder.add(message); @@ -142,10 +141,16 @@ class Peer implements Client, Server { // server since it knows how to send error responses. _serverIncomingForwarder.add(message); } - }).whenComplete(close); + }, onError: (error, stackTrace) { + _done.completeError(error, stackTrace); + _channel.sink.close(); + }, onDone: () { + if (!_done.isCompleted) _done.complete(); + close(); + }); + return done; } @override - Future close() => - Future.wait([_client.close(), _server.close(), _manager.close()]); + Future close() => Future.wait([_client.close(), _server.close()]); } diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index 107544a84..553b67b6a 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -10,7 +10,6 @@ import 'package:stack_trace/stack_trace.dart'; import 'package:stream_channel/stream_channel.dart'; import '../error_code.dart' as error_code; -import 'channel_manager.dart'; import 'exception.dart'; import 'parameters.dart'; import 'utils.dart'; @@ -29,7 +28,7 @@ typedef ErrorCallback = void Function(dynamic error, dynamic stackTrace); /// asynchronously, it's possible for multiple methods to be invoked at the same /// time, or even for a single method to be invoked multiple times at once. class Server { - final ChannelManager _manager; + final StreamChannel _channel; /// The methods registered for this server. final _methods = {}; @@ -40,19 +39,21 @@ class Server { /// [RpcException.methodNotFound] exception. final _fallbacks = Queue(); + final _done = Completer(); + /// Returns a [Future] that completes when the underlying connection is /// closed. /// /// This is the same future that's returned by [listen] and [close]. It may /// complete before [close] is called if the remote endpoint closes the /// connection. - Future get done => _manager.done; + Future get done => _done.future; /// Whether the underlying connection is closed. /// /// Note that this will be `true` before [close] is called if the remote /// endpoint closes the connection. - bool get isClosed => _manager.isClosed; + bool get isClosed => _done.isCompleted; /// A callback that is fired on unhandled exceptions. /// @@ -102,9 +103,8 @@ class Server { /// If [strictProtocolChecks] is false, this [Server] will accept some /// requests which are not conformant with the JSON-RPC 2.0 specification. In /// particular, requests missing the `jsonrpc` parameter will be accepted. - Server.withoutJson(StreamChannel channel, - {this.onUnhandledError, this.strictProtocolChecks = true}) - : _manager = ChannelManager('Server', channel); + Server.withoutJson(this._channel, + {this.onUnhandledError, this.strictProtocolChecks = true}); /// Starts listening to the underlying stream. /// @@ -112,13 +112,25 @@ class Server { /// when it has an error. This is the same as [done]. /// /// [listen] may only be called once. - Future listen() => _manager.listen(_handleRequest); + Future listen() { + _channel.stream.listen(_handleRequest, onError: (error, stackTrace) { + _done.completeError(error, stackTrace); + _channel.sink.close(); + }, onDone: () { + if (!_done.isCompleted) _done.complete(); + }); + return done; + } /// Closes the underlying connection. /// /// Returns a [Future] that completes when all resources have been released. /// This is the same as [done]. - Future close() => _manager.close(); + Future close() { + _channel.sink.close(); + if (!_done.isCompleted) _done.complete(); + return done; + } /// Registers a method named [name] on this server. /// @@ -177,7 +189,7 @@ class Server { if (response == null) return; } - if (!isClosed) _manager.add(response); + if (!isClosed) _channel.sink.add(response); } /// Handles an individual parsed request. diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 806383b6c..551522115 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.2.2-dev +version: 2.2.2 description: >- Utilities to write a client or server using the JSON-RPC 2.0 spec. homepage: https://github.com/dart-lang/json_rpc_2 diff --git a/pkgs/json_rpc_2/test/peer_test.dart b/pkgs/json_rpc_2/test/peer_test.dart index e2c46f8a3..7284330f5 100644 --- a/pkgs/json_rpc_2/test/peer_test.dart +++ b/pkgs/json_rpc_2/test/peer_test.dart @@ -111,7 +111,7 @@ void main() { var peer = json_rpc.Peer.withoutJson(channel); unawaited(peer.listen()); await peer.close(); - }, skip: 'https://github.com/dart-lang/json_rpc_2/issues/55'); + }); group('like a server,', () { test('can receive a call and return a response', () { From 8b0707e5cf7079cedde08e1fcf298538b865b0b9 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Tue, 11 Aug 2020 16:23:49 -0700 Subject: [PATCH 073/127] Avoid an open Peer with a closed Client (dart-lang/json_rpc_2#65) May fix https://github.com/dart-lang/sdk/issues/43012 If a `Peer` is created with a `StreamChannel` that does not follow the stated contract it's possible that the `sink` gets closed without receiving a done event from the `channel` which leaves the `Peer` instance in a state that's inconsistent with the underlying `Client`. The result is that it's possible to get a bad state trying to send a message even with `isClosed` returns `false`. - Make `isClosed` and `done` forward to the `_client` and `_peer` fields so that they can't be inconsistent. - Forward errors to the `_server` so that it can forward them through `done` without an extra `Completer` to manage. - Avoid closing the `sink` in the `Peer`. It will end up being closed by the server when it is handling the error, and it's the same `sink` instance in both places. - Add a test that ensures that `isClosed` behaves as expected following a call to `close()` even when the `StreamChannel` does not follow it's contract. --- pkgs/json_rpc_2/lib/src/peer.dart | 20 ++++++++++---------- pkgs/json_rpc_2/test/peer_test.dart | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/pkgs/json_rpc_2/lib/src/peer.dart b/pkgs/json_rpc_2/lib/src/peer.dart index 31e893651..4cf6aae5b 100644 --- a/pkgs/json_rpc_2/lib/src/peer.dart +++ b/pkgs/json_rpc_2/lib/src/peer.dart @@ -35,11 +35,11 @@ class Peer implements Client, Server { /// they're responses. final _clientIncomingForwarder = StreamController(sync: true); - final _done = Completer(); + Future _done; @override - Future get done => _done.future; + Future get done => _done ??= Future.wait([_client.done, _server.done]); @override - bool get isClosed => _done.isCompleted; + bool get isClosed => _client.isClosed || _server.isClosed; @override ErrorCallback get onUnhandledError => _server?.onUnhandledError; @@ -142,15 +142,15 @@ class Peer implements Client, Server { _serverIncomingForwarder.add(message); } }, onError: (error, stackTrace) { - _done.completeError(error, stackTrace); - _channel.sink.close(); - }, onDone: () { - if (!_done.isCompleted) _done.complete(); - close(); - }); + _serverIncomingForwarder.addError(error, stackTrace); + }, onDone: close); return done; } @override - Future close() => Future.wait([_client.close(), _server.close()]); + Future close() { + _client.close(); + _server.close(); + return done; + } } diff --git a/pkgs/json_rpc_2/test/peer_test.dart b/pkgs/json_rpc_2/test/peer_test.dart index 7284330f5..e20976314 100644 --- a/pkgs/json_rpc_2/test/peer_test.dart +++ b/pkgs/json_rpc_2/test/peer_test.dart @@ -113,6 +113,21 @@ void main() { await peer.close(); }); + test('considered closed with misbehaving StreamChannel', () async { + // If a StreamChannel does not enforce the guarantees stated in it's + // contract - specifically that "Closing the sink causes the stream to close + // before it emits any more events." - The `Peer` should still understand + // when it has been closed manually. + var channel = StreamChannel( + StreamController().stream, + StreamController(), + ); + var peer = json_rpc.Peer.withoutJson(channel); + unawaited(peer.listen()); + unawaited(peer.close()); + expect(peer.isClosed, true); + }); + group('like a server,', () { test('can receive a call and return a response', () { expect(outgoing.first, From f123bfff090def2e2e075e55d6f043c79283cd5a Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Wed, 2 Sep 2020 09:58:43 -0700 Subject: [PATCH 074/127] Accept responses where the ID was converted to a String (dart-lang/json_rpc_2#66) --- pkgs/json_rpc_2/CHANGELOG.md | 4 ++++ pkgs/json_rpc_2/lib/src/client.dart | 8 ++++++-- pkgs/json_rpc_2/test/client/client_test.dart | 21 ++++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index c7f1652e6..f27cacb64 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.2.3-dev (unreleased) + +* Accept responses even if the server converts the ID to a String. + ## 2.2.2 * Fix `Peer.close()` throwing `Bad state: Future already completed`. diff --git a/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart index 33a529c6e..d89a62374 100644 --- a/pkgs/json_rpc_2/lib/src/client.dart +++ b/pkgs/json_rpc_2/lib/src/client.dart @@ -197,7 +197,9 @@ class Client { /// resolved. void _handleSingleResponse(response) { if (!_isResponseValid(response)) return; - var request = _pendingRequests.remove(response['id']); + var id = response['id']; + id = (id is String) ? int.parse(id) : id; + var request = _pendingRequests.remove(id); if (response.containsKey('result')) { request.completer.complete(response['result']); } else { @@ -212,7 +214,9 @@ class Client { bool _isResponseValid(response) { if (response is! Map) return false; if (response['jsonrpc'] != '2.0') return false; - if (!_pendingRequests.containsKey(response['id'])) return false; + var id = response['id']; + id = (id is String) ? int.parse(id) : id; + if (!_pendingRequests.containsKey(id)) return false; if (response.containsKey('result')) return true; if (!response.containsKey('error')) return false; diff --git a/pkgs/json_rpc_2/test/client/client_test.dart b/pkgs/json_rpc_2/test/client/client_test.dart index c1edbe4c7..228acbda1 100644 --- a/pkgs/json_rpc_2/test/client/client_test.dart +++ b/pkgs/json_rpc_2/test/client/client_test.dart @@ -31,6 +31,27 @@ void main() { completion(equals('bar'))); }); + test('sends a message and returns the response with String id', () { + controller.expectRequest((request) { + expect( + request, + allOf([ + containsPair('jsonrpc', '2.0'), + containsPair('method', 'foo'), + containsPair('params', {'param': 'value'}) + ])); + + return { + 'jsonrpc': '2.0', + 'result': 'bar', + 'id': request['id'].toString() + }; + }); + + expect(controller.client.sendRequest('foo', {'param': 'value'}), + completion(equals('bar'))); + }); + test('sends a notification and expects no response', () { controller.expectRequest((request) { expect( From bcf95cdb996c0897ff793f9ad28f3835f675a4c6 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 28 Oct 2020 14:44:14 -0700 Subject: [PATCH 075/127] Remove unused dart:async imports. (dart-lang/json_rpc_2#67) As of Dart 2.1, Future/Stream have been exported from dart:core. --- pkgs/json_rpc_2/test/client/client_test.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkgs/json_rpc_2/test/client/client_test.dart b/pkgs/json_rpc_2/test/client/client_test.dart index 228acbda1..334ddadc9 100644 --- a/pkgs/json_rpc_2/test/client/client_test.dart +++ b/pkgs/json_rpc_2/test/client/client_test.dart @@ -2,8 +2,6 @@ // 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. -import 'dart:async'; - import 'package:test/test.dart'; import 'package:json_rpc_2/error_code.dart' as error_code; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; From e94fc36b0a8a2cabb6c87ccbdf0be9e74a0f6219 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Tue, 29 Dec 2020 11:57:41 -0800 Subject: [PATCH 076/127] Remove unnecessary StackTrace instantiation (dart-lang/json_rpc_2#69) Manually instantiating `StackTrace.current` when completing with an error does not create a meaningfully different stack trace compared to not passing one at all - it has an extra frame but not a meaningful one. --- pkgs/json_rpc_2/lib/src/client.dart | 6 ++---- pkgs/json_rpc_2/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart index d89a62374..ccc4c391f 100644 --- a/pkgs/json_rpc_2/lib/src/client.dart +++ b/pkgs/json_rpc_2/lib/src/client.dart @@ -64,10 +64,8 @@ class Client { Client.withoutJson(this._channel) { done.whenComplete(() { for (var request in _pendingRequests.values) { - request.completer.completeError( - StateError( - 'The client closed with pending request "${request.method}".'), - StackTrace.current); + request.completer.completeError(StateError( + 'The client closed with pending request "${request.method}".')); } _pendingRequests.clear(); }).catchError((_) { diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 551522115..c0c800817 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.2.2 +version: 2.2.3-dev description: >- Utilities to write a client or server using the JSON-RPC 2.0 spec. homepage: https://github.com/dart-lang/json_rpc_2 From 2f76a3a362409e71526ca5153c01fcb1bd1df163 Mon Sep 17 00:00:00 2001 From: Alexander Thomas Date: Mon, 1 Feb 2021 17:47:50 +0100 Subject: [PATCH 077/127] Migrate to GitHub Actions (dart-lang/json_rpc_2#70) * Migrate to GitHub Actions * Delete .travis.yml --- .../.github/workflows/test-package.yml | 85 +++++++++++++++++++ pkgs/json_rpc_2/.travis.yml | 22 ----- 2 files changed, 85 insertions(+), 22 deletions(-) create mode 100644 pkgs/json_rpc_2/.github/workflows/test-package.yml delete mode 100644 pkgs/json_rpc_2/.travis.yml diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml new file mode 100644 index 000000000..27922659f --- /dev/null +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -0,0 +1,85 @@ +name: Dart CI + +on: + # Run on PRs and pushes to the default branch. + push: + branches: [ master ] + pull_request: + branches: [ master ] + schedule: + - cron: "0 0 * * 0" + +env: + PUB_ENVIRONMENT: bot.github + +jobs: + # Check code formatting and static analysis on a single OS (linux) + # against Dart dev. + analyze: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sdk: [dev] + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v0.3 + with: + sdk: ${{ matrix.sdk }} + - id: install + name: Install dependencies + run: dart pub get + - name: Check formatting + run: dart format --output=none --set-exit-if-changed . + if: always() && steps.install.outcome == 'success' + - name: Analyze code + run: dart analyze --fatal-infos + if: always() && steps.install.outcome == 'success' + + # Run tests on a matrix consisting of two dimensions: + # 1. OS: ubuntu-latest, (macos-latest, windows-latest) + # 2. release channel: dev + test: + needs: analyze + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + # Add macos-latest and/or windows-latest if relevant for this package. + os: [ubuntu-latest] + sdk: [dev] + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v0.3 + with: + sdk: ${{ matrix.sdk }} + - id: install + name: Install dependencies + run: dart pub get + - name: Run VM tests + run: dart test --platform vm + if: always() && steps.install.outcome == 'success' + + # Run tests on a matrix consisting of two dimensions: + # 1. OS: ubuntu-latest, (macos-latest, windows-latest) + # 2. release: 2.2.0 + test-legacy-sdk: + needs: analyze + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + # Add macos-latest and/or windows-latest if relevant for this package. + os: [ubuntu-latest] + sdk: [2.2.0] + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v0.3 + with: + sdk: ${{ matrix.sdk }} + - id: install + name: Install dependencies + run: pub get + - name: Run VM tests + run: pub run test --platform vm + if: always() && steps.install.outcome == 'success' diff --git a/pkgs/json_rpc_2/.travis.yml b/pkgs/json_rpc_2/.travis.yml deleted file mode 100644 index 3537f6541..000000000 --- a/pkgs/json_rpc_2/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: dart - -dart: - - dev - - 2.2.0 - -dart_task: - - test - - dartanalyzer: --fatal-infos --fatal-warnings . - -matrix: - include: - - dart: dev - dart_task: dartfmt - -# Only building master means that we don't run two builds for each pull request. -branches: - only: [master] - -cache: - directories: - - $HOME/.pub-cache From e50e7abb9720557a4c8653e17064a1d856ac3f8e Mon Sep 17 00:00:00 2001 From: Aneesh Rao Date: Thu, 25 Mar 2021 03:11:02 +0530 Subject: [PATCH 078/127] Use secure links in README (dart-lang/json_rpc_2#68) --- pkgs/json_rpc_2/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/json_rpc_2/README.md b/pkgs/json_rpc_2/README.md index 64562ace7..fef700880 100644 --- a/pkgs/json_rpc_2/README.md +++ b/pkgs/json_rpc_2/README.md @@ -1,6 +1,6 @@ A library that implements the [JSON-RPC 2.0 spec][spec]. -[spec]: http://www.jsonrpc.org/specification +[spec]: https://www.jsonrpc.org/specification ## Server From 68794da0615058b335553beaa6847993592b2835 Mon Sep 17 00:00:00 2001 From: Franklin Yow <58489007+franklinyow@users.noreply.github.com> Date: Fri, 2 Apr 2021 16:06:42 -0700 Subject: [PATCH 079/127] Update LICENSE Changes to comply with internal review --- pkgs/json_rpc_2/LICENSE | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/LICENSE b/pkgs/json_rpc_2/LICENSE index 5c60afea3..000cd7bec 100644 --- a/pkgs/json_rpc_2/LICENSE +++ b/pkgs/json_rpc_2/LICENSE @@ -1,4 +1,5 @@ -Copyright 2014, the Dart project authors. All rights reserved. +Copyright 2014, the Dart project authors. + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -9,7 +10,7 @@ met: copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Google Inc. nor the names of its + * Neither the name of Google LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. From 5583bb27f40f4cdadfb0557a9fb7f3540be9d461 Mon Sep 17 00:00:00 2001 From: hacker1024 Date: Fri, 23 Apr 2021 05:41:37 +1000 Subject: [PATCH 080/127] Migrate to null safety (dart-lang/json_rpc_2#72) --- .../.github/workflows/test-package.yml | 24 ------------------- pkgs/json_rpc_2/CHANGELOG.md | 3 ++- pkgs/json_rpc_2/lib/error_code.dart | 2 +- pkgs/json_rpc_2/lib/src/client.dart | 8 +++---- pkgs/json_rpc_2/lib/src/peer.dart | 14 +++++------ pkgs/json_rpc_2/lib/src/server.dart | 4 ++-- pkgs/json_rpc_2/lib/src/utils.dart | 10 ++++---- pkgs/json_rpc_2/pubspec.yaml | 12 +++++----- pkgs/json_rpc_2/test/client/client_test.dart | 14 +++++------ pkgs/json_rpc_2/test/client/utils.dart | 7 +++--- pkgs/json_rpc_2/test/server/utils.dart | 21 +++++++--------- 11 files changed, 44 insertions(+), 75 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index 27922659f..21a3c50b6 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -59,27 +59,3 @@ jobs: - name: Run VM tests run: dart test --platform vm if: always() && steps.install.outcome == 'success' - - # Run tests on a matrix consisting of two dimensions: - # 1. OS: ubuntu-latest, (macos-latest, windows-latest) - # 2. release: 2.2.0 - test-legacy-sdk: - needs: analyze - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - # Add macos-latest and/or windows-latest if relevant for this package. - os: [ubuntu-latest] - sdk: [2.2.0] - steps: - - uses: actions/checkout@v2 - - uses: dart-lang/setup-dart@v0.3 - with: - sdk: ${{ matrix.sdk }} - - id: install - name: Install dependencies - run: pub get - - name: Run VM tests - run: pub run test --platform vm - if: always() && steps.install.outcome == 'success' diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index f27cacb64..126ba0034 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,5 +1,6 @@ -## 2.2.3-dev (unreleased) +## 3.0.0-dev +* Migrate to null safety. * Accept responses even if the server converts the ID to a String. ## 2.2.2 diff --git a/pkgs/json_rpc_2/lib/error_code.dart b/pkgs/json_rpc_2/lib/error_code.dart index 4bb080883..f4ca4625d 100644 --- a/pkgs/json_rpc_2/lib/error_code.dart +++ b/pkgs/json_rpc_2/lib/error_code.dart @@ -37,7 +37,7 @@ const SERVER_ERROR = -32000; /// JSON-RPC 2.0 spec. /// /// If [errorCode] isn't defined in the JSON-RPC 2.0 spec, returns null. -String name(int errorCode) { +String? name(int errorCode) { switch (errorCode) { case PARSE_ERROR: return 'parse error'; diff --git a/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart index ccc4c391f..7e040ccaf 100644 --- a/pkgs/json_rpc_2/lib/src/client.dart +++ b/pkgs/json_rpc_2/lib/src/client.dart @@ -24,7 +24,7 @@ class Client { /// The current batch of requests to be sent together. /// /// Each element is a JSON RPC spec compliant message. - List> _batch; + List>? _batch; /// The map of request ids to pending requests. final _pendingRequests = {}; @@ -141,7 +141,7 @@ class Client { /// /// 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, 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 ' @@ -154,7 +154,7 @@ class Client { if (parameters != null) message['params'] = parameters; if (_batch != null) { - _batch.add(message); + _batch!.add(message); } else { _channel.sink.add(message); } @@ -197,7 +197,7 @@ class Client { if (!_isResponseValid(response)) return; var id = response['id']; id = (id is String) ? int.parse(id) : id; - var request = _pendingRequests.remove(id); + var request = _pendingRequests.remove(id)!; if (response.containsKey('result')) { request.completer.complete(response['result']); } else { diff --git a/pkgs/json_rpc_2/lib/src/peer.dart b/pkgs/json_rpc_2/lib/src/peer.dart index 4cf6aae5b..a11cff255 100644 --- a/pkgs/json_rpc_2/lib/src/peer.dart +++ b/pkgs/json_rpc_2/lib/src/peer.dart @@ -21,11 +21,11 @@ class Peer implements Client, Server { /// The underlying client that handles request-sending and response-receiving /// logic. - Client _client; + late final Client _client; /// The underlying server that handles request-receiving and response-sending /// logic. - Server _server; + late final Server _server; /// A stream controller that forwards incoming messages to [_server] if /// they're requests. @@ -35,14 +35,14 @@ class Peer implements Client, Server { /// they're responses. final _clientIncomingForwarder = StreamController(sync: true); - Future _done; @override - Future get done => _done ??= Future.wait([_client.done, _server.done]); + late final Future done = Future.wait([_client.done, _server.done]); + @override bool get isClosed => _client.isClosed || _server.isClosed; @override - ErrorCallback get onUnhandledError => _server?.onUnhandledError; + ErrorCallback? get onUnhandledError => _server.onUnhandledError; @override bool get strictProtocolChecks => _server.strictProtocolChecks; @@ -60,7 +60,7 @@ class Peer implements Client, Server { /// specification. In particular, requests missing the `jsonrpc` parameter /// will be accepted. Peer(StreamChannel channel, - {ErrorCallback onUnhandledError, bool strictProtocolChecks = true}) + {ErrorCallback? onUnhandledError, bool strictProtocolChecks = true}) : this.withoutJson( jsonDocument.bind(channel).transform(respondToFormatExceptions), onUnhandledError: onUnhandledError, @@ -82,7 +82,7 @@ class Peer implements Client, Server { /// specification. In particular, requests missing the `jsonrpc` parameter /// will be accepted. Peer.withoutJson(this._channel, - {ErrorCallback onUnhandledError, bool strictProtocolChecks = true}) { + {ErrorCallback? onUnhandledError, bool strictProtocolChecks = true}) { _server = Server.withoutJson( StreamChannel(_serverIncomingForwarder.stream, _channel.sink), onUnhandledError: onUnhandledError, diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index 553b67b6a..a2b4dc194 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -60,7 +60,7 @@ class Server { /// 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; + final ErrorCallback? onUnhandledError; /// Whether to strictly enforce the JSON-RPC 2.0 specification for received /// messages. @@ -82,7 +82,7 @@ class Server { /// requests which are not conformant with the JSON-RPC 2.0 specification. In /// particular, requests missing the `jsonrpc` parameter will be accepted. Server(StreamChannel channel, - {ErrorCallback onUnhandledError, bool strictProtocolChecks = true}) + {ErrorCallback? onUnhandledError, bool strictProtocolChecks = true}) : this.withoutJson( jsonDocument.bind(channel).transform(respondToFormatExceptions), onUnhandledError: onUnhandledError, diff --git a/pkgs/json_rpc_2/lib/src/utils.dart b/pkgs/json_rpc_2/lib/src/utils.dart index 3f86f0134..37523f0be 100644 --- a/pkgs/json_rpc_2/lib/src/utils.dart +++ b/pkgs/json_rpc_2/lib/src/utils.dart @@ -40,25 +40,25 @@ void tryFinally(Function() body, Function() whenComplete) { whenComplete(); return result; } else { - return result.whenComplete(whenComplete); + result.whenComplete(whenComplete); } } /// A transformer that silently drops [FormatException]s. -final ignoreFormatExceptions = StreamTransformer.fromHandlers( +final ignoreFormatExceptions = StreamTransformer.fromHandlers( handleError: (error, stackTrace, sink) { if (error is FormatException) return; sink.addError(error, stackTrace); }); /// A transformer that sends error responses on [FormatException]s. -final StreamChannelTransformer respondToFormatExceptions = +final StreamChannelTransformer respondToFormatExceptions = _RespondToFormatExceptionsTransformer(); class _RespondToFormatExceptionsTransformer - implements StreamChannelTransformer { + implements StreamChannelTransformer { @override - StreamChannel bind(StreamChannel channel) { + StreamChannel bind(StreamChannel channel) { return channel.changeStream((stream) { return stream.handleError((dynamic error) { final formatException = error as FormatException; diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index c0c800817..e8d1b15da 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,17 +1,17 @@ name: json_rpc_2 -version: 2.2.3-dev +version: 3.0.0-dev description: >- Utilities to write a client or server using the JSON-RPC 2.0 spec. homepage: https://github.com/dart-lang/json_rpc_2 environment: - sdk: ">=2.2.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: - stack_trace: ^1.0.0 + stack_trace: ^1.10.0 stream_channel: ">=1.1.0 <3.0.0" dev_dependencies: - pedantic: ^1.8.0 - test: ^1.0.0 - web_socket_channel: ^1.1.0 + pedantic: ^1.11.0 + test: ^1.16.0 + web_socket_channel: ^2.0.0 diff --git a/pkgs/json_rpc_2/test/client/client_test.dart b/pkgs/json_rpc_2/test/client/client_test.dart index 334ddadc9..1c9ce108f 100644 --- a/pkgs/json_rpc_2/test/client/client_test.dart +++ b/pkgs/json_rpc_2/test/client/client_test.dart @@ -174,14 +174,12 @@ void main() { }; }); - expect(controller.client.sendRequest('foo', {'param': 'value'}), - throwsA(predicate((exception) { - expect(exception, TypeMatcher()); - expect(exception.code, equals(error_code.SERVER_ERROR)); - expect(exception.message, equals('you are bad at requests')); - expect(exception.data, equals('some junk')); - return true; - }))); + expect( + controller.client.sendRequest('foo', {'param': 'value'}), + throwsA(TypeMatcher() + .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'))); }); test('requests throw StateErrors if the client is closed', () { diff --git a/pkgs/json_rpc_2/test/client/utils.dart b/pkgs/json_rpc_2/test/client/utils.dart index 1684b3705..398c55835 100644 --- a/pkgs/json_rpc_2/test/client/utils.dart +++ b/pkgs/json_rpc_2/test/client/utils.dart @@ -19,13 +19,12 @@ class ClientController { final _requestController = StreamController(); /// The client. - json_rpc.Client get client => _client; - json_rpc.Client _client; + late final json_rpc.Client client; ClientController() { - _client = json_rpc.Client( + client = json_rpc.Client( StreamChannel(_responseController.stream, _requestController.sink)); - _client.listen(); + client.listen(); } /// Expects that the client will send a request. diff --git a/pkgs/json_rpc_2/test/server/utils.dart b/pkgs/json_rpc_2/test/server/utils.dart index 7ff491c59..92f1d3255 100644 --- a/pkgs/json_rpc_2/test/server/utils.dart +++ b/pkgs/json_rpc_2/test/server/utils.dart @@ -20,17 +20,16 @@ class ServerController { final _responseController = StreamController(); /// The server. - json_rpc.Server get server => _server; - json_rpc.Server _server; + late final json_rpc.Server server; ServerController( - {json_rpc.ErrorCallback onUnhandledError, + {json_rpc.ErrorCallback? onUnhandledError, bool strictProtocolChecks = true}) { - _server = json_rpc.Server( + server = json_rpc.Server( StreamChannel(_requestController.stream, _responseController.sink), onUnhandledError: onUnhandledError, strictProtocolChecks: strictProtocolChecks); - _server.listen(); + server.listen(); } /// Passes [request], a decoded request, to [server] and returns its decoded @@ -66,11 +65,7 @@ void expectErrorResponse( /// Returns a matcher that matches a [json_rpc.RpcException] with an /// `invalid_params` error code. -Matcher throwsInvalidParams(String message) { - return throwsA(predicate((error) { - expect(error, TypeMatcher()); - expect(error.code, equals(error_code.INVALID_PARAMS)); - expect(error.message, equals(message)); - return true; - })); -} +Matcher throwsInvalidParams(String message) => + throwsA(TypeMatcher() + .having((e) => e.code, 'code', error_code.INVALID_PARAMS) + .having((e) => e.message, 'message', message)); From 180465cb65a0d0b0361ceb6b8d8085318b548ccf Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Thu, 22 Apr 2021 16:13:30 -0700 Subject: [PATCH 081/127] Add testing on 2.12.0 (dart-lang/json_rpc_2#74) Use the latest dart setup action --- pkgs/json_rpc_2/.github/workflows/test-package.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index 21a3c50b6..e47bf6600 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [dev] steps: - uses: actions/checkout@v2 - - uses: dart-lang/setup-dart@v0.3 + - uses: dart-lang/setup-dart@v1.0 with: sdk: ${{ matrix.sdk }} - id: install @@ -47,10 +47,10 @@ jobs: matrix: # Add macos-latest and/or windows-latest if relevant for this package. os: [ubuntu-latest] - sdk: [dev] + sdk: [2.12.0, dev] steps: - uses: actions/checkout@v2 - - uses: dart-lang/setup-dart@v0.3 + - uses: dart-lang/setup-dart@v1.0 with: sdk: ${{ matrix.sdk }} - id: install From 61ad48aca7e4c70ec2e3c29bd23444ec4e953e9d Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Wed, 28 Apr 2021 08:51:20 -0700 Subject: [PATCH 082/127] Prepare to publish with null safety (dart-lang/json_rpc_2#75) Bump the `stream_channel` constraint so the lower bound is migrated for null safety. --- pkgs/json_rpc_2/CHANGELOG.md | 2 +- pkgs/json_rpc_2/pubspec.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 126ba0034..5b3c86082 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.0.0-dev +## 3.0.0 * Migrate to null safety. * Accept responses even if the server converts the ID to a String. diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index e8d1b15da..4e1b8a30d 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 3.0.0-dev +version: 3.0.0 description: >- Utilities to write a client or server using the JSON-RPC 2.0 spec. homepage: https://github.com/dart-lang/json_rpc_2 @@ -9,7 +9,7 @@ environment: dependencies: stack_trace: ^1.10.0 - stream_channel: ">=1.1.0 <3.0.0" + stream_channel: ^2.1.0 dev_dependencies: pedantic: ^1.11.0 From 53a2c4db425afd97d8ee711dd240131a133473d1 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Fri, 30 Apr 2021 17:19:30 -0700 Subject: [PATCH 083/127] Allow a `null` result (dart-lang/json_rpc_2#77) Fixes dart-lang/json_rpc_2#76 Add missing nullable annotation on the `result` variable and a test which fails if the variable is non-nullable. --- pkgs/json_rpc_2/CHANGELOG.md | 4 ++++ pkgs/json_rpc_2/lib/src/server.dart | 2 +- pkgs/json_rpc_2/test/server/server_test.dart | 9 +++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 5b3c86082..6a819f8c0 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.0.1-dev + +* Fix a bug where a `null` result to a request caused an exception. + ## 3.0.0 * Migrate to null safety. diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index a2b4dc194..ac34edff1 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -201,7 +201,7 @@ class Server { var method = _methods[name]; method ??= _tryFallbacks; - Object result; + Object? result; if (method is ZeroArgumentFunction) { if (request.containsKey('params')) { throw RpcException.invalidParams('No parameters are allowed for ' diff --git a/pkgs/json_rpc_2/test/server/server_test.dart b/pkgs/json_rpc_2/test/server/server_test.dart index 14ae1e495..8213ee7c5 100644 --- a/pkgs/json_rpc_2/test/server/server_test.dart +++ b/pkgs/json_rpc_2/test/server/server_test.dart @@ -44,6 +44,15 @@ void main() { completion(equals({'jsonrpc': '2.0', 'result': 'foo', 'id': 1234}))); }); + test('Allows a `null` result', () { + controller.server.registerMethod('foo', () => null); + + expect( + controller + .handleRequest({'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}), + completion(equals({'jsonrpc': '2.0', 'result': null, 'id': 1234}))); + }); + test('a method that takes no parameters rejects parameters', () { controller.server.registerMethod('foo', () => 'foo'); From a007907f3af1be40d6136237b80e31c77f4ca381 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Thu, 6 May 2021 13:42:19 -0700 Subject: [PATCH 084/127] Prepare to publish (dart-lang/json_rpc_2#78) --- pkgs/json_rpc_2/CHANGELOG.md | 2 +- pkgs/json_rpc_2/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 6a819f8c0..d28d50ff9 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.0.1-dev +## 3.0.1 * Fix a bug where a `null` result to a request caused an exception. diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 4e1b8a30d..f7217f63e 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 3.0.0 +version: 3.0.1 description: >- Utilities to write a client or server using the JSON-RPC 2.0 spec. homepage: https://github.com/dart-lang/json_rpc_2 From ffc0ee81f6a0e5129fda1b67a10b0b55e9555443 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Wed, 1 Sep 2021 11:56:38 -0700 Subject: [PATCH 085/127] Fix pre-existing HintCode.UNNECESSARY_TYPE_CHECK_TRUE --- pkgs/json_rpc_2/lib/src/server.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index ac34edff1..afcc4ca72 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -309,7 +309,6 @@ class Server { try { return await iterator.current(params); } on RpcException catch (error) { - if (error is! RpcException) rethrow; if (error.code != error_code.METHOD_NOT_FOUND) rethrow; return _tryNext(); } From 00f21c25dc385d0473ee38c398a98a72a0429fbe Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Fri, 29 Apr 2022 16:28:17 -0700 Subject: [PATCH 086/127] Update pubspec.yaml --- pkgs/json_rpc_2/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index f7217f63e..69ec384cd 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -2,7 +2,7 @@ name: json_rpc_2 version: 3.0.1 description: >- Utilities to write a client or server using the JSON-RPC 2.0 spec. -homepage: https://github.com/dart-lang/json_rpc_2 +repository: https://github.com/dart-lang/json_rpc_2 environment: sdk: ">=2.12.0 <3.0.0" From 6cba9621477815b9aa75ff2a19ffad220cb1411c Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Fri, 29 Apr 2022 23:30:01 +0000 Subject: [PATCH 087/127] rev to a dev version --- pkgs/json_rpc_2/CHANGELOG.md | 5 +++++ pkgs/json_rpc_2/pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index d28d50ff9..e64acb61a 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,8 @@ +## 3.0.2-dev + +* Address a few analysis hint violations. +* Populate the pubspec `repository` field. + ## 3.0.1 * Fix a bug where a `null` result to a request caused an exception. diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 69ec384cd..48bc0b60b 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 3.0.1 +version: 3.0.2-dev description: >- Utilities to write a client or server using the JSON-RPC 2.0 spec. repository: https://github.com/dart-lang/json_rpc_2 From 511460c55d9ce9e0dca7d668880daa1c22aeaa43 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Tue, 14 Jun 2022 17:08:05 -0700 Subject: [PATCH 088/127] prep for publishing 3.0.2 --- pkgs/json_rpc_2/CHANGELOG.md | 3 ++- pkgs/json_rpc_2/README.md | 4 ++++ pkgs/json_rpc_2/analysis_options.yaml | 6 +----- pkgs/json_rpc_2/pubspec.yaml | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index e64acb61a..cd42d75a5 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,5 +1,6 @@ -## 3.0.2-dev +## 3.0.2 +* Switch to using `package:lints`. * Address a few analysis hint violations. * Populate the pubspec `repository` field. diff --git a/pkgs/json_rpc_2/README.md b/pkgs/json_rpc_2/README.md index fef700880..2564d8a00 100644 --- a/pkgs/json_rpc_2/README.md +++ b/pkgs/json_rpc_2/README.md @@ -1,3 +1,7 @@ +[![Dart CI](https://github.com/dart-lang/json_rpc_2/actions/workflows/test-package.yml/badge.svg)](https://github.com/dart-lang/json_rpc_2/actions/workflows/test-package.yml) +[![pub package](https://img.shields.io/pub/v/json_rpc_2.svg)](https://pub.dev/packages/json_rpc_2) +[![package publisher](https://img.shields.io/pub/publisher/json_rpc_2.svg)](https://pub.dev/packages/json_rpc_2/publisher) + A library that implements the [JSON-RPC 2.0 spec][spec]. [spec]: https://www.jsonrpc.org/specification diff --git a/pkgs/json_rpc_2/analysis_options.yaml b/pkgs/json_rpc_2/analysis_options.yaml index bff1a7444..572dd239d 100644 --- a/pkgs/json_rpc_2/analysis_options.yaml +++ b/pkgs/json_rpc_2/analysis_options.yaml @@ -1,5 +1 @@ -# Defines a default set of lint rules enforced for -# projects at Google. For details and rationale, -# see https://github.com/dart-lang/pedantic#enabled-lints. -include: package:pedantic/analysis_options.yaml - +include: package:lints/recommended.yaml diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 48bc0b60b..bdf1afdc7 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 3.0.2-dev +version: 3.0.2 description: >- Utilities to write a client or server using the JSON-RPC 2.0 spec. repository: https://github.com/dart-lang/json_rpc_2 @@ -12,6 +12,6 @@ dependencies: stream_channel: ^2.1.0 dev_dependencies: - pedantic: ^1.11.0 + lints: ^1.0.0 test: ^1.16.0 web_socket_channel: ^2.0.0 From d9c473e65e4eec22fd5988998a3b622a214964ef Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Tue, 14 Jun 2022 17:09:25 -0700 Subject: [PATCH 089/127] address analysis issues from package:lints --- pkgs/json_rpc_2/example/client.dart | 3 ++- pkgs/json_rpc_2/lib/error_code.dart | 2 ++ pkgs/json_rpc_2/lib/src/client.dart | 5 ++++- pkgs/json_rpc_2/lib/src/exception.dart | 8 ++++---- pkgs/json_rpc_2/lib/src/parameters.dart | 4 ++-- pkgs/json_rpc_2/lib/src/server.dart | 2 +- pkgs/json_rpc_2/lib/src/utils.dart | 3 +-- pkgs/json_rpc_2/test/client/client_test.dart | 3 ++- pkgs/json_rpc_2/test/client/stream_test.dart | 7 ++++--- pkgs/json_rpc_2/test/peer_test.dart | 8 ++++---- pkgs/json_rpc_2/test/server/batch_test.dart | 3 ++- pkgs/json_rpc_2/test/server/invalid_request_test.dart | 2 +- pkgs/json_rpc_2/test/server/parameters_test.dart | 9 +++++---- pkgs/json_rpc_2/test/server/server_test.dart | 3 ++- pkgs/json_rpc_2/test/server/stream_test.dart | 7 ++++--- pkgs/json_rpc_2/test/server/utils.dart | 2 +- 16 files changed, 41 insertions(+), 30 deletions(-) diff --git a/pkgs/json_rpc_2/example/client.dart b/pkgs/json_rpc_2/example/client.dart index 243b83e2a..d23be94ca 100644 --- a/pkgs/json_rpc_2/example/client.dart +++ b/pkgs/json_rpc_2/example/client.dart @@ -2,8 +2,9 @@ // 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. +import 'dart:async'; + import 'package:json_rpc_2/json_rpc_2.dart'; -import 'package:pedantic/pedantic.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; void main() async { diff --git a/pkgs/json_rpc_2/lib/error_code.dart b/pkgs/json_rpc_2/lib/error_code.dart index f4ca4625d..14e0543d6 100644 --- a/pkgs/json_rpc_2/lib/error_code.dart +++ b/pkgs/json_rpc_2/lib/error_code.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: constant_identifier_names + /// Error codes defined in the [JSON-RPC 2.0 specificiation][spec]. /// /// These codes are generally used for protocol-level communication. Most of diff --git a/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart index 7e040ccaf..cc1ca59f2 100644 --- a/pkgs/json_rpc_2/lib/src/client.dart +++ b/pkgs/json_rpc_2/lib/src/client.dart @@ -173,7 +173,10 @@ class Client { /// invokes [callback] without creating another batch. This means that /// responses are batched until the first batch ends. void withBatch(Function() callback) { - if (_batch != null) return callback(); + if (_batch != null) { + callback(); + return; + } _batch = []; return tryFinally(callback, () { diff --git a/pkgs/json_rpc_2/lib/src/exception.dart b/pkgs/json_rpc_2/lib/src/exception.dart index 64c97d07f..ad39ff233 100644 --- a/pkgs/json_rpc_2/lib/src/exception.dart +++ b/pkgs/json_rpc_2/lib/src/exception.dart @@ -24,7 +24,7 @@ class RpcException implements Exception { /// This must be a JSON-serializable object. If it's a [Map] without a /// `"request"` key, a copy of the request that caused the error will /// automatically be injected. - final data; + final Object? data; RpcException(this.code, this.message, {this.data}); @@ -44,9 +44,9 @@ class RpcException implements Exception { /// Converts this exception into a JSON-serializable object that's a valid /// JSON-RPC 2.0 error response. Map serialize(request) { - var modifiedData; - if (data is Map && !data.containsKey('request')) { - modifiedData = Map.from(data); + dynamic modifiedData; + if (data is Map && !(data as Map).containsKey('request')) { + modifiedData = Map.from(data as Map); modifiedData['request'] = request; } else if (data == null) { modifiedData = {'request': request}; diff --git a/pkgs/json_rpc_2/lib/src/parameters.dart b/pkgs/json_rpc_2/lib/src/parameters.dart index 665765bb2..7cf3f2fc2 100644 --- a/pkgs/json_rpc_2/lib/src/parameters.dart +++ b/pkgs/json_rpc_2/lib/src/parameters.dart @@ -27,7 +27,7 @@ class Parameters { /// If this is accessed for a [Parameter] that was not passed, the request /// will be automatically rejected. To avoid this, use [Parameter.valueOr]. dynamic get value => _value; - final _value; + final dynamic _value; Parameters(this.method, this._value); @@ -111,7 +111,7 @@ class Parameter extends Parameters { final Parameters _parent; /// The key used to access [this], used to construct [_path]. - final _key; + final dynamic _key; /// A human-readable representation of the path of getters used to get this. /// diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index afcc4ca72..60e66d8ff 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -172,7 +172,7 @@ class Server { /// if no response should be sent. [callback] may send custom /// errors by throwing an [RpcException]. Future _handleRequest(request) async { - var response; + dynamic response; if (request is List) { if (request.isEmpty) { response = RpcException(error_code.INVALID_REQUEST, diff --git a/pkgs/json_rpc_2/lib/src/utils.dart b/pkgs/json_rpc_2/lib/src/utils.dart index 37523f0be..305945c4b 100644 --- a/pkgs/json_rpc_2/lib/src/utils.dart +++ b/pkgs/json_rpc_2/lib/src/utils.dart @@ -28,7 +28,7 @@ String getErrorMessage(error) => /// 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) { - var result; + dynamic result; try { result = body(); } catch (_) { @@ -38,7 +38,6 @@ void tryFinally(Function() body, Function() whenComplete) { if (result is! Future) { whenComplete(); - return result; } else { result.whenComplete(whenComplete); } diff --git a/pkgs/json_rpc_2/test/client/client_test.dart b/pkgs/json_rpc_2/test/client/client_test.dart index 1c9ce108f..e11e3a9a9 100644 --- a/pkgs/json_rpc_2/test/client/client_test.dart +++ b/pkgs/json_rpc_2/test/client/client_test.dart @@ -9,7 +9,8 @@ import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'utils.dart'; void main() { - var controller; + late ClientController controller; + setUp(() => controller = ClientController()); test('sends a message and returns the response', () { diff --git a/pkgs/json_rpc_2/test/client/stream_test.dart b/pkgs/json_rpc_2/test/client/stream_test.dart index 4080064de..286afb68a 100644 --- a/pkgs/json_rpc_2/test/client/stream_test.dart +++ b/pkgs/json_rpc_2/test/client/stream_test.dart @@ -10,9 +10,10 @@ import 'package:test/test.dart'; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; void main() { - var responseController; - var requestController; - var client; + late StreamController responseController; + late StreamController requestController; + late json_rpc.Client client; + setUp(() { responseController = StreamController(); requestController = StreamController(); diff --git a/pkgs/json_rpc_2/test/peer_test.dart b/pkgs/json_rpc_2/test/peer_test.dart index e20976314..663da9f3f 100644 --- a/pkgs/json_rpc_2/test/peer_test.dart +++ b/pkgs/json_rpc_2/test/peer_test.dart @@ -5,7 +5,6 @@ import 'dart:async'; import 'dart:convert'; -import 'package:pedantic/pedantic.dart'; import 'package:stream_channel/stream_channel.dart'; import 'package:test/test.dart'; @@ -13,9 +12,10 @@ import 'package:json_rpc_2/error_code.dart' as error_code; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; void main() { - var incoming; - var outgoing; - var peer; + late StreamSink incoming; + late Stream outgoing; + late json_rpc.Peer peer; + setUp(() { var incomingController = StreamController(); incoming = incomingController.sink; diff --git a/pkgs/json_rpc_2/test/server/batch_test.dart b/pkgs/json_rpc_2/test/server/batch_test.dart index c9411b361..bdf93a70e 100644 --- a/pkgs/json_rpc_2/test/server/batch_test.dart +++ b/pkgs/json_rpc_2/test/server/batch_test.dart @@ -8,7 +8,8 @@ import 'package:json_rpc_2/error_code.dart' as error_code; import 'utils.dart'; void main() { - var controller; + late ServerController controller; + setUp(() { controller = ServerController(); controller.server diff --git a/pkgs/json_rpc_2/test/server/invalid_request_test.dart b/pkgs/json_rpc_2/test/server/invalid_request_test.dart index a67afec1f..05376dd95 100644 --- a/pkgs/json_rpc_2/test/server/invalid_request_test.dart +++ b/pkgs/json_rpc_2/test/server/invalid_request_test.dart @@ -8,7 +8,7 @@ import 'package:json_rpc_2/error_code.dart' as error_code; import 'utils.dart'; void main() { - var controller; + late ServerController controller; setUp(() => controller = ServerController()); test('a non-Array/Object request is invalid', () { diff --git a/pkgs/json_rpc_2/test/server/parameters_test.dart b/pkgs/json_rpc_2/test/server/parameters_test.dart index 069daf3a7..aef67134a 100644 --- a/pkgs/json_rpc_2/test/server/parameters_test.dart +++ b/pkgs/json_rpc_2/test/server/parameters_test.dart @@ -9,7 +9,7 @@ import 'utils.dart'; void main() { group('with named parameters', () { - var parameters; + late json_rpc.Parameters parameters; setUp(() { parameters = json_rpc.Parameters('foo', { 'num': 1.5, @@ -277,7 +277,7 @@ void main() { }); group('with a nested parameter map', () { - var nested; + late json_rpc.Parameter nested; setUp(() => nested = parameters['map']); test('[int] fails with a type error', () { @@ -312,7 +312,8 @@ void main() { }); group('with a nested parameter list', () { - var nested; + late json_rpc.Parameter nested; + setUp(() => nested = parameters['list']); test('[string] fails with a type error', () { @@ -348,7 +349,7 @@ void main() { }); group('with positional parameters', () { - var parameters; + late json_rpc.Parameters parameters; setUp(() => parameters = json_rpc.Parameters('foo', [1, 2, 3, 4, 5])); test('value returns the wrapped value', () { diff --git a/pkgs/json_rpc_2/test/server/server_test.dart b/pkgs/json_rpc_2/test/server/server_test.dart index 8213ee7c5..11ce201f3 100644 --- a/pkgs/json_rpc_2/test/server/server_test.dart +++ b/pkgs/json_rpc_2/test/server/server_test.dart @@ -11,7 +11,8 @@ import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'utils.dart'; void main() { - var controller; + late ServerController controller; + setUp(() => controller = ServerController()); test('calls a registered method with the given name', () { diff --git a/pkgs/json_rpc_2/test/server/stream_test.dart b/pkgs/json_rpc_2/test/server/stream_test.dart index 7105cd601..a6d6463c7 100644 --- a/pkgs/json_rpc_2/test/server/stream_test.dart +++ b/pkgs/json_rpc_2/test/server/stream_test.dart @@ -10,9 +10,10 @@ import 'package:test/test.dart'; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; void main() { - var requestController; - var responseController; - var server; + late StreamController requestController; + late StreamController responseController; + late json_rpc.Server server; + setUp(() { requestController = StreamController(); responseController = StreamController(); diff --git a/pkgs/json_rpc_2/test/server/utils.dart b/pkgs/json_rpc_2/test/server/utils.dart index 92f1d3255..5900d9459 100644 --- a/pkgs/json_rpc_2/test/server/utils.dart +++ b/pkgs/json_rpc_2/test/server/utils.dart @@ -50,7 +50,7 @@ class ServerController { void expectErrorResponse( ServerController controller, request, int errorCode, String message, {data}) { - var id; + dynamic id; if (request is Map) id = request['id']; data ??= {'request': request}; From 8200dfd3eff93399daa9b99e1cfd464bd84115d6 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Tue, 14 Jun 2022 17:24:08 -0700 Subject: [PATCH 090/127] add a few more lints --- pkgs/json_rpc_2/analysis_options.yaml | 14 ++++++++++++++ pkgs/json_rpc_2/test/client/client_test.dart | 2 +- pkgs/json_rpc_2/test/client/stream_test.dart | 3 +-- pkgs/json_rpc_2/test/client/utils.dart | 3 +-- pkgs/json_rpc_2/test/peer_test.dart | 6 +++--- pkgs/json_rpc_2/test/server/batch_test.dart | 2 +- .../test/server/invalid_request_test.dart | 2 +- pkgs/json_rpc_2/test/server/parameters_test.dart | 2 +- pkgs/json_rpc_2/test/server/server_test.dart | 2 +- pkgs/json_rpc_2/test/server/stream_test.dart | 3 +-- pkgs/json_rpc_2/test/server/utils.dart | 5 ++--- 11 files changed, 27 insertions(+), 17 deletions(-) diff --git a/pkgs/json_rpc_2/analysis_options.yaml b/pkgs/json_rpc_2/analysis_options.yaml index 572dd239d..9ec640841 100644 --- a/pkgs/json_rpc_2/analysis_options.yaml +++ b/pkgs/json_rpc_2/analysis_options.yaml @@ -1 +1,15 @@ include: package:lints/recommended.yaml + +linter: + rules: + - always_declare_return_types + # - avoid_dynamic_calls + - avoid_unused_constructor_parameters + - cancel_subscriptions + - directives_ordering + - omit_local_variable_types + - package_api_docs + - prefer_single_quotes + - test_types_in_equals + - throw_in_finally + - unawaited_futures diff --git a/pkgs/json_rpc_2/test/client/client_test.dart b/pkgs/json_rpc_2/test/client/client_test.dart index e11e3a9a9..df9c03356 100644 --- a/pkgs/json_rpc_2/test/client/client_test.dart +++ b/pkgs/json_rpc_2/test/client/client_test.dart @@ -2,9 +2,9 @@ // 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. -import 'package:test/test.dart'; import 'package:json_rpc_2/error_code.dart' as error_code; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; +import 'package:test/test.dart'; import 'utils.dart'; diff --git a/pkgs/json_rpc_2/test/client/stream_test.dart b/pkgs/json_rpc_2/test/client/stream_test.dart index 286afb68a..b33778ed3 100644 --- a/pkgs/json_rpc_2/test/client/stream_test.dart +++ b/pkgs/json_rpc_2/test/client/stream_test.dart @@ -4,11 +4,10 @@ import 'dart:async'; +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'package:stream_channel/stream_channel.dart'; import 'package:test/test.dart'; -import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; - void main() { late StreamController responseController; late StreamController requestController; diff --git a/pkgs/json_rpc_2/test/client/utils.dart b/pkgs/json_rpc_2/test/client/utils.dart index 398c55835..45342cf36 100644 --- a/pkgs/json_rpc_2/test/client/utils.dart +++ b/pkgs/json_rpc_2/test/client/utils.dart @@ -5,11 +5,10 @@ import 'dart:async'; import 'dart:convert'; +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'package:stream_channel/stream_channel.dart'; import 'package:test/test.dart'; -import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; - /// A controller used to test a [json_rpc.Client]. class ClientController { /// The controller for the client's response stream. diff --git a/pkgs/json_rpc_2/test/peer_test.dart b/pkgs/json_rpc_2/test/peer_test.dart index 663da9f3f..4b4c44a89 100644 --- a/pkgs/json_rpc_2/test/peer_test.dart +++ b/pkgs/json_rpc_2/test/peer_test.dart @@ -5,11 +5,10 @@ import 'dart:async'; import 'dart:convert'; -import 'package:stream_channel/stream_channel.dart'; -import 'package:test/test.dart'; - import 'package:json_rpc_2/error_code.dart' as error_code; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; +import 'package:stream_channel/stream_channel.dart'; +import 'package:test/test.dart'; void main() { late StreamSink incoming; @@ -240,6 +239,7 @@ void main() { ); peer ..registerMethod('foo', () => throw exception) + // ignore: unawaited_futures ..listen(); incomingController.add({'jsonrpc': '2.0', 'method': 'foo'}); diff --git a/pkgs/json_rpc_2/test/server/batch_test.dart b/pkgs/json_rpc_2/test/server/batch_test.dart index bdf93a70e..b8b8fea80 100644 --- a/pkgs/json_rpc_2/test/server/batch_test.dart +++ b/pkgs/json_rpc_2/test/server/batch_test.dart @@ -2,8 +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. -import 'package:test/test.dart'; import 'package:json_rpc_2/error_code.dart' as error_code; +import 'package:test/test.dart'; import 'utils.dart'; diff --git a/pkgs/json_rpc_2/test/server/invalid_request_test.dart b/pkgs/json_rpc_2/test/server/invalid_request_test.dart index 05376dd95..4fa4de1ed 100644 --- a/pkgs/json_rpc_2/test/server/invalid_request_test.dart +++ b/pkgs/json_rpc_2/test/server/invalid_request_test.dart @@ -2,8 +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. -import 'package:test/test.dart'; import 'package:json_rpc_2/error_code.dart' as error_code; +import 'package:test/test.dart'; import 'utils.dart'; diff --git a/pkgs/json_rpc_2/test/server/parameters_test.dart b/pkgs/json_rpc_2/test/server/parameters_test.dart index aef67134a..9ecfb1ffa 100644 --- a/pkgs/json_rpc_2/test/server/parameters_test.dart +++ b/pkgs/json_rpc_2/test/server/parameters_test.dart @@ -2,8 +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. -import 'package:test/test.dart'; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; +import 'package:test/test.dart'; import 'utils.dart'; diff --git a/pkgs/json_rpc_2/test/server/server_test.dart b/pkgs/json_rpc_2/test/server/server_test.dart index 11ce201f3..10f2678d8 100644 --- a/pkgs/json_rpc_2/test/server/server_test.dart +++ b/pkgs/json_rpc_2/test/server/server_test.dart @@ -4,9 +4,9 @@ import 'dart:convert'; -import 'package:test/test.dart'; import 'package:json_rpc_2/error_code.dart' as error_code; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; +import 'package:test/test.dart'; import 'utils.dart'; diff --git a/pkgs/json_rpc_2/test/server/stream_test.dart b/pkgs/json_rpc_2/test/server/stream_test.dart index a6d6463c7..2f95150fd 100644 --- a/pkgs/json_rpc_2/test/server/stream_test.dart +++ b/pkgs/json_rpc_2/test/server/stream_test.dart @@ -4,11 +4,10 @@ import 'dart:async'; +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'package:stream_channel/stream_channel.dart'; import 'package:test/test.dart'; -import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; - void main() { late StreamController requestController; late StreamController responseController; diff --git a/pkgs/json_rpc_2/test/server/utils.dart b/pkgs/json_rpc_2/test/server/utils.dart index 5900d9459..481d34230 100644 --- a/pkgs/json_rpc_2/test/server/utils.dart +++ b/pkgs/json_rpc_2/test/server/utils.dart @@ -5,12 +5,11 @@ import 'dart:async'; import 'dart:convert'; +import 'package:json_rpc_2/error_code.dart' as error_code; +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'package:stream_channel/stream_channel.dart'; import 'package:test/test.dart'; -import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; -import 'package:json_rpc_2/error_code.dart' as error_code; - /// A controller used to test a [json_rpc.Server]. class ServerController { /// The controller for the server's request stream. From b72f971008c3259c340ffa9016e2ac9312dd414a Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Tue, 14 Jun 2022 17:24:55 -0700 Subject: [PATCH 091/127] test on 2.15.0 --- pkgs/json_rpc_2/.github/workflows/test-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index e47bf6600..9e0fcc469 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -47,7 +47,7 @@ jobs: matrix: # Add macos-latest and/or windows-latest if relevant for this package. os: [ubuntu-latest] - sdk: [2.12.0, dev] + sdk: [2.15.0, dev] steps: - uses: actions/checkout@v2 - uses: dart-lang/setup-dart@v1.0 From 7485dd7f30b186ab1f6c5259396236baff364d0f Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Wed, 15 Jun 2022 12:42:08 -0700 Subject: [PATCH 092/127] Update pubspec.yaml Co-authored-by: Nate Bosch --- pkgs/json_rpc_2/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index bdf1afdc7..99618b801 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -12,6 +12,6 @@ dependencies: stream_channel: ^2.1.0 dev_dependencies: - lints: ^1.0.0 + lints: ^2.0.0 test: ^1.16.0 web_socket_channel: ^2.0.0 From 47a63cd612155a723de598484b8c6a01b7aad9f5 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Wed, 15 Jun 2022 12:45:06 -0700 Subject: [PATCH 093/127] address a lint --- pkgs/json_rpc_2/lib/src/server.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index 60e66d8ff..5cb293707 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -301,7 +301,7 @@ class Server { Future _tryFallbacks(Parameters params) { var iterator = _fallbacks.toList().iterator; - Future _tryNext() async { + Future tryNext() async { if (!iterator.moveNext()) { throw RpcException.methodNotFound(params.method); } @@ -310,10 +310,10 @@ class Server { return await iterator.current(params); } on RpcException catch (error) { if (error.code != error_code.METHOD_NOT_FOUND) rethrow; - return _tryNext(); + return tryNext(); } } - return _tryNext(); + return tryNext(); } } From 07706599a169c478c6dc8cc300684944c24dd073 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Wed, 15 Jun 2022 12:47:45 -0700 Subject: [PATCH 094/127] widen the dep on package:lints --- pkgs/json_rpc_2/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 99618b801..7d4897e50 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -12,6 +12,6 @@ dependencies: stream_channel: ^2.1.0 dev_dependencies: - lints: ^2.0.0 + lints: '>=1.0.0 < 3.0.0' test: ^1.16.0 web_socket_channel: ^2.0.0 From 48dac85e9a68934364561dc28fb9efb5244c3309 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Wed, 15 Jun 2022 19:33:54 -0700 Subject: [PATCH 095/127] Use a public final field (dart-lang/json_rpc_2#84) There is no setter, so no need for a private field and public forwarding getter. --- pkgs/json_rpc_2/CHANGELOG.md | 2 ++ pkgs/json_rpc_2/lib/src/parameters.dart | 5 ++--- pkgs/json_rpc_2/pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index cd42d75a5..77b1a85b6 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,5 @@ +## 3.0.3-dev + ## 3.0.2 * Switch to using `package:lints`. diff --git a/pkgs/json_rpc_2/lib/src/parameters.dart b/pkgs/json_rpc_2/lib/src/parameters.dart index 7cf3f2fc2..7a80deacd 100644 --- a/pkgs/json_rpc_2/lib/src/parameters.dart +++ b/pkgs/json_rpc_2/lib/src/parameters.dart @@ -26,10 +26,9 @@ class Parameters { /// /// If this is accessed for a [Parameter] that was not passed, the request /// will be automatically rejected. To avoid this, use [Parameter.valueOr]. - dynamic get value => _value; - final dynamic _value; + final dynamic value; - Parameters(this.method, this._value); + Parameters(this.method, this.value); /// Returns a single parameter. /// diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 7d4897e50..f301bac7b 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 3.0.2 +version: 3.0.3-dev description: >- Utilities to write a client or server using the JSON-RPC 2.0 spec. repository: https://github.com/dart-lang/json_rpc_2 From 37b3ff321b4a38ffe34900f14e230299cad8f9da Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Wed, 9 Nov 2022 15:47:35 -0800 Subject: [PATCH 096/127] blast_repo fixes (dart-lang/json_rpc_2#86) Dependabot GitHub Action --- pkgs/json_rpc_2/.github/dependabot.yml | 9 +++++++++ pkgs/json_rpc_2/.github/workflows/test-package.yml | 8 ++++---- 2 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 pkgs/json_rpc_2/.github/dependabot.yml diff --git a/pkgs/json_rpc_2/.github/dependabot.yml b/pkgs/json_rpc_2/.github/dependabot.yml new file mode 100644 index 000000000..1603cdd9e --- /dev/null +++ b/pkgs/json_rpc_2/.github/dependabot.yml @@ -0,0 +1,9 @@ +# Dependabot configuration file. +# See https://docs.github.com/en/code-security/dependabot/dependabot-version-updates +version: 2 + +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index 9e0fcc469..2e5ff01f8 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -22,8 +22,8 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@v2 - - uses: dart-lang/setup-dart@v1.0 + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} - id: install @@ -49,8 +49,8 @@ jobs: os: [ubuntu-latest] sdk: [2.15.0, dev] steps: - - uses: actions/checkout@v2 - - uses: dart-lang/setup-dart@v1.0 + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} - id: install From 75bd4ae37d85f696657e394dc45098fe32b3c30e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Jan 2023 09:33:35 -0800 Subject: [PATCH 097/127] Bump actions/checkout from 3.1.0 to 3.2.0 (dart-lang/json_rpc_2#87) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.1.0 to 3.2.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8...755da8c3cf115ac066823e79a1e1788f8940201b) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/json_rpc_2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index 2e5ff01f8..7450d9c1f 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} @@ -49,7 +49,7 @@ jobs: os: [ubuntu-latest] sdk: [2.15.0, dev] steps: - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} From 9f468c552b3d533d4867a0daa98b79a0ced36d2c Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Thu, 26 Jan 2023 22:57:31 -0800 Subject: [PATCH 098/127] blast_repo fixes (dart-lang/json_rpc_2#89) auto-publish --- pkgs/json_rpc_2/.github/workflows/publish.yaml | 14 ++++++++++++++ pkgs/json_rpc_2/README.md | 5 +++++ 2 files changed, 19 insertions(+) create mode 100644 pkgs/json_rpc_2/.github/workflows/publish.yaml diff --git a/pkgs/json_rpc_2/.github/workflows/publish.yaml b/pkgs/json_rpc_2/.github/workflows/publish.yaml new file mode 100644 index 000000000..fcb7ccb89 --- /dev/null +++ b/pkgs/json_rpc_2/.github/workflows/publish.yaml @@ -0,0 +1,14 @@ +# A CI configuration to auto-publish pub packages. + +name: Publish + +on: + pull_request: + branches: [ master ] + push: + tags: [ 'v[0-9]+.[0-9]+.[0-9]+*' ] + +jobs: + publish: + if: ${{ github.repository_owner == 'dart-lang' }} + uses: dart-lang/ecosystem/.github/workflows/publish.yaml@main diff --git a/pkgs/json_rpc_2/README.md b/pkgs/json_rpc_2/README.md index 2564d8a00..e95a2e8ea 100644 --- a/pkgs/json_rpc_2/README.md +++ b/pkgs/json_rpc_2/README.md @@ -133,3 +133,8 @@ both a client and a server. This package supports this directly using the `Peer` class, which implements both `Client` and `Server`. It supports the same methods as those classes, and automatically makes sure that every message from the other endpoint is routed and handled correctly. + +## Publishing automation + +For information about our publishing automation and release process, see +https://github.com/dart-lang/ecosystem/wiki/Publishing-automation. From 4ab54dc3beaa6f9a44670d7bf018f2b14dc46972 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Feb 2023 12:37:19 -0800 Subject: [PATCH 099/127] Bump actions/checkout from 3.2.0 to 3.3.0 (dart-lang/json_rpc_2#91) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.2.0 to 3.3.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/755da8c3cf115ac066823e79a1e1788f8940201b...ac593985615ec2ede58e132d2e21d2b1cbd6127c) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/json_rpc_2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index 7450d9c1f..586c40639 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} @@ -49,7 +49,7 @@ jobs: os: [ubuntu-latest] sdk: [2.15.0, dev] steps: - - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} From 39e51df7adb37d2031ad8715d9665f82a72ab848 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Feb 2023 12:40:41 -0800 Subject: [PATCH 100/127] Bump dart-lang/setup-dart from 1.3 to 1.4 (dart-lang/json_rpc_2#90) Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.3 to 1.4. - [Release notes](https://github.com/dart-lang/setup-dart/releases) - [Changelog](https://github.com/dart-lang/setup-dart/blob/main/CHANGELOG.md) - [Commits](https://github.com/dart-lang/setup-dart/compare/6a218f2413a3e78e9087f638a238f6b40893203d...a57a6c04cf7d4840e88432aad6281d1e125f0d46) --- updated-dependencies: - dependency-name: dart-lang/setup-dart dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/json_rpc_2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index 586c40639..928afe31a 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [dev] steps: - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d + - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 with: sdk: ${{ matrix.sdk }} - id: install @@ -50,7 +50,7 @@ jobs: sdk: [2.15.0, dev] steps: - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d + - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 with: sdk: ${{ matrix.sdk }} - id: install From bdbec5f01af8081f996a454d6ca96bb8b3221fc6 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 28 Mar 2023 12:26:47 -0700 Subject: [PATCH 101/127] Fix analysis, bump min SDK to 2.19 (dart-lang/json_rpc_2#93) Use latest team lints --- .../.github/workflows/test-package.yml | 2 +- pkgs/json_rpc_2/CHANGELOG.md | 2 ++ pkgs/json_rpc_2/analysis_options.yaml | 16 ++++++++-------- pkgs/json_rpc_2/lib/src/client.dart | 6 +++--- pkgs/json_rpc_2/lib/src/exception.dart | 2 +- pkgs/json_rpc_2/lib/src/parameters.dart | 6 +++--- pkgs/json_rpc_2/lib/src/peer.dart | 6 +++--- pkgs/json_rpc_2/lib/src/server.dart | 9 +++++---- pkgs/json_rpc_2/lib/src/utils.dart | 2 +- pkgs/json_rpc_2/pubspec.yaml | 4 ++-- pkgs/json_rpc_2/test/client/utils.dart | 2 +- pkgs/json_rpc_2/test/server/utils.dart | 6 +++--- 12 files changed, 33 insertions(+), 30 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index 928afe31a..f8d94daae 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -47,7 +47,7 @@ jobs: matrix: # Add macos-latest and/or windows-latest if relevant for this package. os: [ubuntu-latest] - sdk: [2.15.0, dev] + sdk: [2.19.0, dev] steps: - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 77b1a85b6..751a8f81c 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,5 +1,7 @@ ## 3.0.3-dev +* Require Dart 2.19 + ## 3.0.2 * Switch to using `package:lints`. diff --git a/pkgs/json_rpc_2/analysis_options.yaml b/pkgs/json_rpc_2/analysis_options.yaml index 9ec640841..3339609c5 100644 --- a/pkgs/json_rpc_2/analysis_options.yaml +++ b/pkgs/json_rpc_2/analysis_options.yaml @@ -1,15 +1,15 @@ -include: package:lints/recommended.yaml +include: package:dart_flutter_team_lints/analysis_options.yaml + + +analyzer: + language: + strict-casts: false + errors: + avoid_dynamic_calls: ignore linter: rules: - - always_declare_return_types - # - avoid_dynamic_calls - avoid_unused_constructor_parameters - cancel_subscriptions - - directives_ordering - - omit_local_variable_types - package_api_docs - - prefer_single_quotes - test_types_in_equals - - throw_in_finally - - unawaited_futures diff --git a/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart index cc1ca59f2..a3637dd18 100644 --- a/pkgs/json_rpc_2/lib/src/client.dart +++ b/pkgs/json_rpc_2/lib/src/client.dart @@ -56,7 +56,7 @@ class Client { /// Creates a [Client] that communicates using decoded messages over /// [channel]. /// - /// Unlike [new Client], this doesn't read or write JSON strings. Instead, it + /// 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 @@ -113,7 +113,7 @@ class Client { /// /// 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, [parameters]) { + Future sendRequest(String method, [Object? parameters]) { var id = _id++; _send(method, parameters, id); @@ -134,7 +134,7 @@ class Client { /// send a response, it has no return value. /// /// Throws a [StateError] if the client is closed when this method is called. - void sendNotification(String method, [parameters]) => + void sendNotification(String method, [Object? parameters]) => _send(method, parameters); /// A helper method for [sendRequest] and [sendNotification]. diff --git a/pkgs/json_rpc_2/lib/src/exception.dart b/pkgs/json_rpc_2/lib/src/exception.dart index ad39ff233..3b9c0ec08 100644 --- a/pkgs/json_rpc_2/lib/src/exception.dart +++ b/pkgs/json_rpc_2/lib/src/exception.dart @@ -43,7 +43,7 @@ class RpcException implements Exception { /// Converts this exception into a JSON-serializable object that's a valid /// JSON-RPC 2.0 error response. - Map serialize(request) { + Map serialize(Object? request) { dynamic modifiedData; if (data is Map && !(data as Map).containsKey('request')) { modifiedData = Map.from(data as Map); diff --git a/pkgs/json_rpc_2/lib/src/parameters.dart b/pkgs/json_rpc_2/lib/src/parameters.dart index 7a80deacd..f747b98e3 100644 --- a/pkgs/json_rpc_2/lib/src/parameters.dart +++ b/pkgs/json_rpc_2/lib/src/parameters.dart @@ -43,7 +43,7 @@ class Parameters { /// doesn't exist. On the other hand, if it's accessed through a method with a /// default value like [Parameter.valueOr] or [Parameter.asNumOr], the default /// value will be returned. - Parameter operator [](key) { + Parameter operator [](Object? key) { if (key is int) { _assertPositional(); if (key < value.length) { @@ -156,7 +156,7 @@ class Parameter extends Parameters { : super(method, value); /// Returns [value], or [defaultValue] if this parameter wasn't passed. - dynamic valueOr(defaultValue) => value; + dynamic valueOr(Object? defaultValue) => value; /// Asserts that [value] exists and is a number and returns it. /// @@ -320,7 +320,7 @@ class _MissingParameter extends Parameter { : super._(method, null, parent, key); @override - dynamic valueOr(defaultValue) => defaultValue; + dynamic valueOr(Object? defaultValue) => defaultValue; @override num asNumOr(num defaultValue) => defaultValue; diff --git a/pkgs/json_rpc_2/lib/src/peer.dart b/pkgs/json_rpc_2/lib/src/peer.dart index a11cff255..002af31e3 100644 --- a/pkgs/json_rpc_2/lib/src/peer.dart +++ b/pkgs/json_rpc_2/lib/src/peer.dart @@ -68,7 +68,7 @@ class Peer implements Client, Server { /// Creates a [Peer] that communicates using decoded messages over [channel]. /// - /// Unlike [new Peer], this doesn't read or write JSON strings. Instead, it + /// 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 @@ -94,11 +94,11 @@ class Peer implements Client, Server { // Client methods. @override - Future sendRequest(String method, [parameters]) => + Future sendRequest(String method, [Object? parameters]) => _client.sendRequest(method, parameters); @override - void sendNotification(String method, [parameters]) => + void sendNotification(String method, [Object? parameters]) => _client.sendNotification(method, parameters); @override diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index 5cb293707..14ef02e6e 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -91,7 +91,7 @@ class Server { /// Creates a [Server] that communicates using decoded messages over /// [channel]. /// - /// Unlike [new Server], this doesn't read or write JSON strings. Instead, it + /// 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 @@ -193,9 +193,10 @@ class Server { } /// Handles an individual parsed request. - Future _handleSingleRequest(request) async { + Future _handleSingleRequest(Object? request) async { try { _validateRequest(request); + request = request as Map; var name = request['method']; var method = _methods[name]; @@ -220,13 +221,13 @@ class Server { } catch (error, stackTrace) { if (error is RpcException) { if (error.code == error_code.INVALID_REQUEST || - request.containsKey('id')) { + (request is Map && request.containsKey('id'))) { return error.serialize(request); } else { onUnhandledError?.call(error, stackTrace); return null; } - } else if (!request.containsKey('id')) { + } else if (request is Map && !request.containsKey('id')) { onUnhandledError?.call(error, stackTrace); return null; } diff --git a/pkgs/json_rpc_2/lib/src/utils.dart b/pkgs/json_rpc_2/lib/src/utils.dart index 305945c4b..c7cd10fcb 100644 --- a/pkgs/json_rpc_2/lib/src/utils.dart +++ b/pkgs/json_rpc_2/lib/src/utils.dart @@ -19,7 +19,7 @@ final _exceptionPrefix = RegExp(r'^([A-Z][a-zA-Z]*)?(Exception|Error): '); /// /// Many exceptions include the exception class name at the beginning of their /// [toString], so we remove that if it exists. -String getErrorMessage(error) => +String getErrorMessage(Object error) => error.toString().replaceFirst(_exceptionPrefix, ''); /// Like `try`/`finally`, run [body] and ensure that [whenComplete] runs diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index f301bac7b..53f5885eb 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -5,13 +5,13 @@ description: >- repository: https://github.com/dart-lang/json_rpc_2 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.19.0 <3.0.0" dependencies: stack_trace: ^1.10.0 stream_channel: ^2.1.0 dev_dependencies: - lints: '>=1.0.0 < 3.0.0' + dart_flutter_team_lints: ^1.0.0 test: ^1.16.0 web_socket_channel: ^2.0.0 diff --git a/pkgs/json_rpc_2/test/client/utils.dart b/pkgs/json_rpc_2/test/client/utils.dart index 45342cf36..acbfbe2b3 100644 --- a/pkgs/json_rpc_2/test/client/utils.dart +++ b/pkgs/json_rpc_2/test/client/utils.dart @@ -45,7 +45,7 @@ class ClientController { } /// Sends [response], a decoded response, to [client]. - void sendResponse(response) { + void sendResponse(Object? response) { sendJsonResponse(jsonEncode(response)); } diff --git a/pkgs/json_rpc_2/test/server/utils.dart b/pkgs/json_rpc_2/test/server/utils.dart index 481d34230..9fe6eed22 100644 --- a/pkgs/json_rpc_2/test/server/utils.dart +++ b/pkgs/json_rpc_2/test/server/utils.dart @@ -33,7 +33,7 @@ class ServerController { /// Passes [request], a decoded request, to [server] and returns its decoded /// response. - Future handleRequest(request) => + Future handleRequest(Object? request) => handleJsonRequest(jsonEncode(request)).then(jsonDecode); /// Passes [request], a JSON-encoded request, to [server] and returns its @@ -47,8 +47,8 @@ class ServerController { /// Expects that [controller]'s server will return an error response to /// [request] with the given [errorCode], [message], and [data]. void expectErrorResponse( - ServerController controller, request, int errorCode, String message, - {data}) { + ServerController controller, Object? request, int errorCode, String message, + {Object? data}) { dynamic id; if (request is Map) id = request['id']; data ??= {'request': request}; From 756395cc7873ccbc7ab7c203fff73876b1da5cc0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Apr 2023 14:35:34 -0700 Subject: [PATCH 102/127] Bump actions/checkout from 3.3.0 to 3.5.0 (dart-lang/json_rpc_2#95) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.3.0 to 3.5.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/ac593985615ec2ede58e132d2e21d2b1cbd6127c...8f4b7f84864484a7bf31766abe9204da3cbe65b3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/json_rpc_2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index f8d94daae..4d50da56c 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 with: sdk: ${{ matrix.sdk }} @@ -49,7 +49,7 @@ jobs: os: [ubuntu-latest] sdk: [2.19.0, dev] steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 with: sdk: ${{ matrix.sdk }} From cf7c51ffc8b999b4147fb58523eb0d815b72d503 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Apr 2023 14:39:01 -0700 Subject: [PATCH 103/127] Bump dart-lang/setup-dart from 1.4.0 to 1.5.0 (dart-lang/json_rpc_2#94) Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.4.0 to 1.5.0. - [Release notes](https://github.com/dart-lang/setup-dart/releases) - [Changelog](https://github.com/dart-lang/setup-dart/blob/main/CHANGELOG.md) - [Commits](https://github.com/dart-lang/setup-dart/compare/a57a6c04cf7d4840e88432aad6281d1e125f0d46...d6a63dab3335f427404425de0fbfed4686d93c4f) --- updated-dependencies: - dependency-name: dart-lang/setup-dart dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/json_rpc_2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index 4d50da56c..cdda2d399 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [dev] steps: - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 - - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 + - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} - id: install @@ -50,7 +50,7 @@ jobs: sdk: [2.19.0, dev] steps: - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 - - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 + - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} - id: install From 75f0565844e2dde837ccf755557cd465a0273145 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 May 2023 12:22:05 -0700 Subject: [PATCH 104/127] Bump actions/checkout from 3.5.0 to 3.5.2 (dart-lang/json_rpc_2#96) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.0 to 3.5.2. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/8f4b7f84864484a7bf31766abe9204da3cbe65b3...8e5e7e5ab8b370d6c329ec480221332ada57f0ab) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/json_rpc_2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index cdda2d399..310e9aae1 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} @@ -49,7 +49,7 @@ jobs: os: [ubuntu-latest] sdk: [2.19.0, dev] steps: - - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} From ed350459c21fade0eb7edf9b1f8e21bde00deef5 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Mon, 22 May 2023 09:21:11 -0700 Subject: [PATCH 105/127] blast_repo fixes (dart-lang/json_rpc_2#97) dependabot --- pkgs/json_rpc_2/.github/dependabot.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkgs/json_rpc_2/.github/dependabot.yml b/pkgs/json_rpc_2/.github/dependabot.yml index 1603cdd9e..725f03af2 100644 --- a/pkgs/json_rpc_2/.github/dependabot.yml +++ b/pkgs/json_rpc_2/.github/dependabot.yml @@ -3,7 +3,9 @@ version: 2 updates: - - package-ecosystem: "github-actions" - directory: "/" + - package-ecosystem: github-actions + directory: / schedule: - interval: "monthly" + interval: monthly + labels: + - autosubmit From b4a0be8a461eed03e881898de26531f4115e3488 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Tue, 23 May 2023 09:30:43 -0700 Subject: [PATCH 106/127] blast_repo fixes (dart-lang/json_rpc_2#98) no-response --- .../.github/workflows/no-response.yml | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 pkgs/json_rpc_2/.github/workflows/no-response.yml diff --git a/pkgs/json_rpc_2/.github/workflows/no-response.yml b/pkgs/json_rpc_2/.github/workflows/no-response.yml new file mode 100644 index 000000000..8e5ed57cd --- /dev/null +++ b/pkgs/json_rpc_2/.github/workflows/no-response.yml @@ -0,0 +1,37 @@ +# A workflow to close issues where the author hasn't responded to a request for +# more information; see https://github.com/actions/stale. + +name: No Response + +# Run as a daily cron. +on: + schedule: + # Every day at 8am + - cron: '0 8 * * *' + +# All permissions not specified are set to 'none'. +permissions: + issues: write + pull-requests: write + +jobs: + no-response: + runs-on: ubuntu-latest + if: ${{ github.repository_owner == 'dart-lang' }} + steps: + - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 + with: + # Don't automatically mark inactive issues+PRs as stale. + days-before-stale: -1 + # Close needs-info issues and PRs after 14 days of inactivity. + days-before-close: 14 + stale-issue-label: "needs-info" + close-issue-message: > + Without additional information we're not able to resolve this issue. + Feel free to add more info or respond to any questions above and we + can reopen the case. Thanks for your contribution! + stale-pr-label: "needs-info" + close-pr-message: > + Without additional information we're not able to resolve this PR. + Feel free to add more info or respond to any questions above. + Thanks for your contribution! From 4e2db2e8b81b510490054fe62340f9c6202dd697 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Jul 2023 20:21:09 +0000 Subject: [PATCH 107/127] Bump actions/checkout from 3.5.2 to 3.5.3 (dart-lang/json_rpc_2#99) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.2 to 3.5.3.
Release notes

Sourced from actions/checkout's releases.

v3.5.3

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v3...v3.5.3

Changelog

Sourced from actions/checkout's changelog.

Changelog

v3.5.3

v3.5.2

v3.5.1

v3.5.0

v3.4.0

v3.3.0

v3.2.0

v3.1.0

v3.0.2

v3.0.1

v3.0.0

v2.3.1

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=3.5.2&new-version=3.5.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/json_rpc_2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index 310e9aae1..9cc767377 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} @@ -49,7 +49,7 @@ jobs: os: [ubuntu-latest] sdk: [2.19.0, dev] steps: - - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} From 4f2b114ff2c67613b2c29a74f8de7cbacd360c4e Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Wed, 5 Jul 2023 17:59:39 -0700 Subject: [PATCH 108/127] Fix the example to act as a server (dart-lang/json_rpc_2#100) Fixes a bug mentioned in dart-lang/json_rpc_2#50 Both the client and server examples were shown with `WebSocketChannel.connect`. Update the server to bind the `HttpServer` instead of trying to connect to a server that hasn't been started. Add a `client.close()` call to the example so the VM does not continue running. --- pkgs/json_rpc_2/README.md | 14 ++++++++++++-- pkgs/json_rpc_2/example/client.dart | 2 ++ pkgs/json_rpc_2/example/main.dart | 12 ++++++++++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/pkgs/json_rpc_2/README.md b/pkgs/json_rpc_2/README.md index e95a2e8ea..8ccfc57a4 100644 --- a/pkgs/json_rpc_2/README.md +++ b/pkgs/json_rpc_2/README.md @@ -12,12 +12,20 @@ A JSON-RPC 2.0 server exposes a set of methods that can be called by clients. These methods can be registered using `Server.registerMethod`: ```dart +import 'dart:io'; + import 'package:json_rpc_2/json_rpc_2.dart'; +import 'package:web_socket_channel/io.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; -void main() { - var socket = WebSocketChannel.connect(Uri.parse('ws://localhost:4321')); +void main() async { + var httpServer = await HttpServer.bind(InternetAddress.loopbackIPv4, 4321); + var connectedChannels = + httpServer.transform(WebSocketTransformer()).map(IOWebSocketChannel.new); + connectedChannels.listen(handleClient); +} +void handleClient(WebSocketChannel socket) { // The socket is a `StreamChannel` because it might emit binary // `List`, but JSON RPC 2 only works with Strings so we assert it only // emits those by casting it. @@ -122,6 +130,8 @@ void main() async { } on RpcException catch (error) { print('RPC error ${error.code}: ${error.message}'); } + + await client.close(); } ``` diff --git a/pkgs/json_rpc_2/example/client.dart b/pkgs/json_rpc_2/example/client.dart index d23be94ca..aa8f7ed54 100644 --- a/pkgs/json_rpc_2/example/client.dart +++ b/pkgs/json_rpc_2/example/client.dart @@ -38,4 +38,6 @@ void main() async { } on RpcException catch (error) { print('RPC error ${error.code}: ${error.message}'); } + + await client.close(); } diff --git a/pkgs/json_rpc_2/example/main.dart b/pkgs/json_rpc_2/example/main.dart index fc6042e71..7d5ab7331 100644 --- a/pkgs/json_rpc_2/example/main.dart +++ b/pkgs/json_rpc_2/example/main.dart @@ -2,12 +2,20 @@ // 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. +import 'dart:io'; + import 'package:json_rpc_2/json_rpc_2.dart'; +import 'package:web_socket_channel/io.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; -void main() { - var socket = WebSocketChannel.connect(Uri.parse('ws://localhost:4321')); +void main() async { + var httpServer = await HttpServer.bind(InternetAddress.loopbackIPv4, 4321); + var connectedChannels = + httpServer.transform(WebSocketTransformer()).map(IOWebSocketChannel.new); + connectedChannels.listen(handleClient); +} +void handleClient(WebSocketChannel socket) { // The socket is a `StreamChannel` because it might emit binary // `List`, but JSON RPC 2 only works with Strings so we assert it only // emits those by casting it. From 0512cd97e31480206435e9774025e5e68f01e1ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Sep 2023 20:58:49 +0000 Subject: [PATCH 109/127] Bump actions/checkout from 3.5.3 to 3.6.0 (dart-lang/json_rpc_2#101) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.3 to 3.6.0.
Release notes

Sourced from actions/checkout's releases.

v3.6.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v3.5.3...v3.6.0

Changelog

Sourced from actions/checkout's changelog.

Changelog

v3.6.0

v3.5.3

v3.5.2

v3.5.1

v3.5.0

v3.4.0

v3.3.0

v3.2.0

v3.1.0

v3.0.2

v3.0.1

v3.0.0

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=3.5.3&new-version=3.6.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/json_rpc_2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index 9cc767377..90e3d7b70 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} @@ -49,7 +49,7 @@ jobs: os: [ubuntu-latest] sdk: [2.19.0, dev] steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} From a44c18957218ed865588ff2ee2e1c7dd1576838a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 20:44:03 +0000 Subject: [PATCH 110/127] Bump dart-lang/setup-dart from 1.5.0 to 1.5.1 (dart-lang/json_rpc_2#103) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.5.0 to 1.5.1.
Release notes

Sourced from dart-lang/setup-dart's releases.

v1.5.1

  • No longer test the setup-dart action on pre-2.12 SDKs.
  • Upgrade JS interop code to use extension types (the new name for inline classes).
  • The upcoming rename of the be channel to main is now supported with forward compatibility that switches when the rename happens.
Changelog

Sourced from dart-lang/setup-dart's changelog.

v1.6.0

  • Enable provisioning of the latest Dart SDK patch release by specifying just the major and minor version (e.g. 3.2).

v1.5.1

  • No longer test the setup-dart action on pre-2.12 SDKs.
  • Upgrade JS interop code to use extension types (the new name for inline classes).
  • The upcoming rename of the be channel to main is now supported with forward compatibility that switches when the rename happens.

v1.5.0

  • Re-wrote the implementation of the action into Dart.
  • Auto-detect the platform architecture (x64, ia32, arm, arm64).
  • Improved the caching and download resilience of the sdk.
  • Added a new action output: dart-version - the installed version of the sdk.

v1.4.0

  • Automatically create OIDC token for pub.dev.
  • Add a reusable workflow for publishing.

v1.3.0

  • The install location of the Dart SDK is now available in an environment variable, DART_HOME (dart-lang/json_rpc_2#43).
  • Fixed an issue where cached downloads could lead to unzip issues on self-hosted runners (dart-lang/json_rpc_2#35).

v1.2.0

  • Fixed a path issue impacting git dependencies on Windows.

v1.1.0

  • Added a flavor option setup.sh to allow downloading unpublished builds.

v1.0.0

  • Promoted to 1.0 stable.

v0.5

  • Fixed a Windows pub global activate path issue.

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dart-lang/setup-dart&package-manager=github_actions&previous-version=1.5.0&new-version=1.5.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/json_rpc_2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index 90e3d7b70..6290a7888 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [dev] steps: - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f + - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: sdk: ${{ matrix.sdk }} - id: install @@ -50,7 +50,7 @@ jobs: sdk: [2.19.0, dev] steps: - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f + - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: sdk: ${{ matrix.sdk }} - id: install From cf6d473b7e942a6a2dad084a02d79728c96fdfc4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Oct 2023 18:25:18 +0000 Subject: [PATCH 111/127] Bump actions/checkout from 3.6.0 to 4.1.0 (dart-lang/json_rpc_2#104) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 3.6.0 to 4.1.0.
Release notes

Sourced from actions/checkout's releases.

v4.1.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v4.0.0...v4.1.0

v4.0.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v3...v4.0.0

Changelog

Sourced from actions/checkout's changelog.

Changelog

v4.1.0

v4.0.0

v3.6.0

v3.5.3

v3.5.2

v3.5.1

v3.5.0

v3.4.0

v3.3.0

v3.2.0

v3.1.0

v3.0.2

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=3.6.0&new-version=4.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/json_rpc_2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index 6290a7888..1cf12fb3d 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: sdk: ${{ matrix.sdk }} @@ -49,7 +49,7 @@ jobs: os: [ubuntu-latest] sdk: [2.19.0, dev] steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: sdk: ${{ matrix.sdk }} From 9be1cf2d9c29e71e704bc568ffe7c3f9da819833 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Nov 2023 20:56:22 +0000 Subject: [PATCH 112/127] Bump actions/checkout from 4.1.0 to 4.1.1 (dart-lang/json_rpc_2#106) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.0 to 4.1.1.
Release notes

Sourced from actions/checkout's releases.

v4.1.1

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v4.1.0...v4.1.1

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.0&new-version=4.1.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/json_rpc_2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index 1cf12fb3d..b6ab9cbfe 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: sdk: ${{ matrix.sdk }} @@ -49,7 +49,7 @@ jobs: os: [ubuntu-latest] sdk: [2.19.0, dev] steps: - - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: sdk: ${{ matrix.sdk }} From e8c4ff93132d1a9fb10cc11a99b11618d6780ed5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Nov 2023 20:58:22 +0000 Subject: [PATCH 113/127] Bump dart-lang/setup-dart from 1.5.1 to 1.6.0 (dart-lang/json_rpc_2#105) Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.5.1 to 1.6.0.
Release notes

Sourced from dart-lang/setup-dart's releases.

v1.6.0

  • Enable provisioning of the latest Dart SDK patch release by specifying just the major and minor version (e.g. 3.2).
Changelog

Sourced from dart-lang/setup-dart's changelog.

v1.6.0

  • Enable provisioning of the latest Dart SDK patch release by specifying just the major and minor version (e.g. 3.2).

v1.5.1

  • No longer test the setup-dart action on pre-2.12 SDKs.
  • Upgrade JS interop code to use extension types (the new name for inline classes).
  • The upcoming rename of the be channel to main is now supported with forward compatibility that switches when the rename happens.

v1.5.0

  • Re-wrote the implementation of the action into Dart.
  • Auto-detect the platform architecture (x64, ia32, arm, arm64).
  • Improved the caching and download resilience of the sdk.
  • Added a new action output: dart-version - the installed version of the sdk.

v1.4.0

  • Automatically create OIDC token for pub.dev.
  • Add a reusable workflow for publishing.

v1.3.0

  • The install location of the Dart SDK is now available in an environment variable, DART_HOME (dart-lang/json_rpc_2#43).
  • Fixed an issue where cached downloads could lead to unzip issues on self-hosted runners (dart-lang/json_rpc_2#35).

v1.2.0

  • Fixed a path issue impacting git dependencies on Windows.

v1.1.0

  • Added a flavor option setup.sh to allow downloading unpublished builds.

v1.0.0

  • Promoted to 1.0 stable.

v0.5

  • Fixed a Windows pub global activate path issue.

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dart-lang/setup-dart&package-manager=github_actions&previous-version=1.5.1&new-version=1.6.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/json_rpc_2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index b6ab9cbfe..a3cc511e6 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [dev] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 + - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d with: sdk: ${{ matrix.sdk }} - id: install @@ -50,7 +50,7 @@ jobs: sdk: [2.19.0, dev] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 + - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d with: sdk: ${{ matrix.sdk }} - id: install From e1d799fdc1f6a2b48820dae409d510fb784de231 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 20:34:17 +0000 Subject: [PATCH 114/127] Bump actions/stale from 8.0.0 to 9.0.0 (dart-lang/json_rpc_2#108) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/stale](https://github.com/actions/stale) from 8.0.0 to 9.0.0.
Release notes

Sourced from actions/stale's releases.

v9.0.0

Breaking Changes

  1. Action is now stateful: If the action ends because of operations-per-run then the next run will start from the first unprocessed issue skipping the issues processed during the previous run(s). The state is reset when all the issues are processed. This should be considered for scheduling workflow runs.
  2. Version 9 of this action updated the runtime to Node.js 20. All scripts are now run with Node.js 20 instead of Node.js 16 and are affected by any breaking changes between Node.js 16 and 20.

What Else Changed

  1. Performance optimization that removes unnecessary API calls by @​dsame dart-lang/json_rpc_2#1033 fixes dart-lang/json_rpc_2#792
  2. Logs displaying current github API rate limit by @​dsame dart-lang/json_rpc_2#1032 addresses dart-lang/json_rpc_2#1029

For more information, please read the action documentation and its section about statefulness

New Contributors

Full Changelog: https://github.com/actions/stale/compare/v8...v9.0.0

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/stale&package-manager=github_actions&previous-version=8.0.0&new-version=9.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/json_rpc_2/.github/workflows/no-response.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/json_rpc_2/.github/workflows/no-response.yml b/pkgs/json_rpc_2/.github/workflows/no-response.yml index 8e5ed57cd..ab1ac4984 100644 --- a/pkgs/json_rpc_2/.github/workflows/no-response.yml +++ b/pkgs/json_rpc_2/.github/workflows/no-response.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest if: ${{ github.repository_owner == 'dart-lang' }} steps: - - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 + - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e with: # Don't automatically mark inactive issues+PRs as stale. days-before-stale: -1 From 964f524c1f999d4d37d0f793eeb6ba95ee32f248 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 20:10:05 +0000 Subject: [PATCH 115/127] Bump dart-lang/setup-dart from 1.6.0 to 1.6.2 (dart-lang/json_rpc_2#109) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.6.0 to 1.6.2.
Release notes

Sourced from dart-lang/setup-dart's releases.

v1.6.2

v1.6.1

  • Updated the google storage url for main channel releases.
Changelog

Sourced from dart-lang/setup-dart's changelog.

v1.6.2

v1.6.1

  • Updated the google storage url for main channel releases.

v1.6.0

  • Enable provisioning of the latest Dart SDK patch release by specifying just the major and minor version (e.g. 3.2).

v1.5.1

  • No longer test the setup-dart action on pre-2.12 SDKs.
  • Upgrade JS interop code to use extension types (the new name for inline classes).
  • The upcoming rename of the be channel to main is now supported with forward compatibility that switches when the rename happens.

v1.5.0

  • Re-wrote the implementation of the action into Dart.
  • Auto-detect the platform architecture (x64, ia32, arm, arm64).
  • Improved the caching and download resilience of the sdk.
  • Added a new action output: dart-version - the installed version of the sdk.

v1.4.0

  • Automatically create OIDC token for pub.dev.
  • Add a reusable workflow for publishing.

v1.3.0

  • The install location of the Dart SDK is now available in an environment variable, DART_HOME (dart-lang/json_rpc_2#43).
  • Fixed an issue where cached downloads could lead to unzip issues on self-hosted runners (dart-lang/json_rpc_2#35).

v1.2.0

  • Fixed a path issue impacting git dependencies on Windows.

v1.1.0

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dart-lang/setup-dart&package-manager=github_actions&previous-version=1.6.0&new-version=1.6.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/json_rpc_2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index a3cc511e6..04c07a67f 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [dev] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d + - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 with: sdk: ${{ matrix.sdk }} - id: install @@ -50,7 +50,7 @@ jobs: sdk: [2.19.0, dev] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d + - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 with: sdk: ${{ matrix.sdk }} - id: install From b56b1b439f84b7ef8ed1b3c43c2728e2620c43f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 20:33:35 +0000 Subject: [PATCH 116/127] Bump actions/checkout from 4.1.1 to 4.1.2 (dart-lang/json_rpc_2#110) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.1 to 4.1.2.
Release notes

Sourced from actions/checkout's releases.

v4.1.2

We are investigating the following issue with this release and have rolled-back the v4 tag to point to v4.1.1

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v4.1.1...v4.1.2

Changelog

Sourced from actions/checkout's changelog.

Changelog

v4.1.2

v4.1.1

v4.1.0

v4.0.0

v3.6.0

v3.5.3

v3.5.2

v3.5.1

v3.5.0

v3.4.0

v3.3.0

v3.2.0

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.1&new-version=4.1.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/json_rpc_2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index 04c07a67f..8959ce53a 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 with: sdk: ${{ matrix.sdk }} @@ -49,7 +49,7 @@ jobs: os: [ubuntu-latest] sdk: [2.19.0, dev] steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 with: sdk: ${{ matrix.sdk }} From e4925fae9b51b93d2a0559f90def74a2a4819304 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 20:42:04 +0000 Subject: [PATCH 117/127] Bump dart-lang/setup-dart from 1.6.2 to 1.6.4 (dart-lang/json_rpc_2#112) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.6.2 to 1.6.4.
Release notes

Sourced from dart-lang/setup-dart's releases.

v1.6.4

  • Rebuild JS code to include changes from v1.6.3

v1.6.3

Changelog

Sourced from dart-lang/setup-dart's changelog.

v1.6.4

  • Rebuild JS code.

v1.6.3

v1.6.2

v1.6.1

  • Updated the google storage url for main channel releases.

v1.6.0

  • Enable provisioning of the latest Dart SDK patch release by specifying just the major and minor version (e.g. 3.2).

v1.5.1

  • No longer test the setup-dart action on pre-2.12 SDKs.
  • Upgrade JS interop code to use extension types (the new name for inline classes).
  • The upcoming rename of the be channel to main is now supported with forward compatibility that switches when the rename happens.

v1.5.0

  • Re-wrote the implementation of the action into Dart.
  • Auto-detect the platform architecture (x64, ia32, arm, arm64).
  • Improved the caching and download resilience of the sdk.
  • Added a new action output: dart-version - the installed version of the sdk.

v1.4.0

  • Automatically create OIDC token for pub.dev.
  • Add a reusable workflow for publishing.

v1.3.0

  • The install location of the Dart SDK is now available

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dart-lang/setup-dart&package-manager=github_actions&previous-version=1.6.2&new-version=1.6.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/json_rpc_2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index 8959ce53a..2d09a3ff7 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [dev] steps: - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 + - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} - id: install @@ -50,7 +50,7 @@ jobs: sdk: [2.19.0, dev] steps: - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 + - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} - id: install From b101b4e1a354361539611a82047341ce859cf229 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 03:00:14 +0000 Subject: [PATCH 118/127] Bump actions/checkout from 4.1.2 to 4.1.4 (dart-lang/json_rpc_2#111) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.2 to 4.1.4.
Release notes

Sourced from actions/checkout's releases.

v4.1.4

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v4.1.3...v4.1.4

v4.1.3

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v4.1.2...v4.1.3

Changelog

Sourced from actions/checkout's changelog.

Changelog

v4.1.4

v4.1.3

v4.1.2

v4.1.1

v4.1.0

v4.0.0

v3.6.0

v3.5.3

v3.5.2

v3.5.1

v3.5.0

v3.4.0

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.2&new-version=4.1.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/json_rpc_2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index 2d09a3ff7..634d75d48 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} @@ -49,7 +49,7 @@ jobs: os: [ubuntu-latest] sdk: [2.19.0, dev] steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} From 4630061f66d38a233d64b0c4b8762129e741c80a Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Wed, 8 May 2024 10:17:13 -0700 Subject: [PATCH 119/127] blast_repo fixes (dart-lang/json_rpc_2#113) dependabot --- pkgs/json_rpc_2/.github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkgs/json_rpc_2/.github/dependabot.yml b/pkgs/json_rpc_2/.github/dependabot.yml index 725f03af2..cde02ad6a 100644 --- a/pkgs/json_rpc_2/.github/dependabot.yml +++ b/pkgs/json_rpc_2/.github/dependabot.yml @@ -9,3 +9,7 @@ updates: interval: monthly labels: - autosubmit + groups: + github-actions: + patterns: + - "*" From 5218254cf112daf7e02e252d6b52304691adb045 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 May 2024 17:18:50 +0000 Subject: [PATCH 120/127] Bump actions/checkout from 4.1.4 to 4.1.5 in the github-actions group (dart-lang/json_rpc_2#114) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 4.1.4 to 4.1.5
Release notes

Sourced from actions/checkout's releases.

v4.1.5

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v4.1.4...v4.1.5

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.4&new-version=4.1.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
--- pkgs/json_rpc_2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index 634d75d48..228b591d7 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} @@ -49,7 +49,7 @@ jobs: os: [ubuntu-latest] sdk: [2.19.0, dev] steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} From fa30a8dd5aff90cadfbc1a7b6f0c76ce5c440736 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Jun 2024 20:59:14 +0000 Subject: [PATCH 121/127] Bump actions/checkout from 4.1.5 to 4.1.6 in the github-actions group (dart-lang/json_rpc_2#115) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 4.1.5 to 4.1.6
Release notes

Sourced from actions/checkout's releases.

v4.1.6

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v4.1.5...v4.1.6

Changelog

Sourced from actions/checkout's changelog.

Changelog

v4.1.6

v4.1.5

v4.1.4

v4.1.3

v4.1.2

v4.1.1

v4.1.0

v4.0.0

v3.6.0

v3.5.3

v3.5.2

v3.5.1

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.5&new-version=4.1.6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
--- pkgs/json_rpc_2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index 228b591d7..0978fe284 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} @@ -49,7 +49,7 @@ jobs: os: [ubuntu-latest] sdk: [2.19.0, dev] steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} From cf5aaa85026d9897e648ec05ace51170a5d35111 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Wed, 26 Jun 2024 12:31:33 -0700 Subject: [PATCH 122/127] Update lints, test JS & Wasm (dart-lang/json_rpc_2#116) * Update lints, test JS & Wasm * more bumps * require dart 3.4 --- .../.github/workflows/test-package.yml | 5 +++- pkgs/json_rpc_2/CHANGELOG.md | 4 +-- pkgs/json_rpc_2/analysis_options.yaml | 1 - pkgs/json_rpc_2/lib/error_code.dart | 30 ++++++++----------- pkgs/json_rpc_2/lib/src/client.dart | 26 ++++++++-------- pkgs/json_rpc_2/lib/src/exception.dart | 6 ++-- pkgs/json_rpc_2/lib/src/parameters.dart | 24 +++++++-------- pkgs/json_rpc_2/lib/src/peer.dart | 14 ++++----- pkgs/json_rpc_2/lib/src/server.dart | 21 +++++++------ pkgs/json_rpc_2/lib/src/utils.dart | 6 ++-- pkgs/json_rpc_2/pubspec.yaml | 10 +++---- pkgs/json_rpc_2/test/client/client_test.dart | 14 ++++----- pkgs/json_rpc_2/test/client/utils.dart | 4 +-- pkgs/json_rpc_2/test/peer_test.dart | 6 ++-- pkgs/json_rpc_2/test/server/batch_test.dart | 5 ++-- pkgs/json_rpc_2/test/server/server_test.dart | 28 +++++++++++------ pkgs/json_rpc_2/test/server/stream_test.dart | 2 +- pkgs/json_rpc_2/test/server/utils.dart | 2 +- 18 files changed, 109 insertions(+), 99 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index 0978fe284..c8e65b618 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -47,7 +47,7 @@ jobs: 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 @@ jobs: - 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/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 751a8f81c..aec82015f 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/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/pkgs/json_rpc_2/analysis_options.yaml b/pkgs/json_rpc_2/analysis_options.yaml index 3339609c5..873cf41fd 100644 --- a/pkgs/json_rpc_2/analysis_options.yaml +++ b/pkgs/json_rpc_2/analysis_options.yaml @@ -12,4 +12,3 @@ linter: - avoid_unused_constructor_parameters - cancel_subscriptions - package_api_docs - - test_types_in_equals diff --git a/pkgs/json_rpc_2/lib/error_code.dart b/pkgs/json_rpc_2/lib/error_code.dart index 14e0543d6..5f907917d 100644 --- a/pkgs/json_rpc_2/lib/error_code.dart +++ b/pkgs/json_rpc_2/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 @@ const SERVER_ERROR = -32000; /// 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/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart index a3637dd18..182f94584 100644 --- a/pkgs/json_rpc_2/lib/src/client.dart +++ b/pkgs/json_rpc_2/lib/src/client.dart @@ -47,19 +47,19 @@ class Client { /// 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 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 @@ class Client { /// /// [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 @@ class Client { /// /// 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 sendRequest(String method, [Object? parameters]) { var id = _id++; _send(method, parameters, id); - var completer = Completer.sync(); + var completer = Completer.sync(); _pendingRequests[id] = _Request(method, completer, Chain.current()); return completer.future; } @@ -141,7 +142,7 @@ class Client { /// /// 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 @@ class Client { /// 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 Function() callback) { if (_batch != null) { callback(); return; @@ -186,7 +187,7 @@ class Client { } /// 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 @@ class Client { /// 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 @@ class Client { } /// 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/pkgs/json_rpc_2/lib/src/exception.dart b/pkgs/json_rpc_2/lib/src/exception.dart index 3b9c0ec08..906a0534b 100644 --- a/pkgs/json_rpc_2/lib/src/exception.dart +++ b/pkgs/json_rpc_2/lib/src/exception.dart @@ -46,8 +46,10 @@ class RpcException implements Exception { Map 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/pkgs/json_rpc_2/lib/src/parameters.dart b/pkgs/json_rpc_2/lib/src/parameters.dart index f747b98e3..0a188828c 100644 --- a/pkgs/json_rpc_2/lib/src/parameters.dart +++ b/pkgs/json_rpc_2/lib/src/parameters.dart @@ -109,8 +109,8 @@ class Parameter extends Parameters { // 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 @@ class Parameter extends Parameters { 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 @@ class Parameter extends Parameters { /// 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 @@ class Parameter extends Parameters { /// 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 @@ class Parameter extends Parameters { '"$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 @@ class _MissingParameter extends Parameter { @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/pkgs/json_rpc_2/lib/src/peer.dart b/pkgs/json_rpc_2/lib/src/peer.dart index 002af31e3..677b6e15f 100644 --- a/pkgs/json_rpc_2/lib/src/peer.dart +++ b/pkgs/json_rpc_2/lib/src/peer.dart @@ -29,11 +29,11 @@ class Peer implements Client, Server { /// A stream controller that forwards incoming messages to [_server] if /// they're requests. - final _serverIncomingForwarder = StreamController(sync: true); + final _serverIncomingForwarder = StreamController(sync: true); /// A stream controller that forwards incoming messages to [_client] if /// they're responses. - final _clientIncomingForwarder = StreamController(sync: true); + final _clientIncomingForwarder = StreamController(sync: true); @override late final Future done = Future.wait([_client.done, _server.done]); @@ -66,12 +66,12 @@ class Peer implements Client, Server { 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 @@ class Peer implements Client, Server { _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 @@ class Peer implements Client, Server { _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 @@ class Peer implements Client, Server { // 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/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart index 14ef02e6e..2c58b7943 100644 --- a/pkgs/json_rpc_2/lib/src/server.dart +++ b/pkgs/json_rpc_2/lib/src/server.dart @@ -21,8 +21,7 @@ typedef ErrorCallback = void Function(dynamic error, dynamic stackTrace); /// /// 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 @@ class Server { /// 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 @@ class Server { 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 @@ class Server { /// /// [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 @@ class Server { /// 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 @@ class Server { /// [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 @@ class Server { } /// 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/pkgs/json_rpc_2/lib/src/utils.dart b/pkgs/json_rpc_2/lib/src/utils.dart index c7cd10fcb..28bbf21ee 100644 --- a/pkgs/json_rpc_2/lib/src/utils.dart +++ b/pkgs/json_rpc_2/lib/src/utils.dart @@ -9,7 +9,7 @@ import 'package:stream_channel/stream_channel.dart'; 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 @@ final _exceptionPrefix = RegExp(r'^([A-Z][a-zA-Z]*)?(Exception|Error): '); /// 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 @@ String getErrorMessage(Object error) => /// /// 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/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 53f5885eb..ae3ee946c 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/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/pkgs/json_rpc_2/test/client/client_test.dart b/pkgs/json_rpc_2/test/client/client_test.dart index df9c03356..1a4f65d08 100644 --- a/pkgs/json_rpc_2/test/client/client_test.dart +++ b/pkgs/json_rpc_2/test/client/client_test.dart @@ -89,7 +89,7 @@ void main() { test('sends a synchronous batch of requests', () { controller.expectRequest((request) { - expect(request, TypeMatcher()); + expect(request, isA()); expect(request, hasLength(3)); expect(request[0], equals({'jsonrpc': '2.0', 'method': 'foo'})); expect( @@ -121,7 +121,7 @@ void main() { test('sends an asynchronous batch of requests', () { controller.expectRequest((request) { - expect(request, TypeMatcher()); + expect(request, isA()); expect(request, hasLength(3)); expect(request[0], equals({'jsonrpc': '2.0', 'method': 'foo'})); expect( @@ -143,14 +143,12 @@ void main() { }); controller.client.withBatch(() { - return Future.value().then((_) { + return Future.value().then((_) { controller.client.sendNotification('foo'); - return Future.value(); - }).then((_) { + }).then((_) { expect(controller.client.sendRequest('bar', {'param': 'value'}), completion(equals('bar response'))); - return Future.value(); - }).then((_) { + }).then((_) { expect(controller.client.sendRequest('baz'), completion(equals('baz response'))); }); @@ -177,7 +175,7 @@ void main() { expect( controller.client.sendRequest('foo', {'param': 'value'}), - throwsA(TypeMatcher() + throwsA(isA() .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/pkgs/json_rpc_2/test/client/utils.dart b/pkgs/json_rpc_2/test/client/utils.dart index acbfbe2b3..38e187f28 100644 --- a/pkgs/json_rpc_2/test/client/utils.dart +++ b/pkgs/json_rpc_2/test/client/utils.dart @@ -32,7 +32,7 @@ class ClientController { /// 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 @@ class ClientController { 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/pkgs/json_rpc_2/test/peer_test.dart b/pkgs/json_rpc_2/test/peer_test.dart index 4b4c44a89..0df605619 100644 --- a/pkgs/json_rpc_2/test/peer_test.dart +++ b/pkgs/json_rpc_2/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 @@ void main() { }); test('requests terminates when the channel is closed', () async { - var incomingController = StreamController(); + var incomingController = StreamController(); var channel = StreamChannel.withGuarantees( incomingController.stream, - StreamController(), + StreamController(), ); var peer = json_rpc.Peer.withoutJson(channel); unawaited(peer.listen()); diff --git a/pkgs/json_rpc_2/test/server/batch_test.dart b/pkgs/json_rpc_2/test/server/batch_test.dart index b8b8fea80..af883c48e 100644 --- a/pkgs/json_rpc_2/test/server/batch_test.dart +++ b/pkgs/json_rpc_2/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 @@ void main() { 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/pkgs/json_rpc_2/test/server/server_test.dart b/pkgs/json_rpc_2/test/server/server_test.dart index 10f2678d8..b3166ceb2 100644 --- a/pkgs/json_rpc_2/test/server/server_test.dart +++ b/pkgs/json_rpc_2/test/server/server_test.dart @@ -16,7 +16,7 @@ void main() { 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 @@ void main() { expectErrorResponse( controller, - {'jsonrpc': '2.0', 'method': 'foo', 'params': {}, 'id': 1234}, + { + 'jsonrpc': '2.0', + 'method': 'foo', + 'params': {}, + '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 @@ void main() { 'data': { 'request': {'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}, 'full': 'FormatException: bad format', - 'stack': TypeMatcher() + 'stack': isA() } } })); @@ -90,8 +95,8 @@ void main() { controller.server.registerMethod('foo', (args) => 'result'); expect( - controller - .handleRequest({'jsonrpc': '2.0', 'method': 'foo', 'params': {}}), + controller.handleRequest( + {'jsonrpc': '2.0', 'method': 'foo', 'params': {}}), doesNotComplete); }); @@ -102,7 +107,12 @@ void main() { expectErrorResponse( controller, - {'jsonrpc': '2.0', 'method': 'foo', 'params': {}, 'id': 1234}, + { + 'jsonrpc': '2.0', + 'method': 'foo', + 'params': {}, + 'id': 1234 + }, 5, 'Error message.', data: 'data value'); @@ -164,7 +174,7 @@ void main() { 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 @@ void main() { 'data': { 'request': {'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}, 'full': 'FormatException: bad format', - 'stack': TypeMatcher() + 'stack': isA() } } })); diff --git a/pkgs/json_rpc_2/test/server/stream_test.dart b/pkgs/json_rpc_2/test/server/stream_test.dart index 2f95150fd..832e13c7a 100644 --- a/pkgs/json_rpc_2/test/server/stream_test.dart +++ b/pkgs/json_rpc_2/test/server/stream_test.dart @@ -23,7 +23,7 @@ void main() { 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/pkgs/json_rpc_2/test/server/utils.dart b/pkgs/json_rpc_2/test/server/utils.dart index 9fe6eed22..c94628e50 100644 --- a/pkgs/json_rpc_2/test/server/utils.dart +++ b/pkgs/json_rpc_2/test/server/utils.dart @@ -65,6 +65,6 @@ void expectErrorResponse( /// Returns a matcher that matches a [json_rpc.RpcException] with an /// `invalid_params` error code. Matcher throwsInvalidParams(String message) => - throwsA(TypeMatcher() + throwsA(isA() .having((e) => e.code, 'code', error_code.INVALID_PARAMS) .having((e) => e.message, 'message', message)); From 70b30d2733a2535e29d127da2205b7437720fd2d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 20:07:33 +0000 Subject: [PATCH 123/127] Bump the github-actions group with 2 updates (dart-lang/json_rpc_2#117) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the github-actions group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart). Updates `actions/checkout` from 4.1.6 to 4.1.7
Release notes

Sourced from actions/checkout's releases.

v4.1.7

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v4.1.6...v4.1.7

Changelog

Sourced from actions/checkout's changelog.

Changelog

v4.1.7

v4.1.6

v4.1.5

v4.1.4

v4.1.3

v4.1.2

v4.1.1

v4.1.0

v4.0.0

v3.6.0

v3.5.3

... (truncated)

Commits

Updates `dart-lang/setup-dart` from 1.6.4 to 1.6.5
Release notes

Sourced from dart-lang/setup-dart's releases.

v1.6.5

dart-lang/json_rpc_2#118: dart-lang/setup-dartdart-lang/json_rpc_2#118

Changelog

Sourced from dart-lang/setup-dart's changelog.

v1.6.5

dart-lang/json_rpc_2#118: dart-lang/setup-dartdart-lang/json_rpc_2#118

v1.6.4

  • Rebuild JS code.

v1.6.3

v1.6.2

v1.6.1

  • Updated the google storage url for main channel releases.

v1.6.0

  • Enable provisioning of the latest Dart SDK patch release by specifying just the major and minor version (e.g. 3.2).

v1.5.1

  • No longer test the setup-dart action on pre-2.12 SDKs.
  • Upgrade JS interop code to use extension types (the new name for inline classes).
  • The upcoming rename of the be channel to main is now supported with forward compatibility that switches when the rename happens.

v1.5.0

  • Re-wrote the implementation of the action into Dart.
  • Auto-detect the platform architecture (x64, ia32, arm, arm64).
  • Improved the caching and download resilience of the sdk.
  • Added a new action output: dart-version - the installed version of the sdk.

v1.4.0

... (truncated)

Commits

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
--- pkgs/json_rpc_2/.github/workflows/test-package.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index c8e65b618..cf18d9b7b 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -22,8 +22,8 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 with: sdk: ${{ matrix.sdk }} - id: install @@ -49,8 +49,8 @@ jobs: os: [ubuntu-latest] sdk: [3.4, dev] steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 with: sdk: ${{ matrix.sdk }} - id: install From 2a45bcb4e197b77f06d6261c4d935a111f953863 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Oct 2024 22:23:01 +0000 Subject: [PATCH 124/127] Bump actions/checkout from 4.1.7 to 4.2.0 in the github-actions group (dart-lang/json_rpc_2#118) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 4.1.7 to 4.2.0
Release notes

Sourced from actions/checkout's releases.

v4.2.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v4.1.7...v4.2.0

Changelog

Sourced from actions/checkout's changelog.

Changelog

v4.2.0

v4.1.7

v4.1.6

v4.1.5

v4.1.4

v4.1.3

v4.1.2

v4.1.1

v4.1.0

v4.0.0

v3.6.0

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.7&new-version=4.2.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
--- pkgs/json_rpc_2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/pkgs/json_rpc_2/.github/workflows/test-package.yml index cf18d9b7b..8c95f7296 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/pkgs/json_rpc_2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 with: sdk: ${{ matrix.sdk }} @@ -49,7 +49,7 @@ jobs: os: [ubuntu-latest] sdk: [3.4, dev] steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 with: sdk: ${{ matrix.sdk }} From 43e817d855bf273a712dddcd1ca73b66ab14c381 Mon Sep 17 00:00:00 2001 From: Moritz Date: Fri, 25 Oct 2024 15:12:16 +0200 Subject: [PATCH 125/127] Add issue template and other fixes --- .github/ISSUE_TEMPLATE/json_rpc_2.md | 5 +++++ pkgs/json_rpc_2/pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .github/ISSUE_TEMPLATE/json_rpc_2.md diff --git a/.github/ISSUE_TEMPLATE/json_rpc_2.md b/.github/ISSUE_TEMPLATE/json_rpc_2.md new file mode 100644 index 000000000..29310dd09 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/json_rpc_2.md @@ -0,0 +1,5 @@ +--- +name: "package:json_rpc_2" +about: "Create a bug or file a feature request against package:json_rpc_2." +labels: "package:json_rpc_2" +--- \ No newline at end of file diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index ae3ee946c..4d6ff98dd 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -2,7 +2,7 @@ name: json_rpc_2 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 +repository: https://github.com/dart-lang/tools/tree/main/pkgs/json_rpc_2 environment: sdk: ^3.4.0 From a82bab3e9e3e8046ed419d5fa62a667470a097c2 Mon Sep 17 00:00:00 2001 From: Moritz Date: Fri, 25 Oct 2024 15:15:13 +0200 Subject: [PATCH 126/127] Moving fixes --- .github/labeler.yml | 4 ++ .../workflows/json_rpc_2.yaml | 17 +++++++-- README.md | 1 + pkgs/json_rpc_2/.github/dependabot.yml | 15 -------- .../.github/workflows/no-response.yml | 37 ------------------- .../json_rpc_2/.github/workflows/publish.yaml | 14 ------- pkgs/json_rpc_2/CHANGELOG.md | 3 +- pkgs/json_rpc_2/README.md | 2 +- pkgs/json_rpc_2/pubspec.yaml | 2 +- 9 files changed, 23 insertions(+), 72 deletions(-) rename pkgs/json_rpc_2/.github/workflows/test-package.yml => .github/workflows/json_rpc_2.yaml (86%) delete mode 100644 pkgs/json_rpc_2/.github/dependabot.yml delete mode 100644 pkgs/json_rpc_2/.github/workflows/no-response.yml delete mode 100644 pkgs/json_rpc_2/.github/workflows/publish.yaml diff --git a/.github/labeler.yml b/.github/labeler.yml index c3d5de0b9..bb75ff705 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -32,6 +32,10 @@ - changed-files: - any-glob-to-any-file: 'pkgs/graphs/**' +'package:json_rpc_2': + - changed-files: + - any-glob-to-any-file: 'pkgs/json_rpc_2/**' + 'package:mime': - changed-files: - any-glob-to-any-file: 'pkgs/mime/**' diff --git a/pkgs/json_rpc_2/.github/workflows/test-package.yml b/.github/workflows/json_rpc_2.yaml similarity index 86% rename from pkgs/json_rpc_2/.github/workflows/test-package.yml rename to .github/workflows/json_rpc_2.yaml index 8c95f7296..cd1ba682f 100644 --- a/pkgs/json_rpc_2/.github/workflows/test-package.yml +++ b/.github/workflows/json_rpc_2.yaml @@ -1,17 +1,28 @@ -name: Dart CI +name: package:json_rpc_2 on: # Run on PRs and pushes to the default branch. push: - branches: [ master ] + branches: [ main ] + paths: + - '.github/workflows/json_rpc_2.yml' + - 'pkgs/json_rpc_2/**' pull_request: - branches: [ master ] + branches: [ main ] + paths: + - '.github/workflows/json_rpc_2.yml' + - 'pkgs/json_rpc_2/**' schedule: - cron: "0 0 * * 0" env: PUB_ENVIRONMENT: bot.github + +defaults: + run: + working-directory: pkgs/json_rpc_2/ + jobs: # Check code formatting and static analysis on a single OS (linux) # against Dart dev. diff --git a/README.md b/README.md index ac4edf204..a32ee1646 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ don't naturally belong to other topic monorepos (like | [file](pkgs/file/) | A pluggable, mockable file system abstraction for Dart. | [![pub package](https://img.shields.io/pub/v/file.svg)](https://pub.dev/packages/file) | | [file_testing](pkgs/file_testing/) | Testing utilities for package:file (published but unlisted). | [![pub package](https://img.shields.io/pub/v/file_testing.svg)](https://pub.dev/packages/file_testing) | | [graphs](pkgs/graphs/) | Graph algorithms that operate on graphs in any representation | [![pub package](https://img.shields.io/pub/v/graphs.svg)](https://pub.dev/packages/graphs) | +| [json_rpc_2](pkgs/json_rpc_2/) | Utilities to write a client or server using the JSON-RPC 2.0 spec. | [![pub package](https://img.shields.io/pub/v/json_rpc_2.svg)](https://pub.dev/packages/json_rpc_2) | | [mime](pkgs/mime/) | Utilities for handling media (MIME) types. | [![pub package](https://img.shields.io/pub/v/mime.svg)](https://pub.dev/packages/mime) | | [oauth2](pkgs/oauth2/) | A client library for authenticatingand making requests via OAuth2. | [![pub package](https://img.shields.io/pub/v/oauth2.svg)](https://pub.dev/packages/oauth2) | | [source_map_stack_trace](pkgs/source_map_stack_trace/) | A package for applying source maps to stack traces. | [![pub package](https://img.shields.io/pub/v/source_map_stack_trace.svg)](https://pub.dev/packages/source_map_stack_trace) | diff --git a/pkgs/json_rpc_2/.github/dependabot.yml b/pkgs/json_rpc_2/.github/dependabot.yml deleted file mode 100644 index cde02ad6a..000000000 --- a/pkgs/json_rpc_2/.github/dependabot.yml +++ /dev/null @@ -1,15 +0,0 @@ -# Dependabot configuration file. -# See https://docs.github.com/en/code-security/dependabot/dependabot-version-updates -version: 2 - -updates: - - package-ecosystem: github-actions - directory: / - schedule: - interval: monthly - labels: - - autosubmit - groups: - github-actions: - patterns: - - "*" diff --git a/pkgs/json_rpc_2/.github/workflows/no-response.yml b/pkgs/json_rpc_2/.github/workflows/no-response.yml deleted file mode 100644 index ab1ac4984..000000000 --- a/pkgs/json_rpc_2/.github/workflows/no-response.yml +++ /dev/null @@ -1,37 +0,0 @@ -# A workflow to close issues where the author hasn't responded to a request for -# more information; see https://github.com/actions/stale. - -name: No Response - -# Run as a daily cron. -on: - schedule: - # Every day at 8am - - cron: '0 8 * * *' - -# All permissions not specified are set to 'none'. -permissions: - issues: write - pull-requests: write - -jobs: - no-response: - runs-on: ubuntu-latest - if: ${{ github.repository_owner == 'dart-lang' }} - steps: - - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e - with: - # Don't automatically mark inactive issues+PRs as stale. - days-before-stale: -1 - # Close needs-info issues and PRs after 14 days of inactivity. - days-before-close: 14 - stale-issue-label: "needs-info" - close-issue-message: > - Without additional information we're not able to resolve this issue. - Feel free to add more info or respond to any questions above and we - can reopen the case. Thanks for your contribution! - stale-pr-label: "needs-info" - close-pr-message: > - Without additional information we're not able to resolve this PR. - Feel free to add more info or respond to any questions above. - Thanks for your contribution! diff --git a/pkgs/json_rpc_2/.github/workflows/publish.yaml b/pkgs/json_rpc_2/.github/workflows/publish.yaml deleted file mode 100644 index fcb7ccb89..000000000 --- a/pkgs/json_rpc_2/.github/workflows/publish.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# A CI configuration to auto-publish pub packages. - -name: Publish - -on: - pull_request: - branches: [ master ] - push: - tags: [ 'v[0-9]+.[0-9]+.[0-9]+*' ] - -jobs: - publish: - if: ${{ github.repository_owner == 'dart-lang' }} - uses: dart-lang/ecosystem/.github/workflows/publish.yaml@main diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index aec82015f..1f2cf8e8e 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,6 +1,7 @@ -## 3.0.3-wip +## 3.0.3 * Require Dart 3.4 +* Move to `dart-lang/tools` monorepo. ## 3.0.2 diff --git a/pkgs/json_rpc_2/README.md b/pkgs/json_rpc_2/README.md index 8ccfc57a4..a7dda4a8e 100644 --- a/pkgs/json_rpc_2/README.md +++ b/pkgs/json_rpc_2/README.md @@ -1,4 +1,4 @@ -[![Dart CI](https://github.com/dart-lang/json_rpc_2/actions/workflows/test-package.yml/badge.svg)](https://github.com/dart-lang/json_rpc_2/actions/workflows/test-package.yml) +[![Build Status](https://github.com/dart-lang/tools/actions/workflows/json_rpc_2.yaml/badge.svg)](https://github.com/dart-lang/tools/actions/workflows/json_rpc_2.yaml) [![pub package](https://img.shields.io/pub/v/json_rpc_2.svg)](https://pub.dev/packages/json_rpc_2) [![package publisher](https://img.shields.io/pub/publisher/json_rpc_2.svg)](https://pub.dev/packages/json_rpc_2/publisher) diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index 4d6ff98dd..6f5313176 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 3.0.3-wip +version: 3.0.3 description: >- Utilities to write a client or server using the JSON-RPC 2.0 spec. repository: https://github.com/dart-lang/tools/tree/main/pkgs/json_rpc_2 From a587b85b621c4f7af7e76b08e3f52ad049a75f90 Mon Sep 17 00:00:00 2001 From: Moritz Date: Mon, 28 Oct 2024 09:25:40 +0100 Subject: [PATCH 127/127] Update pkgs/json_rpc_2/analysis_options.yaml Co-authored-by: Devon Carew --- pkgs/json_rpc_2/analysis_options.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/pkgs/json_rpc_2/analysis_options.yaml b/pkgs/json_rpc_2/analysis_options.yaml index 873cf41fd..a91d5cace 100644 --- a/pkgs/json_rpc_2/analysis_options.yaml +++ b/pkgs/json_rpc_2/analysis_options.yaml @@ -1,6 +1,5 @@ include: package:dart_flutter_team_lints/analysis_options.yaml - analyzer: language: strict-casts: false