response.go raw

   1  // Copyright 2018 Adam S Levy
   2  //
   3  // Permission is hereby granted, free of charge, to any person obtaining a copy
   4  // of this software and associated documentation files (the "Software"), to
   5  // deal in the Software without restriction, including without limitation the
   6  // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
   7  // sell copies of the Software, and to permit persons to whom the Software is
   8  // furnished to do so, subject to the following conditions:
   9  //
  10  // The above copyright notice and this permission notice shall be included in
  11  // all copies or substantial portions of the Software.
  12  //
  13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  18  // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  19  // IN THE SOFTWARE.
  20  
  21  package jsonrpc2
  22  
  23  import (
  24  	"bytes"
  25  	"encoding/json"
  26  	"fmt"
  27  )
  28  
  29  // version is the valid version string for the "jsonrpc" field required in all
  30  // JSON RPC 2.0 objects.
  31  const version = "2.0"
  32  
  33  // Response represents a JSON-RPC 2.0 Response object.
  34  //
  35  // This type is not needed to use the Client or to write MethodFuncs for the
  36  // HTTPRequestHandler.
  37  //
  38  // Response is intended to be used externally to UnmarshalJSON for custom
  39  // clients, and internally to MarshalJSON for the provided HTTPRequestHandler.
  40  //
  41  // To receive a Response, it is recommended to set Result to a pointer to a
  42  // value that the "result" can be unmarshaled into, if known prior to
  43  // unmarshaling. Similarly, it is recommended to set ID to a pointer to a value
  44  // that the "id" can be unmarshaled into, which should be the same type as the
  45  // Request ID.
  46  type Response struct {
  47  	// Result is REQUIRED on success. This member MUST NOT exist if there
  48  	// was an error invoking the method. The value of this member is
  49  	// determined by the method invoked on the Server.
  50  	Result interface{} `json:"result,omitempty"`
  51  
  52  	// Error is REQUIRED on error. This member MUST NOT exist if there was
  53  	// no error triggered during invocation. The value for this member MUST
  54  	// be an Object as defined in section 5.1.
  55  	//
  56  	// See Response.HasError.
  57  	Error Error `json:"error,omitempty"`
  58  
  59  	// ID is an identifier established by the client that MUST contain a
  60  	// String, Number, or NULL value if included.
  61  	//
  62  	// Since a Request without an ID is not responded to, this member is
  63  	// REQUIRED. It MUST be the same as the value of the id member in the
  64  	// Request Object. If there was an error in detecting the id in the
  65  	// Request Object (e.g. Parse error/Invalid Request), it MUST be Null.
  66  	ID interface{} `json:"id"`
  67  }
  68  
  69  // jResponse adds the required "jsonrpc" field and allows for detecting if the
  70  // "error" field is present so that the rule that a Response may not contain
  71  // both an "error and "result" can be enforced.
  72  type jResponse struct {
  73  	// JSONRPC specifies the version of the JSON-RPC protocol. It MUST be
  74  	// exactly "2.0".
  75  	JSONRPC string `json:"jsonrpc"`
  76  
  77  	// Error allows UnmarshalJSON to detect if "error" was omitted or set
  78  	// to null which is invalid if "result" is included. Additionally it
  79  	// allows MarshalJSON to explicitly omit it or include it during
  80  	// marhsaling.
  81  	Error *Error `json:"error,omitempty"`
  82  
  83  	// *request allows a Request to be used directly while masking its
  84  	// Un/MarshalJSON methods.
  85  	*response
  86  }
  87  
  88  // response masks the Response Un/MarshalJSON methods to avoid recursion.
  89  type response Response
  90  
  91  // MarshalJSON attempts to marshal r into a valid JSON-RPC 2.0 Response.
  92  //
  93  // If r.HasError(), then "result" is omitted from the JSON, and if r.ID is nil,
  94  // it is set to json.RawMessage("null"). An error is only returned if
  95  // r.Error.Data or r.ID is not marshalable.
  96  //
  97  // If !r.HasError(), then if r.ID is nil, an error is returned. If r.Result is
  98  // nil, it is populated with json.RawMessage("null").
  99  //
 100  // Also, an error is returned if r.Result or r.ID is not marshalable.
 101  func (r Response) MarshalJSON() ([]byte, error) {
 102  	jR := jResponse{
 103  		JSONRPC:  version,
 104  		response: (*response)(&r),
 105  	}
 106  	if r.HasError() {
 107  		jR.Error = &r.Error
 108  		r.Result = nil
 109  		if r.ID == nil {
 110  			r.ID = json.RawMessage("null")
 111  		}
 112  	} else {
 113  		if r.ID == nil {
 114  			return nil, fmt.Errorf("r.ID == nil && !r.HasError()")
 115  		}
 116  		if r.Result == nil {
 117  			r.Result = json.RawMessage("null")
 118  		}
 119  	}
 120  	return json.Marshal(jR)
 121  }
 122  
 123  // UnmarshalJSON attempts to unmarshal a JSON-RPC 2.0 Response into r and then
 124  // validates it.
 125  //
 126  // If any fields are unknown other than the application specific fields in the
 127  // "result" object, an error is returned.
 128  //
 129  // If "error" and "result" are both present or not null, a `contains both ...`
 130  // error is returned.
 131  //
 132  // If the "jsonrpc" field is not set to the string "2.0", an `invalid "jsonrpc"
 133  // version: ...` error is returned.
 134  func (r *Response) UnmarshalJSON(data []byte) error {
 135  	// There may be fields in the result not defined in the user provided
 136  	// r.Result, which will cause errors with the json.Decoder below.  So
 137  	// first unmarshal any "result" to a json.RawMessage, which will later
 138  	// be unmarshaled into the userResult.
 139  	userResult := r.Result
 140  	var resultData json.RawMessage
 141  	r.Result = &resultData
 142  	jR := jResponse{Error: &r.Error, response: (*response)(r)}
 143  
 144  	// Catch any unknown fields in the top level JSON RPC Response object.
 145  	d := json.NewDecoder(bytes.NewBuffer(data))
 146  	d.DisallowUnknownFields()
 147  	if err := d.Decode(&jR); err != nil {
 148  		return err
 149  	}
 150  
 151  	if jR.JSONRPC != version {
 152  		return fmt.Errorf(`invalid "jsonrpc" version: %q`, jR.JSONRPC)
 153  	}
 154  
 155  	if r.HasError() {
 156  		if resultData != nil {
 157  			return fmt.Errorf(`contains both "result" and "error"`)
 158  		}
 159  		return nil
 160  	}
 161  
 162  	// Restore the userResult and finish unmarshaling.
 163  	r.Result = userResult
 164  	return json.Unmarshal(resultData, &r.Result)
 165  }
 166  
 167  // HasError returns true is r.Error has any non-zero values.
 168  func (r Response) HasError() bool {
 169  	return !r.Error.IsZero()
 170  }
 171  
 172  // String returns r as a JSON object prefixed with "<-- " to indicate an
 173  // incoming Response.
 174  //
 175  // If r.MarshalJSON returns an error then the error string is returned with
 176  // some context.
 177  func (r Response) String() string {
 178  	b, err := r.MarshalJSON()
 179  	if err != nil {
 180  		return fmt.Sprintf("%#v.MarshalJSON(): %v", r, err)
 181  	}
 182  	return "<-- " + string(b)
 183  }
 184  
 185  // BatchResponse is a type that implements fmt.Stringer for a slice of
 186  // Responses.
 187  type BatchResponse []Response
 188  
 189  // String returns br as a JSON array prefixed with "<-- " to indicate an
 190  // incoming BatchResponse and with newlines separating the elements of br.
 191  func (br BatchResponse) String() string {
 192  	s := "<-- [\n"
 193  	for i, res := range br {
 194  		s += "  " + res.String()[4:]
 195  		if i < len(br)-1 {
 196  			s += ","
 197  		}
 198  		s += "\n"
 199  	}
 200  	return s + "]"
 201  }
 202