v2 / vlib / net / jsonrpc / jsonrpc.v
288 lines · 253 sloc · 8.49 KB · e2e5cf8db56f3562c7baa735061690be936bdf3e
Raw
1module jsonrpc
2
3import json
4
5pub const version = '2.0'
6
7// ---- error helpers ----
8
9pub struct ResponseError {
10pub mut:
11 code int
12 message string
13 data string
14}
15
16// code returns `jsonrpc.ResponseError.code` field value
17pub fn (err ResponseError) code() int {
18 return err.code
19}
20
21// msg returns `jsonrpc.ResponseError.message` field value
22pub fn (err ResponseError) msg() string {
23 return err.message
24}
25
26// err returns `jsonrpc.ResponseError` casted to `IError`
27pub fn (e ResponseError) err() IError {
28 return IError(e)
29}
30
31// ResponseErrorGeneratorParams & response_error are used by server.v :contentReference[oaicite:2]{index=2}
32@[params]
33pub struct ResponseErrorGeneratorParams {
34 error IError @[required]
35 data string
36}
37
38// response_error returns `jsonrpc.ResponseError` created from passed error and data
39@[inline]
40pub fn response_error(params ResponseErrorGeneratorParams) ResponseError {
41 return ResponseError{
42 code: params.error.code()
43 message: params.error.msg()
44 data: params.data
45 }
46}
47
48// error_with_code returns `jsonrpc.ResponseError` with empty data field
49pub fn error_with_code(message string, code int) ResponseError {
50 return ResponseError{
51 code: code
52 message: message
53 data: ''
54 }
55}
56
57// JSON-RPC standard-ish errors :contentReference[oaicite:3]{index=3}
58
59pub const parse_error = error_with_code('Invalid JSON.', -32700)
60pub const invalid_request = error_with_code('Invalid request.', -32600)
61pub const method_not_found = error_with_code('Method not found.', -32601)
62pub const invalid_params = error_with_code('Invalid params', -32602)
63pub const internal_error = error_with_code('Internal error.', -32693)
64pub const server_error_start = error_with_code('Error occurred when starting server.', -32099)
65pub const server_not_initialized = error_with_code('Server not initialized.', -32002)
66pub const unknown_error = error_with_code('Unknown error.', -32001)
67pub const server_error_end = error_with_code('Error occurred when stopping the server.', -32000)
68pub const error_codes = [
69 parse_error.code(),
70 invalid_request.code(),
71 method_not_found.code(),
72 invalid_params.code(),
73 internal_error.code(),
74 server_error_start.code(),
75 server_not_initialized.code(),
76 server_error_end.code(),
77 unknown_error.code(),
78]
79
80// Null represents the null value in JSON.
81pub struct Null {}
82
83// str returns string representation of json null: 'null' (can be used for
84// `jsonrpc.Request` id and params as well as `jsonrpc.Response` result and id)
85pub fn (n Null) str() string {
86 return 'null'
87}
88
89pub const null = Null{}
90
91pub struct Empty {}
92
93// str returns empty string: '' (can be passed to jsonrpc.new_request as id or params to omit fields on encoding)
94pub fn (e Empty) str() string {
95 return ''
96}
97
98pub const empty = Empty{}
99
100// ---- request/response ----
101
102// Request uses raw JSON strings for id and params in the old VLS code. :contentReference[oaicite:4]{index=4}
103pub struct Request {
104pub:
105 jsonrpc string = version
106 method string
107 params string @[omitempty; raw] // raw JSON object/array/null
108 id string @[omitempty] // raw JSON (e.g. 1 or "abc") if empty => notification (no id field)
109}
110
111// new_request is the constructor for Request. ALWAYS use this to initialize new Request.
112// if id is empty string ('') then the Request will be notification (no id field on encode).
113// Pass jsonrpc.Empty{} as params to not generate params field on encode.
114// jsonrpc.null can be used as params to set params field to json null on encode.
115// Limitations: id is always string.
116pub fn new_request[T](method string, params T, id string) Request {
117 return Request{
118 method: method
119 params: try_encode(params)
120 id: id
121 }
122}
123
124// (req Request) encode() returns json string representing Request.
125// In returning string params field can be omited if in new_request was passed jsonrpc.Empty{} as params.
126// In returning string id field can be omited if in new_request was passed empty string ('') as id.
127pub fn (req Request) encode() string {
128 params_payload := if req.params.len == 0 {
129 ''
130 } else {
131 ',"params":${req.params}'
132 }
133 id_payload := if req.id.len != 0 { ',"id":"${req.id}"' } else { '' }
134 return '{"jsonrpc":"${version}","method":"${req.method}"${params_payload}${id_payload}}'
135}
136
137// encode_batch loops through every `jsonrpc.Request` in array, calls encode() for each of them
138// and writes into json list [] splitting with ','
139pub fn (reqs []Request) encode_batch() string {
140 if reqs.len == 0 {
141 return '[]'
142 }
143 mut s := '[' + reqs[0].encode()
144 for req in reqs[1..] {
145 s = s + ',' + req.encode()
146 }
147 return s + ']'
148}
149
150// decode_params tries to decode Request.params into provided type
151pub fn (req Request) decode_params[T]() !T {
152 return try_decode[T](req.params)
153}
154
155// decode_request decodes raw request into JSONRPC Request by reading after \r\n\r\n.
156pub fn decode_request(raw string) !Request {
157 json_payload := raw.all_after('\r\n\r\n')
158 return json.decode(Request, json_payload) or { return err }
159}
160
161// decode_batch_request decodes raw batch request into []jsonrpc.Request by reading after \r\n\r\n.
162pub fn decode_batch_request(raw string) ![]Request {
163 json_payload := raw.all_after('\r\n\r\n')
164 return json.decode([]Request, json_payload) or { return err }
165}
166
167pub struct Response {
168pub:
169 jsonrpc string = version
170 result string @[raw]
171 error ResponseError
172 id string
173}
174
175// new_response is the constructor for Response. ALWAYS use this to initialize new Response.
176// if id is empty string ('') then the Result id will be a json null.
177// Pass jsonrpc.ResponseError{} as error to not generate error field on encode.
178// jsonrpc.null can be used as result to set field to json null on encode.
179// Limitations: id is always string.
180pub fn new_response[T](result T, error ResponseError, id string) Response {
181 return Response{
182 result: if error.code != 0 { '' } else { try_encode(result) }
183 error: error
184 id: id
185 }
186}
187
188// encode() returns json string representing Response.
189// In returning string result field only generates if in new_response was passed `jsonrpc.ResponseError{}` as error value.
190// In returning string id field will be json null if in new_request was passed empty string ('') as id.
191pub fn (resp Response) encode() string {
192 mut s := '{"jsonrpc":"${version}"'
193 if resp.error.code != 0 {
194 s = s + ',"error":' + json.encode(resp.error)
195 } else {
196 s = s + ',"result":' + resp.result
197 }
198 s = s + ',"id":'
199 if resp.id.len == 0 {
200 s = s + null.str()
201 } else {
202 s = s + '"${resp.id}"'
203 }
204 return s + '}'
205}
206
207// encode_batch loops through every `jsonrpc.Response` in array, calls encode() for each of them
208// and writes into json list [] splitting with ','
209pub fn (resps []Response) encode_batch() string {
210 if resps.len == 0 {
211 return '[]'
212 }
213 mut s := '[' + resps[0].encode()
214 for resp in resps[1..] {
215 s = s + ',' + resp.encode()
216 }
217 return s + ']'
218}
219
220// decode_params tries to decode Response.result into provided type
221pub fn (resp Response) decode_result[T]() !T {
222 return try_decode[T](resp.result)
223}
224
225// decode_response decodes raw response into JSONRPC Response by reading after \r\n\r\n.
226pub fn decode_response(raw string) !Response {
227 json_payload := raw.all_after('\r\n\r\n')
228 return json.decode(Response, json_payload) or { return err }
229}
230
231// decode_batch_response decodes raw batch request into []jsonrpc.Request by reading after \r\n\r\n.
232pub fn decode_batch_response(raw string) ![]Response {
233 json_payload := raw.all_after('\r\n\r\n')
234 return json.decode([]Response, json_payload) or { return err }
235}
236
237// try_encode tries to encode passed value to json object, array or primitive
238// currently only for internal use
239fn try_encode[T](data T) string {
240 return $if data is string {
241 '"${data}"'
242 } $else $if data is bool {
243 data.str()
244 } $else $if data is int {
245 data.str()
246 } $else $if data is Null {
247 data.str()
248 } $else $if data is Empty {
249 data.str()
250 } $else {
251 json.encode(data)
252 }
253}
254
255fn try_decode[T](s string) !T {
256 $if T is string {
257 if s[0] == `"` && s[s.len - 1] == `"` {
258 return s.find_between('"', '"\0')
259 }
260 return error('Could not decode data=${s} into type string')
261 } $else $if T is bool {
262 if s == 'true' {
263 return true
264 }
265 if s == 'false' {
266 return false
267 }
268 return error('Could not decode data=${s} into type bool')
269 } $else $if T is int {
270 res := s.int()
271 if res == 0 && s != '0' {
272 return error('Could not decode data=${s} into type int')
273 }
274 return res
275 } $else $if T is Null {
276 if s != null.str() {
277 return error('Could not decode data=${s} into type bool')
278 }
279 return null
280 } $else $if T is Empty {
281 if s.len == 0 {
282 return Empty{}
283 }
284 return error('Params not empty: data=${s}')
285 } $else {
286 return json.decode(T, s) or { return err }
287 }
288}
289