289 lines
8.5 KiB
V
289 lines
8.5 KiB
V
module jsonrpc
|
|
|
|
import json
|
|
|
|
pub const version = '2.0'
|
|
|
|
// ---- error helpers ----
|
|
|
|
pub struct ResponseError {
|
|
pub mut:
|
|
code int
|
|
message string
|
|
data string
|
|
}
|
|
|
|
// code returns `jsonrpc.ResponseError.code` field value
|
|
pub fn (err ResponseError) code() int {
|
|
return err.code
|
|
}
|
|
|
|
// msg returns `jsonrpc.ResponseError.message` field value
|
|
pub fn (err ResponseError) msg() string {
|
|
return err.message
|
|
}
|
|
|
|
// err returns `jsonrpc.ResponseError` casted to `IError`
|
|
pub fn (e ResponseError) err() IError {
|
|
return IError(e)
|
|
}
|
|
|
|
// ResponseErrorGeneratorParams & response_error are used by server.v :contentReference[oaicite:2]{index=2}
|
|
@[params]
|
|
pub struct ResponseErrorGeneratorParams {
|
|
error IError @[required]
|
|
data string
|
|
}
|
|
|
|
// response_error returns `jsonrpc.ResponseError` created from passed error and data
|
|
@[inline]
|
|
pub fn response_error(params ResponseErrorGeneratorParams) ResponseError {
|
|
return ResponseError{
|
|
code: params.error.code()
|
|
message: params.error.msg()
|
|
data: params.data
|
|
}
|
|
}
|
|
|
|
// error_with_code returns `jsonrpc.ResponseError` with empty data field
|
|
pub fn error_with_code(message string, code int) ResponseError {
|
|
return ResponseError{
|
|
code: code
|
|
message: message
|
|
data: ''
|
|
}
|
|
}
|
|
|
|
// JSON-RPC standard-ish errors :contentReference[oaicite:3]{index=3}
|
|
|
|
pub const parse_error = error_with_code('Invalid JSON.', -32700)
|
|
pub const invalid_request = error_with_code('Invalid request.', -32600)
|
|
pub const method_not_found = error_with_code('Method not found.', -32601)
|
|
pub const invalid_params = error_with_code('Invalid params', -32602)
|
|
pub const internal_error = error_with_code('Internal error.', -32693)
|
|
pub const server_error_start = error_with_code('Error occurred when starting server.', -32099)
|
|
pub const server_not_initialized = error_with_code('Server not initialized.', -32002)
|
|
pub const unknown_error = error_with_code('Unknown error.', -32001)
|
|
pub const server_error_end = error_with_code('Error occurred when stopping the server.', -32000)
|
|
pub const error_codes = [
|
|
parse_error.code(),
|
|
invalid_request.code(),
|
|
method_not_found.code(),
|
|
invalid_params.code(),
|
|
internal_error.code(),
|
|
server_error_start.code(),
|
|
server_not_initialized.code(),
|
|
server_error_end.code(),
|
|
unknown_error.code(),
|
|
]
|
|
|
|
// Null represents the null value in JSON.
|
|
pub struct Null {}
|
|
|
|
// str returns string representation of json null: 'null' (can be used for
|
|
// `jsonrpc.Request` id and params as well as `jsonrpc.Response` result and id)
|
|
pub fn (n Null) str() string {
|
|
return 'null'
|
|
}
|
|
|
|
pub const null = Null{}
|
|
|
|
pub struct Empty {}
|
|
|
|
// str returns empty string: '' (can be passed to jsonrpc.new_request as id or params to omit fields on encoding)
|
|
pub fn (e Empty) str() string {
|
|
return ''
|
|
}
|
|
|
|
pub const empty = Empty{}
|
|
|
|
// ---- request/response ----
|
|
|
|
// Request uses raw JSON strings for id and params in the old VLS code. :contentReference[oaicite:4]{index=4}
|
|
pub struct Request {
|
|
pub:
|
|
jsonrpc string = version
|
|
method string
|
|
params string @[omitempty; raw] // raw JSON object/array/null
|
|
id string @[omitempty] // raw JSON (e.g. 1 or "abc") if empty => notification (no id field)
|
|
}
|
|
|
|
// new_request is the constructor for Request. ALWAYS use this to initialize new Request.
|
|
// if id is empty string ('') then the Request will be notification (no id field on encode).
|
|
// Pass jsonrpc.Empty{} as params to not generate params field on encode.
|
|
// jsonrpc.null can be used as params to set params field to json null on encode.
|
|
// Limitations: id is always string.
|
|
pub fn new_request[T](method string, params T, id string) Request {
|
|
return Request{
|
|
method: method
|
|
params: try_encode(params)
|
|
id: id
|
|
}
|
|
}
|
|
|
|
// (req Request) encode() returns json string representing Request.
|
|
// In returning string params field can be omited if in new_request was passed jsonrpc.Empty{} as params.
|
|
// In returning string id field can be omited if in new_request was passed empty string ('') as id.
|
|
pub fn (req Request) encode() string {
|
|
params_payload := if req.params.len == 0 {
|
|
''
|
|
} else {
|
|
',"params":${req.params}'
|
|
}
|
|
id_payload := if req.id.len != 0 { ',"id":"${req.id}"' } else { '' }
|
|
return '{"jsonrpc":"${version}","method":"${req.method}"${params_payload}${id_payload}}'
|
|
}
|
|
|
|
// encode_batch loops through every `jsonrpc.Request` in array, calls encode() for each of them
|
|
// and writes into json list [] splitting with ','
|
|
pub fn (reqs []Request) encode_batch() string {
|
|
if reqs.len == 0 {
|
|
return '[]'
|
|
}
|
|
mut s := '[' + reqs[0].encode()
|
|
for req in reqs[1..] {
|
|
s = s + ',' + req.encode()
|
|
}
|
|
return s + ']'
|
|
}
|
|
|
|
// decode_params tries to decode Request.params into provided type
|
|
pub fn (req Request) decode_params[T]() !T {
|
|
return try_decode[T](req.params)
|
|
}
|
|
|
|
// decode_request decodes raw request into JSONRPC Request by reading after \r\n\r\n.
|
|
pub fn decode_request(raw string) !Request {
|
|
json_payload := raw.all_after('\r\n\r\n')
|
|
return json.decode(Request, json_payload) or { return err }
|
|
}
|
|
|
|
// decode_batch_request decodes raw batch request into []jsonrpc.Request by reading after \r\n\r\n.
|
|
pub fn decode_batch_request(raw string) ![]Request {
|
|
json_payload := raw.all_after('\r\n\r\n')
|
|
return json.decode([]Request, json_payload) or { return err }
|
|
}
|
|
|
|
pub struct Response {
|
|
pub:
|
|
jsonrpc string = version
|
|
result string @[raw]
|
|
error ResponseError
|
|
id string
|
|
}
|
|
|
|
// new_response is the constructor for Response. ALWAYS use this to initialize new Response.
|
|
// if id is empty string ('') then the Result id will be a json null.
|
|
// Pass jsonrpc.ResponseError{} as error to not generate error field on encode.
|
|
// jsonrpc.null can be used as result to set field to json null on encode.
|
|
// Limitations: id is always string.
|
|
pub fn new_response[T](result T, error ResponseError, id string) Response {
|
|
return Response{
|
|
result: if error.code != 0 { '' } else { try_encode(result) }
|
|
error: error
|
|
id: id
|
|
}
|
|
}
|
|
|
|
// encode() returns json string representing Response.
|
|
// In returning string result field only generates if in new_response was passed `jsonrpc.ResponseError{}` as error value.
|
|
// In returning string id field will be json null if in new_request was passed empty string ('') as id.
|
|
pub fn (resp Response) encode() string {
|
|
mut s := '{"jsonrpc":"${version}"'
|
|
if resp.error.code != 0 {
|
|
s = s + ',"error":' + json.encode(resp.error)
|
|
} else {
|
|
s = s + ',"result":' + resp.result
|
|
}
|
|
s = s + ',"id":'
|
|
if resp.id.len == 0 {
|
|
s = s + null.str()
|
|
} else {
|
|
s = s + '"${resp.id}"'
|
|
}
|
|
return s + '}'
|
|
}
|
|
|
|
// encode_batch loops through every `jsonrpc.Response` in array, calls encode() for each of them
|
|
// and writes into json list [] splitting with ','
|
|
pub fn (resps []Response) encode_batch() string {
|
|
if resps.len == 0 {
|
|
return '[]'
|
|
}
|
|
mut s := '[' + resps[0].encode()
|
|
for resp in resps[1..] {
|
|
s = s + ',' + resp.encode()
|
|
}
|
|
return s + ']'
|
|
}
|
|
|
|
// decode_params tries to decode Response.result into provided type
|
|
pub fn (resp Response) decode_result[T]() !T {
|
|
return try_decode[T](resp.result)
|
|
}
|
|
|
|
// decode_response decodes raw response into JSONRPC Response by reading after \r\n\r\n.
|
|
pub fn decode_response(raw string) !Response {
|
|
json_payload := raw.all_after('\r\n\r\n')
|
|
return json.decode(Response, json_payload) or { return err }
|
|
}
|
|
|
|
// decode_batch_response decodes raw batch request into []jsonrpc.Request by reading after \r\n\r\n.
|
|
pub fn decode_batch_response(raw string) ![]Response {
|
|
json_payload := raw.all_after('\r\n\r\n')
|
|
return json.decode([]Response, json_payload) or { return err }
|
|
}
|
|
|
|
// try_encode tries to encode passed value to json object, array or primitive
|
|
// currently only for internal use
|
|
fn try_encode[T](data T) string {
|
|
return $if data is string {
|
|
'"${data}"'
|
|
} $else $if data is bool {
|
|
data.str()
|
|
} $else $if data is int {
|
|
data.str()
|
|
} $else $if data is Null {
|
|
data.str()
|
|
} $else $if data is Empty {
|
|
data.str()
|
|
} $else {
|
|
json.encode(data)
|
|
}
|
|
}
|
|
|
|
fn try_decode[T](s string) !T {
|
|
$if T is string {
|
|
if s[0] == `"` && s[s.len - 1] == `"` {
|
|
return s.find_between('"', '"\0')
|
|
}
|
|
return error('Could not decode data=${s} into type string')
|
|
} $else $if T is bool {
|
|
if s == 'true' {
|
|
return true
|
|
}
|
|
if s == 'false' {
|
|
return false
|
|
}
|
|
return error('Could not decode data=${s} into type bool')
|
|
} $else $if T is int {
|
|
res := s.int()
|
|
if res == 0 && s != '0' {
|
|
return error('Could not decode data=${s} into type int')
|
|
}
|
|
return res
|
|
} $else $if T is Null {
|
|
if s != null.str() {
|
|
return error('Could not decode data=${s} into type bool')
|
|
}
|
|
return null
|
|
} $else $if T is Empty {
|
|
if s.len == 0 {
|
|
return Empty{}
|
|
}
|
|
return error('Params not empty: data=${s}')
|
|
} $else {
|
|
return json.decode(T, s) or { return err }
|
|
}
|
|
}
|