|
| 1 | +# JSONRPC |
| 2 | +[JSON-RPC 2.0](https://www.jsonrpc.org/specification) client+server implementation in pure V. |
| 3 | + |
| 4 | +## Limitaions |
| 5 | +- Request/Response use only string id |
| 6 | +- JSON-RPC 1.0 incompatible |
| 7 | + |
| 8 | +## Features |
| 9 | +- Request/Response single/batch json encoding/decoding |
| 10 | +- Server to work with any io.ReaderWriter |
| 11 | +- Server automatically manages batch Requests and builds batch Response |
| 12 | +- Client to work with any io.ReaderWriter |
| 13 | +- Interceptors for custom events, raw Request, Request, Response, raw Response |
| 14 | + |
| 15 | +## Usage |
| 16 | +### Request/Response operations |
| 17 | +For both Request/Response constructors are provided and must be used for initialization. |
| 18 | +```v |
| 19 | +import net.jsonrpc |
| 20 | +
|
| 21 | +// jsonrpc.new_request(method, params, id) |
| 22 | +mut req := jsonrpc.new_request('kv.create', { |
| 23 | + 'key': 'key' |
| 24 | + 'value': 'value' |
| 25 | +}, 'kv.create.1') |
| 26 | +
|
| 27 | +println(req.encode()) |
| 28 | +// '{"jsonrpc":"2.0","method":"kv.create","params":{"key":"key","value":"value"},"id":"kv.create.1"}' |
| 29 | +
|
| 30 | +// jsonrpc.new_response(result, error, id) |
| 31 | +mut resp := jsonrpc.new_response({ |
| 32 | + 'key': 'key' |
| 33 | + 'value': 'value' |
| 34 | +}, jsonrpc.ResponseError{}, 'kv.create.1') |
| 35 | +
|
| 36 | +println(resp.encode()) |
| 37 | +// '{"jsonrpc":"2.0","result":{"key":"key","value":"value"},"id":"kv.create.1"}' |
| 38 | +``` |
| 39 | +To create a Notification, pass empty string as `Request.id` (`jsonrpc.Empty{}.str()` or |
| 40 | +`jsonrpc.empty.str()` can be used) |
| 41 | +(e.g. `jsonrpc.new_reponse('method', 'params', jsonrpc.empty.str())`). |
| 42 | +To omit Response.params in encoded json string pass `jsonrpc.Empty{}` or `jsonrpc.empty` as |
| 43 | +value in constructor (e.g. `jsonrpc.new_reponse('method', jsonrpc.empty, 'id')`). |
| 44 | +For Response only result or error fields can exist at the same time and not both simultaniously. |
| 45 | +If error passed to `jsonrpc.new_response()` the result value will be ignored on `Response.encode()`. |
| 46 | +The error field is not generated if `jsonrpc.ResponseError{}` provided as error into |
| 47 | +`jsonrpc.new_response()` (e.g. `jsonrpc.new_response("result", jsonrpc.ResponseError{}, "id")`). |
| 48 | +If the empty string passed as `Result.id` it will use `jsonrpc.null` as id (translates to json null) |
| 49 | + |
| 50 | +### Client |
| 51 | +For full usage check client in [example](examples/client.v) |
| 52 | +```v |
| 53 | +import net |
| 54 | +import net.jsonrpc |
| 55 | +
|
| 56 | +addr := '127.0.0.1:42228' |
| 57 | +mut stream := net.dial_tcp(addr)! |
| 58 | +mut c := jsonrpc.new_client(jsonrpc.ClientConfig{ |
| 59 | + stream: stream |
| 60 | +}) |
| 61 | +
|
| 62 | +c.notify('kv.create', { |
| 63 | + 'key': 'bazz' |
| 64 | + 'value': 'barr' |
| 65 | +})! |
| 66 | +``` |
| 67 | +Client can work with any `io.ReaderWriter` provided into stream field value. |
| 68 | + |
| 69 | +### Server |
| 70 | +For ready key/value im-memory storage realized with server check this [example](examples/main.v) |
| 71 | +```v |
| 72 | +import net |
| 73 | +import net.jsonrpc |
| 74 | +
|
| 75 | +fn handle_test(req &jsonrpc.Request, mut wr jsonrpc.ResponseWriter) { |
| 76 | + p := req.decode_params[string]() or { |
| 77 | + wr.write_error(jsonrpc.invalid_params) |
| 78 | + return |
| 79 | + } |
| 80 | +
|
| 81 | + wr.write(p) |
| 82 | +} |
| 83 | +
|
| 84 | +fn handle_conn(mut conn net.TcpConn) { |
| 85 | + defer { conn.close() or {} } |
| 86 | +
|
| 87 | + mut srv := jsonrpc.new_server(jsonrpc.ServerConfig{ |
| 88 | + stream: conn |
| 89 | + handler: handle_test |
| 90 | + }) |
| 91 | +
|
| 92 | + srv.start() |
| 93 | +} |
| 94 | +
|
| 95 | +addr := '127.0.0.1:42228' |
| 96 | +mut l := net.listen_tcp(.ip, addr)! |
| 97 | +println('TCP JSON-RPC server on ${addr} (Content-Length framing)') |
| 98 | +
|
| 99 | +for { |
| 100 | + mut c := l.accept()! |
| 101 | + println('Accepted') |
| 102 | + go handle_conn(mut c) |
| 103 | +} |
| 104 | +``` |
| 105 | +Server can work with any `io.ReaderWriter` provided into stream field value. |
| 106 | +Server requires `jsonrpc.Handler = fn(req &jsonrpc.Request, mut wr jsonrpc.ResponseWriter)` |
| 107 | +to pass decoded `jsonrpc.Request` and to write `jsonrpc.Response` into `jsonrpc.ResponseWriter`. |
| 108 | +On Notification Server does call `jsonrpc.Handler` but it ingores written `jsonrpc.Response`. |
| 109 | + |
| 110 | +### Handler |
| 111 | +`jsonrpc.Handler = fn(req &jsonrpc.Request, mut wr jsonrpc.ResponseWriter)` is the function that |
| 112 | +operates the decoded `jsonrpc.Request` and writes `jsonrpc.Response` into `jsonrpc.ResponseWriter`. |
| 113 | +Before every return `wr.write()` or `wr.write_error()` must be called so the server do not stuck |
| 114 | +waiting for `jsonrpc.Response` to be written. Also only `wr.write()` or `wr.write_error()` must |
| 115 | +be called before return and not both. |
| 116 | + |
| 117 | +### Router |
| 118 | +The simple `jsonrpc.Router` is provided to register `jsonrpc.Handler` to handle specific method. |
| 119 | +The `jsonrpc.Router.handle_jsonrpc` must be passed into `jsonrpc.Server.handler` to handle requests. |
| 120 | +If `jsonrpc.Request.method` has no registered `jsonrpc.Handler`, the router will respond |
| 121 | +with `jsonrpc.method_not_found` error |
| 122 | + |
| 123 | +### Interceptors |
| 124 | +Both `jsonrpc.Client` and `jsonrpc.Server` support `jsonrpc.Interceptors` - the collection of |
| 125 | +on event handlers. There is implementation of all supported interceptors called |
| 126 | +`jsonrpc.LoggingInterceptor`. |
0 commit comments