Skip to content

Commit 24c2957

Browse files
committed
use deno
1 parent d40065c commit 24c2957

18 files changed

+385
-75
lines changed

Diff for: .prettierrc.yaml

-5
This file was deleted.

Diff for: .vscode/settings.json

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"deno.enable": true,
3+
"deno.lint": true,
4+
"deno.unstable": true,
5+
"[javascript]": {
6+
"editor.defaultFormatter": "denoland.vscode-deno"
7+
},
8+
"[typescript]": {
9+
"editor.defaultFormatter": "denoland.vscode-deno"
10+
},
11+
"[javascriptreact]": {
12+
"editor.defaultFormatter": "denoland.vscode-deno"
13+
},
14+
"[typescriptreact]": {
15+
"editor.defaultFormatter": "denoland.vscode-deno"
16+
},
17+
"[html]": {
18+
"editor.defaultFormatter": "denoland.vscode-deno"
19+
},
20+
"[css]": {
21+
"editor.defaultFormatter": "denoland.vscode-deno"
22+
},
23+
"[scss]": {
24+
"editor.defaultFormatter": "denoland.vscode-deno"
25+
},
26+
"[json]": {
27+
"editor.defaultFormatter": "denoland.vscode-deno"
28+
},
29+
"[markdown]": {
30+
"editor.defaultFormatter": "denoland.vscode-deno"
31+
},
32+
"[jsonc]": {
33+
"editor.defaultFormatter": "denoland.vscode-deno"
34+
}
35+
}

Diff for: README.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
# json-rpc-ts
22

3-
Specification <https://www.jsonrpc.org/specification>
3+
4+
A strictly typed json-rpc implemention, with simple api
5+
6+
> Specification <https://www.jsonrpc.org/specification>

Diff for: deno.json

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"imports": {
3+
"std/": "https://deno.land/[email protected]/"
4+
},
5+
"fmt": {
6+
"lineWidth": 80,
7+
"semiColons": false,
8+
"indentWidth": 4,
9+
"singleQuote": true,
10+
"proseWrap": "preserve",
11+
"include": ["src/"],
12+
"exclude": []
13+
}
14+
}

Diff for: deno.lock

+37
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: src/client.ts

+101-29
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,44 @@
1-
import { type JSONRPCMethodSet } from './types'
2-
import { JSONRPCNotification, JSONRPCRequest } from './dto/request'
1+
import { type JSONRPCMethodSet } from './types.ts'
2+
import { JSONRPCNotification, JSONRPCRequest } from './dto/request.ts'
3+
import { JSONRPCErrorResponse, JSONRPCSuccessResponse } from './dto/response.ts'
4+
import { isJSONRPCResponse, JSONRPCResponse } from './dto/response.ts'
35
import {
4-
JSONRPCErrorResponse,
5-
JSONRPCResponse,
6-
isJSONRPCResponse,
7-
} from './dto/response'
8-
import { selfAddIdGenerator, getIDFromGenerator, type IDGenerator } from './id'
9-
import { isJSONRPCError } from './dto/errors'
6+
getIDFromGenerator,
7+
type IDGenerator,
8+
selfAddIdGenerator,
9+
} from './id.ts'
10+
11+
type JSONRPCAnyRequest =
12+
| JSONRPCNotification
13+
| JSONRPCRequest
14+
| Array<JSONRPCNotification | JSONRPCRequest>
1015

1116
/**
1217
* the client cannot parse the server response
1318
*/
1419
export class JSONRPCClientParseError extends Error {
1520
name = 'JSONRPCClientParseError'
21+
request: JSONRPCAnyRequest
22+
constructor(message: string, request: JSONRPCAnyRequest) {
23+
super(message)
24+
this.request = request
25+
}
1626
}
1727

