Changed: DB Params
This commit is contained in:
86
templ/lsp/jsonrpc2/codes.go
Normal file
86
templ/lsp/jsonrpc2/codes.go
Normal file
@@ -0,0 +1,86 @@
|
||||
// SPDX-FileCopyrightText: 2021 The Go Language Server Authors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package jsonrpc2
|
||||
|
||||
// Code is an error code as defined in the JSON-RPC spec.
|
||||
type Code int32
|
||||
|
||||
// list of JSON-RPC error codes.
|
||||
const (
|
||||
// ParseError is the invalid JSON was received by the server.
|
||||
// An error occurred on the server while parsing the JSON text.
|
||||
ParseError Code = -32700
|
||||
|
||||
// InvalidRequest is the JSON sent is not a valid Request object.
|
||||
InvalidRequest Code = -32600
|
||||
|
||||
// MethodNotFound is the method does not exist / is not available.
|
||||
MethodNotFound Code = -32601
|
||||
|
||||
// InvalidParams is the invalid method parameter(s).
|
||||
InvalidParams Code = -32602
|
||||
|
||||
// InternalError is the internal JSON-RPC error.
|
||||
InternalError Code = -32603
|
||||
|
||||
// JSONRPCReservedErrorRangeStart is the start range of JSON RPC reserved error codes.
|
||||
//
|
||||
// It doesn't denote a real error code. No LSP error codes should
|
||||
// be defined between the start and end range. For backwards
|
||||
// compatibility the "ServerNotInitialized" and the "UnknownErrorCode"
|
||||
// are left in the range.
|
||||
//
|
||||
// @since 3.16.0.
|
||||
JSONRPCReservedErrorRangeStart Code = -32099
|
||||
|
||||
// CodeServerErrorStart reserved for implementation-defined server-errors.
|
||||
//
|
||||
// Deprecated: Use JSONRPCReservedErrorRangeStart instead.
|
||||
CodeServerErrorStart = JSONRPCReservedErrorRangeStart
|
||||
|
||||
// ServerNotInitialized is the error of server not initialized.
|
||||
ServerNotInitialized Code = -32002
|
||||
|
||||
// UnknownError should be used for all non coded errors.
|
||||
UnknownError Code = -32001
|
||||
|
||||
// JSONRPCReservedErrorRangeEnd is the start range of JSON RPC reserved error codes.
|
||||
//
|
||||
// It doesn't denote a real error code.
|
||||
//
|
||||
// @since 3.16.0.
|
||||
JSONRPCReservedErrorRangeEnd Code = -32000
|
||||
|
||||
// CodeServerErrorEnd reserved for implementation-defined server-errors.
|
||||
//
|
||||
// Deprecated: Use JSONRPCReservedErrorRangeEnd instead.
|
||||
CodeServerErrorEnd = JSONRPCReservedErrorRangeEnd
|
||||
)
|
||||
|
||||
// This file contains the Go forms of the wire specification.
|
||||
//
|
||||
// See http://www.jsonrpc.org/specification for details.
|
||||
//
|
||||
// list of JSON-RPC errors.
|
||||
var (
|
||||
// ErrUnknown should be used for all non coded errors.
|
||||
ErrUnknown = NewError(UnknownError, "JSON-RPC unknown error")
|
||||
|
||||
// ErrParse is used when invalid JSON was received by the server.
|
||||
ErrParse = NewError(ParseError, "JSON-RPC parse error")
|
||||
|
||||
// ErrInvalidRequest is used when the JSON sent is not a valid Request object.
|
||||
ErrInvalidRequest = NewError(InvalidRequest, "JSON-RPC invalid request")
|
||||
|
||||
// ErrMethodNotFound should be returned by the handler when the method does
|
||||
// not exist / is not available.
|
||||
ErrMethodNotFound = NewError(MethodNotFound, "JSON-RPC method not found")
|
||||
|
||||
// ErrInvalidParams should be returned by the handler when method
|
||||
// parameter(s) were invalid.
|
||||
ErrInvalidParams = NewError(InvalidParams, "JSON-RPC invalid params")
|
||||
|
||||
// ErrInternal is not currently returned but defined for completeness.
|
||||
ErrInternal = NewError(InternalError, "JSON-RPC internal error")
|
||||
)
|
244
templ/lsp/jsonrpc2/conn.go
Normal file
244
templ/lsp/jsonrpc2/conn.go
Normal file
@@ -0,0 +1,244 @@
|
||||
// SPDX-FileCopyrightText: 2021 The Go Language Server Authors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package jsonrpc2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// Conn is the common interface to jsonrpc clients and servers.
|
||||
//
|
||||
// Conn is bidirectional; it does not have a designated server or client end.
|
||||
// It manages the jsonrpc2 protocol, connecting responses back to their calls.
|
||||
type Conn interface {
|
||||
// Call invokes the target method and waits for a response.
|
||||
//
|
||||
// The params will be marshaled to JSON before sending over the wire, and will
|
||||
// be handed to the method invoked.
|
||||
//
|
||||
// The response will be unmarshaled from JSON into the result.
|
||||
//
|
||||
// The id returned will be unique from this connection, and can be used for
|
||||
// logging or tracking.
|
||||
Call(ctx context.Context, method string, params, result any) (ID, error)
|
||||
|
||||
// Notify invokes the target method but does not wait for a response.
|
||||
//
|
||||
// The params will be marshaled to JSON before sending over the wire, and will
|
||||
// be handed to the method invoked.
|
||||
Notify(ctx context.Context, method string, params any) error
|
||||
|
||||
// Go starts a goroutine to handle the connection.
|
||||
//
|
||||
// It must be called exactly once for each Conn. It returns immediately.
|
||||
// Must block on Done() to wait for the connection to shut down.
|
||||
//
|
||||
// This is a temporary measure, this should be started automatically in the
|
||||
// future.
|
||||
Go(ctx context.Context, handler Handler)
|
||||
|
||||
// Close closes the connection and it's underlying stream.
|
||||
//
|
||||
// It does not wait for the close to complete, use the Done() channel for
|
||||
// that.
|
||||
Close() error
|
||||
|
||||
// Done returns a channel that will be closed when the processing goroutine
|
||||
// has terminated, which will happen if Close() is called or an underlying
|
||||
// stream is closed.
|
||||
Done() <-chan struct{}
|
||||
|
||||
// Err returns an error if there was one from within the processing goroutine.
|
||||
//
|
||||
// If err returns non nil, the connection will be already closed or closing.
|
||||
Err() error
|
||||
}
|
||||
|
||||
type conn struct {
|
||||
seq int32 // access atomically
|
||||
writeMu sync.Mutex // protects writes to the stream
|
||||
stream Stream // supplied stream
|
||||
pendingMu sync.Mutex // protects the pending map
|
||||
pending map[ID]chan *Response // holds the pending response channel with the ID as the key.
|
||||
|
||||
done chan struct{} // closed when done
|
||||
err atomic.Value // holds run error
|
||||
}
|
||||
|
||||
// NewConn creates a new connection object around the supplied stream.
|
||||
func NewConn(s Stream) Conn {
|
||||
conn := &conn{
|
||||
stream: s,
|
||||
pending: make(map[ID]chan *Response),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
// Call implements Conn.
|
||||
func (c *conn) Call(ctx context.Context, method string, params, result any) (id ID, err error) {
|
||||
// generate a new request identifier
|
||||
id = NewNumberID(atomic.AddInt32(&c.seq, 1))
|
||||
call, err := NewCall(id, method, params)
|
||||
if err != nil {
|
||||
return id, fmt.Errorf("marshaling call parameters: %w", err)
|
||||
}
|
||||
|
||||
// We have to add ourselves to the pending map before we send, otherwise we
|
||||
// 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 *Response, 1)
|
||||
|
||||
c.pendingMu.Lock()
|
||||
c.pending[id] = rchan
|
||||
c.pendingMu.Unlock()
|
||||
|
||||
defer func() {
|
||||
c.pendingMu.Lock()
|
||||
delete(c.pending, id)
|
||||
c.pendingMu.Unlock()
|
||||
}()
|
||||
|
||||
// now we are ready to send
|
||||
_, err = c.write(ctx, call)
|
||||
if err != nil {
|
||||
// sending failed, we will never get a response, so don't leave it pending
|
||||
return id, err
|
||||
}
|
||||
|
||||
// now wait for the response
|
||||
select {
|
||||
case resp := <-rchan:
|
||||
// is it an error response?
|
||||
if resp.err != nil {
|
||||
return id, resp.err
|
||||
}
|
||||
|
||||
if result == nil || len(resp.result) == 0 {
|
||||
return id, nil
|
||||
}
|
||||
|
||||
dec := json.NewDecoder(bytes.NewReader(resp.result))
|
||||
if err := dec.Decode(result); err != nil {
|
||||
return id, fmt.Errorf("unmarshaling result: %w", err)
|
||||
}
|
||||
|
||||
return id, nil
|
||||
|
||||
case <-ctx.Done():
|
||||
return id, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// Notify implements Conn.
|
||||
func (c *conn) Notify(ctx context.Context, method string, params any) (err error) {
|
||||
notify, err := NewNotification(method, params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshaling notify parameters: %w", err)
|
||||
}
|
||||
|
||||
_, err = c.write(ctx, notify)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) replier(req Message) Replier {
|
||||
return func(ctx context.Context, result any, err error) error {
|
||||
call, ok := req.(*Call)
|
||||
if !ok {
|
||||
// request was a notify, no need to respond
|
||||
return nil
|
||||
}
|
||||
|
||||
response, err := NewResponse(call.id, result, err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.write(ctx, response)
|
||||
if err != nil {
|
||||
// TODO(iancottrell): if a stream write fails, we really need to shut down the whole stream
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *conn) write(ctx context.Context, msg Message) (int64, error) {
|
||||
c.writeMu.Lock()
|
||||
n, err := c.stream.Write(ctx, msg)
|
||||
c.writeMu.Unlock()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("write to stream: %w", err)
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Go implements Conn.
|
||||
func (c *conn) Go(ctx context.Context, handler Handler) {
|
||||
go c.run(ctx, handler)
|
||||
}
|
||||
|
||||
func (c *conn) run(ctx context.Context, handler Handler) {
|
||||
defer close(c.done)
|
||||
|
||||
for {
|
||||
// get the next message
|
||||
msg, _, err := c.stream.Read(ctx)
|
||||
if err != nil {
|
||||
// The stream failed, we cannot continue.
|
||||
c.fail(err)
|
||||
return
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case Request:
|
||||
if err := handler(ctx, c.replier(msg), msg); err != nil {
|
||||
c.fail(err)
|
||||
}
|
||||
|
||||
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]
|
||||
c.pendingMu.Unlock()
|
||||
if ok {
|
||||
rchan <- msg
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close implements Conn.
|
||||
func (c *conn) Close() error {
|
||||
return c.stream.Close()
|
||||
}
|
||||
|
||||
// Done implements Conn.
|
||||
func (c *conn) Done() <-chan struct{} {
|
||||
return c.done
|
||||
}
|
||||
|
||||
// Err implements Conn.
|
||||
func (c *conn) Err() error {
|
||||
if err := c.err.Load(); err != nil {
|
||||
return err.(error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fail sets a failure condition on the stream and closes it.
|
||||
func (c *conn) fail(err error) {
|
||||
c.err.Store(err)
|
||||
c.stream.Close()
|
||||
}
|
70
templ/lsp/jsonrpc2/errors.go
Normal file
70
templ/lsp/jsonrpc2/errors.go
Normal file
@@ -0,0 +1,70 @@
|
||||
// SPDX-FileCopyrightText: 2019 The Go Language Server Authors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package jsonrpc2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// Error represents a JSON-RPC error.
|
||||
type Error struct {
|
||||
// Code a number indicating the error type that occurred.
|
||||
Code Code `json:"code"`
|
||||
|
||||
// Message a string providing a short description of the error.
|
||||
Message string `json:"message"`
|
||||
|
||||
// Data a Primitive or Structured value that contains additional
|
||||
// information about the error. Can be omitted.
|
||||
Data *json.RawMessage `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// compile time check whether the Error implements error interface.
|
||||
var _ error = (*Error)(nil)
|
||||
|
||||
// Error implements error.Error.
|
||||
func (e *Error) Error() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// Unwrap implements errors.Unwrap.
|
||||
//
|
||||
// Returns the error underlying the receiver, which may be nil.
|
||||
func (e *Error) Unwrap() error { return errors.New(e.Message) }
|
||||
|
||||
// NewError builds a Error struct for the suppied code and message.
|
||||
func NewError(c Code, message string) *Error {
|
||||
return &Error{
|
||||
Code: c,
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
// Errorf builds a Error struct for the suppied code, format and args.
|
||||
func Errorf(c Code, format string, args ...any) *Error {
|
||||
return &Error{
|
||||
Code: c,
|
||||
Message: fmt.Sprintf(format, args...),
|
||||
}
|
||||
}
|
||||
|
||||
// constErr represents a error constant.
|
||||
type constErr string
|
||||
|
||||
// compile time check whether the constErr implements error interface.
|
||||
var _ error = (*constErr)(nil)
|
||||
|
||||
// Error implements error.Error.
|
||||
func (e constErr) Error() string { return string(e) }
|
||||
|
||||
const (
|
||||
// ErrIdleTimeout is returned when serving timed out waiting for new connections.
|
||||
ErrIdleTimeout = constErr("timed out waiting for new connections")
|
||||
)
|
120
templ/lsp/jsonrpc2/handler.go
Normal file
120
templ/lsp/jsonrpc2/handler.go
Normal file
@@ -0,0 +1,120 @@
|
||||
// SPDX-FileCopyrightText: 2019 The Go Language Server Authors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package jsonrpc2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
// Replier is passed to handlers to allow them to reply to the request.
|
||||
//
|
||||
// If err is set then result will be ignored.
|
||||
type Replier func(ctx context.Context, result any, err error) error
|
||||
|
||||
// MethodNotFoundHandler 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 MethodNotFoundHandler(ctx context.Context, reply Replier, req Request) error {
|
||||
return reply(ctx, nil, fmt.Errorf("%q: %w", req.Method(), ErrMethodNotFound))
|
||||
}
|
||||
|
||||
// ReplyHandler creates a Handler that panics if the wrapped handler does
|
||||
// not call Reply for every request that it is passed.
|
||||
func ReplyHandler(handler Handler) (h Handler) {
|
||||
h = Handler(func(ctx context.Context, reply Replier, req Request) error {
|
||||
called := false
|
||||
err := handler(ctx, func(ctx context.Context, result any, err error) error {
|
||||
if called {
|
||||
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()))
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// CancelHandler returns a handler that supports cancellation, and a function
|
||||
// that can be used to trigger canceling in progress requests.
|
||||
func CancelHandler(handler Handler) (h Handler, canceller func(id ID)) {
|
||||
var mu sync.Mutex
|
||||
handling := make(map[ID]context.CancelFunc)
|
||||
|
||||
h = Handler(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[call.ID()] = cancel
|
||||
mu.Unlock()
|
||||
|
||||
innerReply := reply
|
||||
reply = func(ctx context.Context, result any, err error) error {
|
||||
mu.Lock()
|
||||
delete(handling, call.ID())
|
||||
mu.Unlock()
|
||||
return innerReply(ctx, result, err)
|
||||
}
|
||||
}
|
||||
return handler(ctx, reply, req)
|
||||
})
|
||||
|
||||
canceller = func(id ID) {
|
||||
mu.Lock()
|
||||
cancel, found := handling[id]
|
||||
mu.Unlock()
|
||||
if found {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
||||
return h, canceller
|
||||
}
|
||||
|
||||
// AsyncHandler returns a handler that processes each request goes in its own
|
||||
// goroutine.
|
||||
//
|
||||
// The handler returns immediately, without the request being processed.
|
||||
// Each request then waits for the previous request to finish before it starts.
|
||||
//
|
||||
// This allows the stream to unblock at the cost of unbounded goroutines
|
||||
// all stalled on the previous one.
|
||||
func AsyncHandler(handler Handler) (h Handler) {
|
||||
nextRequest := make(chan struct{})
|
||||
close(nextRequest)
|
||||
|
||||
h = Handler(func(ctx context.Context, reply Replier, req Request) error {
|
||||
waitForPrevious := nextRequest
|
||||
nextRequest = make(chan struct{})
|
||||
unlockNext := nextRequest
|
||||
innerReply := reply
|
||||
reply = func(ctx context.Context, result any, err error) error {
|
||||
close(unlockNext)
|
||||
return innerReply(ctx, result, err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-waitForPrevious
|
||||
_ = handler(ctx, reply, req)
|
||||
}()
|
||||
return nil
|
||||
})
|
||||
|
||||
return h
|
||||
}
|
7
templ/lsp/jsonrpc2/jsonrpc2.go
Normal file
7
templ/lsp/jsonrpc2/jsonrpc2.go
Normal file
@@ -0,0 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2019 The Go Language Server Authors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package jsonrpc2 is an implementation of the JSON-RPC 2 specification for Go.
|
||||
//
|
||||
// https://www.jsonrpc.org/specification
|
||||
package jsonrpc2 // import "github.com/a-h/templ/lsp/jsonrpc2"
|
171
templ/lsp/jsonrpc2/jsonrpc2_test.go
Normal file
171
templ/lsp/jsonrpc2/jsonrpc2_test.go
Normal file
@@ -0,0 +1,171 @@
|
||||
// SPDX-FileCopyrightText: 2021 The Go Language Server Authors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package jsonrpc2_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"path"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"github.com/a-h/templ/lsp/jsonrpc2"
|
||||
)
|
||||
|
||||
const (
|
||||
methodNoArgs = "no_args"
|
||||
methodOneString = "one_string"
|
||||
methodOneNumber = "one_number"
|
||||
methodJoin = "join"
|
||||
)
|
||||
|
||||
type callTest struct {
|
||||
method string
|
||||
params any
|
||||
expect any
|
||||
}
|
||||
|
||||
var callTests = []callTest{
|
||||
{
|
||||
method: methodNoArgs,
|
||||
params: nil,
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
method: methodOneString,
|
||||
params: "fish",
|
||||
expect: "got:fish",
|
||||
},
|
||||
{
|
||||
method: methodOneNumber,
|
||||
params: 10,
|
||||
expect: "got:10",
|
||||
},
|
||||
{
|
||||
method: methodJoin,
|
||||
params: []string{"a", "b", "c"},
|
||||
expect: "a/b/c",
|
||||
},
|
||||
// TODO: expand the test cases
|
||||
}
|
||||
|
||||
func (test *callTest) newResults() any {
|
||||
switch e := test.expect.(type) {
|
||||
case []any:
|
||||
var r []any
|
||||
for _, v := range e {
|
||||
r = append(r, reflect.New(reflect.TypeOf(v)).Interface())
|
||||
}
|
||||
return r
|
||||
|
||||
case nil:
|
||||
return nil
|
||||
|
||||
default:
|
||||
return reflect.New(reflect.TypeOf(test.expect)).Interface()
|
||||
}
|
||||
}
|
||||
|
||||
func (test *callTest) verifyResults(t *testing.T, results any) {
|
||||
t.Helper()
|
||||
|
||||
if results == nil {
|
||||
return
|
||||
}
|
||||
|
||||
val := reflect.Indirect(reflect.ValueOf(results)).Interface()
|
||||
if !reflect.DeepEqual(val, test.expect) {
|
||||
t.Errorf("%v:Results are incorrect, got %+v expect %+v", test.method, val, test.expect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequest(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
a, b, done := prepare(ctx, t)
|
||||
defer done()
|
||||
|
||||
for _, test := range callTests {
|
||||
t.Run(test.method, func(t *testing.T) {
|
||||
results := test.newResults()
|
||||
if _, err := a.Call(ctx, test.method, test.params, results); err != nil {
|
||||
t.Fatalf("%v:Call failed: %v", test.method, err)
|
||||
}
|
||||
test.verifyResults(t, results)
|
||||
|
||||
if _, err := b.Call(ctx, test.method, test.params, results); err != nil {
|
||||
t.Fatalf("%v:Call failed: %v", test.method, err)
|
||||
}
|
||||
test.verifyResults(t, results)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func prepare(ctx context.Context, t *testing.T) (a, b jsonrpc2.Conn, done func()) {
|
||||
t.Helper()
|
||||
|
||||
// make a wait group that can be used to wait for the system to shut down
|
||||
aPipe, bPipe := net.Pipe()
|
||||
a = run(ctx, aPipe)
|
||||
b = run(ctx, bPipe)
|
||||
done = func() {
|
||||
a.Close()
|
||||
b.Close()
|
||||
<-a.Done()
|
||||
<-b.Done()
|
||||
}
|
||||
|
||||
return a, b, done
|
||||
}
|
||||
|
||||
func run(ctx context.Context, nc io.ReadWriteCloser) jsonrpc2.Conn {
|
||||
stream := jsonrpc2.NewStream(nc)
|
||||
conn := jsonrpc2.NewConn(stream)
|
||||
conn.Go(ctx, testHandler())
|
||||
|
||||
return conn
|
||||
}
|
||||
|
||||
func testHandler() jsonrpc2.Handler {
|
||||
return func(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error {
|
||||
switch req.Method() {
|
||||
case methodNoArgs:
|
||||
if len(req.Params()) > 0 {
|
||||
return reply(ctx, nil, fmt.Errorf("expected no params: %w", jsonrpc2.ErrInvalidParams))
|
||||
}
|
||||
return reply(ctx, true, nil)
|
||||
|
||||
case methodOneString:
|
||||
var v string
|
||||
dec := json.NewDecoder(bytes.NewReader(req.Params()))
|
||||
if err := dec.Decode(&v); err != nil {
|
||||
return reply(ctx, nil, fmt.Errorf("%s: %w", jsonrpc2.ErrParse, err))
|
||||
}
|
||||
return reply(ctx, "got:"+v, nil)
|
||||
|
||||
case methodOneNumber:
|
||||
var v int
|
||||
dec := json.NewDecoder(bytes.NewReader(req.Params()))
|
||||
if err := dec.Decode(&v); err != nil {
|
||||
return reply(ctx, nil, fmt.Errorf("%s: %w", jsonrpc2.ErrParse, err))
|
||||
}
|
||||
return reply(ctx, fmt.Sprintf("got:%d", v), nil)
|
||||
|
||||
case methodJoin:
|
||||
var v []string
|
||||
dec := json.NewDecoder(bytes.NewReader(req.Params()))
|
||||
if err := dec.Decode(&v); err != nil {
|
||||
return reply(ctx, nil, fmt.Errorf("%s: %w", jsonrpc2.ErrParse, err))
|
||||
}
|
||||
return reply(ctx, path.Join(v...), nil)
|
||||
|
||||
default:
|
||||
return jsonrpc2.MethodNotFoundHandler(ctx, reply, req)
|
||||
}
|
||||
}
|
||||
}
|
354
templ/lsp/jsonrpc2/message.go
Normal file
354
templ/lsp/jsonrpc2/message.go
Normal file
@@ -0,0 +1,354 @@
|
||||
// SPDX-FileCopyrightText: 2021 The Go Language Server Authors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package jsonrpc2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// Message is the interface to all JSON-RPC 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, *Response and *Notification.
|
||||
type Message interface {
|
||||
// jsonrpc2Message is used to make the set of message implementations a
|
||||
// closed set.
|
||||
jsonrpc2Message()
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// jsonrpc2Request is used to make the set of request implementations closed.
|
||||
jsonrpc2Request()
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// make sure a Call implements the Request, json.Marshaler and json.Unmarshaler and interfaces.
|
||||
var (
|
||||
_ Request = (*Call)(nil)
|
||||
_ json.Marshaler = (*Call)(nil)
|
||||
_ json.Unmarshaler = (*Call)(nil)
|
||||
)
|
||||
|
||||
// NewCall constructs a new Call message for the supplied ID, method and
|
||||
// parameters.
|
||||
func NewCall(id ID, method string, params any) (*Call, error) {
|
||||
p, merr := marshalInterface(params)
|
||||
req := &Call{
|
||||
id: id,
|
||||
method: method,
|
||||
params: p,
|
||||
}
|
||||
return req, merr
|
||||
}
|
||||
|
||||
// ID returns the current call id.
|
||||
func (c *Call) ID() ID { return c.id }
|
||||
|
||||
// Method implements Request.
|
||||
func (c *Call) Method() string { return c.method }
|
||||
|
||||
// Params implements Request.
|
||||
func (c *Call) Params() json.RawMessage { return c.params }
|
||||
|
||||
// jsonrpc2Message implements Request.
|
||||
func (Call) jsonrpc2Message() {}
|
||||
|
||||
// jsonrpc2Request implements Request.
|
||||
func (Call) jsonrpc2Request() {}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (c Call) MarshalJSON() ([]byte, error) {
|
||||
req := wireRequest{
|
||||
Method: c.method,
|
||||
Params: &c.params,
|
||||
ID: &c.id,
|
||||
}
|
||||
data, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return data, fmt.Errorf("marshaling call: %w", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (c *Call) UnmarshalJSON(data []byte) error {
|
||||
var req wireRequest
|
||||
dec := json.NewDecoder(bytes.NewReader(data))
|
||||
if err := dec.Decode(&req); err != nil {
|
||||
return fmt.Errorf("unmarshaling call: %w", err)
|
||||
}
|
||||
|
||||
c.method = req.Method
|
||||
if req.Params != nil {
|
||||
c.params = *req.Params
|
||||
}
|
||||
if req.ID != nil {
|
||||
c.id = *req.ID
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Response is a reply to a Request.
|
||||
//
|
||||
// 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
|
||||
}
|
||||
|
||||
// make sure a Response implements the Message, json.Marshaler and json.Unmarshaler and interfaces.
|
||||
var (
|
||||
_ Message = (*Response)(nil)
|
||||
_ json.Marshaler = (*Response)(nil)
|
||||
_ json.Unmarshaler = (*Response)(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 any, err error) (*Response, error) {
|
||||
r, merr := marshalInterface(result)
|
||||
resp := &Response{
|
||||
id: id,
|
||||
result: r,
|
||||
err: err,
|
||||
}
|
||||
return resp, merr
|
||||
}
|
||||
|
||||
// ID returns the current response id.
|
||||
func (r *Response) ID() ID { return r.id }
|
||||
|
||||
// Result returns the Response result.
|
||||
func (r *Response) Result() json.RawMessage { return r.result }
|
||||
|
||||
// Err returns the Response error.
|
||||
func (r *Response) Err() error { return r.err }
|
||||
|
||||
// jsonrpc2Message implements Message.
|
||||
func (r *Response) jsonrpc2Message() {}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (r Response) MarshalJSON() ([]byte, error) {
|
||||
resp := &wireResponse{
|
||||
Error: toError(r.err),
|
||||
ID: &r.id,
|
||||
}
|
||||
if resp.Error == nil {
|
||||
resp.Result = &r.result
|
||||
}
|
||||
|
||||
data, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
return data, fmt.Errorf("marshaling notification: %w", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (r *Response) UnmarshalJSON(data []byte) error {
|
||||
var resp wireResponse
|
||||
dec := json.NewDecoder(bytes.NewReader(data))
|
||||
if err := dec.Decode(&resp); err != nil {
|
||||
return fmt.Errorf("unmarshaling jsonrpc response: %w", err)
|
||||
}
|
||||
|
||||
if resp.Result != nil {
|
||||
r.result = *resp.Result
|
||||
}
|
||||
if resp.Error != nil {
|
||||
r.err = resp.Error
|
||||
}
|
||||
if resp.ID != nil {
|
||||
r.id = *resp.ID
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func toError(err error) *Error {
|
||||
if err == nil {
|
||||
// no error, the response is complete
|
||||
return nil
|
||||
}
|
||||
|
||||
var wrapped *Error
|
||||
if errors.As(err, &wrapped) {
|
||||
// already a wire error, just use it
|
||||
return wrapped
|
||||
}
|
||||
|
||||
result := &Error{Message: err.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
|
||||
result.Code = wrapped.Code
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// make sure a Notification implements the Request, json.Marshaler and json.Unmarshaler and interfaces.
|
||||
var (
|
||||
_ Request = (*Notification)(nil)
|
||||
_ json.Marshaler = (*Notification)(nil)
|
||||
_ json.Unmarshaler = (*Notification)(nil)
|
||||
)
|
||||
|
||||
// NewNotification constructs a new Notification message for the supplied
|
||||
// method and parameters.
|
||||
func NewNotification(method string, params any) (*Notification, error) {
|
||||
p, merr := marshalInterface(params)
|
||||
notify := &Notification{
|
||||
method: method,
|
||||
params: p,
|
||||
}
|
||||
return notify, merr
|
||||
}
|
||||
|
||||
// Method implements Request.
|
||||
func (n *Notification) Method() string { return n.method }
|
||||
|
||||
// Params implements Request.
|
||||
func (n *Notification) Params() json.RawMessage { return n.params }
|
||||
|
||||
// jsonrpc2Message implements Request.
|
||||
func (Notification) jsonrpc2Message() {}
|
||||
|
||||
// jsonrpc2Request implements Request.
|
||||
func (Notification) jsonrpc2Request() {}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (n Notification) MarshalJSON() ([]byte, error) {
|
||||
req := wireRequest{
|
||||
Method: n.method,
|
||||
Params: &n.params,
|
||||
}
|
||||
data, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return data, fmt.Errorf("marshaling notification: %w", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (n *Notification) UnmarshalJSON(data []byte) error {
|
||||
var req wireRequest
|
||||
dec := json.NewDecoder(bytes.NewReader(data))
|
||||
if err := dec.Decode(&req); err != nil {
|
||||
return fmt.Errorf("unmarshaling notification: %w", err)
|
||||
}
|
||||
|
||||
n.method = req.Method
|
||||
if req.Params != nil {
|
||||
n.params = *req.Params
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodeMessage decodes data to Message.
|
||||
func DecodeMessage(data []byte) (Message, error) {
|
||||
var msg combined
|
||||
dec := json.NewDecoder(bytes.NewReader(data))
|
||||
if err := dec.Decode(&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
|
||||
}
|
||||
|
||||
resp := &Response{
|
||||
id: *msg.ID,
|
||||
}
|
||||
if msg.Error != nil {
|
||||
resp.err = msg.Error
|
||||
}
|
||||
if msg.Result != nil {
|
||||
resp.result = *msg.Result
|
||||
}
|
||||
|
||||
return resp, 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
|
||||
}
|
||||
|
||||
// marshalInterface marshal obj to json.RawMessage.
|
||||
func marshalInterface(obj any) (json.RawMessage, error) {
|
||||
data, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return json.RawMessage{}, fmt.Errorf("failed to marshal json: %w", err)
|
||||
}
|
||||
return json.RawMessage(data), nil
|
||||
}
|
129
templ/lsp/jsonrpc2/serve.go
Normal file
129
templ/lsp/jsonrpc2/serve.go
Normal file
@@ -0,0 +1,129 @@
|
||||
// SPDX-FileCopyrightText: 2021 The Go Language Server Authors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package jsonrpc2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NOTE: This file provides an experimental API for serving multiple remote
|
||||
// jsonrpc2 clients over the network. For now, it is intentionally similar to
|
||||
// net/http, but that may change in the future as we figure out the correct
|
||||
// semantics.
|
||||
|
||||
// StreamServer is used to serve incoming jsonrpc2 clients communicating over
|
||||
// a newly created connection.
|
||||
type StreamServer interface {
|
||||
ServeStream(context.Context, Conn) error
|
||||
}
|
||||
|
||||
// ServerFunc is an adapter that implements the StreamServer interface
|
||||
// using an ordinary function.
|
||||
type ServerFunc func(context.Context, Conn) error
|
||||
|
||||
// ServeStream implements StreamServer.
|
||||
//
|
||||
// ServeStream calls f(ctx, s).
|
||||
func (f ServerFunc) ServeStream(ctx context.Context, c Conn) error {
|
||||
return f(ctx, c)
|
||||
}
|
||||
|
||||
// HandlerServer returns a StreamServer that handles incoming streams using the
|
||||
// provided handler.
|
||||
func HandlerServer(h Handler) StreamServer {
|
||||
return ServerFunc(func(ctx context.Context, conn Conn) error {
|
||||
conn.Go(ctx, h)
|
||||
<-conn.Done()
|
||||
return conn.Err()
|
||||
})
|
||||
}
|
||||
|
||||
// ListenAndServe starts an jsonrpc2 server on the given address.
|
||||
//
|
||||
// If idleTimeout is non-zero, ListenAndServe exits after there are no clients for
|
||||
// this duration, otherwise it exits only on error.
|
||||
func ListenAndServe(ctx context.Context, network, addr string, server StreamServer, idleTimeout time.Duration) error {
|
||||
ln, err := net.Listen(network, addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to listen %s:%s: %w", network, addr, err)
|
||||
}
|
||||
defer ln.Close()
|
||||
|
||||
if network == "unix" {
|
||||
defer os.Remove(addr)
|
||||
}
|
||||
|
||||
return Serve(ctx, ln, server, idleTimeout)
|
||||
}
|
||||
|
||||
// Serve accepts incoming connections from the network, and handles them using
|
||||
// the provided server. If idleTimeout is non-zero, ListenAndServe exits after
|
||||
// there are no clients for this duration, otherwise it exits only on error.
|
||||
func Serve(ctx context.Context, ln net.Listener, server StreamServer, idleTimeout time.Duration) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
// Max duration: ~290 years; surely that's long enough.
|
||||
const forever = 1<<63 - 1
|
||||
if idleTimeout <= 0 {
|
||||
idleTimeout = forever
|
||||
}
|
||||
connTimer := time.NewTimer(idleTimeout)
|
||||
|
||||
newConns := make(chan net.Conn)
|
||||
doneListening := make(chan error)
|
||||
closedConns := make(chan error)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
nc, err := ln.Accept()
|
||||
if err != nil {
|
||||
select {
|
||||
case doneListening <- fmt.Errorf("accept: %w", err):
|
||||
case <-ctx.Done():
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
newConns <- nc
|
||||
}
|
||||
}()
|
||||
|
||||
activeConns := 0
|
||||
for {
|
||||
select {
|
||||
case netConn := <-newConns:
|
||||
activeConns++
|
||||
connTimer.Stop()
|
||||
stream := NewStream(netConn)
|
||||
go func() {
|
||||
conn := NewConn(stream)
|
||||
closedConns <- server.ServeStream(ctx, conn)
|
||||
stream.Close()
|
||||
}()
|
||||
|
||||
case err := <-doneListening:
|
||||
return err
|
||||
|
||||
case <-closedConns:
|
||||
// if !isClosingError(err) {
|
||||
// }
|
||||
|
||||
activeConns--
|
||||
if activeConns == 0 {
|
||||
connTimer.Reset(idleTimeout)
|
||||
}
|
||||
|
||||
case <-connTimer.C:
|
||||
return ErrIdleTimeout
|
||||
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
60
templ/lsp/jsonrpc2/serve_test.go
Normal file
60
templ/lsp/jsonrpc2/serve_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// SPDX-FileCopyrightText: 2021 The Go Language Server Authors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package jsonrpc2_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/a-h/templ/lsp/jsonrpc2"
|
||||
)
|
||||
|
||||
func TestIdleTimeout(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
ln, err := net.Listen("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ln.Close()
|
||||
|
||||
connect := func() net.Conn {
|
||||
conn, err := net.DialTimeout("tcp", ln.Addr().String(), 5*time.Second)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
server := jsonrpc2.HandlerServer(jsonrpc2.MethodNotFoundHandler)
|
||||
var (
|
||||
runErr error
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
runErr = jsonrpc2.Serve(ctx, ln, server, 100*time.Millisecond)
|
||||
}()
|
||||
|
||||
// Exercise some connection/disconnection patterns, and then assert that when
|
||||
// our timer fires, the server exits.
|
||||
conn1 := connect()
|
||||
conn2 := connect()
|
||||
conn1.Close()
|
||||
conn2.Close()
|
||||
conn3 := connect()
|
||||
conn3.Close()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if !errors.Is(runErr, jsonrpc2.ErrIdleTimeout) {
|
||||
t.Errorf("run() returned error %v, want %v", runErr, jsonrpc2.ErrIdleTimeout)
|
||||
}
|
||||
}
|
226
templ/lsp/jsonrpc2/stream.go
Normal file
226
templ/lsp/jsonrpc2/stream.go
Normal file
@@ -0,0 +1,226 @@
|
||||
// SPDX-FileCopyrightText: 2018 The Go Language Server Authors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package jsonrpc2
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
stdjson "encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
const (
|
||||
// HdrContentLength is the HTTP header name of the length of the content part in bytes. This header is required.
|
||||
// This entity header indicates the size of the entity-body, in bytes, sent to the recipient.
|
||||
//
|
||||
// RFC 7230, section 3.3.2: Content-Length:
|
||||
// https://tools.ietf.org/html/rfc7230#section-3.3.2
|
||||
HdrContentLength = "Content-Length"
|
||||
|
||||
// HeaderContentType is the mime type of the content part. Defaults to "application/vscode-jsonrpc; charset=utf-8".
|
||||
// This entity header is used to indicate the media type of the resource.
|
||||
//
|
||||
// RFC 7231, section 3.1.1.5: Content-Type:
|
||||
// https://tools.ietf.org/html/rfc7231#section-3.1.1.5
|
||||
HdrContentType = "Content-Type"
|
||||
|
||||
// HeaderContentSeparator is the header and content part separator.
|
||||
HdrContentSeparator = "\r\n\r\n"
|
||||
)
|
||||
|
||||
// Framer wraps a network connection up into a Stream.
|
||||
//
|
||||
// It is responsible for the framing and encoding of messages into wire form.
|
||||
// NewRawStream and NewStream are implementations of a Framer.
|
||||
type Framer func(conn io.ReadWriteCloser) Stream
|
||||
|
||||
// Stream abstracts the transport mechanics from the JSON RPC protocol.
|
||||
//
|
||||
// A Conn reads and writes messages using the stream it was provided on
|
||||
// construction, and assumes that each call to Read or Write fully transfers
|
||||
// a single message, or returns an error.
|
||||
//
|
||||
// A stream is not safe for concurrent use, it is expected it will be used by
|
||||
// a single Conn in a safe manner.
|
||||
type Stream interface {
|
||||
// Read gets the next message from the stream.
|
||||
Read(context.Context) (Message, int64, error)
|
||||
|
||||
// Write sends a message to the stream.
|
||||
Write(context.Context, Message) (int64, error)
|
||||
|
||||
// Close closes the connection.
|
||||
// Any blocked Read or Write operations will be unblocked and return errors.
|
||||
Close() error
|
||||
}
|
||||
|
||||
type rawStream struct {
|
||||
conn io.ReadWriteCloser
|
||||
in *stdjson.Decoder
|
||||
}
|
||||
|
||||
// NewRawStream returns a Stream built on top of a io.ReadWriteCloser.
|
||||
//
|
||||
// The messages are sent with no wrapping, and rely on json decode consistency
|
||||
// to determine message boundaries.
|
||||
func NewRawStream(conn io.ReadWriteCloser) Stream {
|
||||
return &rawStream{
|
||||
conn: conn,
|
||||
in: stdjson.NewDecoder(conn), // TODO(zchee): why test fail using segmentio json.Decoder?
|
||||
}
|
||||
}
|
||||
|
||||
// Read implements Stream.Read.
|
||||
func (s *rawStream) Read(ctx context.Context) (Message, int64, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, 0, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
var raw stdjson.RawMessage
|
||||
if err := s.in.Decode(&raw); err != nil {
|
||||
return nil, 0, fmt.Errorf("decoding raw message: %w", err)
|
||||
}
|
||||
|
||||
msg, err := DecodeMessage(raw)
|
||||
return msg, int64(len(raw)), err
|
||||
}
|
||||
|
||||
// Write implements Stream.Write.
|
||||
func (s *rawStream) Write(ctx context.Context, msg Message) (int64, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return 0, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
data, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("marshaling message: %w", err)
|
||||
}
|
||||
|
||||
n, err := s.conn.Write(data)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("write to stream: %w", err)
|
||||
}
|
||||
|
||||
return int64(n), nil
|
||||
}
|
||||
|
||||
// Close implements Stream.Close.
|
||||
func (s *rawStream) Close() error {
|
||||
return s.conn.Close()
|
||||
}
|
||||
|
||||
type stream struct {
|
||||
conn io.ReadWriteCloser
|
||||
in *bufio.Reader
|
||||
}
|
||||
|
||||
// NewStream returns a Stream built on top of a io.ReadWriteCloser.
|
||||
//
|
||||
// The messages are sent with HTTP content length and MIME type headers.
|
||||
// This is the format used by LSP and others.
|
||||
func NewStream(conn io.ReadWriteCloser) Stream {
|
||||
return &stream{
|
||||
conn: conn,
|
||||
in: bufio.NewReader(conn),
|
||||
}
|
||||
}
|
||||
|
||||
// Read implements Stream.Read.
|
||||
func (s *stream) Read(ctx context.Context) (Message, int64, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, 0, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
var total int64
|
||||
var length int64
|
||||
// read the header, stop on the first empty line
|
||||
for {
|
||||
line, err := s.in.ReadString('\n')
|
||||
total += int64(len(line))
|
||||
if err != nil {
|
||||
return nil, total, fmt.Errorf("failed reading header line: %w", err)
|
||||
}
|
||||
|
||||
line = strings.TrimSpace(line)
|
||||
// check we have a header line
|
||||
if line == "" {
|
||||
break
|
||||
}
|
||||
|
||||
colon := strings.IndexRune(line, ':')
|
||||
if colon < 0 {
|
||||
return nil, total, fmt.Errorf("invalid header line %q", line)
|
||||
}
|
||||
|
||||
name, value := line[:colon], strings.TrimSpace(line[colon+1:])
|
||||
switch name {
|
||||
case HdrContentLength:
|
||||
if length, err = strconv.ParseInt(value, 10, 32); err != nil {
|
||||
return nil, total, fmt.Errorf("failed parsing %s: %v: %w", HdrContentLength, value, err)
|
||||
}
|
||||
if length <= 0 {
|
||||
return nil, total, fmt.Errorf("invalid %s: %v", HdrContentLength, length)
|
||||
}
|
||||
default:
|
||||
// ignoring unknown headers
|
||||
}
|
||||
}
|
||||
|
||||
if length == 0 {
|
||||
return nil, total, fmt.Errorf("missing %s header", HdrContentLength)
|
||||
}
|
||||
|
||||
data := make([]byte, length)
|
||||
if _, err := io.ReadFull(s.in, data); err != nil {
|
||||
return nil, total, fmt.Errorf("read full of data: %w", err)
|
||||
}
|
||||
|
||||
total += length
|
||||
msg, err := DecodeMessage(data)
|
||||
return msg, total, err
|
||||
}
|
||||
|
||||
// Write implements Stream.Write.
|
||||
func (s *stream) Write(ctx context.Context, msg Message) (int64, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return 0, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
data, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("marshaling message: %w", err)
|
||||
}
|
||||
|
||||
n, err := fmt.Fprintf(s.conn, "%s: %v%s", HdrContentLength, len(data), HdrContentSeparator)
|
||||
total := int64(n)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("write data to conn: %w", err)
|
||||
}
|
||||
|
||||
n, err = s.conn.Write(data)
|
||||
total += int64(n)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("write data to conn: %w", err)
|
||||
}
|
||||
|
||||
return total, nil
|
||||
}
|
||||
|
||||
// Close implements Stream.Close.
|
||||
func (s *stream) Close() error {
|
||||
return s.conn.Close()
|
||||
}
|
140
templ/lsp/jsonrpc2/wire.go
Normal file
140
templ/lsp/jsonrpc2/wire.go
Normal file
@@ -0,0 +1,140 @@
|
||||
// SPDX-FileCopyrightText: 2021 The Go Language Server Authors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package jsonrpc2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// Version represents a JSON-RPC version.
|
||||
const Version = "2.0"
|
||||
|
||||
// version is a special 0 sized struct that encodes as the jsonrpc version tag.
|
||||
//
|
||||
// It will fail during decode if it is not the correct version tag in the stream.
|
||||
type version struct{}
|
||||
|
||||
// compile time check whether the version implements a json.Marshaler and json.Unmarshaler interfaces.
|
||||
var (
|
||||
_ json.Marshaler = (*version)(nil)
|
||||
_ json.Unmarshaler = (*version)(nil)
|
||||
)
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (version) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(Version)
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (version) UnmarshalJSON(data []byte) error {
|
||||
version := ""
|
||||
if err := json.Unmarshal(data, &version); err != nil {
|
||||
return fmt.Errorf("failed to Unmarshal: %w", err)
|
||||
}
|
||||
if version != Version {
|
||||
return fmt.Errorf("invalid RPC version %v", version)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ID is a Request identifier.
|
||||
//
|
||||
// Only one of either the Name or Number members will be set, using the
|
||||
// number form if the Name is the empty string.
|
||||
type ID struct {
|
||||
name string
|
||||
number int32
|
||||
}
|
||||
|
||||
// compile time check whether the ID implements a fmt.Formatter, json.Marshaler and json.Unmarshaler interfaces.
|
||||
var (
|
||||
_ fmt.Formatter = (*ID)(nil)
|
||||
_ json.Marshaler = (*ID)(nil)
|
||||
_ json.Unmarshaler = (*ID)(nil)
|
||||
)
|
||||
|
||||
// NewNumberID returns a new number request ID.
|
||||
func NewNumberID(v int32) ID { return ID{number: v} }
|
||||
|
||||
// NewStringID returns a new string request ID.
|
||||
func NewStringID(v string) ID { return ID{name: v} }
|
||||
|
||||
// Format writes the ID to the formatter.
|
||||
//
|
||||
// If the rune is q the representation is non ambiguous,
|
||||
// string forms are quoted, number forms are preceded by a #.
|
||||
func (id ID) Format(f fmt.State, r rune) {
|
||||
numF, strF := `%d`, `%s`
|
||||
if r == 'q' {
|
||||
numF, strF = `#%d`, `%q`
|
||||
}
|
||||
|
||||
switch {
|
||||
case id.name != "":
|
||||
fmt.Fprintf(f, strF, id.name)
|
||||
default:
|
||||
fmt.Fprintf(f, numF, id.number)
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (id *ID) MarshalJSON() ([]byte, error) {
|
||||
if id.name != "" {
|
||||
return json.Marshal(id.name)
|
||||
}
|
||||
return json.Marshal(id.number)
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (id *ID) UnmarshalJSON(data []byte) error {
|
||||
*id = ID{}
|
||||
if err := json.Unmarshal(data, &id.number); err == nil {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(data, &id.name)
|
||||
}
|
||||
|
||||
// wireRequest is sent to a server to represent a Call or Notify operaton.
|
||||
type wireRequest struct {
|
||||
// VersionTag is always encoded as the string "2.0"
|
||||
VersionTag version `json:"jsonrpc"`
|
||||
// Method is a string containing the method name to invoke.
|
||||
Method string `json:"method"`
|
||||
// Params is either a struct or an array with the parameters of the method.
|
||||
Params *json.RawMessage `json:"params,omitempty"`
|
||||
// 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 `json:"id,omitempty"`
|
||||
}
|
||||
|
||||
// wireResponse is a reply to a Request.
|
||||
//
|
||||
// It will always have the ID field set to tie it back to a request, and will
|
||||
// have either the Result or Error fields set depending on whether it is a
|
||||
// success or failure wireResponse.
|
||||
type wireResponse struct {
|
||||
// VersionTag is always encoded as the string "2.0"
|
||||
VersionTag version `json:"jsonrpc"`
|
||||
// Result is the response value, and is required on success.
|
||||
Result *json.RawMessage `json:"result,omitempty"`
|
||||
// Error is a structured error response if the call fails.
|
||||
Error *Error `json:"error,omitempty"`
|
||||
// ID must be set and is the identifier of the Request this is a response to.
|
||||
ID *ID `json:"id,omitempty"`
|
||||
}
|
||||
|
||||
// combined has all the fields of both Request and Response.
|
||||
//
|
||||
// We can decode this and then work out which it is.
|
||||
type combined struct {
|
||||
VersionTag version `json:"jsonrpc"`
|
||||
ID *ID `json:"id,omitempty"`
|
||||
Method string `json:"method"`
|
||||
Params *json.RawMessage `json:"params,omitempty"`
|
||||
Result *json.RawMessage `json:"result,omitempty"`
|
||||
Error *Error `json:"error,omitempty"`
|
||||
}
|
156
templ/lsp/jsonrpc2/wire_test.go
Normal file
156
templ/lsp/jsonrpc2/wire_test.go
Normal file
@@ -0,0 +1,156 @@
|
||||
// SPDX-FileCopyrightText: 2021 The Go Language Server Authors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package jsonrpc2_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"github.com/a-h/templ/lsp/jsonrpc2"
|
||||
)
|
||||
|
||||
var wireIDTestData = []struct {
|
||||
name string
|
||||
id jsonrpc2.ID
|
||||
encoded []byte
|
||||
plain string
|
||||
quoted string
|
||||
}{
|
||||
{
|
||||
name: `empty`,
|
||||
encoded: []byte(`0`),
|
||||
plain: `0`,
|
||||
quoted: `#0`,
|
||||
}, {
|
||||
name: `number`,
|
||||
id: jsonrpc2.NewNumberID(43),
|
||||
encoded: []byte(`43`),
|
||||
plain: `43`,
|
||||
quoted: `#43`,
|
||||
}, {
|
||||
name: `string`,
|
||||
id: jsonrpc2.NewStringID("life"),
|
||||
encoded: []byte(`"life"`),
|
||||
plain: `life`,
|
||||
quoted: `"life"`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestIDFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tt := range wireIDTestData {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if got := fmt.Sprint(tt.id); got != tt.plain {
|
||||
t.Errorf("got %s expected %s", got, tt.plain)
|
||||
}
|
||||
if got := fmt.Sprintf("%q", tt.id); got != tt.quoted {
|
||||
t.Errorf("got %s want %s", got, tt.quoted)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIDEncode(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tt := range wireIDTestData {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data, err := json.Marshal(&tt.id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
checkJSON(t, data, tt.encoded)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIDDecode(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tt := range wireIDTestData {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var got *jsonrpc2.ID
|
||||
dec := json.NewDecoder(bytes.NewReader(tt.encoded))
|
||||
if err := dec.Decode(&got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if reflect.ValueOf(&got).IsZero() {
|
||||
t.Fatalf("got nil want %s", tt.id)
|
||||
}
|
||||
|
||||
if *got != tt.id {
|
||||
t.Fatalf("got %s want %s", got, tt.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorEncode(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
b, err := json.Marshal(jsonrpc2.NewError(0, ""))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
checkJSON(t, b, []byte(`{
|
||||
"code": 0,
|
||||
"message": ""
|
||||
}`))
|
||||
}
|
||||
|
||||
func TestErrorResponse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// originally reported in #39719, this checks that result is not present if
|
||||
// it is an error response
|
||||
r, _ := jsonrpc2.NewResponse(jsonrpc2.NewNumberID(3), nil, fmt.Errorf("computing fix edits"))
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
checkJSON(t, data, []byte(`{
|
||||
"jsonrpc":"2.0",
|
||||
"error":{
|
||||
"code":0,
|
||||
"message":"computing fix edits"
|
||||
},
|
||||
"id":3
|
||||
}`))
|
||||
}
|
||||
|
||||
func checkJSON(t *testing.T, got, want []byte) {
|
||||
t.Helper()
|
||||
|
||||
// compare the compact form, to allow for formatting differences
|
||||
g := &bytes.Buffer{}
|
||||
if err := json.Compact(g, got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
w := &bytes.Buffer{}
|
||||
if err := json.Compact(w, want); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if g.String() != w.String() {
|
||||
t.Fatalf("Got:\n%s\nWant:\n%s", g, w)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user