1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package jsonrpc2
6
7import (
8	"encoding/json"
9
10	errors "golang.org/x/xerrors"
11)
12
13// ID is a Request identifier.
14type ID struct {
15	value interface{}
16}
17
18// Message is the interface to all jsonrpc2 message types.
19// They share no common functionality, but are a closed set of concrete types
20// that are allowed to implement this interface. The message types are *Request
21// and *Response.
22type Message interface {
23	// marshal builds the wire form from the API form.
24	// It is private, which makes the set of Message implementations closed.
25	marshal(to *wireCombined)
26}
27
28// Request is a Message sent to a peer to request behavior.
29// If it has an ID it is a call, otherwise it is a notification.
30type Request struct {
31	// ID of this request, used to tie the Response back to the request.
32	// This will be nil for notifications.
33	ID ID
34	// Method is a string containing the method name to invoke.
35	Method string
36	// Params is either a struct or an array with the parameters of the method.
37	Params json.RawMessage
38}
39
40// Response is a Message used as a reply to a call Request.
41// It will have the same ID as the call it is a response to.
42type Response struct {
43	// result is the content of the response.
44	Result json.RawMessage
45	// err is set only if the call failed.
46	Error error
47	// id of the request this is a response to.
48	ID ID
49}
50
51// StringID creates a new string request identifier.
52func StringID(s string) ID { return ID{value: s} }
53
54// Int64ID creates a new integer request identifier.
55func Int64ID(i int64) ID { return ID{value: i} }
56
57// IsValid returns true if the ID is a valid identifier.
58// The default value for ID will return false.
59func (id ID) IsValid() bool { return id.value != nil }
60
61// Raw returns the underlying value of the ID.
62func (id ID) Raw() interface{} { return id.value }
63
64// NewNotification constructs a new Notification message for the supplied
65// method and parameters.
66func NewNotification(method string, params interface{}) (*Request, error) {
67	p, merr := marshalToRaw(params)
68	return &Request{Method: method, Params: p}, merr
69}
70
71// NewCall constructs a new Call message for the supplied ID, method and
72// parameters.
73func NewCall(id ID, method string, params interface{}) (*Request, error) {
74	p, merr := marshalToRaw(params)
75	return &Request{ID: id, Method: method, Params: p}, merr
76}
77
78func (msg *Request) IsCall() bool { return msg.ID.IsValid() }
79
80func (msg *Request) marshal(to *wireCombined) {
81	to.ID = msg.ID.value
82	to.Method = msg.Method
83	to.Params = msg.Params
84}
85
86// NewResponse constructs a new Response message that is a reply to the
87// supplied. If err is set result may be ignored.
88func NewResponse(id ID, result interface{}, rerr error) (*Response, error) {
89	r, merr := marshalToRaw(result)
90	return &Response{ID: id, Result: r, Error: rerr}, merr
91}
92
93func (msg *Response) marshal(to *wireCombined) {
94	to.ID = msg.ID.value
95	to.Error = toWireError(msg.Error)
96	to.Result = msg.Result
97}
98
99func toWireError(err error) *wireError {
100	if err == nil {
101		// no error, the response is complete
102		return nil
103	}
104	if err, ok := err.(*wireError); ok {
105		// already a wire error, just use it
106		return err
107	}
108	result := &wireError{Message: err.Error()}
109	var wrapped *wireError
110	if errors.As(err, &wrapped) {
111		// if we wrapped a wire error, keep the code from the wrapped error
112		// but the message from the outer error
113		result.Code = wrapped.Code
114	}
115	return result
116}
117
118func EncodeMessage(msg Message) ([]byte, error) {
119	wire := wireCombined{VersionTag: wireVersion}
120	msg.marshal(&wire)
121	data, err := json.Marshal(&wire)
122	if err != nil {
123		return data, errors.Errorf("marshaling jsonrpc message: %w", err)
124	}
125	return data, nil
126}
127
128func DecodeMessage(data []byte) (Message, error) {
129	msg := wireCombined{}
130	if err := json.Unmarshal(data, &msg); err != nil {
131		return nil, errors.Errorf("unmarshaling jsonrpc message: %w", err)
132	}
133	if msg.VersionTag != wireVersion {
134		return nil, errors.Errorf("invalid message version tag %s expected %s", msg.VersionTag, wireVersion)
135	}
136	id := ID{}
137	switch v := msg.ID.(type) {
138	case nil:
139	case float64:
140		// coerce the id type to int64 if it is float64, the spec does not allow fractional parts
141		id = Int64ID(int64(v))
142	case int64:
143		id = Int64ID(v)
144	case string:
145		id = StringID(v)
146	default:
147		return nil, errors.Errorf("invalid message id type <%T>%v", v, v)
148	}
149	if msg.Method != "" {
150		// has a method, must be a call
151		return &Request{
152			Method: msg.Method,
153			ID:     id,
154			Params: msg.Params,
155		}, nil
156	}
157	// no method, should be a response
158	if !id.IsValid() {
159		return nil, ErrInvalidRequest
160	}
161	resp := &Response{
162		ID:     id,
163		Result: msg.Result,
164	}
165	// we have to check if msg.Error is nil to avoid a typed error
166	if msg.Error != nil {
167		resp.Error = msg.Error
168	}
169	return resp, nil
170}
171
172func marshalToRaw(obj interface{}) (json.RawMessage, error) {
173	if obj == nil {
174		return nil, nil
175	}
176	data, err := json.Marshal(obj)
177	if err != nil {
178		return nil, err
179	}
180	return json.RawMessage(data), nil
181}
182