Skip to content

Commit 11a7719

Browse files
authored
allow custom ID generators in Clients, and support String ids (#2077)
This is to support using this package to write a dart analyzer LSP client, which requires String ids. Also closes #738 This is technically breaking because there was some code which tries to coerce string ids back into integers, but this should never have happened. Any server stringifying integer IDs is not spec compliant. Let me know if we want to release this as non-breaking instead of breaking, I think it would be fine personally, but don't feel strongly either way.
1 parent 93276f5 commit 11a7719

File tree

6 files changed

+258
-184
lines changed

6 files changed

+258
-184
lines changed

pkgs/json_rpc_2/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 4.0.0
2+
3+
* Add custom ID generator option to clients, which allows for `String` ids.
4+
* **Breaking**: When `String` ids are present in a response, we no longer
5+
automatically try to parse them as integers. This behavior was never a part
6+
of the spec, and is not compatible with allowing custom ID generators.
7+
18
## 3.0.3
29

310
* Require Dart 3.4

pkgs/json_rpc_2/lib/src/client.dart

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,18 @@ import 'utils.dart';
1818
class Client {
1919
final StreamChannel<dynamic> _channel;
2020

21-
/// The next request id.
22-
var _id = 0;
21+
/// A function to generate the next request id.
22+
Object Function() _idGenerator;
2323

2424
/// The current batch of requests to be sent together.
2525
///
2626
/// Each element is a JSON RPC spec compliant message.
2727
List<Map<String, dynamic>>? _batch;
2828

2929
/// The map of request ids to pending requests.
30-
final _pendingRequests = <int, _Request>{};
30+
///
31+
/// Keys must be of type `int` or `String`.
32+
final _pendingRequests = <Object, _Request>{};
3133

3234
final _done = Completer<void>();
3335

@@ -49,9 +51,14 @@ class Client {
4951
///
5052
/// Note that the client won't begin listening to [channel] until
5153
/// [Client.listen] is called.
52-
Client(StreamChannel<String> channel)
54+
///
55+
/// If [idGenerator] is passed, it will be called to generate an ID for each
56+
/// request. Defaults to an auto-incrementing `int`. The value returned must
57+
/// be either an `int` or `String`.
58+
Client(StreamChannel<String> channel, {Object Function()? idGenerator})
5359
: this.withoutJson(
54-
jsonDocument.bind(channel).transformStream(ignoreFormatExceptions));
60+
jsonDocument.bind(channel).transformStream(ignoreFormatExceptions),
61+
idGenerator: idGenerator);
5562

5663
/// Creates a [Client] that communicates using decoded messages over
5764
/// [_channel].
@@ -61,7 +68,12 @@ class Client {
6168
///
6269
/// Note that the client won't begin listening to [_channel] until
6370
/// [Client.listen] is called.
64-
Client.withoutJson(this._channel) {
71+
///
72+
/// If [_idGenerator] is passed, it will be called to generate an ID for each
73+
/// request. Defaults to an auto-incrementing `int`. The value returned must
74+
/// be either an `int` or `String`.
75+
Client.withoutJson(this._channel, {Object Function()? idGenerator})
76+
: _idGenerator = idGenerator ?? _createIncrementingIdGenerator() {
6577
done.whenComplete(() {
6678
for (var request in _pendingRequests.values) {
6779
request.completer.completeError(StateError(
@@ -115,7 +127,7 @@ class Client {
115127
/// Throws a [StateError] if the client is closed while the request is in
116128
/// flight, or if the client is closed when this method is called.
117129
Future<Object?> sendRequest(String method, [Object? parameters]) {
118-
var id = _id++;
130+
var id = _idGenerator();
119131
_send(method, parameters, id);
120132

121133
var completer = Completer<Object?>.sync();
@@ -142,7 +154,7 @@ class Client {
142154
///
143155
/// Sends a request to invoke [method] with [parameters]. If [id] is given,
144156
/// the request uses that id.
145-
void _send(String method, Object? parameters, [int? id]) {
157+
void _send(String method, Object? parameters, [Object? id]) {
146158
if (parameters is Iterable) parameters = parameters.toList();
147159
if (parameters is! Map && parameters is! List && parameters != null) {
148160
throw ArgumentError('Only maps and lists may be used as JSON-RPC '
@@ -201,7 +213,6 @@ class Client {
201213
if (!_isResponseValid(response_)) return;
202214
final response = response_ as Map;
203215
var id = response['id'];
204-
id = (id is String) ? int.parse(id) : id;
205216
var request = _pendingRequests.remove(id)!;
206217
if (response.containsKey('result')) {
207218
request.completer.complete(response['result']);
@@ -218,7 +229,6 @@ class Client {
218229
if (response is! Map) return false;
219230
if (response['jsonrpc'] != '2.0') return false;
220231
var id = response['id'];
221-
id = (id is String) ? int.parse(id) : id;
222232
if (!_pendingRequests.containsKey(id)) return false;
223233
if (response.containsKey('result')) return true;
224234

@@ -244,3 +254,11 @@ class _Request {
244254

245255
_Request(this.method, this.completer, this.chain);
246256
}
257+
258+
/// The default ID generator, uses an auto incrementing integer.
259+
///
260+
/// Each call returns a new function which starts back a `0`.
261+
int Function() _createIncrementingIdGenerator() {
262+
var nextId = 0;
263+
return () => nextId++;
264+
}

pkgs/json_rpc_2/lib/src/peer.dart

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,20 @@ class Peer implements Client, Server {
5959
/// some requests which are not conformant with the JSON-RPC 2.0
6060
/// specification. In particular, requests missing the `jsonrpc` parameter
6161
/// will be accepted.
62-
Peer(StreamChannel<String> channel,
63-
{ErrorCallback? onUnhandledError, bool strictProtocolChecks = true})
64-
: this.withoutJson(
62+
///
63+
/// If [idGenerator] is passed, it will be called to generate an ID for each
64+
/// request. Defaults to an auto-incrementing `int`. The value returned must
65+
/// be either an `int` or `String`.
66+
Peer(
67+
StreamChannel<String> channel, {
68+
ErrorCallback? onUnhandledError,
69+
bool strictProtocolChecks = true,
70+
Object Function()? idGenerator,
71+
}) : this.withoutJson(
6572
jsonDocument.bind(channel).transform(respondToFormatExceptions),
6673
onUnhandledError: onUnhandledError,
67-
strictProtocolChecks: strictProtocolChecks);
74+
strictProtocolChecks: strictProtocolChecks,
75+
idGenerator: idGenerator);
6876

6977
/// Creates a [Peer] that communicates using decoded messages over [_channel].
7078
///
@@ -81,14 +89,24 @@ class Peer implements Client, Server {
8189
/// some requests which are not conformant with the JSON-RPC 2.0
8290
/// specification. In particular, requests missing the `jsonrpc` parameter
8391
/// will be accepted.
92+
///
93+
/// If [idGenerator] is passed, it will be called to generate an ID for each
94+
/// request. Defaults to an auto-incrementing `int`. The value returned must
95+
/// be either an `int` or `String`.
8496
Peer.withoutJson(this._channel,
85-
{ErrorCallback? onUnhandledError, bool strictProtocolChecks = true}) {
97+
{ErrorCallback? onUnhandledError,
98+
bool strictProtocolChecks = true,
99+
Object Function()? idGenerator}) {
86100
_server = Server.withoutJson(
87101
StreamChannel(_serverIncomingForwarder.stream, _channel.sink),
88102
onUnhandledError: onUnhandledError,
89103
strictProtocolChecks: strictProtocolChecks);
90104
_client = Client.withoutJson(
91-
StreamChannel(_clientIncomingForwarder.stream, _channel.sink));
105+
StreamChannel(
106+
_clientIncomingForwarder.stream,
107+
_channel.sink,
108+
),
109+
idGenerator: idGenerator);
92110
}
93111

94112
// Client methods.

pkgs/json_rpc_2/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: json_rpc_2
2-
version: 3.0.3
2+
version: 4.0.0
33
description: >-
44
Utilities to write a client or server using the JSON-RPC 2.0 spec.
55
repository: https://github.com/dart-lang/tools/tree/main/pkgs/json_rpc_2

0 commit comments

Comments
 (0)