internal/jsonrpc2: add concrete types for Call, Notify and Response

The previous implementation was exposing the details of the wire format
and resulted in non idomatic go, detecting the presence of absence of
values in fields to deterimine the message type.
Now the messages are distinct types and we use type switches instead.
Request still exists as an interface to expose the shared behaviour of
Call and Notification, as this is the type accepted by handlers.
The set of messages is deliberately closed by using a private methods on the
interfaces.

Change-Id: I2cf15ee3923ef4688670c62896f81f760c77fe04
Reviewed-on: https://go-review.googlesource.com/c/tools/+/228719
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/internal/jsonrpc2/handler.go b/internal/jsonrpc2/handler.go
index 52cd62f..0cd106f2 100644
--- a/internal/jsonrpc2/handler.go
+++ b/internal/jsonrpc2/handler.go
@@ -14,7 +14,7 @@
 
 // Handler is invoked to handle incoming requests.
 // The Replier sends a reply to the request and must be called exactly once.
-type Handler func(ctx context.Context, reply Replier, req *Request) error
+type Handler func(ctx context.Context, reply Replier, req Request) error
 
 // Replier is passed to handlers to allow them to reply to the request.
 // If err is set then result will be ignored.
@@ -23,24 +23,24 @@
 // MethodNotFound is a Handler that replies to all call requests with the
 // standard method not found response.
 // This should normally be the final handler in a chain.
