net.jsonrpc #
JSONRPC
JSON-RPC 2.0 client+server implementation in pure V.
Limitaions
- Request/Response use only string id
- JSON-RPC 1.0 incompatible
Features
- Request/Response single/batch json encoding/decoding
- Server to work with any io.ReaderWriter
- Server automatically manages batch Requests and builds batch Response
- Client to work with any io.ReaderWriter
- Interceptors for custom events, raw Request, Request, Response, raw Response
Usage
Request/Response operations
For both Request/Response constructors are provided and must be used for initialization.
import net.jsonrpc
// jsonrpc.new_request(method, params, id)
mut req := jsonrpc.new_request('kv.create', {
'key': 'key'
'value': 'value'
}, 'kv.create.1')
println(req.encode())
// '{"jsonrpc":"2.0","method":"kv.create","params":{"key":"key","value":"value"},"id":"kv.create.1"}'
// jsonrpc.new_response(result, error, id)
mut resp := jsonrpc.new_response({
'key': 'key'
'value': 'value'
}, jsonrpc.ResponseError{}, 'kv.create.1')
println(resp.encode())
// '{"jsonrpc":"2.0","result":{"key":"key","value":"value"},"id":"kv.create.1"}'
To create a Notification, pass empty string as Request.id (jsonrpc.Empty{}.str() or jsonrpc.empty.str() can be used) (e.g. jsonrpc.new_reponse('method', 'params', jsonrpc.empty.str())). To omit Response.params in encoded json string pass jsonrpc.Empty{} or jsonrpc.empty as value in constructor (e.g. jsonrpc.new_reponse('method', jsonrpc.empty, 'id')). For Response only result or error fields can exist at the same time and not both simultaniously. If error passed to jsonrpc.new_response() the result value will be ignored on Response.encode(). The error field is not generated if jsonrpc.ResponseError{} provided as error into jsonrpc.new_response() (e.g. jsonrpc.new_response("result", jsonrpc.ResponseError{}, "id")). If the empty string passed as Result.id it will use jsonrpc.null as id (translates to json null)
Client
For full usage check client in example
import net
import net.jsonrpc
addr := '127.0.0.1:42228'
mut stream := net.dial_tcp(addr)!
mut c := jsonrpc.new_client(jsonrpc.ClientConfig{
stream: stream
})
c.notify('kv.create', {
'key': 'bazz'
'value': 'barr'
})!
Client can work with any io.ReaderWriter provided into stream field value.
Server
For ready key/value im-memory storage realized with server check this example
import net
import net.jsonrpc
fn handle_test(req &jsonrpc.Request, mut wr jsonrpc.ResponseWriter) {
p := req.decode_params[string]() or {
wr.write_error(jsonrpc.invalid_params)
return
}
wr.write(p)
}
fn handle_conn(mut conn net.TcpConn) {
defer { conn.close() or {} }
mut srv := jsonrpc.new_server(jsonrpc.ServerConfig{
stream: conn
handler: handle_test
})
srv.start()
}
addr := '127.0.0.1:42228'
mut l := net.listen_tcp(.ip, addr)!
println('TCP JSON-RPC server on ${addr} (Content-Length framing)')
for {
mut c := l.accept()!
println('Accepted')
go handle_conn(mut c)
}
Server can work with any io.ReaderWriter provided into stream field value. Server requires jsonrpc.Handler = fn(req &jsonrpc.Request, mut wr jsonrpc.ResponseWriter) to pass decoded jsonrpc.Request and to write jsonrpc.Response into jsonrpc.ResponseWriter. On Notification Server does call jsonrpc.Handler but it ingores written jsonrpc.Response.
Handler
jsonrpc.Handler = fn(req &jsonrpc.Request, mut wr jsonrpc.ResponseWriter) is the function that operates the decoded jsonrpc.Request and writes jsonrpc.Response into jsonrpc.ResponseWriter. Before every return wr.write() or wr.write_error() must be called so the server do not stuck waiting for jsonrpc.Response to be written. Also only wr.write() or wr.write_error() must be called before return and not both.
Router
The simple jsonrpc.Router is provided to register jsonrpc.Handler to handle specific method. The jsonrpc.Router.handle_jsonrpc must be passed into jsonrpc.Server.handler to handle requests. If jsonrpc.Request.method has no registered jsonrpc.Handler, the router will respond with jsonrpc.method_not_found error
Interceptors
Both jsonrpc.Client and jsonrpc.Server support jsonrpc.Interceptors - the collection of on event handlers. There is implementation of all supported interceptors called jsonrpc.LoggingInterceptor.
Constants #
const version = '2.0'
const parse_error = error_with_code('Invalid JSON.', -32700)
JSON-RPC standard-ish errors :contentReference[oaicite:3]{index=3}
const invalid_request = error_with_code('Invalid request.', -32600)
const method_not_found = error_with_code('Method not found.', -32601)
const invalid_params = error_with_code('Invalid params', -32602)
const internal_error = error_with_code('Internal error.', -32693)
const server_error_start = error_with_code('Error occurred when starting server.',
-32099)
const server_not_initialized = error_with_code('Server not initialized.', -32002)
const unknown_error = error_with_code('Unknown error.', -32001)
const server_error_end = error_with_code('Error occurred when stopping the server.',
-32000)
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(),
]
const null = Null{}
const empty = Empty{}
fn decode_batch_request #
fn decode_batch_request(raw string) ![]Request
decode_batch_request decodes raw batch request into []jsonrpc.Request by reading after \r\n\r\n.
fn decode_batch_response #
fn decode_batch_response(raw string) ![]Response
decode_batch_response decodes raw batch request into []jsonrpc.Request by reading after \r\n\r\n.
fn decode_request #
fn decode_request(raw string) !Request
decode_request decodes raw request into JSONRPC Request by reading after \r\n\r\n.
fn decode_response #
fn decode_response(raw string) !Response
decode_response decodes raw response into JSONRPC Response by reading after \r\n\r\n.
fn dispatch_event #
fn dispatch_event(ints []EventInterceptor, event_name string, data string)
dispatch_event sends event_name and data to provided jsonrpc.EventInterceptors
fn error_with_code #
fn error_with_code(message string, code int) ResponseError
error_with_code returns jsonrpc.ResponseError with empty data field
fn intercept_encoded_request #
fn intercept_encoded_request(ints []EncodedRequestInterceptor, req []u8) !
intercept_encoded_request sends raw request data before attempting to decode it into jsonrpc.Request to provided jsonrpc.EncodedRequestInterceptors
fn intercept_encoded_response #
fn intercept_encoded_response(ints []EncodedResponseInterceptor, resp []u8)
intercept_encoded_response sends raw encoded data representing jsonrpc.Response to provided jsonrpc.EncodedResponseInterceptors
fn intercept_request #
fn intercept_request(ints []RequestInterceptor, req &Request) !
intercept_request sends decoded jsonrpc.Request to provided jsonrpc.RequestInterceptors
fn intercept_response #
fn intercept_response(ints []ResponseInterceptor, resp &Response)
intercept_response sends decoded jsonrpc.Response to provided jsonrpc.ResponseInterceptors
fn new_client #
fn new_client(cfg ClientConfig) Client
new_client creates new jsonrpc.Client with stream to read/write and interceptors
fn new_request #
fn new_request[T](method string, params T, id string) Request
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.
fn new_response #
fn new_response[T](result T, error ResponseError, id string) Response
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.
fn new_server #
fn new_server(cfg ServerConfig) Server
new_server creates new jsonrpc.Server with stream to read/write, the jsonrpc.Handler to handle Requests/Responses and interceptors
fn response_error #
fn response_error(params ResponseErrorGeneratorParams) ResponseError
response_error returns jsonrpc.ResponseError created from passed error and data
type EncodedRequestInterceptor #
type EncodedRequestInterceptor = fn (req []u8) !
EncodedRequestInterceptor called on jsonrpc.intercept_encoded_request
type EncodedResponseInterceptor #
type EncodedResponseInterceptor = fn (resp []u8)
EncodedResponseInterceptor called on jsonrpc.intercept_encoded_response
type EventInterceptor #
type EventInterceptor = fn (name string, data string)
EventInterceptor called on jsonrpc.dispatch_event
type Handler #
type Handler = fn (req &Request, mut wr ResponseWriter)
Handler is the function called when jsonrpc.Request is decoded and jsonrpc.Response is required which is written into jsonrpc.ResponseWriter. Before returning from Handler either wr.write() or wr.write_error() must be called or the stream will stuck awaiting writing jsonrpc.Response
type RequestInterceptor #
type RequestInterceptor = fn (req &Request) !
RequestInterceptor called on jsonrpc.intercept_request
type ResponseInterceptor #
type ResponseInterceptor = fn (resp &Response)
ResponseInterceptor called on jsonrpc.intercept_response
fn ([]Request) encode_batch #
fn (reqs []Request) encode_batch() string
encode_batch loops through every jsonrpc.Request in array, calls encode() for each of them and writes into json list [] splitting with ','
fn ([]Response) encode_batch #
fn (resps []Response) encode_batch() string
encode_batch loops through every jsonrpc.Response in array, calls encode() for each of them and writes into json list [] splitting with ','
struct Client #
struct Client {
mut:
stream io.ReaderWriter
interceptors Interceptors
}
fn (Client) notify #
fn (mut c Client) notify[T](method string, params T) !
notify sends JSON-RPC 2.0 Notification and returns without waiting for jsonrpc.Response
fn (Client) request #
fn (mut c Client) request[T](method string, params T, id string) !Response
request new jsonrpc.Request and return jsonrpc.Response
fn (Client) batch #
fn (mut c Client) batch(reqs []Request) ![]Response
batch sends batch of jsonrpc.Request and returns batch ofjsonrpc.Response
struct ClientConfig #
struct ClientConfig {
pub mut:
stream io.ReaderWriter
interceptors Interceptors
}
struct Empty #
struct Empty {}
fn (Empty) str #
fn (e Empty) str() string
str returns empty string: '' (can be passed to jsonrpc.new_request as id or params to omit fields on encoding)
struct Interceptors #
struct Interceptors {
pub mut:
event []EventInterceptor
encoded_request []EncodedRequestInterceptor
request []RequestInterceptor
response []ResponseInterceptor
encoded_response []EncodedResponseInterceptor
}
Interceptors collection of all supported interceptors to be called on events
fn (Interceptors) get_interceptor #
fn (i Interceptors) get_interceptor[T]() ?&T
get_interceptor tries to find and return interceptor of provided type from jsonrpc.Interceptors
struct LoggingInterceptor #
struct LoggingInterceptor {
pub mut:
log log.Log
}
LoggingInterceptor is simple logging full-fledged Interceptor messages will be written in log.get_level() Level
fn (LoggingInterceptor) on_event #
fn (mut l LoggingInterceptor) on_event(name string, data string)
on_event logs event name and data into provided log
fn (LoggingInterceptor) on_encoded_request #
fn (mut l LoggingInterceptor) on_encoded_request(req []u8) !
on_encoded_request logs json encoded jsonrpc.Request as string
fn (LoggingInterceptor) on_request #
fn (mut l LoggingInterceptor) on_request(req &Request) !
on_request logs jsonrpc.Request method, params and id
fn (LoggingInterceptor) on_response #
fn (mut l LoggingInterceptor) on_response(resp &Response)
on_response logs jsonrpc.Response result, error and id
fn (LoggingInterceptor) on_encoded_response #
fn (mut l LoggingInterceptor) on_encoded_response(resp []u8)
on_encoded_response logs json encoded jsonrpc.Response as string
struct Null #
struct Null {}
Null represents the null value in JSON.
fn (Null) str #
fn (n Null) str() string
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)
struct Request #
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)
}
Request uses raw JSON strings for id and params in the old VLS code. :contentReference[oaicite:4]{index=4}
fn (Request) encode #
fn (req Request) encode() string
(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.
fn (Request) decode_params #
fn (req Request) decode_params[T]() !T
decode_params tries to decode Request.params into provided type
struct Response #
struct Response {
pub:
jsonrpc string = version
result string @[raw]
error ResponseError
id string
}
fn (Response) encode #
fn (resp Response) encode() string
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.
fn (Response) decode_result #
fn (resp Response) decode_result[T]() !T
decode_params tries to decode Response.result into provided type
struct ResponseError #
struct ResponseError {
pub mut:
code int
message string
data string
}
---- error helpers ----
fn (ResponseError) code #
fn (err ResponseError) code() int
code returns jsonrpc.ResponseError.code field value
fn (ResponseError) msg #
fn (err ResponseError) msg() string
msg returns jsonrpc.ResponseError.message field value
fn (ResponseError) err #
fn (e ResponseError) err() IError
err returns jsonrpc.ResponseError casted to IError
struct ResponseErrorGeneratorParams #
struct ResponseErrorGeneratorParams {
error IError @[required]
data string
}
ResponseErrorGeneratorParams & response_error are used by server.v :contentReference[oaicite:2]{index=2}
struct ResponseWriter #
struct ResponseWriter {
mut:
sb strings.Builder
is_batch bool
server &Server
pub mut:
req_id string
writer io.ReaderWriter
}
fn (ResponseWriter) write #
fn (mut rw ResponseWriter) write[T](payload T)
write payload into jsonrpc.Response.result. call when need to send data in response
fn (ResponseWriter) write_empty #
fn (mut rw ResponseWriter) write_empty()
write_empty writes jsonrpc.null as response
fn (ResponseWriter) write_error #
fn (mut rw ResponseWriter) write_error(err IError)
write_error into the jsonrpc.Response of current request
struct Router #
struct Router {
mut:
methods map[string]Handler
}
Router is simple map of method names and their Handlers
fn (Router) handle_jsonrpc #
fn (r Router) handle_jsonrpc(req &Request, mut wr ResponseWriter)
handle_jsonrpc must be passed into Server handler field to operate it simply tries to invoke registered methods and if none valid found writes jsonrpc.method_not_found error into jsonrpc.ResponseWriter
fn (Router) register #
fn (mut r Router) register(method string, handler Handler) bool
register handler to operate when method found in incoming jsonrpc.Request
struct Server #
struct Server {
mut:
stream io.ReaderWriter
handler Handler @[required]
interceptors Interceptors
}
Server represents a JSONRPC server that sends/receives data from a stream (an io.ReaderWriter) and uses Content-Length framing. :contentReference[oaicite:6]{index=6}
fn (Server) is_interceptor_enabled #
fn (s &Server) is_interceptor_enabled[T]() bool
is_interceptor_enabled checks if interceptor of provided type is enabled on jsonrpc.Server
fn (Server) respond #
fn (mut s Server) respond() !
respond reads bytes from stream, pass them to the interceptors.encoded_request, tries to decode into jsonrpc.Request and pass to interceptors.request and on fail it responds with jsonrpc.parse_error after that it calls handlers (batch requests are handled automatically as well as writing batch response) and passes recieved jsonrpc.Response into interceptors.response and the last step is to encode jsonrpc.Response, pass it into interceptors.encoded_response and write to stream
fn (Server) start #
fn (mut s Server) start()
start Server loop to operate on stream passed into constructor it calls Server.respond() method in loop
struct ServerConfig #
struct ServerConfig {
pub mut:
stream io.ReaderWriter
handler Handler @[required]
interceptors Interceptors
}
- README
- Constants
- fn decode_batch_request
- fn decode_batch_response
- fn decode_request
- fn decode_response
- fn dispatch_event
- fn error_with_code
- fn intercept_encoded_request
- fn intercept_encoded_response
- fn intercept_request
- fn intercept_response
- fn new_client
- fn new_request
- fn new_response
- fn new_server
- fn response_error
- type EncodedRequestInterceptor
- type EncodedResponseInterceptor
- type EventInterceptor
- type Handler
- type RequestInterceptor
- type ResponseInterceptor
- type []Request
- type []Response
- struct Client
- struct ClientConfig
- struct Empty
- struct Interceptors
- struct LoggingInterceptor
- struct Null
- struct Request
- struct Response
- struct ResponseError
- struct ResponseErrorGeneratorParams
- struct ResponseWriter
- struct Router
- struct Server
- struct ServerConfig