Skip to content

Commit 6753241

Browse files
committed
API v2: completely rewritten with EEPs 68, 43
1 parent 7fbe581 commit 6753241

File tree

5 files changed

+945
-677
lines changed

5 files changed

+945
-677
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ priv
44
*.o
55
*.beam
66
*.plt
7+
_build/
8+
rebar3
9+
rebar.lock

README.md

Lines changed: 46 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,160 +1,79 @@
11
JSON-RPC 2.0 for Erlang
22
=======================
33

4-
Transport agnostic library for JSON-RPC 2.0 servers and clients.
4+
Library for JSON-RPC 2.0 using JSON library (EEP 68) from stdlib, logger (OTP 21.0) and Maps (EEP 43).
5+
6+
7+
Dependencies
8+
------------
9+
10+
* Erlang/OTP 27.0+
511

6-
This page contains the manual for the server part, the `jsonrpc2` module. The client part has a
7-
separate module `jsonrpc2_client`. Client docs are yet to be written. For documentation on the
8-
client library, see the source code: [jsonrpc2_client.erl](src/jsonrpc2_client.erl).
912

1013
Features
1114
--------
1215

13-
* can use any JSON encoder and decoder that supports the eep0018 style terms
14-
format,
15-
* transport neutral
16-
* dispatches parsed requests to a simple callback function
16+
* transport neutral;
17+
* dispatches parsed requests to a simple callback function;
1718
* supports an optional callback "map" function for batch requests, e.g. to
18-
support concurrent processing of the requests in a batch,
19-
* handles rpc calls and notifications,
20-
* supports named and unnamed parameters,
19+
support concurrent processing of the requests in a batch or collecting statistics;
20+
* handles rpc calls and notifications;
21+
* supports named and unnamed parameters;
2122
* includes unit tests for all examples in the JSON-RPC 2.0 specification.
2223

23-
Example
24+
Server examples
2425
-------
2526

