Skip to content

Commit 2acb41a

Browse files
committed
fix json stringify bug && add docs
1 parent 9741855 commit 2acb41a

File tree

11 files changed

+196
-127
lines changed

11 files changed

+196
-127
lines changed

src/client.ts

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type JSONRPCMethodSet } from './types.ts'
1+
import type { JSONRPCMethodSet, JSONRPCSettledResult } from './types.ts'
22
import { JSONRPCNotification, JSONRPCRequest } from './dto/request.ts'
33
import { JSONRPCErrorResponse, JSONRPCSuccessResponse } from './dto/response.ts'
44
import { isJSONRPCResponse, JSONRPCResponse } from './dto/response.ts'
@@ -14,7 +14,7 @@ type JSONRPCAnyRequest =
1414
| Array<JSONRPCNotification | JSONRPCRequest>
1515

1616
/**
17-
* the client cannot parse the server response
17+
* The client cannot parse the server response
1818
*/
1919
export class JSONRPCClientParseError extends Error {
2020
name = 'JSONRPCClientParseError'
@@ -26,7 +26,7 @@ export class JSONRPCClientParseError extends Error {
2626
}
2727

2828
/**
29-
* just wrap the JSON.parse function
29+
* Just wrap the JSON.parse function, with potential `JSONRPCClientParseError`
3030
*/
3131
function parseJSON(
3232
text: string,
@@ -42,13 +42,22 @@ function parseJSON(
4242
}
4343
}
4444

45+
/**
46+
* Provide a external `processor` function to the constructor,
47+
* it should accept a string (json encoded from one or more json rpc request)
48+
* and your customized code should send this string to a json rpc server
49+
* for any response represented as **string**
50+
*
51+
* The constructor optionally accept a customized id generator, otherwise it use a
52+
* self added number
53+
*/
4554
export class JSONRPCClient<MethodSet extends JSONRPCMethodSet> {
4655
/**
4756
* MUST be an infinite iterator
4857
*/
4958
private idGenerator: IDGenerator
5059
/**
51-
* the extern function to request the server for response
60+
* The extern function to request the server for response
5261
*/
5362
private processor: (input: string) => Promise<string>
5463

@@ -84,19 +93,9 @@ export class JSONRPCClient<MethodSet extends JSONRPCMethodSet> {
8493
return notification
8594
}
8695

87-
private processOneJsonValue(
88-
jsonValue: unknown,
89-
associatedRequest: JSONRPCAnyRequest,
90-
): JSONRPCResponse {
91-
if (!isJSONRPCResponse(jsonValue)) {
92-
throw new JSONRPCClientParseError(
93-
`The server sent an incorrect response object`,
94-
associatedRequest,
95-
)
96-
}
97-
return jsonValue
98-
}
99-
96+
/**
97+
* Send `JSONRPCRequest` to server, returns `JSONRPCValue` or throw `JSONRPCErrorInterface` (or `JSONRPCClientParseError`)
98+
*/
10099
async request<T extends keyof MethodSet>(
101100
method: T extends string ? T : never,
102101
params?: Parameters<MethodSet[T]>[0],
@@ -105,15 +104,29 @@ export class JSONRPCClient<MethodSet extends JSONRPCMethodSet> {
105104
// responsed json string
106105
const jsonString = await this.processor(JSON.stringify(request))
107106
const jsonValue = parseJSON(jsonString, request)
107+
108108
// parsed response
109-
const response = this.processOneJsonValue(jsonValue, request)
109+
if (!isJSONRPCResponse(jsonValue)) {
110+
throw new JSONRPCClientParseError(
111+
`The server sent an incorrect response object`,
112+
request,
113+
)
114+
}
115+
// now jsonValue become JSONRPCResponse
116+
const response: JSONRPCResponse = jsonValue
117+
110118
if ('error' in response) {
111-
return Promise.reject(response.error)
119+
throw response.error
112120
} else {
113-
return response.result
121+
// response.result is now JSONRPCValue
122+
return response.result as ReturnType<MethodSet[T]>
114123
}
115124
}
116125

126+
/**
127+
* Send `JSONRPCNotification` to server, no returns,
128+
* only throws if your provided `processor` function throws
129+
*/
117130
async notify<T extends keyof MethodSet>(
118131
method: T extends string ? T : never,
119132
params?: Parameters<MethodSet[T]>[0],
@@ -123,13 +136,12 @@ export class JSONRPCClient<MethodSet extends JSONRPCMethodSet> {
123136
}
124137

125138
/**
126-
* You should use the createRequest() or createNotifaction() method to
139+
* You should use the `createRequest()` or `createNotifaction()` method to
127140
* create the requests array
128141
*/
129142
async batch(
130143
...requests: Array<JSONRPCRequest | JSONRPCNotification>
131-
// deno-lint-ignore no-explicit-any
132-
): Promise<PromiseSettledResult<any>[] | PromiseSettledResult<any>> {
144+
): Promise<JSONRPCSettledResult | JSONRPCSettledResult[]> {
133145
// responsed json string
134146
const jsonString = await this.processor(JSON.stringify(requests))
135147
const requestCount = requests.filter((r) => 'id' in r).length
@@ -166,7 +178,7 @@ export class JSONRPCClient<MethodSet extends JSONRPCMethodSet> {
166178

167179
if (!jsonValue.every(isJSONRPCResponse)) {
168180
throw new JSONRPCClientParseError(
169-
`The server returned batch response contains invalid value`,
181+
`The server returned batch response contains invalid one`,
170182
requests,
171183
)
172184
}

src/dto/errors.test.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,12 @@ import { isJSONRPCError } from './errors.ts'
44
Deno.test('isJSONRPCError', () => {
55
assertEquals(
66
isJSONRPCError({
7-
id: null,
87
code: 6,
98
message: 'null',
109
}),
1110
true,
1211
)
1312

14-
assertEquals(
15-
isJSONRPCError({
16-
code: 6,
17-
message: 'null',
18-
}),
19-
false,
20-
)
21-
2213
assertEquals(
2314
isJSONRPCError({
2415
id: 6,

src/dto/errors.ts

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
// deno-lint-ignore-file no-explicit-any
2-
import { isJSONRPCID } from '../id.ts'
1+
import type { JSONRPCValue } from '../types.ts'
32
/**
43
* 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
54
*/
@@ -19,45 +18,34 @@ export interface JSONRPCErrorInterface {
1918
* This may be omitted.
2019
* The value of this member is defined by the Server (e.g. detailed error information, nested errors etc.).
2120
*/
22-
data?: any
21+
data?: JSONRPCValue
2322
}
2423

2524
export class JSONRPCError extends Error implements JSONRPCErrorInterface {
2625
public code: number
2726
public message: string
28-
public data?: any
27+
public data?: JSONRPCValue
2928

3029
public constructor(object: JSONRPCErrorInterface) {
3130
super(object.message)
3231
this.code = object.code
3332
this.message = object.message
3433
this.data = object.data
3534
}
36-
37-
public toString() {
38-
return JSON.stringify({
39-
code: this.code,
40-
message: this.message,
41-
data: this.data,
42-
})
43-
}
44-
45-
public toJSON() {
46-
return this.toString()
47-
}
4835
}
4936

37+
/**
38+
* This ONLY check if `x` is `JSONRPCErrorInterface`
39+
*
40+
* Because it'a shared method between client and server, but `JSONRPCError` is server side only
41+
*/
5042
export function isJSONRPCError(x: unknown): x is JSONRPCErrorInterface {
5143
if (!x || typeof x !== 'object') {
5244
return false
5345
}
54-
if (x instanceof JSONRPCError) {
55-
return true
56-
}
5746
if (
5847
typeof Reflect.get(x, 'code') === 'number' &&
59-
typeof Reflect.get(x, 'message') === 'string' &&
60-
isJSONRPCID(Reflect.get(x, 'id'))
48+
typeof Reflect.get(x, 'message') === 'string'
6149
) {
6250
return true
6351
}
@@ -66,7 +54,7 @@ export function isJSONRPCError(x: unknown): x is JSONRPCErrorInterface {
6654
}
6755

6856
export class JSONRPCParseError extends JSONRPCError {
69-
constructor(data?: any) {
57+
constructor(data?: JSONRPCValue) {
7058
super({
7159
code: -32700,
7260
message: 'Parse error',
@@ -76,7 +64,7 @@ export class JSONRPCParseError extends JSONRPCError {
7664
}
7765

7866
export class JSONRPCInvalidRequestError extends JSONRPCError {
79-
constructor(data?: any) {
67+
constructor(data?: JSONRPCValue) {
8068
super({
8169
code: -32600,
8270
message: 'Invalid Request',
@@ -86,7 +74,7 @@ export class JSONRPCInvalidRequestError extends JSONRPCError {
8674
}
8775

8876
export class JSONRPCMethodNotFoundError extends JSONRPCError {
89-
constructor(data?: any) {
77+
constructor(data?: JSONRPCValue) {
9078
super({
9179
code: -32601,
9280
message: 'Method not found',
@@ -96,7 +84,7 @@ export class JSONRPCMethodNotFoundError extends JSONRPCError {
9684
}
9785

9886
export class JSONRPCInvalidParamsError extends JSONRPCError {
99-
constructor(data?: any) {
87+
constructor(data?: JSONRPCValue) {
10088
super({
10189
code: -32602,
10290
message: 'Invalid params',
@@ -106,7 +94,7 @@ export class JSONRPCInvalidParamsError extends JSONRPCError {
10694
}
10795

10896
export class JSONRPCInternalError extends JSONRPCError {
109-
constructor(data?: any) {
97+
constructor(data?: JSONRPCValue) {
11098
super({
11199
code: -32603,
112100
message: 'Internal error',

src/dto/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './errors.ts'
2+
export * from './request.ts'
3+
export * from './response.ts'

src/dto/request.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
import type { JSONRPCValue, WithOptionalJSONRPCVersion } from '../types.ts'
12
import { isJSONRPCID, type JSONRPCID } from '../id.ts'
23

4+
/**
5+
* You should create this object via method from `JSONRPCClient`, it generate id for you.
6+
*
7+
* It's less likely that you need to create this object manually.
8+
*/
39
export class JSONRPCNotification {
410
public jsonrpc = '2.0' as const
511
/**
@@ -9,15 +15,11 @@ export class JSONRPCNotification {
915
/**
1016
* A Structured value that holds the parameter values to be used during the invocation of the method. This member MAY be omitted.
1117
*/
12-
// deno-lint-ignore no-explicit-any
13-
public params?: any
18+
public params?: JSONRPCValue
1419

15-
public constructor(object: {
16-
jsonrpc?: '2.0'
17-
method: string
18-
// deno-lint-ignore no-explicit-any
19-
params?: any
20-
}) {
20+
public constructor(
21+
object: WithOptionalJSONRPCVersion<JSONRPCNotification>,
22+
) {
2123
this.method = object.method
2224
this.params = object.params
2325
}
@@ -31,19 +33,18 @@ export class JSONRPCNotification {
3133
}
3234
}
3335

36+
/**
37+
* You should create this object via method from `JSONRPCClient`, it generate id for you.
38+
*
39+
* It's less likely that you need to create this object manually.
40+
*/
3441
export class JSONRPCRequest extends JSONRPCNotification {
3542
/**
3643
* An identifier established by the Client that MUST contain a String, Number, or NULL value if included. If it is not included it is assumed to be a notification. The value SHOULD normally not be Null and Numbers SHOULD NOT contain fractional parts
3744
*/
3845
public id: JSONRPCID
3946

40-
public constructor(object: {
41-
jsonrpc?: '2.0'
42-
method: string
43-
// deno-lint-ignore no-explicit-any
44-
params?: any
45-
id: JSONRPCID
46-
}) {
47+
public constructor(object: WithOptionalJSONRPCVersion<JSONRPCRequest>) {
4748
super(object)
4849
this.id = object.id
4950
}
@@ -59,18 +60,17 @@ export class JSONRPCRequest extends JSONRPCNotification {
5960
}
6061

6162
/**
62-
* WARN: this check if `x` is JSONRPCNotification or JSONRPCRequest
63-
* to check if `x` is `JSONRPCRequest`, ONLY need to check `'id' in x`
63+
* WARN: this check if `x` is `JSONRPCNotification` or `JSONRPCRequest`
64+
*
65+
* To check if `x` is `JSONRPCRequest`, ONLY need to check `'id' in x`
6466
*/
6567
export function isJSONRPCRequest(
6668
x: unknown,
6769
): x is JSONRPCNotification | JSONRPCRequest {
6870
if (!x || typeof x !== 'object') {
6971
return false
7072
}
71-
if (x instanceof JSONRPCNotification) {
72-
return true
73-
} else if (
73+
if (
7474
Reflect.get(x, 'jsonrpc') === '2.0' &&
7575
typeof Reflect.get(x, 'method') === 'string'
7676
) {

0 commit comments

Comments
 (0)