-func MethodNotFound(ctx context.Context, reply Replier, r *Request) error {
-	return reply(ctx, nil, fmt.Errorf("%w: %q", ErrMethodNotFound, r.Method))
+func MethodNotFound(ctx context.Context, reply Replier, req Request) error {
+	return reply(ctx, nil, fmt.Errorf("%w: %q", ErrMethodNotFound, req.Method()))
 }
 
 // MustReplyHandler creates a Handler that panics if the wrapped handler does
 // not call Reply for every request that it is passed.
 func MustReplyHandler(handler Handler) Handler {
-	return func(ctx context.Context, reply Replier, req *Request) error {
+	return func(ctx context.Context, reply Replier, req Request) error {
 		called := false
 		err := handler(ctx, func(ctx context.Context, result interface{}, err error) error {
 			if called {
-				panic(fmt.Errorf("request %q replied to more than once", req.Method))
+				panic(fmt.Errorf("request %q replied to more than once", req.Method()))
 			}
 			called = true
 			return reply(ctx, result, err)
 		}, req)
 		if !called {
-			panic(fmt.Errorf("request %q was never replied to", req.Method))
+			panic(fmt.Errorf("request %q was never replied to", req.Method()))
 		}
 		return err
 	}
@@ -51,17 +51,17 @@
 func CancelHandler(handler Handler) (Handler, func(id ID)) {
 	var mu sync.Mutex
 	handling := make(map[ID]context.CancelFunc)
-	wrapped := func(ctx context.Context, reply Replier, req *Request) error {
-		if req.ID != nil {
+	wrapped := func(ctx context.Context, reply Replier, req Request) error {
+		if call, ok := req.(*Call); ok {
 			cancelCtx, cancel := context.WithCancel(ctx)
 			ctx = cancelCtx
 			mu.Lock()
-			handling[*req.ID] = cancel
+			handling[call.ID()] = cancel
 			mu.Unlock()
 			innerReply := reply
 			reply = func(ctx context.Context, result interface{}, err error) error {
 				mu.Lock()
-				delete(handling, *req.ID)
+				delete(handling, call.ID())
 				mu.Unlock()
 				return innerReply(ctx, result, err)
 			}
@@ -87,7 +87,7 @@
 func AsyncHandler(handler Handler) Handler {
 	nextRequest := make(chan struct{})
 	close(nextRequest)
-	return func(ctx context.Context, reply Replier, req *Request) error {
+	return func(ctx context.Context, reply Replier, req Request) error {
 		waitForPrevious := nextRequest
 		nextRequest = make(chan struct{})
 		unlockNext := nextRequest
diff --git a/internal/jsonrpc2/jsonrpc2.go b/internal/jsonrpc2/jsonrpc2.go
index 175810f..b7364fa 100644
--- a/internal/jsonrpc2/jsonrpc2.go
+++ b/internal/jsonrpc2/jsonrpc2.go
@@ -10,7 +10,6 @@
 import (
 	"context"
 	"encoding/json"
-	"errors"
 	"fmt"
 	"sync"
 	"sync/atomic"
@@ -33,20 +32,7 @@
 	seq       int64 // must only be accessed using atomic operations
 	stream    Stream
 	pendingMu sync.Mutex // protects the pending map
-	pending   map[ID]chan *wireResponse
-}
-
-// Request is sent to a server to represent a Call or Notify operaton.
-type Request struct {
-	// The Wire values of the request.
-	// Method is a string containing the method name to invoke.
-	Method string
-	// Params is either a struct or an array with the parameters of the method.
-	Params *json.RawMessage
-	// The id of this request, used to tie the Response back to the request.
-	// Will be either a string or a number. If not set, the Request is a notify,
-	// and no response is possible.
-	ID *ID
+	pending   map[ID]chan *Response
 }
 
 type constError string
@@ -58,7 +44,7 @@
 func NewConn(s Stream) *Conn {
 	conn := &Conn{
 		stream:  s,
-		pending: make(map[ID]chan *wireResponse),
+		pending: make(map[ID]chan *Response),
 	}
 	return conn
 }
@@ -67,22 +53,17 @@
 // It will return as soon as the notification has been sent, as no response is
 // possible.
 func (c *Conn) Notify(ctx context.Context, method string, params interface{}) (err error) {
-	jsonParams, err := marshalToRaw(params)
+	notify, err := NewNotification(method, params)
 	if err != nil {
 		return fmt.Errorf("marshaling notify parameters: %v", err)
 	}
-	request := &wireRequest{
-		Method: method,
-		Params: jsonParams,
-	}
-	data, err := json.Marshal(request)
+	data, err := json.Marshal(notify)
 	if err != nil {
 		return fmt.Errorf("marshaling notify request: %v", err)
 	}
-	ctx, done := event.StartSpan(ctx, request.Method,
-		tag.Method.Of(request.Method),
+	ctx, done := event.StartSpan(ctx, method,
+		tag.Method.Of(method),
 		tag.RPCDirection.Of(tag.Outbound),
-		tag.RPCID.Of(fmt.Sprintf("%q", request.ID)),
 	)
 	defer func() {
 		recordStatus(ctx, err)
@@ -101,24 +82,19 @@
 func (c *Conn) Call(ctx context.Context, method string, params, result interface{}) (_ ID, err error) {
 	// generate a new request identifier
 	id := ID{number: atomic.AddInt64(&c.seq, 1)}
-	jsonParams, err := marshalToRaw(params)
+	call, err := NewCall(id, method, params)
 	if err != nil {
 		return id, fmt.Errorf("marshaling call parameters: %v", err)
 	}
-	request := &wireRequest{
-		ID:     &id,
-		Method: method,
-		Params: jsonParams,
-	}
 	// marshal the request now it is complete
-	data, err := json.Marshal(request)
+	data, err := json.Marshal(call)
 	if err != nil {
 		return id, fmt.Errorf("marshaling call request: %v", err)
 	}
-	ctx, done := event.StartSpan(ctx, request.Method,
-		tag.Method.Of(request.Method),
+	ctx, done := event.StartSpan(ctx, method,
+		tag.Method.Of(method),
 		tag.RPCDirection.Of(tag.Outbound),
-		tag.RPCID.Of(fmt.Sprintf("%q", request.ID)),
+		tag.RPCID.Of(fmt.Sprintf("%q", id)),
 	)
 	defer func() {
 		recordStatus(ctx, err)
@@ -129,7 +105,7 @@
 	// are racing the response. Also add a buffer to rchan, so that if we get a
 	// wire response between the time this call is cancelled and id is deleted
 	// from c.pending, the send to rchan will not block.
-	rchan := make(chan *wireResponse, 1)
+	rchan := make(chan *Response, 1)
 	c.pendingMu.Lock()
 	c.pending[id] = rchan
 	c.pendingMu.Unlock()
@@ -149,13 +125,13 @@
 	select {
 	case response := <-rchan:
 		// is it an error response?
-		if response.Error != nil {
-			return id, response.Error
+		if response.err != nil {
+			return id, response.err
 		}
-		if result == nil || response.Result == nil {
+		if result == nil || len(response.result) == 0 {
 			return id, nil
 		}
-		if err := json.Unmarshal(*response.Result, result); err != nil {
+		if err := json.Unmarshal(response.result, result); err != nil {
 			return id, fmt.Errorf("unmarshaling result: %v", err)
 		}
 		return id, nil
@@ -164,43 +140,22 @@
 	}
 }
 
-func replier(conn *Conn, r *Request, spanDone func()) Replier {
+func replier(conn *Conn, req Request, spanDone func()) Replier {
 	return func(ctx context.Context, result interface{}, err error) error {
 		defer func() {
 			recordStatus(ctx, err)
 			spanDone()
 		}()
-
-		if r.ID == nil {
+		call, ok := req.(*Call)
+		if !ok {
 			// request was a notify, no need to respond
 			return nil
 		}
-
-		var raw *json.RawMessage
-		if err == nil {
-			raw, err = marshalToRaw(result)
-		}
-		response := &wireResponse{
-			Result: raw,
-			ID:     r.ID,
-		}
-		if err != nil {
-			if callErr, ok := err.(*wireError); ok {
-				response.Error = callErr
-			} else {
-				response.Error = &wireError{Message: err.Error()}
-				var wrapped *wireError
-				if errors.As(err, &wrapped) {
-					// if we wrapped a wire error, keep the code from the wrapped error
-					// but the message from the outer error
-					response.Error.Code = wrapped.Code
-				}
-			}
-		}
-		data, err := json.Marshal(response)
+		response, err := NewResponse(call.id, result, err)
 		if err != nil {
 			return err
 		}
+		data, err := json.Marshal(response)
 		n, err := conn.stream.Write(ctx, data)
 		event.Record(ctx, tag.SentBytes.Of(n))
 
@@ -227,61 +182,51 @@
 			return err
 		}
 		// read a combined message
-		msg := &wireCombined{}
-		if err := json.Unmarshal(data, msg); err != nil {
+		msg, err := DecodeMessage(data)
+		if err != nil {
 			// a badly formed message arrived, log it and continue
 			// we trust the stream to have isolated the error to just this message
 			continue
 		}
-		// Work out whether this is a request or response.
-		switch {
-		case msg.Method != "":
-			// If method is set it must be a request.
-			reqCtx, spanDone := event.StartSpan(runCtx, msg.Method,
-				tag.Method.Of(msg.Method),
+		switch msg := msg.(type) {
+		case Request:
+			tags := []event.Tag{
+				tag.Method.Of(msg.Method()),
 				tag.RPCDirection.Of(tag.Inbound),
-				tag.RPCID.Of(fmt.Sprintf("%q", msg.ID)),
-			)
+				{}, // reserved for ID if present
+			}
+			if call, ok := msg.(*Call); ok {
+				tags[len(tags)-1] = tag.RPCID.Of(fmt.Sprintf("%q", call.ID()))
+			} else {
+				tags = tags[:len(tags)-1]
+			}
+			reqCtx, spanDone := event.StartSpan(runCtx, msg.Method(), tags...)
 			event.Record(reqCtx,
 				tag.Started.Of(1),
 				tag.ReceivedBytes.Of(n))
-
-			req := &Request{
-				Method: msg.Method,
-				Params: msg.Params,
-				ID:     msg.ID,
-			}
-
-			if err := handler(reqCtx, replier(c, req, spanDone), req); err != nil {
+			if err := handler(reqCtx, replier(c, msg, spanDone), msg); err != nil {
 				// delivery failed, not much we can do
 				event.Error(reqCtx, "jsonrpc2 message delivery failed", err)
 			}
-		case msg.ID != nil:
+		case *Response:
 			// If method is not set, this should be a response, in which case we must
 			// have an id to send the response back to the caller.
 			c.pendingMu.Lock()
-			rchan, ok := c.pending[*msg.ID]
+			rchan, ok := c.pending[msg.id]
 			c.pendingMu.Unlock()
 			if ok {
-				response := &wireResponse{
-					Result: msg.Result,
-					Error:  msg.Error,
-					ID:     msg.ID,
-				}
-				rchan <- response
+				rchan <- msg
 			}
-		default:
 		}
 	}
 }
 
-func marshalToRaw(obj interface{}) (*json.RawMessage, error) {
+func marshalToRaw(obj interface{}) (json.RawMessage, error) {
 	data, err := json.Marshal(obj)
 	if err != nil {
-		return nil, err
+		return json.RawMessage{}, err
 	}
-	raw := json.RawMessage(data)
-	return &raw, nil
+	return json.RawMessage(data), nil
 }
 
 func recordStatus(ctx context.Context, err error) {
diff --git a/internal/jsonrpc2/jsonrpc2_test.go b/internal/jsonrpc2/jsonrpc2_test.go
index 55b3d87..a733558 100644
--- a/internal/jsonrpc2/jsonrpc2_test.go
+++ b/internal/jsonrpc2/jsonrpc2_test.go
@@ -134,28 +134,28 @@
 }
 
 func testHandler(log bool) jsonrpc2.Handler {
-	return func(ctx context.Context, reply jsonrpc2.Replier, req *jsonrpc2.Request) error {
-		switch req.Method {
+	return func(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error {
+		switch req.Method() {
 		case "no_args":
-			if req.Params != nil {
+			if len(req.Params()) > 0 {
 				return reply(ctx, nil, fmt.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams))
 			}
 			return reply(ctx, true, nil)
 		case "one_string":
 			var v string
-			if err := json.Unmarshal(*req.Params, &v); err != nil {
+			if err := json.Unmarshal(req.Params(), &v); err != nil {
 				return reply(ctx, nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err))
 			}
 			return reply(ctx, "got:"+v, nil)
 		case "one_number":
 			var v int
-			if err := json.Unmarshal(*req.Params, &v); err != nil {
+			if err := json.Unmarshal(req.Params(), &v); err != nil {
 				return reply(ctx, nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err))
 			}
 			return reply(ctx, fmt.Sprintf("got:%d", v), nil)
 		case "join":
 			var v []string
-			if err := json.Unmarshal(*req.Params, &v); err != nil {
+			if err := json.Unmarshal(req.Params(), &v); err != nil {
 				return reply(ctx, nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err))
 			}
 			return reply(ctx, path.Join(v...), nil)
diff --git a/internal/jsonrpc2/messages.go b/internal/jsonrpc2/messages.go
new file mode 100644
index 0000000..45efc53
--- /dev/null
+++ b/internal/jsonrpc2/messages.go
@@ -0,0 +1,227 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package jsonrpc2
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+)
+
+// Message is the interface to all jsonrpc2 message types.
+// They share no common functionality, but are a closed set of concrete types
+// that are allowed to implement this interface. The message types are *Call,
+// *Notification and *Response.
+type Message interface {
+	// isJSONRPC2Message is used to make the set of message implementations a
+	// closed set.
+	isJSONRPC2Message()
+}
+
+// Request is the shared interface to jsonrpc2 messages that request
+// a method be invoked.
+// The request types are a closed set of *Call and *Notification.
+type Request interface {
+	Message
+	// Method is a string containing the method name to invoke.
+	Method() string
+	// Params is either a struct or an array with the parameters of the method.
+	Params() json.RawMessage
+	// isJSONRPC2Request is used to make the set of request implementations closed.
+	isJSONRPC2Request()
+}
+
+// Notification is a request for which a response cannot occur, and as such
+// it has not ID.
+type Notification struct {
+	// Method is a string containing the method name to invoke.
+	method string
+	params json.RawMessage
+}
+
+// Call is a request that expects a response.
+// The response will have a matching ID.
+type Call struct {
+	// Method is a string containing the method name to invoke.
+	method string
+	// Params is either a struct or an array with the parameters of the method.
+	params json.RawMessage
+	// id of this request, used to tie the Response back to the request.
+	id ID
+}
+
+// Response is a reply to a Call.
+// It will have the same ID as the call it is a response to.
+type Response struct {
+	// result is the content of the response.
+	result json.RawMessage
+	// err is set only if the call failed.
+	err error
+	// ID of the request this is a response to.
+	id ID
+}
+
+// NewNotification constructs a new Notification message for the supplied
+// method and parameters.
+func NewNotification(method string, params interface{}) (*Notification, error) {
+	p, merr := marshalToRaw(params)
+	return &Notification{method: method, params: p}, merr
+}
+
+func (msg *Notification) Method() string          { return msg.method }
+func (msg *Notification) Params() json.RawMessage { return msg.params }
+func (msg *Notification) isJSONRPC2Message()      {}
+func (msg *Notification) isJSONRPC2Request()      {}
+
+func (n *Notification) MarshalJSON() ([]byte, error) {
+	msg := wireRequest{Method: n.method, Params: &n.params}
+	data, err := json.Marshal(msg)
+	if err != nil {
+		return data, fmt.Errorf("marshaling notification: %w", err)
+	}
+	return data, nil
+}
+
+func (n *Notification) UnmarshalJSON(data []byte) error {
+	msg := wireRequest{}
+	if err := json.Unmarshal(data, &msg); err != nil {
+		return fmt.Errorf("unmarshaling notification: %w", err)
+	}
+	n.method = msg.Method
+	if msg.Params != nil {
+		n.params = *msg.Params
+	}
+	return nil
+}
+
+// NewCall constructs a new Call message for the supplied ID, method and
+// parameters.
+func NewCall(id ID, method string, params interface{}) (*Call, error) {
+	p, merr := marshalToRaw(params)
+	return &Call{id: id, method: method, params: p}, merr
+}
+
+func (msg *Call) Method() string          { return msg.method }
+func (msg *Call) Params() json.RawMessage { return msg.params }
+func (msg *Call) ID() ID                  { return msg.id }
+func (msg *Call) isJSONRPC2Message()      {}
+func (msg *Call) isJSONRPC2Request()      {}
+
+func (c *Call) MarshalJSON() ([]byte, error) {
+	msg := wireRequest{Method: c.method, Params: &c.params, ID: &c.id}
+	data, err := json.Marshal(msg)
+	if err != nil {
+		return data, fmt.Errorf("marshaling call: %w", err)
+	}
+	return data, nil
+}
+
+func (c *Call) UnmarshalJSON(data []byte) error {
+	msg := wireRequest{}
+	if err := json.Unmarshal(data, &msg); err != nil {
+		return fmt.Errorf("unmarshaling call: %w", err)
+	}
+	c.method = msg.Method
+	if msg.Params != nil {
+		c.params = *msg.Params
+	}
+	if msg.ID != nil {
+		c.id = *msg.ID
+	}
+	return nil
+}
+
+// NewResponse constructs a new Response message that is a reply to the
+// supplied. If err is set result may be ignored.
+func NewResponse(id ID, result interface{}, err error) (*Response, error) {
+	r, merr := marshalToRaw(result)
+	return &Response{id: id, result: r, err: err}, merr
+}
+
+func (msg *Response) ID() ID                  { return msg.id }
+func (msg *Response) Result() json.RawMessage { return msg.result }
+func (msg *Response) Err() error              { return msg.err }
+func (msg *Response) isJSONRPC2Message()      {}
+
+func (r *Response) MarshalJSON() ([]byte, error) {
+	msg := &wireResponse{Result: &r.result, Error: toWireError(r.err), ID: &r.id}
+	data, err := json.Marshal(msg)
+	if err != nil {
+		return data, fmt.Errorf("marshaling notification: %w", err)
+	}
+	return data, nil
+}
+
+func toWireError(err error) *wireError {
+	if err == nil {
+		// no error, the response is complete
+		return nil
+	}
+	if err, ok := err.(*wireError); ok {
+		// already a wire error, just use it
+		return err
+	}
+	result := &wireError{Message: err.Error()}
+	var wrapped *wireError
+	if errors.As(err, &wrapped) {
+		// if we wrapped a wire error, keep the code from the wrapped error
+		// but the message from the outer error
+		result.Code = wrapped.Code
+	}
+	return result
+}
+
+func (r *Response) UnmarshalJSON(data []byte) error {
+	msg := wireResponse{}
+	if err := json.Unmarshal(data, &msg); err != nil {
+		return fmt.Errorf("unmarshaling jsonrpc response: %w", err)
+	}
+	if msg.Result != nil {
+		r.result = *msg.Result
+	}
+	if msg.Error != nil {
+		r.err = msg.Error
+	}
+	if msg.ID != nil {
+		r.id = *msg.ID
+	}
+	return nil
+}
+
+func DecodeMessage(data []byte) (Message, error) {
+	msg := wireCombined{}
+	if err := json.Unmarshal(data, &msg); err != nil {
+		return nil, fmt.Errorf("unmarshaling jsonrpc message: %w", err)
+	}
+	if msg.Method == "" {
+		// no method, should be a response
+		if msg.ID == nil {
+			return nil, ErrInvalidRequest
+		}
+		response := &Response{id: *msg.ID}
+		if msg.Error != nil {
+			response.err = msg.Error
+		}
+		if msg.Result != nil {
+			response.result = *msg.Result
+		}
+		return response, nil
+	}
+	// has a method, must be a request
+	if msg.ID == nil {
+		// request with no ID is a notify
+		notify := &Notification{method: msg.Method}
+		if msg.Params != nil {
+			notify.params = *msg.Params
+		}
+		return notify, nil
+	}
+	// request with an ID, must be a call
+	call := &Call{method: msg.Method, id: *msg.ID}
+	if msg.Params != nil {
+		call.params = *msg.Params
+	}
+	return call, nil
+}
diff --git a/internal/jsonrpc2/servertest/servertest_test.go b/internal/jsonrpc2/servertest/servertest_test.go
index 43b30b8..75a0613 100644
--- a/internal/jsonrpc2/servertest/servertest_test.go
+++ b/internal/jsonrpc2/servertest/servertest_test.go
@@ -16,7 +16,7 @@
 	Msg string
 }
 
-func fakeHandler(ctx context.Context, reply jsonrpc2.Replier, req *jsonrpc2.Request) error {
+func fakeHandler(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error {
 	return reply(ctx, &msg{"pong"}, nil)
 }
 
diff --git a/internal/lsp/lsprpc/lsprpc.go b/internal/lsp/lsprpc/lsprpc.go
index b5a8aed..5662591 100644
--- a/internal/lsp/lsprpc/lsprpc.go
+++ b/internal/lsp/lsprpc/lsprpc.go
@@ -416,10 +416,10 @@
 // instance from exiting. In the future it may also intercept 'shutdown' to
 // provide more graceful shutdown of the client connection.
 func forwarderHandler(handler jsonrpc2.Handler) jsonrpc2.Handler {
-	return func(ctx context.Context, reply jsonrpc2.Replier, r *jsonrpc2.Request) error {
+	return func(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error {
 		// TODO(golang.org/issues/34111): we should more gracefully disconnect here,
 		// once that process exists.
-		if r.Method == "exit" {
+		if r.Method() == "exit" {
 			ForwarderExitFunc(0)
 			// reply nil here to consume the message: in
 			// tests, ForwarderExitFunc may be overridden to something that doesn't
@@ -486,12 +486,12 @@
 )
 
 func handshaker(client *debugClient, goplsPath string, handler jsonrpc2.Handler) jsonrpc2.Handler {
-	return func(ctx context.Context, reply jsonrpc2.Replier, r *jsonrpc2.Request) error {
-		switch r.Method {
+	return func(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error {
+		switch r.Method() {
 		case handshakeMethod:
 			var req handshakeRequest
-			if err := json.Unmarshal(*r.Params, &req); err != nil {
-				sendError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &req); err != nil {
+				sendError(ctx, reply, err)
 				return nil
 			}
 			client.debugAddress = req.DebugAddr
@@ -532,7 +532,7 @@
 	}
 }
 
-func sendError(ctx context.Context, reply jsonrpc2.Replier, req *jsonrpc2.Request, err error) {
+func sendError(ctx context.Context, reply jsonrpc2.Replier, err error) {
 	err = fmt.Errorf("%w: %v", jsonrpc2.ErrParse, err)
 	if err := reply(ctx, nil, err); err != nil {
 		event.Error(ctx, "", err)
diff --git a/internal/lsp/protocol/protocol.go b/internal/lsp/protocol/protocol.go
index fb602a6..92e3d33 100644
--- a/internal/lsp/protocol/protocol.go
+++ b/internal/lsp/protocol/protocol.go
@@ -40,20 +40,20 @@
 
 func CancelHandler(handler jsonrpc2.Handler) jsonrpc2.Handler {
 	handler, canceller := jsonrpc2.CancelHandler(handler)
-	return func(ctx context.Context, reply jsonrpc2.Replier, req *jsonrpc2.Request) error {
-		if req.Method != "$/cancelRequest" {
+	return func(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error {
+		if req.Method() != "$/cancelRequest" {
 			return handler(ctx, reply, req)
 		}
 		var params CancelParams
-		if err := json.Unmarshal(*req.Params, &params); err != nil {
-			return sendParseError(ctx, reply, req, err)
+		if err := json.Unmarshal(req.Params(), &params); err != nil {
+			return sendParseError(ctx, reply, err)
 		}
 		if n, ok := params.ID.(float64); ok {
 			canceller(*jsonrpc2.NewIntID(int64(n)))
 		} else if s, ok := params.ID.(string); ok {
 			canceller(*jsonrpc2.NewStringID(s))
 		} else {
-			return sendParseError(ctx, reply, req, fmt.Errorf("request ID %v malformed", params.ID))
+			return sendParseError(ctx, reply, fmt.Errorf("request ID %v malformed", params.ID))
 		}
 		return reply(ctx, nil, nil)
 	}
@@ -75,6 +75,6 @@
 	conn.Notify(ctx, "$/cancelRequest", &CancelParams{ID: &id})
 }
 
-func sendParseError(ctx context.Context, reply jsonrpc2.Replier, req *jsonrpc2.Request, err error) error {
+func sendParseError(ctx context.Context, reply jsonrpc2.Replier, err error) error {
 	return reply(ctx, nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err))
 }
diff --git a/internal/lsp/protocol/tsclient.go b/internal/lsp/protocol/tsclient.go
index 6d9639f..fbb7cfc 100644
--- a/internal/lsp/protocol/tsclient.go
+++ b/internal/lsp/protocol/tsclient.go
@@ -32,92 +32,92 @@
 }
 
 func ClientHandler(client Client, handler jsonrpc2.Handler) jsonrpc2.Handler {
-	return func(ctx context.Context, reply jsonrpc2.Replier, r *jsonrpc2.Request) error {
+	return func(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error {
 		if ctx.Err() != nil {
 			ctx := xcontext.Detach(ctx)
 			return reply(ctx, nil, RequestCancelledError)
 		}
-		switch r.Method {
+		switch r.Method() {
 		case "window/showMessage": // notif
 			var params ShowMessageParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			err := client.ShowMessage(ctx, &params)
 			return reply(ctx, nil, err)
 		case "window/logMessage": // notif
 			var params LogMessageParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			err := client.LogMessage(ctx, &params)
 			return reply(ctx, nil, err)
 		case "telemetry/event": // notif
 			var params interface{}
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			err := client.Event(ctx, &params)
 			return reply(ctx, nil, err)
 		case "textDocument/publishDiagnostics": // notif
 			var params PublishDiagnosticsParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			err := client.PublishDiagnostics(ctx, &params)
 			return reply(ctx, nil, err)
 		case "$/progress": // notif
 			var params ProgressParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			err := client.Progress(ctx, &params)
 			return reply(ctx, nil, err)
 		case "workspace/workspaceFolders": // req
-			if r.Params != nil {
+			if len(r.Params()) > 0 {
 				return reply(ctx, nil, fmt.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams))
 			}
 			resp, err := client.WorkspaceFolders(ctx)
 			return reply(ctx, resp, err)
 		case "workspace/configuration": // req
 			var params ParamConfiguration
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := client.Configuration(ctx, &params)
 			return reply(ctx, resp, err)
 		case "window/workDoneProgress/create": // req
 			var params WorkDoneProgressCreateParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			err := client.WorkDoneProgressCreate(ctx, &params)
 			return reply(ctx, nil, err)
 		case "client/registerCapability": // req
 			var params RegistrationParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			err := client.RegisterCapability(ctx, &params)
 			return reply(ctx, nil, err)
 		case "client/unregisterCapability": // req
 			var params UnregistrationParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			err := client.UnregisterCapability(ctx, &params)
 			return reply(ctx, nil, err)
 		case "window/showMessageRequest": // req
 			var params ShowMessageRequestParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := client.ShowMessageRequest(ctx, &params)
 			return reply(ctx, resp, err)
 		case "workspace/applyEdit": // req
 			var params ApplyWorkspaceEditParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := client.ApplyEdit(ctx, &params)
 			return reply(ctx, resp, err)
diff --git a/internal/lsp/protocol/tsserver.go b/internal/lsp/protocol/tsserver.go
index 02f4513..4c808bd 100644
--- a/internal/lsp/protocol/tsserver.go
+++ b/internal/lsp/protocol/tsserver.go
@@ -70,30 +70,30 @@
 }
 
 func ServerHandler(server Server, handler jsonrpc2.Handler) jsonrpc2.Handler {
-	return func(ctx context.Context, reply jsonrpc2.Replier, r *jsonrpc2.Request) error {
+	return func(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error {
 		if ctx.Err() != nil {
 			ctx := xcontext.Detach(ctx)
 			return reply(ctx, nil, RequestCancelledError)
 		}
-		switch r.Method {
+		switch r.Method() {
 		case "workspace/didChangeWorkspaceFolders": // notif
 			var params DidChangeWorkspaceFoldersParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			err := server.DidChangeWorkspaceFolders(ctx, &params)
 			return reply(ctx, nil, err)
 		case "window/workDoneProgress/cancel": // notif
 			var params WorkDoneProgressCancelParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			err := server.WorkDoneProgressCancel(ctx, &params)
 			return reply(ctx, nil, err)
 		case "initialized": // notif
 			var params InitializedParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			err := server.Initialized(ctx, &params)
 			return reply(ctx, nil, err)
@@ -101,324 +101,324 @@
 			return server.Exit(ctx)
 		case "workspace/didChangeConfiguration": // notif
 			var params DidChangeConfigurationParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			err := server.DidChangeConfiguration(ctx, &params)
 			return reply(ctx, nil, err)
 		case "textDocument/didOpen": // notif
 			var params DidOpenTextDocumentParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			err := server.DidOpen(ctx, &params)
 			return reply(ctx, nil, err)
 		case "textDocument/didChange": // notif
 			var params DidChangeTextDocumentParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			err := server.DidChange(ctx, &params)
 			return reply(ctx, nil, err)
 		case "textDocument/didClose": // notif
 			var params DidCloseTextDocumentParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			err := server.DidClose(ctx, &params)
 			return reply(ctx, nil, err)
 		case "textDocument/didSave": // notif
 			var params DidSaveTextDocumentParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			err := server.DidSave(ctx, &params)
 			return reply(ctx, nil, err)
 		case "textDocument/willSave": // notif
 			var params WillSaveTextDocumentParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			err := server.WillSave(ctx, &params)
 			return reply(ctx, nil, err)
 		case "workspace/didChangeWatchedFiles": // notif
 			var params DidChangeWatchedFilesParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			err := server.DidChangeWatchedFiles(ctx, &params)
 			return reply(ctx, nil, err)
 		case "$/setTraceNotification": // notif
 			var params SetTraceParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			err := server.SetTraceNotification(ctx, &params)
 			return reply(ctx, nil, err)
 		case "$/logTraceNotification": // notif
 			var params LogTraceParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			err := server.LogTraceNotification(ctx, &params)
 			return reply(ctx, nil, err)
 		case "textDocument/implementation": // req
 			var params ImplementationParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.Implementation(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/typeDefinition": // req
 			var params TypeDefinitionParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.TypeDefinition(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/documentColor": // req
 			var params DocumentColorParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.DocumentColor(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/colorPresentation": // req
 			var params ColorPresentationParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.ColorPresentation(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/foldingRange": // req
 			var params FoldingRangeParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.FoldingRange(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/declaration": // req
 			var params DeclarationParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.Declaration(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/selectionRange": // req
 			var params SelectionRangeParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.SelectionRange(ctx, &params)
 			return reply(ctx, resp, err)
 		case "initialize": // req
 			var params ParamInitialize
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.Initialize(ctx, &params)
 			return reply(ctx, resp, err)
 		case "shutdown": // req
-			if r.Params != nil {
+			if len(r.Params()) > 0 {
 				return reply(ctx, nil, fmt.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams))
 			}
 			err := server.Shutdown(ctx)
 			return reply(ctx, nil, err)
 		case "textDocument/willSaveWaitUntil": // req
 			var params WillSaveTextDocumentParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.WillSaveWaitUntil(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/completion": // req
 			var params CompletionParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.Completion(ctx, &params)
 			return reply(ctx, resp, err)
 		case "completionItem/resolve": // req
 			var params CompletionItem
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.Resolve(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/hover": // req
 			var params HoverParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.Hover(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/signatureHelp": // req
 			var params SignatureHelpParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.SignatureHelp(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/definition": // req
 			var params DefinitionParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.Definition(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/references": // req
 			var params ReferenceParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.References(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/documentHighlight": // req
 			var params DocumentHighlightParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.DocumentHighlight(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/documentSymbol": // req
 			var params DocumentSymbolParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.DocumentSymbol(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/codeAction": // req
 			var params CodeActionParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.CodeAction(ctx, &params)
 			return reply(ctx, resp, err)
 		case "workspace/symbol": // req
 			var params WorkspaceSymbolParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.Symbol(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/codeLens": // req
 			var params CodeLensParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.CodeLens(ctx, &params)
 			return reply(ctx, resp, err)
 		case "codeLens/resolve": // req
 			var params CodeLens
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.ResolveCodeLens(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/documentLink": // req
 			var params DocumentLinkParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.DocumentLink(ctx, &params)
 			return reply(ctx, resp, err)
 		case "documentLink/resolve": // req
 			var params DocumentLink
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.ResolveDocumentLink(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/formatting": // req
 			var params DocumentFormattingParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.Formatting(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/rangeFormatting": // req
 			var params DocumentRangeFormattingParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.RangeFormatting(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/onTypeFormatting": // req
 			var params DocumentOnTypeFormattingParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.OnTypeFormatting(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/rename": // req
 			var params RenameParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.Rename(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/prepareRename": // req
 			var params PrepareRenameParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.PrepareRename(ctx, &params)
 			return reply(ctx, resp, err)
 		case "workspace/executeCommand": // req
 			var params ExecuteCommandParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.ExecuteCommand(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/prepareCallHierarchy": // req
 			var params CallHierarchyPrepareParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.PrepareCallHierarchy(ctx, &params)
 			return reply(ctx, resp, err)
 		case "callHierarchy/incomingCalls": // req
 			var params CallHierarchyIncomingCallsParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.IncomingCalls(ctx, &params)
 			return reply(ctx, resp, err)
 		case "callHierarchy/outgoingCalls": // req
 			var params CallHierarchyOutgoingCallsParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.OutgoingCalls(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/semanticTokens": // req
 			var params SemanticTokensParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.SemanticTokens(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/semanticTokens/edits": // req
 			var params SemanticTokensEditsParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.SemanticTokensEdits(ctx, &params)
 			return reply(ctx, resp, err)
 		case "textDocument/semanticTokens/range": // req
 			var params SemanticTokensRangeParams
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
 			resp, err := server.SemanticTokensRange(ctx, &params)
 			return reply(ctx, resp, err)
 		default:
 			var params interface{}
-			if err := json.Unmarshal(*r.Params, &params); err != nil {
-				return sendParseError(ctx, reply, r, err)
+			if err := json.Unmarshal(r.Params(), &params); err != nil {
+				return sendParseError(ctx, reply, err)
 			}
-			resp, err := server.NonstandardRequest(ctx, r.Method, params)
+			resp, err := server.NonstandardRequest(ctx, r.Method(), params)
 			return reply(ctx, resp, err)
 
 		}
diff --git a/internal/lsp/protocol/typescript/code.ts b/internal/lsp/protocol/typescript/code.ts
index c9963eb..54d808f 100644
--- a/internal/lsp/protocol/typescript/code.ts
+++ b/internal/lsp/protocol/typescript/code.ts
@@ -907,7 +907,7 @@
 };
 
 // commonly used output
-const notNil = `if r.Params != nil {
+const notNil = `if len(r.Params()) > 0 {
   return reply(ctx, nil, fmt.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams))
 }`;
 
@@ -923,8 +923,8 @@
   let case1 = notNil;
   if (a != '' && a != 'void') {
     case1 = `var params ${a}
-    if err := json.Unmarshal(*r.Params, &params); err != nil {
-      return sendParseError(ctx, reply, r, err)
+    if err := json.Unmarshal(r.Params(), &params); err != nil {
+      return sendParseError(ctx, reply, err)
     }
     err:= ${side.name}.${nm}(ctx, &params)
     return reply(ctx, nil, err)`
@@ -958,8 +958,8 @@
   if (a != '') {
     if (extraTypes.has('Param' + nm)) a = 'Param' + nm
     case1 = `var params ${a}
-    if err := json.Unmarshal(*r.Params, &params); err != nil {
-      return sendParseError(ctx, reply, r, err)
+    if err := json.Unmarshal(r.Params(), &params); err != nil {
+      return sendParseError(ctx, reply, err)
     }`;
   }
   const arg2 = a == '' ? '' : ', &params';
@@ -1082,12 +1082,12 @@
   side.methods.forEach((v) => {f(v)});
   f('}\n');
   f(`func ${a}Handler(${side.name} ${a}, handler jsonrpc2.Handler) jsonrpc2.Handler {
-        return func(ctx context.Context, reply jsonrpc2.Replier, r *jsonrpc2.Request) error {
+        return func(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error {
             if ctx.Err() != nil {
               ctx := xcontext.Detach(ctx)
               return reply(ctx, nil, RequestCancelledError)
             }
-            switch r.Method {`);
+            switch r.Method() {`);
   side.cases.forEach((v) => {f(v)});
   f(`
           }
@@ -1118,10 +1118,10 @@
     return handler(ctx, reply, r)`)
   server.cases.push(`default:
   var params interface{}
-  if err := json.Unmarshal(*r.Params, &params); err != nil {
-    return sendParseError(ctx, reply, r, err)
+  if err := json.Unmarshal(r.Params(), &params); err != nil {
+    return sendParseError(ctx, reply, err)
   }
-  resp, err := server.NonstandardRequest(ctx, r.Method, params)
+  resp, err := server.NonstandardRequest(ctx, r.Method(), params)
   return reply(ctx, resp, err)
 `)
 }