26-
``` erlang
27-
1> Json = <<"{\"jsonrpc\": \"2.0\", \"method\": \"foo\", \"params\": [1,2,3], \"id\": 1}">>.
28-
<<"{\"jsonroc\": \"2.0\", \"method\": \"foo\", \"params\": [1,2,3], \"id\": 1}">>
29-
2>
30-
2> MyHandler = fun (<<"foo">>, Params) -> lists:reverse(Params);
31-
2> (_, _) -> throw(method_not_found)
32-
2> end.
33-
#Fun<erl_eval.12.82930912>
34-
3>
35-
3> jsonrpc2:handle(Json, MyHandler, fun jiffy:decode/1, fun jiffy:encode/1).
36-
{reply,<<"{\"jsonrpc\":\"2.0\",\"result\":[3,2,1],\"id\":1}">>}
37-
4>
38-
4> jsonrpc2:handle(<<"dummy">>, MyHandler, fun jiffy:decode/1, fun jiffy:encode/1).
39-
{reply,<<"{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"Parse error.\"},\"id\":null}">>}
40-
5>
41-
5> jsonrpc2:handle(<<"{\"x\":42}">>, MyHandler, fun jiffy:decode/1, fun jiffy:encode/1).
42-
{reply,<<"{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid Request.\"},\"id\":null}">>}
43-
```
44-
45-
Types
46-
-----
47-
48-
```Erlang
49-
json() :: true | false | null | binary() | [json()] | {[{binary(), json()}]}.
50-
51-
handlerfun() :: fun((method(), params()) -> json()).
52-
method() :: binary().
53-
params() :: [json()] | {[{binary(), json()}]}.
54-
55-
mapfun() :: fun((fun((A) -> B), [A]) -> [B]). %% the same as lists:map/2
56-
```
57-
58-
Functions
59-
---------
27+
```erlang
28+
1> Body = ~'{"jsonrpc": "2.0", "method": "foo", "params": [1,2,3], "id": 1}'.
29+
<<"{\"jsonrpc\": \"2.0\", \"method\": \"foo\", \"params\": [1,2,3], \"id\": 1}">>
6030

61-
Any of the `jsonrpc2:handle/2,3,4,5` functions can be used to handle JSON-RPC
62-
request by delegating the actual procedure call to a handler callback function.
63-
They all return `{reply, Data}` where Data is a result or an error response or
64-
`noreply` when no response should be sent to the client. The handler callback
65-
function must return a term that can be encoded to JSON using the
66-
representation explained on the page https://github.com/davisp/jiffy#data-format,
67-
as required by jiffy and other compatible JSON parses.
31+
2> Handler = fun(~"foo", Params) -> {ok, lists:reverse(Params)}; (_, _) -> {error, method_not_found} end.
32+
#Fun<erl_eval.41.81571850>
6833

69-
```Erlang
70-
handle(json(), handlerfun()) -> {reply, json()} | noreply
71-
```
34+
3> jsonrpc2:handle(Body, Handler).
35+
{reply,<<"{\"id\":1,\"result\":[3,2,1],\"jsonrpc\":\"2.0\"}">>}
7236

73-
Handles decoded JSON and returns a reply as decoded JSON or noreply. Use
74-
this if you want to handle JSON encoding separately.
37+
4> jsonrpc2:handle(~"dummy", Handler).
38+
=WARNING REPORT==== 10-Aug-2025::13:58:12.336132 ===
39+
Failed to decode request in JSON format: error {invalid_byte,100}
40+
{reply,<<"{\"error\":{\"code\":-32700,\"message\":\"Parse error.\"},\"id\":null,\"jsonrpc\":\"2.0\"}">>}
7541

76-
```Erlang
77-
handle(json(), handlerfun(), mapfun()) -> {reply, json()} | noreply
78-
```
42+
5> jsonrpc2:handle(~'{"x": 42}', Handler).
43+
{reply,<<"{\"error\":{\"code\":-32600,\"message\":\"Invalid Request.\"},\"id\":null,\"jsonrpc\":\"2.0\"}">>}
7944

80-
Like `handle/2`, handles decoded JSON, but takes an extra
81-
"map" function callback to be used instead of `lists:map/2`
82-
for batch processing. The map function should be a function that behaves
83-
similarly to `lists:map/2`, such as the `plists:map/2`
84-
from the plists library for concurrent batch handling.
8545

86-
```Erlang
87-
handle(Req::term(), handlerfun(), JsonDecode::fun(), JsonEncode::fun()) ->
88-
{reply, term()} | noreply
89-
```
46+
6> Term = #{~"jsonrpc" => ~"2.0", ~"method" => ~"foo", ~"params" => [1,2,3], ~"id" => 1}.
47+
#{<<"id">> => 1,<<"jsonrpc">> => <<"2.0">>,
48+
<<"method">> => <<"foo">>,
49+
<<"params">> => [1,2,3]}
9050

91-
Handles JSON as binary or string. Uses the supplied functions
92-
JsonDecode to parse the JSON request and JsonEncode to encode the reply as JSON.
51+
7> jsonrpc2:handle_term(Term, Handler).
52+
{reply,#{id => 1,result => [3,2,1],jsonrpc => <<"2.0">>}}
9353

94-
```Erlang
95-
handle(Req::term(), handlerfun(), mapfun(), JsonDecode::fun(),
96-
JsonEncode::fun()) -> {reply, term()} | noreply
9754
```
9855

99-
Like `handle/4`, but also takes a map function for batch
100-
processing. See `handle/3` above.
10156

102-
Error Handling
57+
Client example
10358
--------------
10459

105-
A requests that is not valid JSON results in a "Parse error" JSON-RPC response.
106-
107-
An invalid JSON-RPC request (though valid JSON) results in an "Invalid Request"
108-
response. In these two cases the handler callback function is never called.
109-
110-
To produce an error response from the handler function, you may throw one of
111-
the exceptions below. They will be caught and turned into a corresponding
112-
JSON-RPC error response.
113-
114-
* `throw(method_not_found)` is reported as "Method not found" (-32601)
115-
* `throw(invalid_params)` is reported as "Invalid params" (-32602)
116-
* `throw(internal_error)` is reported as "Internal error" (-32603)
117-
* `throw(server_error)` is reported as "Server error" (-32000)
118-
119-
If you also want to include `data` in the JSON-RPC error response, throw a pair
120-
with the error type and the data, such as `{internal_error, Data}`.
121-
122-
For your own *application-defined errors*, it is possible to set a custom error
123-
code by throwing a tuple with the atom `jsonrpc2`, an integer error code, a
124-
binary message and optional data.
12560

126-
* `throw({jsonrpc2, Code, Message)`
127-
* `throw({jsonrpc2, Code, Message, Data})`
128-
129-
If any other exception is thrown or an error occurs in the handler, this is
130-
caught, an error message is logged (using the standard error logger
131-
`error_logger:error_msg/2`) and an "Internal error" response is returned.
61+
```erlang
62+
1> RequestsSpec = [
63+
#{method => add, params => [7000, -77]},
64+
#{method => add, params => [-1, 900], id => 0}
65+
].
66+
[#{params => [7000,-77],method => add},
67+
#{id => 0,params => [-1,900],method => add}]
13268

133-
If you're working with already parsed JSON, i.e. you're using `handle/2` or
134-
`handle/3`, you may want to produce an error message that you can use when the
135-
client sends invalid JSON that can't be parsed. Use `jsonrpc2:parseerror()` to
136-
create the appropriate error response for this purpose.
69+
2> TransportFn = fun(Body) -> {ok, {_,_,Resp}} = httpc:request(post, {"http://localhost:8080/rpc", [{"Content-Length", integer_to_binary(iolist_size(Body))}], "application/json", Body}, [], [{body_format, binary}]), Resp end.
70+
#Fun<erl_eval.42.81571850>
13771

138-
Examples:
72+
jsonrpc2_client:multi_call(RequestsSpec, TransportFn, 1).
73+
[{ok,899,undefined}]
13974

140-
```erlang
141-
my_handler(<<"Foo">>, [X, Y]) when is_integer(X), is_integer(Y) ->
142-
{[{<<"Foo says">>}, X + Y + 42}]};
143-
my_handler(<<"Foo">>, _SomeOtherParams) ->
144-
throw(invalid_params);
145-
my_handler(<<"Logout">>, [Username]) ->
146-
throw({jsonrpc2, 123, <<"Not logged in">>});
147-
my_handler(_SomeOtherMethod, _) ->
148-
throw(method_not_found).
14975
```
15076

151-
Compatible JSON parsers
152-
-----------------------
153-
154-
* Jiffy, https://github.com/davisp/jiffy
155-
* erlang-json, https://github.com/hio/erlang-json
156-
* Mochijson2 using ```mochijson2:decode(Bin, [{format, eep18}])```
157-
* Probably more...
15877

15978
Links
16079
-----
@@ -163,11 +82,13 @@ Links
16382
* rjsonrpc2, a "restricted" implementation of JSON-RPC 2.0, https://github.com/imprest/rjsonrpc2
16483
* ejrpc2, another JSON-RPC 2 library, https://github.com/jvliwanag/ejrpc2
16584

85+
16686
License
16787
-------
16888

16989
```
17090
Copyright 2013-2014 Viktor Söderqvist
91+
Copyright 2025 Andy (https://github.com/m-2k)
17192
17293
Licensed under the Apache License, Version 2.0 (the "License");
17394
you may not use this file except in compliance with the License.
@@ -181,15 +102,3 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
181102
See the License for the specific language governing permissions and
182103
limitations under the License.
183104
```
184-
185-
**Author's note:**
186-
The Apache 2.0 is a very permissive license just like MIT and BSD, but as
187-
FSF notes, it includes "certain patent termination and indemnification
188-
provisions", which is a good thing. We (the authours) cannot come to you
189-
(the users) to claim any patents we might have on something in the code.
190-
191-
If you have any compatibility issues with this license, keep in mind that if
192-
you're using this as an external dependency (e.g. with Rebar or Erlang.mk)
193-
you're not actually distributing this dependency anyway. Even if you do
194-
distribute dependencies, they are not actually linked together until they
195-
are loaded and run in the BEAM unless you compile the release with HiPE.

src/jsonrpc2.app.src

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
%% Copyright 2013-2014 Viktor Söderqvist
2+
%% Copyright 2025 Andy (https://github.com/m-2k)
23
%%
34
%% Copying and distribution of this file, with or without modification,
45
%% are permitted in any medium without royalty provided the copyright
@@ -7,7 +8,7 @@
78

89
{application, jsonrpc2, [
910
{description, "JSON-RPC 2.0 request handler"},
10-
{vsn, "0.9.2"},
11+
{vsn, "2.0.0"},
1112
{modules, [jsonrpc2, jsonrpc2_client]},
1213
{registered, []},
1314
{applications, [

0 commit comments

Comments
 (0)