Skip to content
This repository was archived by the owner on Aug 20, 2025. It is now read-only.

Commit 96483e9

Browse files
authored
Upgrade to TypeScript 3.2 and fix protocol types (#9)
TypeScript 3.2 brings typed `Function.call`, which has revealed a deficiency in our JSON-RPC type definitions. The `params` argument for the `RequestHandler` and `NotificationHandler` callbacks was incompatible with `undefined` in its union, even though an incoming RPC request could have its `params` property omitted. Correct this mistake by including `undefined` in the `RPCParams` union type. Finish fixing #5 by also allowing `params` to be omitted for notifications. Fix the unit tests which did not catch this issue before.
1 parent d46f0e2 commit 96483e9

File tree

4 files changed

+75
-168
lines changed

4 files changed

+75
-168
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"ts-jest": "^23.10.4",
3131
"tslint": "^5.9.1",
3232
"tslint-config-airbnb": "^5.11.1",
33-
"typescript": "^3.1.6"
33+
"typescript": "^3.2.2"
3434
},
3535
"dependencies": {
3636
"@types/error-subclass": "^2.2.0",

src/protocol.test.ts

Lines changed: 68 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -9,216 +9,122 @@ declare class Object {
99

1010
describe('parse', () => {
1111
describe('a valid Request', () => {
12-
const vectors: {
13-
desc: string;
14-
id: string | number;
15-
method: string;
16-
params?: object | any[];
17-
}[] = [
18-
{
19-
desc: 'with a numeric id',
20-
id: -7,
21-
method: 'foo.doBar/baz',
22-
},
23-
{
24-
desc: 'with a string id',
25-
id: 'three',
26-
method: 'hello',
27-
},
28-
{
29-
desc: 'with params as empty array',
30-
id: 55,
31-
method: 'foo',
32-
params: [],
33-
},
34-
{
35-
desc: 'with params by-position',
12+
it.each([
13+
['with a numeric id', { id: -7, method: 'foo.doBar/baz' }],
14+
['with a string id', { id: 'three', method: 'hello' }],
15+
['with params as empty array', { id: 55, method: 'foo', params: [] }],
16+
['with params by-position', {
3617
id: 42,
3718
method: 'bar',
3819
params: [3, 'three', { three: 3 }, ['four', 'five']],
39-
},
40-
{
41-
desc: 'with params by-name',
20+
}],
21+
['with params by-name', {
4222
id: 8,
4323
method: 'baz',
4424
params: { foo: 'bar', quux: 'yes', 3: 5 } as object,
45-
},
46-
];
47-
vectors.forEach((vector) => {
48-
test(vector.desc, () => {
49-
const { id, method, params } = vector;
50-
expect(jrpc.parse({
51-
id, method, params, jsonrpc: '2.0',
52-
})).toEqual({ id, method, params, kind: 'request' });
53-
});
25+
}],
26+
])('%s', (_, vector) => {
27+
const { id, method, params } = vector;
28+
// We can't reassemble the message from the spread object as any
29+
// properties that do not exist in the test vector would come into
30+
// existence on the reassembled message, with a value of
31+
// undefined. This would invalidate the tests as a property
32+
// that exists with the value undefined cannot be represented in
33+
// JSON.
34+
expect(jrpc.parse({ ...vector, jsonrpc: '2.0' }))
35+
.toEqual({ id, method, params, kind: 'request' });
5436
});
5537
});
5638

5739
describe('a valid Notification', () => {
58-
const vectors: {
59-
desc: string;
60-
method: string;
61-
params?: object | any[];
62-
}[] = [
63-
{
64-
desc: 'with no params',
65-
method: 'notifoo',
66-
},
67-
{
68-
desc: 'with params as empty array',
69-
method: 'blah',
70-
params: [],
71-
},
72-
{
73-
desc: 'with params by-position',
40+
it.each([
41+
['with no params', { method: 'notifoo' }],
42+
['with params as empty array', { method: 'blah', params: [] }],
43+
['with params by-position', {
7444
method: 'a.method',
7545
params: [
76-
['hello', 'bonjour', -7.2],
46+
['hello', 'bonjour', -7.2],
7747
'abc',
78-
{ a: 3, b: 'four' },
48+
{ a: 3, b: 'four' },
7949
],
80-
},
81-
{
82-
desc: 'with params by-name',
50+
}],
51+
['with params by-name', {
8352
method: 'yo',
8453
params: {
8554
a: 1,
8655
b: 'two',
8756
c: [3, 'four', 5],
8857
d: { e: 'f' },
89-
} as object,
90-
},
91-
];
92-
vectors.forEach((vector) => {
93-
test(vector.desc, () => {
94-
const { method, params } = vector;
95-
expect(jrpc.parse({
96-
method, params, jsonrpc: '2.0',
97-
})).toEqual({ method, params, kind: 'notification' });
98-
});
58+
},
59+
}],
60+
])('%s', (_, vector) => {
61+
const { method, params } = vector;
62+
expect(jrpc.parse({ ...vector, jsonrpc: '2.0' }))
63+
.toEqual({ method, params, kind: 'notification' });
9964
});
10065
});
10166

10267
describe('a valid Response', () => {
103-
const vectors: {
104-
desc: string;
105-
id: string | number;
106-
result: any;
107-
}[] = [
108-
{
109-
desc: 'with numeric id',
110-
id: -7,
111-
result: null,
112-
},
113-
{
114-
desc: 'with string id',
115-
id: 'abc',
116-
result: null,
117-
},
118-
{
119-
desc: 'with numeric result',
120-
id: 5,
121-
result: 3.7,
122-
},
123-
{
124-
desc: 'with string result',
125-
id: 48,
126-
result: 'hello',
127-
},
128-
{
129-
desc: 'with boolean result',
130-
id: 1,
131-
result: true,
132-
},
133-
{
134-
desc: 'with array result',
68+
it.each([
69+
['with numeric id', { id: -7, result: null }],
70+
['with string id', { id: 'abc', result: null }],
71+
['with numeric result', { id: 5, result: 3.7 }],
72+
['with string result', { id: 48, result: 'hello' }],
73+
['with boolean result', { id: 1, result: true }],
74+
['with array result', {
13575
id: 86,
13676
result: ['one', 2, { three: 3 }, [4]],
137-
},
138-
{
139-
desc: 'with object result',
140-
id: 104,
141-
result: { yes: 'yup' },
142-
},
143-
];
144-
vectors.forEach((vector) => {
145-
test(vector.desc, () => {
146-
const { id, result } = vector;
147-
expect(jrpc.parse({
148-
id, result, jsonrpc: '2.0',
149-
})).toEqual({
150-
id, result, kind: 'response',
151-
});
152-
});
77+
}],
78+
['with object result', { id: 104, result: { yes: 'yup' } }],
79+
])('%s', (_, vector) => {
80+
const { id, result } = vector;
81+
expect(jrpc.parse({ ...vector, jsonrpc: '2.0' }))
82+
.toEqual({ id, result, kind: 'response' });
15383
});
15484
});
15585

15686
describe('a valid Error', () => {
157-
const vectors: {
158-
desc: string;
159-
id: string | number | null;
160-
error: {
161-
code: number;
162-
message: string;
163-
data?: any;
164-
};
165-
}[] = [
166-
{
167-
desc: 'with no id or data',
87+
it.each([
88+
['with no id or data', {
16889
id: null,
16990
error: { code: -37000, message: 'you dun goofed' },
170-
},
171-
{
172-
desc: 'with numeric id, no data',
91+
}],
92+
['with numeric id, no data', {
17393
id: 7,
17494
error: { code: 42, message: 'everything' },
175-
},
176-
{
177-
desc: 'with string id, no data',
95+
}],
96+
['with string id, no data', {
17897
id: 'asdf',
17998
error: { code: 0, message: 'zero' },
180-
},
181-
{
182-
desc: 'with boolean data',
99+
}],
100+
['with boolean data', {
183101
id: 2,
184102
error: { code: 33, message: 'm', data: false },
185-
},
186-
{
187-
desc: 'with numeric data',
103+
}],
104+
['with numeric data', {
188105
id: 3,
189106
error: { code: 34, message: 'm', data: 8 },
190-
},
191-
{
192-
desc: 'with string data',
107+
}],
108+
['with string data', {
193109
id: 4,
194110
error: { code: 35, message: 'q', data: 'nope' },
195-
},
196-
{
197-
desc: 'with null data',
111+
}],
112+
['with null data', {
198113
id: 88,
199114
error: { code: 123, message: 'yes', data: null },
200-
},
201-
{
202-
desc: 'with array data',
115+
}],
116+
['with array data', {
203117
id: 5,
204118
error: { code: 36, message: 'r', data: [1, 2, 'three'] },
205-
},
206-
{
207-
desc: 'with object data',
119+
}],
120+
['with object data', {
208121
id: 6,
209122
error: { code: 37, message: 's', data: { foo: 'bar' } },
210-
},
211-
];
212-
213-
vectors.forEach((vector) => {
214-
test(vector.desc, () => {
215-
const { id, error } = vector;
216-
expect(jrpc.parse({
217-
id, error, jsonrpc: '2.0',
218-
})).toEqual({
219-
id, error, kind: 'error',
220-
});
221-
});
123+
}],
124+
])('%s', (_, vector) => {
125+
const { id, error } = vector;
126+
expect(jrpc.parse({ ...vector, jsonrpc: '2.0' }))
127+
.toEqual({ id, error, kind: 'error' });
222128
});
223129
});
224130

src/protocol.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export const RPCID = t.union([t.Integer, t.string]);
3030
export type RPCID = t.TypeOf<typeof RPCID>;
3131

3232
/** JSON-RPC Request params */
33-
export const RPCParams = Structured;
33+
export const RPCParams = t.union([Structured, t.undefined]);
3434
export type RPCParams = t.TypeOf<typeof RPCParams>;
3535

3636
/** `error` member of a JSON-RPC Error object */
@@ -65,9 +65,9 @@ export const NotificationJSON = t.intersection([
6565
t.interface({
6666
jsonrpc: t.literal('2.0'),
6767
method: t.string,
68-
params: t.union([RPCParams, t.undefined]),
6968
}),
7069
t.partial({
70+
params: RPCParams,
7171
id: t.undefined,
7272
result: t.undefined,
7373
error: t.undefined,

yarn.lock

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3091,9 +3091,10 @@ type-check@~0.3.2:
30913091
dependencies:
30923092
prelude-ls "~1.1.2"
30933093

3094-
typescript@^3.1.6:
3095-
version "3.1.6"
3096-
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.6.tgz#b6543a83cfc8c2befb3f4c8fba6896f5b0c9be68"
3094+
typescript@^3.2.2:
3095+
version "3.2.2"
3096+
resolved "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz#fe8101c46aa123f8353523ebdcf5730c2ae493e5"
3097+
integrity sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==
30973098

30983099
uglify-js@^3.1.4:
30993100
version "3.4.9"

0 commit comments

Comments
 (0)