Skip to content

Commit 684f199

Browse files
committed
Replace io-ts with zod, remove array params
1 parent e30be05 commit 684f199

File tree

4 files changed

+90
-93
lines changed

4 files changed

+90
-93
lines changed

package-lock.json

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,7 @@
8282
"eslint": "^6.8.0",
8383
"eslint-config-prettier": "^6.9.0",
8484
"eslint-plugin-prettier": "^3.1.2",
85-
"fp-ts": "^2.4.0",
8685
"husky": "^3.0.0",
87-
"io-ts": "^2.0.4",
8886
"jest": "^24.9.0",
8987
"lint-staged": "^9.2.0",
9088
"prettier": "^1.19.1",
@@ -93,11 +91,7 @@
9391
"ts-jest": "^24.0.2",
9492
"typescript": "^3.7.4"
9593
},
96-
"peerDependencies": {
97-
"io-ts": "^2.0.4",
98-
"fp-ts": "^2.4.0"
99-
},
10094
"dependencies": {
101-
"make-error": "^1.3.5"
95+
"zod": "^1.0.9"
10296
}
10397
}

src/index.spec.ts

Lines changed: 55 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import * as t from "io-ts";
2-
import { isLeft, isRight } from "fp-ts/lib/Either";
31
import { expectType, TypeEqual } from "ts-expect";
42
import {
3+
schema,
54
createServer,
65
parse,
76
createClient,
@@ -14,12 +13,12 @@ import {
1413
describe("json rpc", () => {
1514
const methods = {
1615
hello: {
17-
request: t.type({}),
18-
response: t.string
16+
request: schema.object({}),
17+
response: schema.string()
1918
},
2019
echo: {
21-
request: t.type({ arg: t.string }),
22-
response: t.string
20+
request: schema.object({ arg: schema.string() }),
21+
response: schema.string()
2322
}
2423
};
2524

@@ -29,7 +28,7 @@ describe("json rpc", () => {
2928
};
3029

3130
const server = createServer(methods, resolvers);
32-
const client = createClient(methods, x => server(x, undefined));
31+
const client = createClient(methods, server);
3332

3433
describe("types", () => {
3534
type Requests = ClientRequests<typeof methods>;
@@ -40,23 +39,11 @@ describe("json rpc", () => {
4039

4140
describe("parse", () => {
4241
it("should parse json", () => {
43-
const result = parse("{}");
44-
45-
expect(isRight(result)).toEqual(true);
46-
47-
if (isRight(result)) {
48-
expect(result.right).toEqual({});
49-
}
42+
expect(parse("{}")).toEqual({});
5043
});
5144

5245
it("should fail to parse malformed json", () => {
53-
const result = parse("[");
54-
55-
expect(isLeft(result)).toEqual(true);
56-
57-
if (isLeft(result)) {
58-
expect(result.left).toEqual({ code: -32700, message: "Parse error" });
59-
}
46+
expect(() => parse("[")).toThrow(SyntaxError);
6047
});
6148
});
6249

@@ -85,21 +72,6 @@ describe("json rpc", () => {
8572
expect(res).toEqual(undefined);
8673
});
8774

88-
it("should accept an array of parameters", async () => {
89-
const res = await server({
90-
jsonrpc: "2.0",
91-
id: "test",
92-
method: "echo",
93-
params: ["test"]
94-
});
95-
96-
expect(res).toEqual({
97-
jsonrpc: "2.0",
98-
id: "test",
99-
result: "test"
100-
});
101-
});
102-
10375
it("should accept an object of parameters", async () => {
10476
const res = await server({
10577
jsonrpc: "2.0",
@@ -247,8 +219,7 @@ describe("json rpc", () => {
247219
id: "test",
248220
error: {
249221
code: -32602,
250-
message:
251-
"Invalid value undefined supplied to : { arg: string }/arg: string"
222+
message: "arg: Non-string type: undefined"
252223
}
253224
});
254225
});
@@ -331,13 +302,13 @@ describe("json rpc", () => {
331302
expect(result).toEqual(undefined);
332303
});
333304

334-
it("should throw rpc errors", async () => {
305+
it("should throw on invalid argument", async () => {
335306
await expect(
336307
client({
337308
method: "echo",
338309
params: {} as any
339310
})
340-
).rejects.toBeInstanceOf(RpcError);
311+
).rejects.toBeInstanceOf(Error);
341312
});
342313

343314
describe("without type checking", () => {
@@ -395,17 +366,52 @@ describe("json rpc", () => {
395366
expect(result).toEqual([undefined, "test", undefined]);
396367
});
397368

398-
it("should return rpc errors", async () => {
399-
const result = await client.many([
400-
{
401-
method: "echo",
402-
params: {} as any
403-
}
404-
]);
369+
it("should throw on invalid argument", async () => {
370+
expect(
371+
client.many([
372+
{
373+
method: "echo",
374+
params: {} as any
375+
}
376+
])
377+
).rejects.toBeInstanceOf(Error);
378+
});
379+
});
380+
});
405381

406-
expect(result.length).toEqual(1);
407-
expect(result[0]).toBeInstanceOf(RpcError);
382+
describe("intersection types", () => {
383+
const methods = {
384+
test: {
385+
request: schema.intersection(
386+
schema.object({ url: schema.string() }),
387+
schema.object({ accept: schema.string().optional() })
388+
),
389+
response: schema.string()
390+
}
391+
};
392+
393+
const server = createServer(methods, {
394+
test: ({ url, accept }) => `${url}#${accept}`
395+
});
396+
397+
const client = createClient(methods, server);
398+
399+
it("should support intersection types", async () => {
400+
const result = await client({
401+
method: "test",
402+
params: { url: "http://example.com", accept: "json" }
408403
});
404+
405+
expect(result).toEqual(`http://example.com#json`);
406+
});
407+
408+
it("should support intersection type with optional key", async () => {
409+
const result = await client({
410+
method: "test",
411+
params: { url: "http://example.com" }
412+
});
413+
414+
expect(result).toEqual(`http://example.com#undefined`);
409415
});
410416
});
411417
});

src/index.ts

Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { BaseError } from "make-error";
2-
import { TypeOf, Any, TypeC } from "io-ts";
3-
import { PathReporter } from "io-ts/lib/PathReporter";
4-
import { either } from "fp-ts";
1+
import { TypeOf, ZodAny } from "zod";
2+
import * as schema from "zod";
53

6-
export interface Method<T extends TypeC<any>, U extends Any> {
4+
export { schema };
5+
6+
export interface Method<T extends ZodAny, U extends ZodAny> {
77
request: T;
88
response: U;
99
}
1010

11-
export type Methods = Record<string, Method<TypeC<any>, Any>>;
11+
export type Methods = Record<string, Method<ZodAny, ZodAny>>;
1212

1313
/**
1414
* Metadata for JSON-RPC requests.
@@ -87,19 +87,23 @@ export interface JsonRpcFailure<T> extends JsonRpcResponse {
8787
// Define commonly used failure messages.
8888
const INVALID_REQUEST = { code: -32600, message: "Invalid request" };
8989
const METHOD_NOT_FOUND = { code: -32601, message: "Method not found" };
90-
const PARSE_ERROR = { code: -32700, message: "Parse error" };
9190

9291
/**
9392
* Parse raw input into JSON.
9493
*/
95-
export function parse(input: string): either.Either<JsonRpcError, unknown> {
96-
return either.parseJSON(input, () => PARSE_ERROR);
94+
export function parse(input: string): unknown {
95+
try {
96+
return JSON.parse(input);
97+
} catch (err) {
98+
err.code = -32700;
99+
throw err;
100+
}
97101
}
98102

99103
/**
100104
* Create a custom RPC error to report issues.
101105
*/
102-
export class RpcError<T = void> extends BaseError {
106+
export class RpcError<T = void> extends Error {
103107
constructor(public message: string, public code = -32603, public data: T) {
104108
super(message);
105109
}
@@ -166,25 +170,21 @@ async function processRequest<T extends Methods, C = void>(
166170
let data = undefined;
167171
const { request, response } = methods[method];
168172

169-
if (Array.isArray(params)) {
170-
data = Object.keys(request.props).reduce<any>((obj, key, index) => {
171-
obj[key] = params[index];
172-
return obj;
173-
}, {});
174-
} else if (params === undefined || typeof params === "object") {
173+
if (params === undefined || typeof params === "object") {
175174
data = params ?? {};
176175
} else {
177176
return failure(INVALID_REQUEST, id);
178177
}
179178

180-
const input =
181-
options.decode === false ? either.right(data) : request.decode(data);
179+
let input: unknown;
182180

183-
if (either.isLeft(input)) {
181+
try {
182+
input = options.decode === false ? data : request.parse(data);
183+
} catch (err) {
184184
return failure(
185185
{
186186
code: -32602,
187-
message: PathReporter.report(input).join("; ")
187+
message: err.message
188188
},
189189
id
190190
);
@@ -194,9 +194,9 @@ async function processRequest<T extends Methods, C = void>(
194194
const metadata: Metadata = { id, isNotification };
195195

196196
try {
197-
const data = await resolvers[method](input.right, context, metadata);
197+
const data = await resolvers[method](input, context, metadata);
198198
if (isNotification) return; // Do not encode response for notifications.
199-
return success(options.encode === false ? data : response.encode(data), id);
199+
return success(options.encode === false ? data : response.parse(data), id);
200200
} catch (err) {
201201
return failure(
202202
{
@@ -297,7 +297,7 @@ export function createClient<T extends Methods, U = void>(
297297

298298
return {
299299
method,
300-
params: options.encode === false ? params : request.encode(params),
300+
params: options.encode === false ? params : request.parse(params),
301301
id: async ? undefined : counter++,
302302
process: (body: unknown): unknown => {
303303
if (body === undefined) {
@@ -324,20 +324,11 @@ export function createClient<T extends Methods, U = void>(
324324
);
325325
}
326326

327-
const output =
328-
options.decode === false
329-
? either.right(result)
330-
: response.decode(result);
331-
332-
if (either.isLeft(output)) {
333-
return new RpcError(
334-
PathReporter.report(output).join("; "),
335-
-2,
336-
output.left
337-
);
327+
try {
328+
return response.parse(result);
329+
} catch (err) {
330+
return new RpcError(err.message, -2, { result });
338331
}
339-
340-
return output.right;
341332
}
342333
};
343334
}

0 commit comments

Comments
 (0)