Documentation ¶
Overview ¶
Package jsonrpc2 is a complete and strictly conforming implementation of the JSON-RPC 2.0 protocol for both clients and servers.
The full specification can be found at https://www.jsonrpc.org.
Clients ¶
The simplest way to make a JSON-RPC 2.0 request is to use the provided Client.Request.
var c jsonrpc2.Client params := []float64{1, 2, 3} var result int err := c.Request(nil, "http://localhost:8080", "sum", params, &result) if _, ok := err.(jsonrpc2.Error); ok { // received Error Request } if err != nil { // some JSON marshaling or network error } fmt.Printf("The sum of %v is %v.\n", params, result)
For clients that do not wish to use the provided Client, the Request and Response types can be used directly.
req, _ := json.Marshal(jsonrpc2.Request{Method: "subtract", Params: []int{5, 1}, ID: 0, }) httpRes, _ := http.Post("www.example.com", "application/json", bytes.NewReader(req)) resBytes, _ := ioutil.ReadAll(httpRes.Body) res := jsonrpc2.Response{Result: &MyCustomResultType{}, ID: new(int)} json.Unmarshal(respBytes, &res)
Servers ¶
Servers define their own MethodFuncs and associate them with a name in a MethodMap that is passed to HTTPRequestHandler() to return a corresponding http.HandlerFunc. See HTTPRequestHandler for more details.
func getUser(ctx context.Context, params json.RawMessage) interface{} { var u User if err := json.Unmarshal(params, &u); err != nil { return jsonrpc2.InvalidParams(err) } conn, err := mydbpkg.GetDBConn() if err != nil { // The handler will recover, print debug info if enabled, and // return an Internal Error to the client. panic(err) } if err := u.Select(conn); err != nil { return jsonrpc2.NewError(-30000, "user not found", u.ID) } return u } func StartServer() { methods := jsonrpc2.MethodMap{"version": versionMethod} http.ListenAndServe(":8080", jsonrpc2.HTTPRequestHandler(methods, log.New(os.Stderr, "", 0))) }
Example ¶
This example makes all of the calls from the examples in the JSON-RPC 2.0 specification and prints them in a similar format.
package main import ( "bytes" "context" "encoding/json" "fmt" "io/ioutil" "log" "net/http" "os" "github.com/AccumulateNetwork/jsonrpc2/v15" ) var endpoint = "http://localhost:18888" // Functions for making requests and printing the Requests and Responses. func post(b []byte) []byte { httpResp, _ := http.Post(endpoint, "", bytes.NewReader(b)) respBytes, _ := ioutil.ReadAll(httpResp.Body) return respBytes } func postNewRequest(method string, id, params interface{}) { postRequest(jsonrpc2.Request{Method: method, ID: id, Params: params}) } func postRequest(request interface{}) { fmt.Println(request) reqBytes, _ := json.Marshal(request) respBytes := post(reqBytes) parseResponse(respBytes) } func parseResponse(respBytes []byte) { var response interface{} if len(respBytes) == 0 { return } else if string(respBytes[0]) == "[" { response = &jsonrpc2.BatchResponse{} } else { response = &jsonrpc2.Response{} } if err := json.Unmarshal(respBytes, response); err != nil { fmt.Println(string(respBytes), err) return } fmt.Println(response) fmt.Println() } func postBytes(req string) { fmt.Println("-->", req) respBytes := post([]byte(req)) parseResponse(respBytes) } // The RPC methods called in the JSON-RPC 2.0 specification examples. func subtract(_ context.Context, params json.RawMessage) interface{} { // Parse either a params array of numbers or named numbers params. var a []float64 if err := json.Unmarshal(params, &a); err == nil { if len(a) != 2 { return jsonrpc2.ErrorInvalidParams("Invalid number of array params") } return a[0] - a[1] } var p struct { Subtrahend *float64 Minuend *float64 } if err := json.Unmarshal(params, &p); err != nil || p.Subtrahend == nil || p.Minuend == nil { return jsonrpc2.ErrorInvalidParams(`Required fields "subtrahend" and ` + `"minuend" must be valid numbers.`) } return *p.Minuend - *p.Subtrahend } func sum(_ context.Context, params json.RawMessage) interface{} { var p []float64 if err := json.Unmarshal(params, &p); err != nil { return jsonrpc2.ErrorInvalidParams(err) } sum := float64(0) for _, x := range p { sum += x } return sum } func notifyHello(_ context.Context, _ json.RawMessage) interface{} { return "" } func getData(_ context.Context, _ json.RawMessage) interface{} { return []interface{}{"hello", 5} } // This example makes all of the calls from the examples in the JSON-RPC 2.0 // specification and prints them in a similar format. func main() { // Start the server. go func() { // Register RPC methods. methods := jsonrpc2.MethodMap{ "subtract": subtract, "sum": sum, "notify_hello": notifyHello, "get_data": getData, } jsonrpc2.DebugMethodFunc = true handler := jsonrpc2.HTTPRequestHandler(methods, log.New(os.Stdout, "", 0)) http.ListenAndServe(":18888", handler) }() // Make requests. fmt.Println("Syntax:") fmt.Println("--> data sent to Server") fmt.Println("<-- data sent to Client") fmt.Println("") fmt.Println("rpc call with positional parameters:") postNewRequest("subtract", 1, []int{42, 23}) postNewRequest("subtract", 2, []int{23, 42}) fmt.Println("rpc call with named parameters:") postNewRequest("subtract", 3, map[string]int{"subtrahend": 23, "minuend": 42}) postNewRequest("subtract", 4, map[string]int{"minuend": 42, "subtrahend": 23}) fmt.Println("a Notification:") postNewRequest("update", nil, []int{1, 2, 3, 4, 5}) postNewRequest("foobar", nil, nil) fmt.Println() fmt.Println("rpc call of non-existent method:") postNewRequest("foobar", "1", nil) fmt.Println("rpc call with invalid JSON:") postBytes(`{"jsonrpc":"2.0","method":"foobar,"params":"bar","baz]`) fmt.Println("rpc call with invalid Request object:") postBytes(`{"jsonrpc":"2.0","method":1,"params":"bar"}`) fmt.Println("rpc call Batch, invalid JSON:") postBytes( `[ {"jsonrpc":"2.0","method":"sum","params":[1,2,4],"id":"1"}, {"jsonrpc":"2.0","method" ]`) fmt.Println("rpc call with an empty Array:") postBytes(`[]`) fmt.Println("rpc call with an invalid Batch (but not empty):") postBytes(`[1]`) fmt.Println("rpc call with invalid Batch:") postBytes(`[1,2,3]`) fmt.Println("rpc call Batch:") postBytes(`[ {"jsonrpc":"2.0","method":"sum","params":[1,2,4],"id":"1"}, {"jsonrpc":"2.0","method":"notify_hello","params":[7]}, {"jsonrpc":"2.0","method":"subtract","params":[42,23],"id":"2"}, {"foo":"boo"}, {"jsonrpc":"2.0","method":"foo.get","params":{"name":"myself"},"id":"5"}, {"jsonrpc":"2.0","method":"get_data","id":"9"} ]`) fmt.Println("rpc call Batch (all notifications):") postRequest(jsonrpc2.BatchRequest{ jsonrpc2.Request{Method: "notify_sum", ID: nil, Params: []int{1, 2, 4}}, jsonrpc2.Request{Method: "notify_hello", ID: nil, Params: []int{7}}, }) fmt.Println("<-- //Nothing is returned for all notification batches") }
Output: Syntax: --> data sent to Server <-- data sent to Client rpc call with positional parameters: --> {"jsonrpc":"2.0","method":"subtract","params":[42,23],"id":1} <-- {"jsonrpc":"2.0","result":19,"id":1} --> {"jsonrpc":"2.0","method":"subtract","params":[23,42],"id":2} <-- {"jsonrpc":"2.0","result":-19,"id":2} rpc call with named parameters: --> {"jsonrpc":"2.0","method":"subtract","params":{"minuend":42,"subtrahend":23},"id":3} <-- {"jsonrpc":"2.0","result":19,"id":3} --> {"jsonrpc":"2.0","method":"subtract","params":{"minuend":42,"subtrahend":23},"id":4} <-- {"jsonrpc":"2.0","result":19,"id":4} a Notification: --> {"jsonrpc":"2.0","method":"update","params":[1,2,3,4,5]} --> {"jsonrpc":"2.0","method":"foobar"} rpc call of non-existent method: --> {"jsonrpc":"2.0","method":"foobar","id":"1"} <-- {"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not found","data":"foobar"},"id":"1"} rpc call with invalid JSON: --> {"jsonrpc":"2.0","method":"foobar,"params":"bar","baz] <-- {"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":null} rpc call with invalid Request object: --> {"jsonrpc":"2.0","method":1,"params":"bar"} <-- {"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request","data":"json: cannot unmarshal number into Go struct field jRequest.method of type string"},"id":null} rpc call Batch, invalid JSON: --> [ {"jsonrpc":"2.0","method":"sum","params":[1,2,4],"id":"1"}, {"jsonrpc":"2.0","method" ] <-- {"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":null} rpc call with an empty Array: --> [] <-- {"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request","data":"empty batch request"},"id":null} rpc call with an invalid Batch (but not empty): --> [1] <-- [ {"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request","data":"json: cannot unmarshal number into Go value of type jsonrpc2.jRequest"},"id":null} ] rpc call with invalid Batch: --> [1,2,3] <-- [ {"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request","data":"json: cannot unmarshal number into Go value of type jsonrpc2.jRequest"},"id":null}, {"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request","data":"json: cannot unmarshal number into Go value of type jsonrpc2.jRequest"},"id":null}, {"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request","data":"json: cannot unmarshal number into Go value of type jsonrpc2.jRequest"},"id":null} ] rpc call Batch: --> [ {"jsonrpc":"2.0","method":"sum","params":[1,2,4],"id":"1"}, {"jsonrpc":"2.0","method":"notify_hello","params":[7]}, {"jsonrpc":"2.0","method":"subtract","params":[42,23],"id":"2"}, {"foo":"boo"}, {"jsonrpc":"2.0","method":"foo.get","params":{"name":"myself"},"id":"5"}, {"jsonrpc":"2.0","method":"get_data","id":"9"} ] <-- [ {"jsonrpc":"2.0","result":7,"id":"1"}, {"jsonrpc":"2.0","result":19,"id":"2"}, {"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request","data":"json: unknown field \"foo\""},"id":null}, {"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not found","data":"foo.get"},"id":"5"}, {"jsonrpc":"2.0","result":["hello",5],"id":"9"} ] rpc call Batch (all notifications): --> [ {"jsonrpc":"2.0","method":"notify_sum","params":[1,2,4]}, {"jsonrpc":"2.0","method":"notify_hello","params":[7]} ] <-- //Nothing is returned for all notification batches
Index ¶
Examples ¶
Constants ¶
const ( // ErrorCodeMinReserved is the minimum reserved error code. Method // defined errors may be less than this value. ErrorCodeMinReserved ErrorCode = -32768 // ErrorCodeParse means the received data was not valid JSON. ErrorCodeParse ErrorCode = -32700 ErrorMessageParse = "Parse error" // ErrorCodeInvalidRequest means the received valid JSON is not a valid // Request object. ErrorCodeInvalidRequest ErrorCode = -32600 ErrorMessageInvalidRequest = "Invalid Request" // ErrorCodeMethodNotFound means the requested method is not defined // for the HTTPRequestHandler. ErrorCodeMethodNotFound ErrorCode = -32601 ErrorMessageMethodNotFound = "Method not found" // ErrorCodeInvalidParams means a method is called with invalid method // parameter(s). // // MethodFuncs are responsible for detecting and returning this error. ErrorCodeInvalidParams ErrorCode = -32602 ErrorMessageInvalidParams = "Invalid params" // ErrorCodeInternal means an internal error occurred such as a // MethodFunc panic. ErrorCodeInternal ErrorCode = -32603 ErrorMessageInternal = "Internal error" // ErrorCodeMaxReserved is the maximum reserved error code. Method // defined errors may be greater than this value. ErrorCodeMaxReserved ErrorCode = -32000 )
Official JSON-RPC 2.0 Spec Error Codes and Messages
Variables ¶
var DebugMethodFunc = false
DebugMethodFunc controls whether additional debug information is printed to stdout in the event of an InternalError when a MethodFunc is called.
This can be helpful when troubleshooting panics or Internal Errors from a MethodFunc.
Functions ¶
func HTTPRequestHandler ¶
func HTTPRequestHandler(methods MethodMap, lgr Logger) http.HandlerFunc
HTTPRequestHandler returns an http.HandlerFunc for the given methods.
The returned http.HandlerFunc efficiently handles any conforming single or batch requests or notifications, accurately catches all defined protocol errors, calls the appropriate MethodFuncs, recovers from any panics or invalid return values, and returns an Internal Error or Response with the correct ID, if not a Notification.
See MethodFunc for more details.
It is not safe to modify methods while the returned http.HandlerFunc is in use.
This will panic if a method name beginning with "rpc." is used. See MethodMap for more details.
The handler will use lgr to log any errors and debug information, if DebugMethodFunc is true. If lgr is nil, the default Logger from the log package is used.
Types ¶
type BatchRequest ¶
type BatchRequest []Request
BatchRequest is a type that implements fmt.Stringer for a slice of Requests.
func (BatchRequest) String ¶
func (br BatchRequest) String() string
String returns br as a JSON array prefixed with "--> " to indicate an outgoing BatchRequest and with newlines separating the elements of br.
type BatchResponse ¶
type BatchResponse []Response
BatchResponse is a type that implements fmt.Stringer for a slice of Responses.
func (BatchResponse) String ¶
func (br BatchResponse) String() string
String returns br as a JSON array prefixed with "<-- " to indicate an incoming BatchResponse and with newlines separating the elements of br.
type Client ¶
type Client struct { http.Client DebugRequest bool Log Logger BasicAuth bool User string Password string Header http.Header }
Client embeds http.Client and provides a convenient way to make JSON-RPC 2.0 requests.
func (*Client) Request ¶
Request uses c to make a JSON-RPC 2.0 Request to url with the given method and params, and then parses the Response using the provided result, which should be a pointer so that it may be populated.
If ctx is not nil, it is added to the http.Request.
If the http.Response is received without error, but cannot be parsed into a Response, then an ErrorUnexpectedHTTPResponse is returned containing the Unmarshaling error, the raw bytes of the http.Response.Body, and the http.Response.
If the Response.HasError() is true, then the Error is returned.
Other potential errors can result from json.Marshal and params, http.NewRequest and url, or network errors from c.Do.
A pseudorandom uint between 1 and 5000 is used for the Request.ID.
The "Content-Type":"application/json" header is added to the http.Request, and then headers in c.Header are added, which may override the "Content-Type".
If c.BasicAuth is true then http.Request.SetBasicAuth(c.User, c.Password) is be called.
If c.DebugRequest is true then the Request and Response are printed using c.Log. If c.Log == nil, then c.Log = log.New(os.Stderr, "", 0).
type Error ¶
type Error struct { // Code is a number that indicates the error type that occurred. Code ErrorCode `json:"code"` // Message is a short description of the error. The message SHOULD be // limited to a concise single sentence. Message string `json:"message"` // Data is a Primitive or Structured value that contains additional // information about the error. This may be omitted. The value of this // member is defined by the Server (e.g. detailed error information, // nested errors etc.). Data interface{} `json:"data,omitempty"` }
Error represents a JSON-RPC 2.0 Error object, which is used in the Response object. MethodFuncs may return an Error or *Error to return an Error Response to the client.
func ErrorInvalidParams ¶
func ErrorInvalidParams(data interface{}) Error
ErrorInvalidParams returns
NewError(ErrorCodeInvalidParams, ErrorMessageInvalidParams, data)
MethodFuncs are responsible for detecting invalid parameters and returning this error.
func NewError ¶
NewError returns an Error with code, msg, and data.
The data must not cause an error when passed to json.Marshal, else any MethodFunc returning it will return an Internal Error to clients instead.
If data is type error, then the Error() string is used instead, since otherwise the error may not json.Marshal properly.
type ErrorCode ¶
type ErrorCode int
ErrorCode indicates the Error type that occurred.
The error codes from and including -32768 to -32000 are reserved for pre-defined errors. Any code within this range, but not defined explicitly below is reserved for future use.
See ErrorCodeMinReserved.
func (ErrorCode) IsReserved ¶
IsReserved returns true if c is within the reserved error code range:
[LowestReservedErrorCode, HighestReservedErrorCode]
type ErrorUnexpectedHTTPResponse ¶
ErrorUnexpectedHTTPResponse wraps errors that occur during unmarshling of the http.Response.Body into a Response, along with the full bytes of the http.Response.Body and the http.Response itself.
func (ErrorUnexpectedHTTPResponse) Error ¶
func (err ErrorUnexpectedHTTPResponse) Error() string
Error returns err.UnmarshalingErr.Error().
func (*ErrorUnexpectedHTTPResponse) Unwrap ¶
func (err *ErrorUnexpectedHTTPResponse) Unwrap() error
Unwrap return err.UnmarshalingErr.
type Logger ¶
type Logger interface { Println(...interface{}) Printf(string, ...interface{}) }
Logger allows custom log types to be used with the Client when Client.DebugRequest is true.
type MethodFunc ¶
type MethodFunc func(ctx context.Context, params json.RawMessage) interface{}
MethodFunc is the function signature used for RPC methods.
MethodFuncs are invoked by the HTTPRequestHandler when a valid Request is received. MethodFuncs do not need to concern themselves with the details of JSON-RPC 2.0 outside of the "params" field, as all parsing and validation is handled by the handler.
The handler will call a MethodFunc with ctx set to the corresponding http.Request.Context() and params set to the JSON data from the "params" field of the Request. If "params" was omitted or null, params will be nil. Otherwise, params is guaranteed to be valid JSON that represents a JSON Object or Array.
A MethodFunc is responsible for application specific parsing of params. A MethodFunc should return an ErrorInvalidParams if there is any issue parsing expected parameters.
To return a success Response to the client a MethodFunc must return a non-error value, that will not cause an error when passed to json.Marshal, to be used as the Response.Result. Any marshaling error will cause a panic and an Internal Error will be returned to the client.
To return an Error Response to the client, a MethodFunc must return a valid Error. A valid Error must use ErrorCodeInvalidParams or any ErrorCode outside of the reserved range, and the Error.Data must not cause an error when passed to json.Marshal. If the Error is not valid, a panic will occur and an Internal Error will be returned to the client.
If a MethodFunc panics or returns any other error, an Internal Error is returned to the client. If the returned error is anything other than context.Canceled or context.DeadlineExceeded, a panic will occur.
For additional debug output from a MethodFunc regarding the cause of an Internal Error, set DebugMethodFunc to true. Information about the method call and a stack trace will be printed on panics.
type MethodMap ¶
type MethodMap map[string]MethodFunc
MethodMap associates method names with MethodFuncs and is passed to HTTPRequestHandler to generate a corresponding http.HandlerFunc.
Method names that begin with the word rpc followed by a period character (U+002E or ASCII 46) are reserved for rpc-internal methods and extensions and MUST NOT be used for anything else. If such a method name is detected this will panic. No such internal rpc methods are yet defined in this implementation.
type Request ¶
type Request struct { // Method is a string containing the name of the method to be invoked. Method string `json:"method"` // Params is a structured value that holds the parameter values to be // used during the invocation of the method. // // This member MAY be omitted. Params interface{} `json:"params,omitempty"` // ID is an identifier established by the Client that MUST contain a // String, Number, or NULL value if included. // // If it is not included it is assumed to be a notification. The value // SHOULD normally not be Null and Numbers SHOULD NOT contain // fractional parts. ID interface{} `json:"id,omitempty"` }
Request represents a JSON-RPC 2.0 Request or Notification object.
This type is not needed to use the Client or to write MethodFuncs for the HTTPRequestHandler.
Request is intended to be used externally to MarshalJSON for custom clients, and internally to UnmarshalJSON for the HTTPRequestHandler.
To make a Request, you must populate ID. If ID is empty, the Request is treated as a Notification and does not receive a Response. See MarshalJSON for more details.
func (Request) MarshalJSON ¶
MarshalJSON attempts to marshal r into a valid JSON-RPC 2.0 Request or Notification object.
If r.ID is nil, then the returned data represents a Notification.
If r.ID is not nil, then the returned data represents a Request. Also, an `invalid "id": ...` error is returned if the r.ID does not marshal into a valid JSON number, string, or null. Although technically permitted, it is not recommended to use json.RawMessage("null") as an ID, as this is used by Responses when there is an error parsing "id".
If r.Params is not nil, then an `invalid "params": ...` error is returned if it does not marshal into a valid JSON object, array, or null.
An empty Method, though not recommended, is technically valid and does not cause an error.
func (Request) String ¶
String returns r as a JSON object prefixed with "--> " to indicate an outgoing Request.
If r.MarshalJSON returns an error then the error string is returned with some context.
func (*Request) UnmarshalJSON ¶
UnmarshalJSON attempts to unmarshal a JSON-RPC 2.0 Request or Notification into r and then validates it.
To allow for precise JSON type validation and to avoid unneeded unmarshaling by the HTTPRequestHandler, "id" and "params" are both unmarshaled into json.RawMessage. After a successful call, r.ID and r.Params are set to a json.RawMessage that is either nil or contains the raw JSON for the respective field. So, if no error is returned, this is guaranteed to not panic:
id, params := r.ID.(json.RawMessage), r.Params.(json.RawMessage)
If "id" is omitted, then r.ID is set to json.RawMessage(nil). If "id" is null, then r.ID is set to json.RawMessage("null").
If "params" is null or omitted, then r.Params is set to json.RawMessage(nil).
If any fields are unknown, an error is returned.
If the "jsonrpc" field is not set to the string "2.0", an `invalid "jsonrpc" version: ...` error is be returned.
If the "method" field is omitted or null, a `missing "method"` error is returned. An explicitly empty "method" string does not cause an error.
If the "id" value is not a JSON number, string, or null, an `invalid "id": ...` error is returned.
If the "params" value is not a JSON array, object, or null, an `invalid "params": ...` error is returned.
type Response ¶
type Response struct { // Result is REQUIRED on success. This member MUST NOT exist if there // was an error invoking the method. The value of this member is // determined by the method invoked on the Server. Result interface{} `json:"result,omitempty"` // Error is REQUIRED on error. This member MUST NOT exist if there was // no error triggered during invocation. The value for this member MUST // be an Object as defined in section 5.1. // // See Response.HasError. Error Error `json:"error,omitempty"` // ID is an identifier established by the client that MUST contain a // String, Number, or NULL value if included. // // Since a Request without an ID is not responded to, this member is // REQUIRED. It MUST be the same as the value of the id member in the // Request Object. If there was an error in detecting the id in the // Request Object (e.g. Parse error/Invalid Request), it MUST be Null. ID interface{} `json:"id"` }
Response represents a JSON-RPC 2.0 Response object.
This type is not needed to use the Client or to write MethodFuncs for the HTTPRequestHandler.
Response is intended to be used externally to UnmarshalJSON for custom clients, and internally to MarshalJSON for the provided HTTPRequestHandler.
To receive a Response, it is recommended to set Result to a pointer to a value that the "result" can be unmarshaled into, if known prior to unmarshaling. Similarly, it is recommended to set ID to a pointer to a value that the "id" can be unmarshaled into, which should be the same type as the Request ID.
func (Response) MarshalJSON ¶
MarshalJSON attempts to marshal r into a valid JSON-RPC 2.0 Response.
If r.HasError(), then "result" is omitted from the JSON, and if r.ID is nil, it is set to json.RawMessage("null"). An error is only returned if r.Error.Data or r.ID is not marshalable.
If !r.HasError(), then if r.ID is nil, an error is returned. If r.Result is nil, it is populated with json.RawMessage("null").
Also, an error is returned if r.Result or r.ID is not marshalable.
func (Response) String ¶
String returns r as a JSON object prefixed with "<-- " to indicate an incoming Response.
If r.MarshalJSON returns an error then the error string is returned with some context.
func (*Response) UnmarshalJSON ¶
UnmarshalJSON attempts to unmarshal a JSON-RPC 2.0 Response into r and then validates it.
If any fields are unknown other than the application specific fields in the "result" object, an error is returned.
If "error" and "result" are both present or not null, a `contains both ...` error is returned.
If the "jsonrpc" field is not set to the string "2.0", an `invalid "jsonrpc" version: ...` error is returned.