1828
/**
1929
* just wrap the JSON.parse function
2030
*/
21-
function parseJSON(text: string): unknown {
31+
function parseJSON(
32+
text: string,
33+
associatedRequest: JSONRPCAnyRequest,
34+
): unknown {
2235
try {
2336
return JSON.parse(text)
2437
} catch {
25-
throw new JSONRPCClientParseError(`The server send an malformed json`)
38+
throw new JSONRPCClientParseError(
39+
`The server send an malformed json`,
40+
associatedRequest,
41+
)
2642
}
2743
}
2844

@@ -38,15 +54,15 @@ export class JSONRPCClient<MethodSet extends JSONRPCMethodSet> {
3854

3955
constructor(
4056
processor: (input: string) => Promise<string>,
41-
idGenerator?: IDGenerator
57+
idGenerator?: IDGenerator,
4258
) {
4359
this.processor = processor
4460
this.idGenerator = idGenerator || selfAddIdGenerator()
4561
}
4662

4763
public createRequest<T extends keyof MethodSet>(
4864
method: T extends string ? T : never,
49-
params?: Parameters<MethodSet[T]>[0]
65+
params?: Parameters<MethodSet[T]>[0],
5066
): JSONRPCRequest {
5167
const id = getIDFromGenerator(this.idGenerator)
5268
const request = new JSONRPCRequest({
@@ -59,7 +75,7 @@ export class JSONRPCClient<MethodSet extends JSONRPCMethodSet> {
5975

6076
public createNotifaction<T extends keyof MethodSet>(
6177
method: T extends string ? T : never,
62-
params?: Parameters<MethodSet[T]>[0]
78+
params?: Parameters<MethodSet[T]>[0],
6379
): JSONRPCNotification {
6480
const notification = new JSONRPCNotification({
6581
method,
@@ -68,25 +84,29 @@ export class JSONRPCClient<MethodSet extends JSONRPCMethodSet> {
6884
return notification
6985
}
7086

71-
private processOneJsonValue(jsonValue: unknown): JSONRPCResponse {
87+
private processOneJsonValue(
88+
jsonValue: unknown,
89+
associatedRequest: JSONRPCAnyRequest,
90+
): JSONRPCResponse {
7291
if (!isJSONRPCResponse(jsonValue)) {
7392
throw new JSONRPCClientParseError(
74-
`The server sent an incorrect response object`
93+
`The server sent an incorrect response object`,
94+
associatedRequest,
7595
)
7696
}
7797
return jsonValue
7898
}
7999

80100
async request<T extends keyof MethodSet>(
81101
method: T extends string ? T : never,
82-
params?: Parameters<MethodSet[T]>[0]
102+
params?: Parameters<MethodSet[T]>[0],
83103
): Promise<ReturnType<MethodSet[T]>> {
84104
const request = this.createRequest(method, params)
85105
// responsed json string
86106
const jsonString = await this.processor(JSON.stringify(request))
87-
const jsonValue = parseJSON(jsonString)
107+
const jsonValue = parseJSON(jsonString, request)
88108
// parsed response
89-
const response = this.processOneJsonValue(jsonValue)
109+
const response = this.processOneJsonValue(jsonValue, request)
90110
if ('error' in response) {
91111
return Promise.reject(response.error)
92112
} else {
@@ -96,50 +116,102 @@ export class JSONRPCClient<MethodSet extends JSONRPCMethodSet> {
96116

97117
async notify<T extends keyof MethodSet>(
98118
method: T extends string ? T : never,
99-
params?: Parameters<MethodSet[T]>[0]
119+
params?: Parameters<MethodSet[T]>[0],
100120
): Promise<void> {
101121
const notification = this.createNotifaction(method, params)
102122
await this.processor(JSON.stringify(notification))
103123
}
104124

105125
/**
106-
* You should use the createRequest or createNotifaction method to
126+
* You should use the createRequest() or createNotifaction() method to
107127
* create the requests array
108128
*/
109129
async batch(
110130
...requests: Array<JSONRPCRequest | JSONRPCNotification>
111-
): Promise<JSONRPCErrorResponse | Array<JSONRPCResponse> | undefined> {
131+
// deno-lint-ignore no-explicit-any
132+
): Promise<PromiseSettledResult<any>[] | PromiseSettledResult<any>> {
112133
// responsed json string
113134
const jsonString = await this.processor(JSON.stringify(requests))
114-
if (requests.every((r) => !('id' in r))) {
135+
const requestCount = requests.filter((r) => 'id' in r).length
136+
if (requestCount === 0) {
115137
// all the requests are notification
116-
return
138+
return []
117139
}
118140
// parsed response
119-
const jsonValue = parseJSON(jsonString)
141+
const jsonValue = parseJSON(jsonString, requests)
120142

121143
if (!Array.isArray(jsonValue)) {
122144
if (isJSONRPCResponse(jsonValue) && 'error' in jsonValue) {
123145
// If the batch rpc call itself fails to be recognized as an valid JSON or as an Array with at least one value,
124146
// the response from the Server MUST be a single Response object.
125-
return jsonValue
147+
return {
148+
status: 'rejected',
149+
reason: jsonValue.error,
150+
}
126151
}
127152

128153
// requests contains request, so response must be an array
129154
throw new JSONRPCClientParseError(
130-
`The server incorrectly handle the batch request`
155+
`The server incorrectly handle the batch request`,
156+
requests,
157+
)
158+
}
159+
160+
if (jsonValue.length !== requestCount) {
161+
throw new JSONRPCClientParseError(
162+
`The server returned batch response does not match the request count`,
163+
requests,
131164
)
132165
}
133166

134-
const responses: JSONRPCResponse[] = []
167+
if (!jsonValue.every(isJSONRPCResponse)) {
168+
throw new JSONRPCClientParseError(
169+
`The server returned batch response contains invalid value`,
170+
requests,
171+
)
172+
}
173+
174+
const unorderedResponses: JSONRPCResponse[] = jsonValue
175+
let errorStartIndex = 0
176+
// deno-lint-ignore no-explicit-any
177+
const responses: PromiseSettledResult<any>[] = [] // ordered
178+
135179
for (const request of requests) {
136180
if (!('id' in request)) {
137181
continue
138182
}
139-
const { id } = request
140-
// TODO
183+
141184
// The Response objects being returned from a batch call MAY be returned in any order within the Array.
142185
// The Client SHOULD match contexts between the set of Request objects and the resulting set of Response objects based on the id member within each Object.
186+
187+
const index = unorderedResponses.findIndex(
188+
(response) => response.id === request.id,
189+
)
190+
if (index === -1) {
191+
// no corresponding id, so the response will be JSONRPCErrorResponse
192+
// find the first (not been scanned) error
193+
const errRespIndex = unorderedResponses
194+
.slice(errorStartIndex)
195+
.findIndex(
196+
(response) =>
197+
'error' in response && response.id === null,
198+
)
199+
// this implemention expect that all the JSONRPCErrorResponse are ordered
200+
responses.push({
201+
status: 'rejected',
202+
reason: (unorderedResponses[
203+
errRespIndex
204+
] as JSONRPCErrorResponse).error,
205+
})
206+
// update the error start index
207+
errorStartIndex = errRespIndex
208+
} else {
209+
responses.push({
210+
status: 'fulfilled',
211+
value: (unorderedResponses[index] as JSONRPCSuccessResponse)
212+
.result,
213+
})
214+
}
143215
}
144216
return responses
145217
}

Diff for: src/dto/errors.test.ts

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { assertEquals } from 'std/assert/mod.ts'
2+
import { isJSONRPCError } from './errors.ts'
3+
4+
Deno.test('isJSONRPCError', () => {
5+
assertEquals(
6+
isJSONRPCError({
7+
id: null,
8+
code: 6,
9+
message: 'null',
10+
}),
11+
true,
12+
)
13+
14+
assertEquals(
15+
isJSONRPCError({
16+
code: 6,
17+
message: 'null',
18+
}),
19+
false,
20+
)
21+
22+
assertEquals(
23+
isJSONRPCError({
24+
id: 6,
25+
message: 'null',
26+
}),
27+
false,
28+
)
29+
30+
assertEquals(
31+
isJSONRPCError({
32+
id: 6,
33+
code: 6,
34+
message: null,
35+
}),
36+
false,
37+
)
38+
})

Diff for: src/dto/errors.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// deno-lint-ignore-file no-explicit-any
2+
import { isJSONRPCID } from '../id.ts'
13
/**
24
* The error codes from and including -32768 to -32000 are reserved for pre-defined errors. Any code within this range, but not defined explicitly below is reserved for future use. The error codes are nearly the same as those suggested for XML-RPC at the following url: http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
35
*/
@@ -54,7 +56,8 @@ export function isJSONRPCError(x: unknown): x is JSONRPCErrorInterface {
5456
}
5557
if (
5658
typeof Reflect.get(x, 'code') === 'number' &&
57-
typeof Reflect.get(x, 'message') === 'string'
59+
typeof Reflect.get(x, 'message') === 'string' &&
60+
isJSONRPCID(Reflect.get(x, 'id'))
5861
) {
5962
return true
6063
}

0 commit comments

Comments
 (0)