Skip to content

Commit

Permalink
Add support for JSON-RPC v2
Browse files Browse the repository at this point in the history
This adds a `version` argument to `Jsonrpc.string_of_{call;response}`,
and updates the `of_string` functions accordingly.

The main changes compared to JSON-RPC v1 are:
1. And added version field: `{jsonrpc: "2.0"}`.
2. Support for named parameters in requests:
   `params: {"name1": "value2", "name2", "value2"}`.
3. Responses have a `result` OR an `error`, but not both.

Regarding (2), since the `RPC.call` type defines its `params` as a `t list`, it
does not have support for named parameters in the type itself. Positional
params are still treated as in V1, by putting them in an array (`params:
[value1, value2]`). To overcome this, we assume that if the first parameter in
this list has an `Rpc.Dict` type, and this is the only parameter, that the
dictionary contains named parameters.

Fixes mirage#32

Signed-off-by: Rob Hoes <[email protected]>
  • Loading branch information
robhoes authored and jonludlam committed Dec 12, 2016
1 parent 4b83d6a commit adc5f3b
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 38 deletions.
131 changes: 98 additions & 33 deletions lib/jsonrpc.ml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

open Rpc

type version = V1 | V2

let rec list_iter_between f o = function
| [] -> ()
| [h] -> f h
Expand Down Expand Up @@ -80,34 +82,66 @@ let new_id =
let count = ref 0L in
(fun () -> count := Int64.add 1L !count; !count)

let string_of_call call =
let json = Dict [
"method", String call.name;
"params", Enum call.params;
"id", Int (new_id ());
] in
let string_of_call ?(version=V1) call =
let json =
match version with
| V1 ->
Dict [
"method", String call.name;
"params", Enum call.params;
"id", Int (new_id ());
]
| V2 ->
let params =
match call.params with
| Dict x :: [] -> Dict x
| _ -> Enum call.params
in
Dict [
"jsonrpc", String "2.0";
"method", String call.name;
"params", params;
"id", Int (new_id ());
]
in
to_string json

let json_of_response response =
let json_of_response version response =
if response.Rpc.success then
Dict [
"result", response.Rpc.contents;
"error", Null;
"id", Int 0L
]
match version with
| V1 ->
Dict [
"result", response.Rpc.contents;
"error", Null;
"id", Int 0L
]
| V2 ->
Dict [
"jsonrpc", String "2.0";
"result", response.Rpc.contents;
"id", Int 0L
]
else
Dict [
"result", Null;
"error", response.Rpc.contents;
"id", Int 0L
]

let string_of_response response =
let json = json_of_response response in
match version with
| V1 ->
Dict [
"result", Null;
"error", response.Rpc.contents;
"id", Int 0L
]
| V2 ->
Dict [
"jsonrpc", String "2.0";
"error", response.Rpc.contents;
"id", Int 0L
]

let string_of_response ?(version=V1) response =
let json = json_of_response version response in
to_string json

let a_of_response ~empty ~append response =
let json = json_of_response response in
let a_of_response ?(version=V1) ~empty ~append response =
let json = json_of_response version response in
to_a ~empty ~append json

type error =
Expand Down Expand Up @@ -504,36 +538,67 @@ let of_a ~next_char b =
exception Malformed_method_request of string
exception Malformed_method_response of string

let get name dict =
let get' name dict =
if List.mem_assoc name dict then
List.assoc name dict
else begin
Some (List.assoc name dict)
else
None

let get name dict =
match get' name dict with
| None ->
if Rpc.get_debug ()
then Printf.eprintf "%s was not found in the dictionary\n" name;
let str = List.map (fun (n,_) -> Printf.sprintf "%s=..." n) dict in
let str = Printf.sprintf "{%s}" (String.concat "," str) in
raise (Malformed_method_request str)
end
| Some v -> v

let call_of_string str =
match of_string str with
| Dict d ->
let name = match get "method" d with String s -> s | _ -> raise (Malformed_method_request str) in
let params = match get "params" d with Enum l -> l | _ -> raise (Malformed_method_request str) in
let params =
match get' "jsonrpc" d with
| None ->
(match get "params" d with Enum l -> l | _ -> raise (Malformed_method_request str))
| Some (String "2.0") ->
begin match get "params" d with
| Enum l -> l
| Dict l -> [Dict l]
| _ -> raise (Malformed_method_request str)
end
| _ ->
raise (Malformed_method_request "jsonrpc")
in
let (_:int64) = match get "id" d with Int i -> i | _ -> raise (Malformed_method_request str) in
call name params
| _ -> raise (Malformed_method_request str)

let response_of_stream str =
match Parser.of_stream str with
| Dict d ->
let result = get "result" d in
let error = get "error" d in
let (_:int64) = match get "id" d with Int i -> i | _ -> raise (Malformed_method_response "id") in
begin match result, error with
| v, Null -> success v
| Null, v -> failure v
| x,y -> raise (Malformed_method_response (Printf.sprintf "<result=%s><error=%s>" (Rpc.to_string x) (Rpc.to_string y)))
begin match get' "jsonrpc" d with
| None ->
let result = get "result" d in
let error = get "error" d in
begin match result, error with
| v, Null -> success v
| Null, v -> failure v
| x,y -> raise (Malformed_method_response (Printf.sprintf "<result=%s><error=%s>" (Rpc.to_string x) (Rpc.to_string y)))
end
| Some (String "2.0") ->
let result = get' "result" d in
let error = get' "error" d in
begin match result, error with
| Some v, None -> success v
| None, Some v -> failure v
| Some x, Some y -> raise (Malformed_method_response (Printf.sprintf "<result=%s><error=%s>" (Rpc.to_string x) (Rpc.to_string y)))
| None, None -> raise (Malformed_method_response (Printf.sprintf "neither <result> nor <error> was found"))
end
| _ ->
raise (Malformed_method_response "jsonrpc")
end
| rpc -> raise (Malformed_method_response (Printf.sprintf "<response_of_stream(%s)>" (to_string rpc)))

Expand Down
11 changes: 6 additions & 5 deletions lib/jsonrpc.mli
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
type version = V1 | V2

val list_iter_between : ('a -> unit) -> (unit -> 'b) -> 'a list -> unit
val escape_string : string -> string
val to_fct : Rpc.t -> (string -> unit) -> unit
val to_buffer : Rpc.t -> Buffer.t -> unit
val to_string : Rpc.t -> string
val to_a : empty:(unit -> 'a) -> append:('a -> string -> unit) -> Rpc.t -> 'a
val new_id : unit -> int64
val string_of_call : Rpc.call -> string
val json_of_response : Rpc.response -> Rpc.t
val string_of_response : Rpc.response -> string
val a_of_response :
empty:(unit -> 'a) -> append:('a -> string -> unit) -> Rpc.response -> 'a
val string_of_call: ?version:version -> Rpc.call -> string
val json_of_response : version -> Rpc.response -> Rpc.t
val string_of_response: ?version:version -> Rpc.response -> string
val a_of_response : ?version:version -> empty:(unit -> 'a) -> append:('a -> string -> unit) -> Rpc.response -> 'a
type error =
Unexpected_char of int * char * string
| Invalid_value of int * string * string
Expand Down

0 comments on commit adc5f3b

Please sign in to comment.