mirror of
https://github.com/sourcegraph/jsonrpc2.git
synced 2026-06-16 20:20:03 +02:00
Compare commits
17 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4756698b1e | ||
|
|
ef3ea8b2ea | ||
|
|
3c4c92ad61 | ||
|
|
ddb146fd0d | ||
|
|
2cc94179e1 | ||
|
|
534fd43609 | ||
|
|
4963d1c241 | ||
|
|
dd69e185fa | ||
|
|
bf47ec21a6 | ||
|
|
cd64a673da | ||
|
|
943e53c8e9 | ||
|
|
e4e2e6324c | ||
|
|
510183e882 | ||
|
|
8a0bf06edf | ||
|
|
b9c1fbdb96 | ||
|
|
5d80b29f44 | ||
|
|
040dc22f8a |
15 changed files with 500 additions and 237 deletions
10
.github/dependabot.yml
vendored
Normal file
10
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "monthly"
|
||||||
|
groups:
|
||||||
|
github-actions:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
|
|
@ -4,6 +4,10 @@ on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
strategy:
|
strategy:
|
||||||
|
|
@ -14,9 +18,9 @@ jobs:
|
||||||
name: Go ${{ matrix.go }}
|
name: Go ${{ matrix.go }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v6
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
id: go
|
id: go
|
||||||
|
|
|
||||||
13
.github/workflows/lsif.yml
vendored
13
.github/workflows/lsif.yml
vendored
|
|
@ -1,13 +0,0 @@
|
||||||
name: LSIF
|
|
||||||
on:
|
|
||||||
- push
|
|
||||||
jobs:
|
|
||||||
lsif-go:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container: sourcegraph/lsif-go
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- name: Generate LSIF data
|
|
||||||
run: lsif-go
|
|
||||||
- name: Upload LSIF data
|
|
||||||
run: src lsif upload -github-token=${{ secrets.GITHUB_TOKEN }}
|
|
||||||
20
.github/workflows/scip.yml
vendored
Normal file
20
.github/workflows/scip.yml
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
name: SCIP
|
||||||
|
'on':
|
||||||
|
- push
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
jobs:
|
||||||
|
scip-go:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container: sourcegraph/scip-go
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- name: Get src-cli
|
||||||
|
run: curl -L https://sourcegraph.com/.api/src-cli/src_linux_amd64 -o /usr/local/bin/src;
|
||||||
|
chmod +x /usr/local/bin/src
|
||||||
|
- name: Set directory to safe for git
|
||||||
|
run: git config --global --add safe.directory $GITHUB_WORKSPACE
|
||||||
|
- name: Generate SCIP data
|
||||||
|
run: scip-go
|
||||||
|
- name: Upload SCIP data
|
||||||
|
run: src code-intel upload -github-token=${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
@ -3,9 +3,8 @@
|
||||||
|
|
||||||
Package jsonrpc2 provides a [Go](https://golang.org) implementation of [JSON-RPC 2.0](http://www.jsonrpc.org/specification).
|
Package jsonrpc2 provides a [Go](https://golang.org) implementation of [JSON-RPC 2.0](http://www.jsonrpc.org/specification).
|
||||||
|
|
||||||
This package is **experimental** until further notice.
|
* [Documentation](https://pkg.go.dev/github.com/sourcegraph/jsonrpc2)
|
||||||
|
* [Open the code in Sourcegraph](https://sourcegraph.com/github.com/sourcegraph/jsonrpc2)
|
||||||
[**Open the code in Sourcegraph**](https://sourcegraph.com/github.com/sourcegraph/jsonrpc2)
|
|
||||||
|
|
||||||
## Known issues
|
## Known issues
|
||||||
|
|
||||||
|
|
|
||||||
12
SECURITY.md
Normal file
12
SECURITY.md
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
Security updates are applied only to the latest release.
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to evaluate and fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released.
|
||||||
|
|
||||||
|
Please disclose it privately via email to security@sourcegraph.com. We will work with you to understand and resolve the issue promptly.
|
||||||
|
|
||||||
136
conn.go
136
conn.go
|
|
@ -27,6 +27,7 @@ type Conn struct {
|
||||||
|
|
||||||
sending sync.Mutex
|
sending sync.Mutex
|
||||||
|
|
||||||
|
cancelCtx context.CancelFunc
|
||||||
disconnect chan struct{}
|
disconnect chan struct{}
|
||||||
|
|
||||||
logger Logger
|
logger Logger
|
||||||
|
|
@ -43,13 +44,19 @@ var _ JSONRPC2 = (*Conn)(nil)
|
||||||
// JSON-RPC protocol is symmetric, so a Conn runs on both ends of a
|
// JSON-RPC protocol is symmetric, so a Conn runs on both ends of a
|
||||||
// client-server connection.
|
// client-server connection.
|
||||||
//
|
//
|
||||||
// NewClient consumes conn, so you should call Close on the returned
|
// NewConn consumes stream, so you should call Close on the returned
|
||||||
// client not on the given conn.
|
// Conn not on the given stream or its underlying connection.
|
||||||
|
//
|
||||||
|
// Conn is closed when the given context's Done channel is closed.
|
||||||
func NewConn(ctx context.Context, stream ObjectStream, h Handler, opts ...ConnOpt) *Conn {
|
func NewConn(ctx context.Context, stream ObjectStream, h Handler, opts ...ConnOpt) *Conn {
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
c := &Conn{
|
c := &Conn{
|
||||||
stream: stream,
|
stream: stream,
|
||||||
h: h,
|
h: h,
|
||||||
pending: map[ID]*call{},
|
pending: map[ID]*call{},
|
||||||
|
cancelCtx: cancel,
|
||||||
disconnect: make(chan struct{}),
|
disconnect: make(chan struct{}),
|
||||||
logger: log.New(os.Stderr, "", log.LstdFlags),
|
logger: log.New(os.Stderr, "", log.LstdFlags),
|
||||||
}
|
}
|
||||||
|
|
@ -60,6 +67,12 @@ func NewConn(ctx context.Context, stream ObjectStream, h Handler, opts ...ConnOp
|
||||||
opt(c)
|
opt(c)
|
||||||
}
|
}
|
||||||
go c.readMessages(ctx)
|
go c.readMessages(ctx)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
c.close(nil)
|
||||||
|
}()
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -166,9 +179,7 @@ func (c *Conn) SendResponse(ctx context.Context, resp *Response) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) close(cause error) error {
|
func (c *Conn) close(cause error) error {
|
||||||
c.sending.Lock()
|
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.sending.Unlock()
|
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
if c.closed {
|
if c.closed {
|
||||||
|
|
@ -184,20 +195,23 @@ func (c *Conn) close(cause error) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
close(c.disconnect)
|
close(c.disconnect)
|
||||||
|
c.cancelCtx()
|
||||||
c.closed = true
|
c.closed = true
|
||||||
return c.stream.Close()
|
return c.stream.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) readMessages(ctx context.Context) {
|
func (c *Conn) readMessages(ctx context.Context) {
|
||||||
var err error
|
for {
|
||||||
for err == nil {
|
|
||||||
var m anyMessage
|
var m anyMessage
|
||||||
err = c.stream.ReadObject(&m)
|
err := c.stream.ReadObject(&m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
c.close(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
// TODO: handle the case where both request and response are nil.
|
||||||
|
|
||||||
case m.request != nil:
|
case m.request != nil:
|
||||||
for _, onRecv := range c.onRecv {
|
for _, onRecv := range c.onRecv {
|
||||||
onRecv(m.request, nil)
|
onRecv(m.request, nil)
|
||||||
|
|
@ -206,49 +220,53 @@ func (c *Conn) readMessages(ctx context.Context) {
|
||||||
|
|
||||||
case m.response != nil:
|
case m.response != nil:
|
||||||
resp := m.response
|
resp := m.response
|
||||||
if resp != nil {
|
id := resp.ID
|
||||||
id := resp.ID
|
c.mu.Lock()
|
||||||
c.mu.Lock()
|
call := c.pending[id]
|
||||||
call := c.pending[id]
|
delete(c.pending, id)
|
||||||
delete(c.pending, id)
|
c.mu.Unlock()
|
||||||
c.mu.Unlock()
|
|
||||||
|
|
||||||
if call != nil {
|
var req *Request
|
||||||
call.response = resp
|
if call != nil {
|
||||||
}
|
call.response = resp
|
||||||
|
req = call.request
|
||||||
if len(c.onRecv) > 0 {
|
|
||||||
var req *Request
|
|
||||||
if call != nil {
|
|
||||||
req = call.request
|
|
||||||
}
|
|
||||||
for _, onRecv := range c.onRecv {
|
|
||||||
onRecv(req, resp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case call == nil:
|
|
||||||
c.logger.Printf("jsonrpc2: ignoring response #%s with no corresponding request\n", id)
|
|
||||||
|
|
||||||
case resp.Error != nil:
|
|
||||||
call.done <- resp.Error
|
|
||||||
close(call.done)
|
|
||||||
|
|
||||||
default:
|
|
||||||
call.done <- nil
|
|
||||||
close(call.done)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, onRecv := range c.onRecv {
|
||||||
|
onRecv(req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if call == nil {
|
||||||
|
c.logger.Printf("jsonrpc2: ignoring response #%s with no corresponding request\n", id)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if resp.Error != nil {
|
||||||
|
err = resp.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
call.done <- err
|
||||||
|
close(call.done)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.close(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) send(_ context.Context, m *anyMessage, wait bool) (cc *call, err error) {
|
func (c *Conn) send(_ context.Context, m *anyMessage, wait bool) (cc *call, err error) {
|
||||||
c.sending.Lock()
|
c.sending.Lock()
|
||||||
defer c.sending.Unlock()
|
defer c.sending.Unlock()
|
||||||
|
|
||||||
|
// double check the error isn't due to being closed while sending.
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
c.mu.Lock()
|
||||||
|
if c.closed {
|
||||||
|
err = ErrClosed
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// m.request.ID could be changed, so we store a copy to correctly
|
// m.request.ID could be changed, so we store a copy to correctly
|
||||||
// clean up pending
|
// clean up pending
|
||||||
var id ID
|
var id ID
|
||||||
|
|
@ -330,25 +348,20 @@ type Waiter struct {
|
||||||
// error is returned.
|
// error is returned.
|
||||||
func (w Waiter) Wait(ctx context.Context, result interface{}) error {
|
func (w Waiter) Wait(ctx context.Context, result interface{}) error {
|
||||||
select {
|
select {
|
||||||
case err, ok := <-w.call.done:
|
|
||||||
if !ok {
|
|
||||||
err = ErrClosed
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if result != nil {
|
|
||||||
if w.call.response.Result == nil {
|
|
||||||
w.call.response.Result = &jsonNull
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(*w.call.response.Result, result); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
|
|
||||||
|
case err, ok := <-w.call.done:
|
||||||
|
if !ok {
|
||||||
|
return ErrClosed
|
||||||
|
}
|
||||||
|
if err != nil || result == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if w.call.response.Result == nil {
|
||||||
|
w.call.response.Result = &jsonNull
|
||||||
|
}
|
||||||
|
return json.Unmarshal(*w.call.response.Result, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -414,12 +427,7 @@ func (m *anyMessage) UnmarshalJSON(data []byte) error {
|
||||||
return errors.New("jsonrpc2: invalid empty batch")
|
return errors.New("jsonrpc2: invalid empty batch")
|
||||||
}
|
}
|
||||||
for i := range msgs {
|
for i := range msgs {
|
||||||
if err := checkType(&msg{
|
if err := checkType(&msgs[i]); err != nil {
|
||||||
ID: msgs[i].ID,
|
|
||||||
Method: msgs[i].Method,
|
|
||||||
Result: msgs[i].Result,
|
|
||||||
Error: msgs[i].Error,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
46
conn_opt.go
46
conn_opt.go
|
|
@ -43,6 +43,20 @@ func LogMessages(logger Logger) ConnOpt {
|
||||||
|
|
||||||
OnRecv(func(req *Request, resp *Response) {
|
OnRecv(func(req *Request, resp *Response) {
|
||||||
switch {
|
switch {
|
||||||
|
case resp != nil:
|
||||||
|
method := "(no matching request)"
|
||||||
|
if req != nil {
|
||||||
|
method = req.Method
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case resp.Result != nil:
|
||||||
|
result, _ := json.Marshal(resp.Result)
|
||||||
|
logger.Printf("jsonrpc2: --> result #%s: %s: %s\n", resp.ID, method, result)
|
||||||
|
case resp.Error != nil:
|
||||||
|
err, _ := json.Marshal(resp.Error)
|
||||||
|
logger.Printf("jsonrpc2: --> error #%s: %s: %s\n", resp.ID, method, err)
|
||||||
|
}
|
||||||
|
|
||||||
case req != nil:
|
case req != nil:
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
reqMethods[req.ID] = req.Method
|
reqMethods[req.ID] = req.Method
|
||||||
|
|
@ -54,34 +68,10 @@ func LogMessages(logger Logger) ConnOpt {
|
||||||
} else {
|
} else {
|
||||||
logger.Printf("jsonrpc2: --> request #%s: %s: %s\n", req.ID, req.Method, params)
|
logger.Printf("jsonrpc2: --> request #%s: %s: %s\n", req.ID, req.Method, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
case resp != nil:
|
|
||||||
var method string
|
|
||||||
if req != nil {
|
|
||||||
method = req.Method
|
|
||||||
} else {
|
|
||||||
method = "(no matching request)"
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case resp.Result != nil:
|
|
||||||
result, _ := json.Marshal(resp.Result)
|
|
||||||
logger.Printf("jsonrpc2: --> result #%s: %s: %s\n", resp.ID, method, result)
|
|
||||||
case resp.Error != nil:
|
|
||||||
err, _ := json.Marshal(resp.Error)
|
|
||||||
logger.Printf("jsonrpc2: --> error #%s: %s: %s\n", resp.ID, method, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})(c)
|
})(c)
|
||||||
OnSend(func(req *Request, resp *Response) {
|
OnSend(func(req *Request, resp *Response) {
|
||||||
switch {
|
switch {
|
||||||
case req != nil:
|
|
||||||
params, _ := json.Marshal(req.Params)
|
|
||||||
if req.Notif {
|
|
||||||
logger.Printf("jsonrpc2: <-- notif: %s: %s\n", req.Method, params)
|
|
||||||
} else {
|
|
||||||
logger.Printf("jsonrpc2: <-- request #%s: %s: %s\n", req.ID, req.Method, params)
|
|
||||||
}
|
|
||||||
|
|
||||||
case resp != nil:
|
case resp != nil:
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
method := reqMethods[resp.ID]
|
method := reqMethods[resp.ID]
|
||||||
|
|
@ -98,6 +88,14 @@ func LogMessages(logger Logger) ConnOpt {
|
||||||
err, _ := json.Marshal(resp.Error)
|
err, _ := json.Marshal(resp.Error)
|
||||||
logger.Printf("jsonrpc2: <-- error #%s: %s: %s\n", resp.ID, method, err)
|
logger.Printf("jsonrpc2: <-- error #%s: %s: %s\n", resp.ID, method, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case req != nil:
|
||||||
|
params, _ := json.Marshal(req.Params)
|
||||||
|
if req.Notif {
|
||||||
|
logger.Printf("jsonrpc2: <-- notif: %s: %s\n", req.Method, params)
|
||||||
|
} else {
|
||||||
|
logger.Printf("jsonrpc2: <-- request #%s: %s: %s\n", req.ID, req.Method, params)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})(c)
|
})(c)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,3 +51,80 @@ func TestSetLogger(t *testing.T) {
|
||||||
t.Fatalf("got %q, want %q", got, want)
|
t.Fatalf("got %q, want %q", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dummyHandler struct {
|
||||||
|
t *testing.T
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *dummyHandler) Handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) {
|
||||||
|
if !req.Notif {
|
||||||
|
err := conn.Reply(ctx, req.ID, nil)
|
||||||
|
if err != nil {
|
||||||
|
h.t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogMessages(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
rd, wr := io.Pipe()
|
||||||
|
defer rd.Close()
|
||||||
|
defer wr.Close()
|
||||||
|
|
||||||
|
buf := bufio.NewReader(rd)
|
||||||
|
logger := log.New(wr, "", log.Lmsgprefix)
|
||||||
|
|
||||||
|
a, b := net.Pipe()
|
||||||
|
connA := jsonrpc2.NewConn(
|
||||||
|
ctx,
|
||||||
|
jsonrpc2.NewBufferedStream(a, jsonrpc2.VSCodeObjectCodec{}),
|
||||||
|
&dummyHandler{t},
|
||||||
|
jsonrpc2.LogMessages(logger),
|
||||||
|
)
|
||||||
|
connB := jsonrpc2.NewConn(
|
||||||
|
ctx,
|
||||||
|
jsonrpc2.NewBufferedStream(b, jsonrpc2.VSCodeObjectCodec{}),
|
||||||
|
&dummyHandler{t},
|
||||||
|
)
|
||||||
|
defer connA.Close()
|
||||||
|
defer connB.Close()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := connA.Call(ctx, "method1", nil, nil); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := connB.Call(ctx, "method2", nil, nil); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := connA.Notify(ctx, "notification1", nil); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := connB.Notify(ctx, "notification2", nil); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for i, want := range []string{
|
||||||
|
"jsonrpc2: <-- request #0: method1: null\n",
|
||||||
|
"jsonrpc2: --> result #0: method1: null\n",
|
||||||
|
"jsonrpc2: --> request #0: method2: null\n",
|
||||||
|
"jsonrpc2: <-- result #0: method2: null\n",
|
||||||
|
"jsonrpc2: <-- notif: notification1: null\n",
|
||||||
|
"jsonrpc2: --> notif: notification2: null\n",
|
||||||
|
} {
|
||||||
|
got, err := buf.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("message %v: got %q, want %q", i, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
182
conn_test.go
182
conn_test.go
|
|
@ -14,6 +14,58 @@ import (
|
||||||
"github.com/sourcegraph/jsonrpc2"
|
"github.com/sourcegraph/jsonrpc2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestConn(t *testing.T) {
|
||||||
|
|
||||||
|
t.Run("closes when context is done", func(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
connA, connB := Pipe(ctx, noopHandler{}, noopHandler{})
|
||||||
|
defer connA.Close()
|
||||||
|
defer connB.Close()
|
||||||
|
|
||||||
|
cancel()
|
||||||
|
<-connA.DisconnectNotify()
|
||||||
|
|
||||||
|
got := connA.Close()
|
||||||
|
want := jsonrpc2.ErrClosed
|
||||||
|
if got != want {
|
||||||
|
t.Fatalf("got %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("cancels context when closed", func(t *testing.T) {
|
||||||
|
ctxCanceled := make(chan struct{})
|
||||||
|
|
||||||
|
handler := handlerFunc(func(ctx context.Context, c *jsonrpc2.Conn, r *jsonrpc2.Request) {
|
||||||
|
// Block until the context is canceled.
|
||||||
|
<-ctx.Done()
|
||||||
|
close(ctxCanceled)
|
||||||
|
})
|
||||||
|
|
||||||
|
connA, connB := Pipe(context.Background(), noopHandler{}, jsonrpc2.AsyncHandler(handler))
|
||||||
|
defer connA.Close()
|
||||||
|
defer connB.Close()
|
||||||
|
|
||||||
|
// Send a notification from connA to connB to trigger connB's handler
|
||||||
|
// function.
|
||||||
|
if err := connA.Notify(context.Background(), "foo", nil, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disconnect connA from connB.
|
||||||
|
if err := connA.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctxCanceled:
|
||||||
|
// Test passed, the handler's context was canceled.
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("context not canceled")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
var paramsTests = []struct {
|
var paramsTests = []struct {
|
||||||
sendParams interface{}
|
sendParams interface{}
|
||||||
wantParams *json.RawMessage
|
wantParams *json.RawMessage
|
||||||
|
|
@ -118,38 +170,77 @@ func TestConn_DisconnectNotify(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConn_Close(t *testing.T) {
|
func TestConn_Close(t *testing.T) {
|
||||||
t.Run("waiting for response", func(t *testing.T) {
|
cases := []struct {
|
||||||
connA, connB := net.Pipe()
|
name string
|
||||||
nodeA := jsonrpc2.NewConn(
|
run func(*testing.T, context.Context, *jsonrpc2.Conn)
|
||||||
context.Background(),
|
}{{
|
||||||
jsonrpc2.NewPlainObjectStream(connA), noopHandler{},
|
name: "during Call",
|
||||||
)
|
run: func(t *testing.T, ctx context.Context, conn *jsonrpc2.Conn) {
|
||||||
defer nodeA.Close()
|
ready := make(chan struct{})
|
||||||
nodeB := jsonrpc2.NewConn(
|
done := make(chan struct{})
|
||||||
context.Background(),
|
go func() {
|
||||||
jsonrpc2.NewPlainObjectStream(connB),
|
close(ready)
|
||||||
noopHandler{},
|
err := conn.Call(ctx, "m", nil, nil)
|
||||||
)
|
if err != jsonrpc2.ErrClosed {
|
||||||
defer nodeB.Close()
|
t.Errorf("got error %v, want %v", err, jsonrpc2.ErrClosed)
|
||||||
|
}
|
||||||
ready := make(chan struct{})
|
close(done)
|
||||||
done := make(chan struct{})
|
}()
|
||||||
go func() {
|
// Wait for the request to be sent before we close the connection.
|
||||||
close(ready)
|
<-ready
|
||||||
err := nodeB.Call(context.Background(), "m", nil, nil)
|
if err := conn.Close(); err != nil && err != jsonrpc2.ErrClosed {
|
||||||
if err != jsonrpc2.ErrClosed {
|
t.Error(err)
|
||||||
t.Errorf("got error %v, want %v", err, jsonrpc2.ErrClosed)
|
|
||||||
}
|
}
|
||||||
close(done)
|
<-done
|
||||||
}()
|
},
|
||||||
// Wait for the request to be sent before we close the connection.
|
}, {
|
||||||
<-ready
|
name: "during Wait",
|
||||||
if err := nodeB.Close(); err != nil && err != jsonrpc2.ErrClosed {
|
run: func(t *testing.T, ctx context.Context, conn *jsonrpc2.Conn) {
|
||||||
t.Error(err)
|
call, err := conn.DispatchCall(ctx, "m", nil, nil)
|
||||||
}
|
if err != nil {
|
||||||
assertDisconnect(t, nodeB, connB)
|
t.Fatal(err)
|
||||||
<-done
|
}
|
||||||
})
|
if err := conn.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := call.Wait(ctx, nil); err != jsonrpc2.ErrClosed {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "during Dispatch",
|
||||||
|
run: func(t *testing.T, ctx context.Context, conn *jsonrpc2.Conn) {
|
||||||
|
if err := conn.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := conn.DispatchCall(ctx, "m", nil, nil); err != jsonrpc2.ErrClosed {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
connA, connB := net.Pipe()
|
||||||
|
nodeA := jsonrpc2.NewConn(
|
||||||
|
ctx,
|
||||||
|
jsonrpc2.NewPlainObjectStream(connA), noopHandler{},
|
||||||
|
)
|
||||||
|
defer nodeA.Close()
|
||||||
|
nodeB := jsonrpc2.NewConn(
|
||||||
|
ctx,
|
||||||
|
jsonrpc2.NewPlainObjectStream(connB),
|
||||||
|
noopHandler{},
|
||||||
|
)
|
||||||
|
defer nodeB.Close()
|
||||||
|
|
||||||
|
tc.run(t, ctx, nodeB)
|
||||||
|
|
||||||
|
assertDisconnect(t, nodeB, connB)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testParams(t *testing.T, want *json.RawMessage, fn func(c *jsonrpc2.Conn) error) {
|
func testParams(t *testing.T, want *json.RawMessage, fn func(c *jsonrpc2.Conn) error) {
|
||||||
|
|
@ -159,12 +250,12 @@ func testParams(t *testing.T, want *json.RawMessage, fn func(c *jsonrpc2.Conn) e
|
||||||
wg.Done()
|
wg.Done()
|
||||||
})
|
})
|
||||||
|
|
||||||
client, server := newClientServer(handler)
|
connA, connB := Pipe(context.Background(), noopHandler{}, handler)
|
||||||
defer client.Close()
|
defer connA.Close()
|
||||||
defer server.Close()
|
defer connB.Close()
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
if err := fn(client); err != nil {
|
if err := fn(connA); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
@ -203,18 +294,11 @@ func assertRawJSONMessage(t *testing.T, got *json.RawMessage, want *json.RawMess
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newClientServer(handler jsonrpc2.Handler) (client *jsonrpc2.Conn, server *jsonrpc2.Conn) {
|
// Pipe returns two jsonrpc2.Conn, connected via a synchronous, in-memory, full
|
||||||
ctx := context.Background()
|
// duplex network connection.
|
||||||
connA, connB := net.Pipe()
|
func Pipe(ctx context.Context, handlerA, handlerB jsonrpc2.Handler) (connA *jsonrpc2.Conn, connB *jsonrpc2.Conn) {
|
||||||
client = jsonrpc2.NewConn(
|
a, b := net.Pipe()
|
||||||
ctx,
|
connA = jsonrpc2.NewConn(ctx, jsonrpc2.NewPlainObjectStream(a), handlerA)
|
||||||
jsonrpc2.NewPlainObjectStream(connA),
|
connB = jsonrpc2.NewConn(ctx, jsonrpc2.NewPlainObjectStream(b), handlerB)
|
||||||
noopHandler{},
|
return connA, connB
|
||||||
)
|
|
||||||
server = jsonrpc2.NewConn(
|
|
||||||
ctx,
|
|
||||||
jsonrpc2.NewPlainObjectStream(connB),
|
|
||||||
handler,
|
|
||||||
)
|
|
||||||
return client, server
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
78
example_params_test.go
Normal file
78
example_params_test.go
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
package jsonrpc2_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sourcegraph/jsonrpc2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Send a JSON-RPC notification with its params member omitted.
|
||||||
|
func ExampleConn_Notify_paramsOmitted() {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
connA, connB := net.Pipe()
|
||||||
|
defer connA.Close()
|
||||||
|
defer connB.Close()
|
||||||
|
|
||||||
|
rpcConn := jsonrpc2.NewConn(ctx, jsonrpc2.NewPlainObjectStream(connA), nil)
|
||||||
|
|
||||||
|
// Send the JSON-RPC notification.
|
||||||
|
go func() {
|
||||||
|
// Set params to nil.
|
||||||
|
if err := rpcConn.Notify(ctx, "foo", nil); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "notify:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Read the raw JSON-RPC notification on connB.
|
||||||
|
//
|
||||||
|
// Reading the raw JSON-RPC request is for the purpose of this example only.
|
||||||
|
// Use a jsonrpc2.Handler to read parsed requests.
|
||||||
|
buf := make([]byte, 64)
|
||||||
|
n, err := connB.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "read:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s\n", buf[:n])
|
||||||
|
|
||||||
|
// Output: {"jsonrpc":"2.0","method":"foo"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a JSON-RPC notification with its params member set to null.
|
||||||
|
func ExampleConn_Notify_nullParams() {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
connA, connB := net.Pipe()
|
||||||
|
defer connA.Close()
|
||||||
|
defer connB.Close()
|
||||||
|
|
||||||
|
rpcConn := jsonrpc2.NewConn(ctx, jsonrpc2.NewPlainObjectStream(connA), nil)
|
||||||
|
|
||||||
|
// Send the JSON-RPC notification.
|
||||||
|
go func() {
|
||||||
|
// Set params to the JSON null value.
|
||||||
|
params := json.RawMessage("null")
|
||||||
|
if err := rpcConn.Notify(ctx, "foo", params); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "notify:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Read the raw JSON-RPC notification on connB.
|
||||||
|
//
|
||||||
|
// Reading the raw JSON-RPC request is for the purpose of this example only.
|
||||||
|
// Use a jsonrpc2.Handler to read parsed requests.
|
||||||
|
buf := make([]byte, 64)
|
||||||
|
n, err := connB.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "read:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s\n", buf[:n])
|
||||||
|
|
||||||
|
// Output: {"jsonrpc":"2.0","method":"foo","params":null}
|
||||||
|
}
|
||||||
|
|
@ -2,77 +2,63 @@ package jsonrpc2_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sourcegraph/jsonrpc2"
|
"github.com/sourcegraph/jsonrpc2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Send a JSON-RPC notification with its params member omitted.
|
func Example() {
|
||||||
func ExampleConn_Notify_paramsOmitted() {
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Create an in-memory network connection. This connection is used below to
|
||||||
|
// transport the JSON-RPC messages. However, any io.ReadWriteCloser may be
|
||||||
|
// used to send/receive JSON-RPC messages.
|
||||||
connA, connB := net.Pipe()
|
connA, connB := net.Pipe()
|
||||||
defer connA.Close()
|
|
||||||
defer connB.Close()
|
|
||||||
|
|
||||||
rpcConn := jsonrpc2.NewConn(ctx, jsonrpc2.NewPlainObjectStream(connA), nil)
|
// The following JSON-RPC connection is both a client and a server. It can
|
||||||
|
// send requests as well as receive requests. The incoming requests are
|
||||||
|
// handled by myHandler.
|
||||||
|
jsonrpcConnA := jsonrpc2.NewConn(ctx, jsonrpc2.NewPlainObjectStream(connA), &myHandler{})
|
||||||
|
defer jsonrpcConnA.Close()
|
||||||
|
|
||||||
// Send the JSON-RPC notification.
|
// The following JSON-RPC connection has no handler, meaning that it is
|
||||||
go func() {
|
// configured to only be a client. It can send requests and receive the
|
||||||
// Set params to nil.
|
// responses to those requests, but it will ignore any incoming requests.
|
||||||
if err := rpcConn.Notify(ctx, "foo", nil); err != nil {
|
jsonrpcConnB := jsonrpc2.NewConn(ctx, jsonrpc2.NewPlainObjectStream(connB), nil)
|
||||||
fmt.Fprintln(os.Stderr, "notify:", err)
|
defer jsonrpcConnB.Close()
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Read the raw JSON-RPC notification on connB.
|
// Send a request from jsonrpcConnB to jsonrpcConnA. The result of a
|
||||||
//
|
// successful call is stored in the result variable.
|
||||||
// Reading the raw JSON-RPC request is for the purpose of this example only.
|
var result string
|
||||||
// Use a jsonrpc2.Handler to read parsed requests.
|
if err := jsonrpcConnB.Call(ctx, "sayHello", nil, &result); err != nil {
|
||||||
buf := make([]byte, 64)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
n, err := connB.Read(buf)
|
return
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "read:", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s\n", buf[:n])
|
fmt.Println(result)
|
||||||
|
|
||||||
// Output: {"jsonrpc":"2.0","method":"foo"}
|
// Output: hello world
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a JSON-RPC notification with its params member set to null.
|
// myHandler is the jsonrpc2.Handler used by jsonrpcConnA.
|
||||||
func ExampleConn_Notify_nullParams() {
|
type myHandler struct{}
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
connA, connB := net.Pipe()
|
// Handle implements the jsonrpc2.Handler interface.
|
||||||
defer connA.Close()
|
func (h *myHandler) Handle(ctx context.Context, c *jsonrpc2.Conn, r *jsonrpc2.Request) {
|
||||||
defer connB.Close()
|
switch r.Method {
|
||||||
|
case "sayHello":
|
||||||
rpcConn := jsonrpc2.NewConn(ctx, jsonrpc2.NewPlainObjectStream(connA), nil)
|
if err := c.Reply(ctx, r.ID, "hello world"); err != nil {
|
||||||
|
log.Println(err)
|
||||||
// Send the JSON-RPC notification.
|
return
|
||||||
go func() {
|
}
|
||||||
// Set params to the JSON null value.
|
default:
|
||||||
params := json.RawMessage("null")
|
err := &jsonrpc2.Error{Code: jsonrpc2.CodeMethodNotFound, Message: "Method not found"}
|
||||||
if err := rpcConn.Notify(ctx, "foo", params); err != nil {
|
if err := c.ReplyWithError(ctx, r.ID, err); err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "notify:", err)
|
log.Println(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
|
|
||||||
// Read the raw JSON-RPC notification on connB.
|
|
||||||
//
|
|
||||||
// Reading the raw JSON-RPC request is for the purpose of this example only.
|
|
||||||
// Use a jsonrpc2.Handler to read parsed requests.
|
|
||||||
buf := make([]byte, 64)
|
|
||||||
n, err := connB.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "read:", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s\n", buf[:n])
|
|
||||||
|
|
||||||
// Output: {"jsonrpc":"2.0","method":"foo","params":null}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,20 +30,16 @@ func (h *HandlerWithErrorConfigurer) Handle(ctx context.Context, conn *Conn, req
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = resp.SetResult(result)
|
err = resp.SetResult(result)
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
if e, ok := err.(*Error); ok {
|
if e, ok := err.(*Error); ok {
|
||||||
resp.Error = e
|
resp.Error = e
|
||||||
} else {
|
} else if err != nil {
|
||||||
resp.Error = &Error{Message: err.Error()}
|
resp.Error = &Error{Message: err.Error()}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !req.Notif {
|
err = conn.SendResponse(ctx, resp)
|
||||||
if err := conn.SendResponse(ctx, resp); err != nil {
|
if err != nil && (err != ErrClosed || !h.suppressErrClosed) {
|
||||||
if err != ErrClosed || !h.suppressErrClosed {
|
conn.logger.Printf("jsonrpc2 handler: sending response %s: %v\n", resp.ID, err)
|
||||||
conn.logger.Printf("jsonrpc2 handler: sending response %s: %v\n", resp.ID, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,10 +59,10 @@ const (
|
||||||
|
|
||||||
// Handler handles JSON-RPC requests and notifications.
|
// Handler handles JSON-RPC requests and notifications.
|
||||||
type Handler interface {
|
type Handler interface {
|
||||||
// Handle is called to handle a request. No other requests are handled
|
// Handle is called to handle a request. No other requests are handled until
|
||||||
// until it returns. If you do not require strict ordering behavior
|
// it returns. If you do not require strict ordering behavior of received
|
||||||
// of received RPCs, it is suggested to wrap your handler in
|
// RPCs, it is suggested to wrap your handler in AsyncHandler. The context
|
||||||
// AsyncHandler.
|
// is automatically canceled when the connection closes.
|
||||||
Handle(context.Context, *Conn, *Request)
|
Handle(context.Context, *Conn, *Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
32
request.go
32
request.go
|
|
@ -55,6 +55,10 @@ func (r Request) MarshalJSON() ([]byte, error) {
|
||||||
// UnmarshalJSON implements json.Unmarshaler.
|
// UnmarshalJSON implements json.Unmarshaler.
|
||||||
func (r *Request) UnmarshalJSON(data []byte) error {
|
func (r *Request) UnmarshalJSON(data []byte) error {
|
||||||
r2 := make(map[string]interface{})
|
r2 := make(map[string]interface{})
|
||||||
|
pop := func(key string) interface{} {
|
||||||
|
defer delete(r2, key)
|
||||||
|
return r2[key]
|
||||||
|
}
|
||||||
|
|
||||||
// Detect if the "params" or "meta" fields are JSON "null" or just not
|
// Detect if the "params" or "meta" fields are JSON "null" or just not
|
||||||
// present by seeing if the field gets overwritten to nil.
|
// present by seeing if the field gets overwritten to nil.
|
||||||
|
|
@ -68,36 +72,37 @@ func (r *Request) UnmarshalJSON(data []byte) error {
|
||||||
if err := decoder.Decode(&r2); err != nil {
|
if err := decoder.Decode(&r2); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var ok bool
|
var ok bool
|
||||||
r.Method, ok = r2["method"].(string)
|
r.Method, ok = pop("method").(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("missing method field")
|
return errors.New("missing method field")
|
||||||
}
|
}
|
||||||
switch {
|
switch params := pop("params"); params {
|
||||||
case r2["params"] == nil:
|
case nil:
|
||||||
r.Params = &jsonNull
|
r.Params = &jsonNull
|
||||||
case r2["params"] == emptyParams:
|
case emptyParams:
|
||||||
r.Params = nil
|
r.Params = nil
|
||||||
default:
|
default:
|
||||||
b, err := json.Marshal(r2["params"])
|
b, err := json.Marshal(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal params: %w", err)
|
return fmt.Errorf("failed to marshal params: %w", err)
|
||||||
}
|
}
|
||||||
r.Params = (*json.RawMessage)(&b)
|
r.Params = (*json.RawMessage)(&b)
|
||||||
}
|
}
|
||||||
switch {
|
switch meta := pop("meta"); meta {
|
||||||
case r2["meta"] == nil:
|
case nil:
|
||||||
r.Meta = &jsonNull
|
r.Meta = &jsonNull
|
||||||
case r2["meta"] == emptyMeta:
|
case emptyMeta:
|
||||||
r.Meta = nil
|
r.Meta = nil
|
||||||
default:
|
default:
|
||||||
b, err := json.Marshal(r2["meta"])
|
b, err := json.Marshal(meta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal Meta: %w", err)
|
return fmt.Errorf("failed to marshal Meta: %w", err)
|
||||||
}
|
}
|
||||||
r.Meta = (*json.RawMessage)(&b)
|
r.Meta = (*json.RawMessage)(&b)
|
||||||
}
|
}
|
||||||
switch rawID := r2["id"].(type) {
|
switch rawID := pop("id").(type) {
|
||||||
case nil:
|
case nil:
|
||||||
r.ID = ID{}
|
r.ID = ID{}
|
||||||
r.Notif = true
|
r.Notif = true
|
||||||
|
|
@ -115,13 +120,12 @@ func (r *Request) UnmarshalJSON(data []byte) error {
|
||||||
return fmt.Errorf("unexpected ID type: %T", rawID)
|
return fmt.Errorf("unexpected ID type: %T", rawID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The jsonrpc field should not be added to ExtraFields.
|
||||||
|
delete(r2, "jsonrpc")
|
||||||
|
|
||||||
// Clear the extra fields before populating them again.
|
// Clear the extra fields before populating them again.
|
||||||
r.ExtraFields = nil
|
r.ExtraFields = nil
|
||||||
for name, value := range r2 {
|
for name, value := range r2 {
|
||||||
switch name {
|
|
||||||
case "id", "jsonrpc", "meta", "method", "params":
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
r.ExtraFields = append(r.ExtraFields, RequestField{
|
r.ExtraFields = append(r.ExtraFields, RequestField{
|
||||||
Name: name,
|
Name: name,
|
||||||
Value: value,
|
Value: value,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue