1
0
mirror of https://github.com/golang/go synced 2024-11-18 08:54:45 -07:00

internal/jsonrpc2: replace NewErrorf with fmt.Errorf

We utilize error wrapping to recover the error codes when needed.
The code constants are also replaced by fully declared errors with
human readable messages.

Change-Id: I8edeb05f5028e99966e4ca28151f644f008d4e7d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/227837
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Ian Cottrell 2020-04-09 22:37:52 -04:00
parent 79a7a3126e
commit 2595e72532
12 changed files with 57 additions and 52 deletions

View File

@ -22,7 +22,7 @@ type Handler func(context.Context, *Request) error
// standard method not found response.
// This should normally be the final handler in a chain.
func MethodNotFound(ctx context.Context, r *Request) error {
return r.Reply(ctx, nil, NewErrorf(CodeMethodNotFound, "method %q not found", r.Method))
return r.Reply(ctx, nil, fmt.Errorf("%w: %q", ErrMethodNotFound, r.Method))
}
// MustReply creates a Handler that panics if the wrapped handler does

View File

@ -10,6 +10,7 @@ package jsonrpc2
import (
"context"
"encoding/json"
"errors"
"fmt"
"sync"
"sync/atomic"
@ -50,15 +51,6 @@ type constError string
func (e constError) Error() string { return string(e) }
// NewErrorf builds a Error struct for the supplied message and code.
// If args is not empty, message and args will be passed to Sprintf.
func NewErrorf(code int64, format string, args ...interface{}) *Error {
return &Error{
Code: code,
Message: fmt.Sprintf(format, args...),
}
}
// NewConn creates a new connection object around the supplied stream.
// You must call Run for the connection to be active.
func NewConn(s Stream) *Conn {
@ -212,7 +204,13 @@ func (r *Request) Reply(ctx context.Context, result interface{}, err error) erro
if callErr, ok := err.(*Error); ok {
response.Error = callErr
} else {
response.Error = NewErrorf(0, "%s", err)
response.Error = &Error{Message: err.Error()}
var wrapped *Error
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)

View File

@ -123,25 +123,25 @@ func testHandler(log bool) jsonrpc2.Handler {
switch r.Method {
case "no_args":
if r.Params != nil {
return r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params"))
return r.Reply(ctx, nil, fmt.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams))
}
return r.Reply(ctx, true, nil)
case "one_string":
var v string
if err := json.Unmarshal(*r.Params, &v); err != nil {
return r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error()))
return r.Reply(ctx, nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err))
}
return r.Reply(ctx, "got:"+v, nil)
case "one_number":
var v int
if err := json.Unmarshal(*r.Params, &v); err != nil {
return r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error()))
return r.Reply(ctx, nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err))
}
return r.Reply(ctx, fmt.Sprintf("got:%d", v), nil)
case "join":
var v []string
if err := json.Unmarshal(*r.Params, &v); err != nil {
return r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error()))
return r.Reply(ctx, nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err))
}
return r.Reply(ctx, path.Join(v...), nil)
default:

View File

