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 protocol
6
7import (
8	"context"
9	"encoding/json"
10	"fmt"
11
12	"golang.org/x/tools/internal/event"
13	"golang.org/x/tools/internal/jsonrpc2"
14	"golang.org/x/tools/internal/xcontext"
15	errors "golang.org/x/xerrors"
16)
17
18var (
19	// RequestCancelledError should be used when a request is cancelled early.
20	RequestCancelledError = jsonrpc2.NewError(-32800, "JSON RPC cancelled")
21)
22
23// ClientDispatcher returns a Client that dispatches LSP requests across the
24// given jsonrpc2 connection.
25func ClientDispatcher(conn jsonrpc2.Conn) Client {
26	return &clientDispatcher{Conn: conn}
27}
28
29type clientDispatcher struct {
30	jsonrpc2.Conn
31}
32
33// ServerDispatcher returns a Server that dispatches LSP requests across the
34// given jsonrpc2 connection.
35func ServerDispatcher(conn jsonrpc2.Conn) Server {
36	return &serverDispatcher{Conn: conn}
37}
38
39type serverDispatcher struct {
40	jsonrpc2.Conn
41}
42
43func ClientHandler(client Client, handler jsonrpc2.Handler) jsonrpc2.Handler {
44	return func(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error {
45		if ctx.Err() != nil {
46			ctx := xcontext.Detach(ctx)
47			return reply(ctx, nil, RequestCancelledError)
48		}
49		handled, err := clientDispatch(ctx, client, reply, req)
50		if handled || err != nil {
51			return err
52		}
53		return handler(ctx, reply, req)
54	}
55}
56
57func ServerHandler(server Server, handler jsonrpc2.Handler) jsonrpc2.Handler {
58	return func(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error {
59		if ctx.Err() != nil {
60			ctx := xcontext.Detach(ctx)
61			return reply(ctx, nil, RequestCancelledError)
62		}
63		handled, err := serverDispatch(ctx, server, reply, req)
64		if handled || err != nil {
65			return err
66		}
67		//TODO: This code is wrong, it ignores handler and assumes non standard
68		// request handles everything
69		// non standard request should just be a layered handler.
70		var params interface{}
71		if err := json.Unmarshal(req.Params(), &params); err != nil {
72			return sendParseError(ctx, reply, err)
73		}
74		resp, err := server.NonstandardRequest(ctx, req.Method(), params)
75		return reply(ctx, resp, err)
76
77	}
78}
79func Handlers(handler jsonrpc2.Handler) jsonrpc2.Handler {
80	return CancelHandler(
81		jsonrpc2.AsyncHandler(
82			jsonrpc2.MustReplyHandler(handler)))
83}
84
85func CancelHandler(handler jsonrpc2.Handler) jsonrpc2.Handler {
86	handler, canceller := jsonrpc2.CancelHandler(handler)
87	return func(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error {
88		if req.Method() != "$/cancelRequest" {
89			// TODO(iancottrell): See if we can generate a reply for the request to be cancelled
90			// at the point of cancellation rather than waiting for gopls to naturally reply.
91			// To do that, we need to keep track of whether a reply has been sent already and
92			// be careful about racing between the two paths.
93			// TODO(iancottrell): Add a test that watches the stream and verifies the response
94			// for the cancelled request flows.
95			replyWithDetachedContext := func(ctx context.Context, resp interface{}, err error) error {
96				// https://microsoft.github.io/language-server-protocol/specifications/specification-current/#cancelRequest
97				if ctx.Err() != nil && err == nil {
98					err = RequestCancelledError
99				}
100				ctx = xcontext.Detach(ctx)
101				return reply(ctx, resp, err)
102			}
103			return handler(ctx, replyWithDetachedContext, req)
104		}
105		var params CancelParams
106		if err := json.Unmarshal(req.Params(), &params); err != nil {
107			return sendParseError(ctx, reply, err)
108		}
109		if n, ok := params.ID.(float64); ok {
110			canceller(jsonrpc2.NewIntID(int64(n)))
111		} else if s, ok := params.ID.(string); ok {
112			canceller(jsonrpc2.NewStringID(s))
113		} else {
114			return sendParseError(ctx, reply, fmt.Errorf("request ID %v malformed", params.ID))
115		}
116		return reply(ctx, nil, nil)
117	}
118}
119
120func Call(ctx context.Context, conn jsonrpc2.Conn, method string, params interface{}, result interface{}) error {
121	id, err := conn.Call(ctx, method, params, result)
122	if ctx.Err() != nil {
123		cancelCall(ctx, conn, id)
124	}
125	return err
126}
127
128func cancelCall(ctx context.Context, conn jsonrpc2.Conn, id jsonrpc2.ID) {
129	ctx = xcontext.Detach(ctx)
130	ctx, done := event.Start(ctx, "protocol.canceller")
131	defer done()
132	// Note that only *jsonrpc2.ID implements json.Marshaler.
133	conn.Notify(ctx, "$/cancelRequest", &CancelParams{ID: &id})
134}
135
136func sendParseError(ctx context.Context, reply jsonrpc2.Replier, err error) error {
137	return reply(ctx, nil, errors.Errorf("%w: %s", jsonrpc2.ErrParse, err))
138}
139