@ -13,25 +13,25 @@ import (
// this file contains the go forms of the wire specification
// see http://www.jsonrpc.org/specification for details
const (
// CodeUnknownError should be used for all non coded errors.
CodeUnknownError = -32001
// CodeParseError is used when invalid JSON was received by the server.
CodeParseError = -32700
//CodeInvalidRequest is used when the JSON sent is not a valid Request object.
CodeInvalidRequest = -32600
// CodeMethodNotFound should be returned by the handler when the method does
var (
// ErrUnknown should be used for all non coded errors.
ErrUnknown = NewError(-32001, "JSON RPC unknown error")
// ErrParse is used when invalid JSON was received by the server.
ErrParse = NewError(-32700, "JSON RPC parse error")
//ErrInvalidRequest is used when the JSON sent is not a valid Request object.
ErrInvalidRequest = NewError(-32600, "JSON RPC invalid request")
// ErrMethodNotFound should be returned by the handler when the method does
// not exist / is not available.
CodeMethodNotFound = -32601
// CodeInvalidParams should be returned by the handler when method
ErrMethodNotFound = NewError(-32601, "JSON RPC method not found")
// ErrInvalidParams should be returned by the handler when method
// parameter(s) were invalid.
CodeInvalidParams = -32602
// CodeInternalError is not currently returned but defined for completeness.
CodeInternalError = -32603
ErrInvalidParams = NewError(-32602, "JSON RPC invalid params")
// ErrInternal is not currently returned but defined for completeness.
ErrInternal = NewError(-32603, "JSON RPC internal error")
//CodeServerOverloaded is returned when a message was refused due to a
//ErrServerOverloaded is returned when a message was refused due to a
//server being temporarily unable to accept any new messages.
CodeServerOverloaded = -32000
ErrServerOverloaded = NewError(-32000, "JSON RPC overloaded")
)
// WireRequest is sent to a server to represent a Call or Notify operaton.
@ -87,10 +87,14 @@ type ID struct {
Number int64
}
func (err *Error) Error() string {
if err == nil {
return ""
func NewError(code int64, message string) error {
return &Error{
Code: code,
Message: message,
}
}
func (err *Error) Error() string {
return err.Message
}

View File

@ -25,7 +25,7 @@ func (s *Server) initialize(ctx context.Context, params *protocol.ParamInitializ
s.stateMu.Lock()
if s.state >= serverInitializing {
defer s.stateMu.Unlock()
return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidRequest, "initialize called while server in %v state", s.state)
return nil, fmt.Errorf("%w: initialize called while server in %v state", jsonrpc2.ErrInvalidRequest, s.state)
}
s.state = serverInitializing
s.stateMu.Unlock()
@ -118,7 +118,7 @@ func (s *Server) initialized(ctx context.Context, params *protocol.InitializedPa
s.stateMu.Lock()
if s.state >= serverInitialized {
defer s.stateMu.Unlock()
return jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidRequest, "initalized called while server in %v state", s.state)
return fmt.Errorf("%w: initalized called while server in %v state", jsonrpc2.ErrInvalidRequest, s.state)
}
s.state = serverInitialized
s.stateMu.Unlock()

View File

@ -534,7 +534,7 @@ func handshaker(client *debugClient, goplsPath string, handler jsonrpc2.Handler)
func sendError(ctx context.Context, req *jsonrpc2.Request, err error) {
if _, ok := err.(*jsonrpc2.Error); !ok {
err = jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err)
err = fmt.Errorf("%w: %v", jsonrpc2.ErrParse, err)
}
if err := req.Reply(ctx, nil, err); err != nil {
event.Error(ctx, "", err)

View File

@ -14,9 +14,9 @@ import (
"golang.org/x/tools/internal/xcontext"
)
const (
var (
// RequestCancelledError should be used when a request is cancelled early.
RequestCancelledError = -32800
RequestCancelledError = jsonrpc2.NewError(-32800, "JSON RPC cancelled")
)
// ClientDispatcher returns a Client that dispatches LSP requests across the
@ -79,7 +79,7 @@ func cancelCall(ctx context.Context, conn *jsonrpc2.Conn, id jsonrpc2.ID) {
func sendParseError(ctx context.Context, req *jsonrpc2.Request, err error) error {
if _, ok := err.(*jsonrpc2.Error); !ok {
err = jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err)
err = fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)
}
return req.Reply(ctx, nil, err)
}

View File

@ -10,6 +10,7 @@ package protocol
import (
"context"
"encoding/json"
"fmt"
"golang.org/x/tools/internal/jsonrpc2"
"golang.org/x/tools/internal/xcontext"
@ -34,7 +35,7 @@ func ClientHandler(client Client, handler jsonrpc2.Handler) jsonrpc2.Handler {
return func(ctx context.Context, r *jsonrpc2.Request) error {
if ctx.Err() != nil {
ctx := xcontext.Detach(ctx)
return r.Reply(ctx, nil, jsonrpc2.NewErrorf(RequestCancelledError, ""))
return r.Reply(ctx, nil, RequestCancelledError)
}
switch r.Method {
case "window/showMessage": // notif
@ -74,7 +75,7 @@ func ClientHandler(client Client, handler jsonrpc2.Handler) jsonrpc2.Handler {
return r.Reply(ctx, nil, err)
case "workspace/workspaceFolders": // req
if r.Params != nil {
return r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params"))
return r.Reply(ctx, nil, fmt.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams))
}
resp, err := client.WorkspaceFolders(ctx)
return r.Reply(ctx, resp, err)

View File

@ -10,6 +10,7 @@ package protocol
import (
"context"
"encoding/json"
"fmt"
"golang.org/x/tools/internal/jsonrpc2"
"golang.org/x/tools/internal/xcontext"
@ -72,7 +73,7 @@ func ServerHandler(server Server, handler jsonrpc2.Handler) jsonrpc2.Handler {
return func(ctx context.Context, r *jsonrpc2.Request) error {
if ctx.Err() != nil {
ctx := xcontext.Detach(ctx)
return r.Reply(ctx, nil, jsonrpc2.NewErrorf(RequestCancelledError, ""))
return r.Reply(ctx, nil, RequestCancelledError)
}
switch r.Method {
case "workspace/didChangeWorkspaceFolders": // notif
@ -219,7 +220,7 @@ func ServerHandler(server Server, handler jsonrpc2.Handler) jsonrpc2.Handler {
return r.Reply(ctx, resp, err)
case "shutdown": // req
if r.Params != nil {
return r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params"))
return r.Reply(ctx, nil, fmt.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams))
}
err := server.Shutdown(ctx)
return r.Reply(ctx, nil, err)

View File

@ -908,7 +908,7 @@ let server: side = {
// commonly used output
const notNil = `if r.Params != nil {
return r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params"))
return r.Reply(ctx, nil, fmt.Errorf("%w: expected no params", jsonrpc2.CodeInvalidParams))
}`;
// Go code for notifications. Side is client or server, m is the request
@ -1071,6 +1071,7 @@ function output(side: side) {
import (
"context"
"encoding/json"
"fmt"
"golang.org/x/tools/internal/jsonrpc2"
"golang.org/x/tools/internal/xcontext"
@ -1084,7 +1085,7 @@ function output(side: side) {
return func(ctx context.Context, r *jsonrpc2.Request) error {
if ctx.Err() != nil {
ctx := xcontext.Detach(ctx)
return r.Reply(ctx, nil, jsonrpc2.NewErrorf(RequestCancelledError, ""))
return r.Reply(ctx, nil, RequestCancelledError)
}
switch r.Method {`);
side.cases.forEach((v) => {f(v)});

View File

@ -160,8 +160,8 @@ func (s *Server) clearInProgress(token string) {
s.inProgressMu.Unlock()
}
func notImplemented(method string) *jsonrpc2.Error {
return jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not yet implemented", method)
func notImplemented(method string) error {
return fmt.Errorf("%w: %q not yet implemented", jsonrpc2.ErrMethodNotFound, method)
}
//go:generate helper/helper -d protocol/tsserver.go -o server_gen.go -u .

View File

@ -243,7 +243,7 @@ func (s *Server) wasFirstChange(uri span.URI) bool {
func (s *Server) changedText(ctx context.Context, uri span.URI, changes []protocol.TextDocumentContentChangeEvent) ([]byte, error) {
if len(changes) == 0 {
return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "no content changes provided")
return nil, fmt.Errorf("%w: no content changes provided", jsonrpc2.ErrInternal)
}
// Check if the client sent the full content of the file.
@ -257,7 +257,7 @@ func (s *Server) changedText(ctx context.Context, uri span.URI, changes []protoc
func (s *Server) applyIncrementalChanges(ctx context.Context, uri span.URI, changes []protocol.TextDocumentContentChangeEvent) ([]byte, error) {
content, _, err := s.session.GetFile(uri).Read(ctx)
if err != nil {
return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "file not found (%v)", err)
return nil, fmt.Errorf("%w: file not found (%v)", jsonrpc2.ErrInternal, err)
}
for _, change := range changes {
// Make sure to update column mapper along with the content.
@ -268,18 +268,18 @@ func (s *Server) applyIncrementalChanges(ctx context.Context, uri span.URI, chan
Content: content,
}
if change.Range == nil {
return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "unexpected nil range for change")
return nil, fmt.Errorf("%w: unexpected nil range for change", jsonrpc2.ErrInternal)
}
spn, err := m.RangeSpan(*change.Range)
if err != nil {
return nil, err
}
if !spn.HasOffset() {
return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "invalid range for content change")
return nil, fmt.Errorf("%w: invalid range for content change", jsonrpc2.ErrInternal)
}
start, end := spn.Start().Offset(), spn.End().Offset()
if end < start {
return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "invalid range for content change")
return nil, fmt.Errorf("%w: invalid range for content change", jsonrpc2.ErrInternal)
}
var buf bytes.Buffer
buf.Write(content[:start])