turbo-editors/turbo-corepublic Fork 0
v0.9.0
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

🛟 Updated. 28d5985 · on v0.9.0 · k33g · 18h ago
jsonrpc.go · 146 lines · 5.0 KBGo Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// Package jsonrpc is a JSON-RPC 2.0 connection over a framed byte stream.
//
// It holds everything the protocol says and nothing about any particular use
// of it: requests and their answers, notifications, requests arriving the
// other way, and the error object a failure travels in. How a message is
// marked off from the next one is the caller's, through a Framer — the
// Language Server Protocol puts an HTTP-style header in front of each message,
// the Agent Client Protocol separates them with newlines, and everything above
// that layer is the same.
//
// A connection is safe for concurrent use: several goroutines may call and
// notify at once, and answers are matched back to their callers by request id.
// The ids a peer chooses are its own and never collide with the ones sent from
// here, because the two directions are tracked separately.
//
//	conn := jsonrpc.NewConn(stream, framer, onNotify, onRequest)
//	go conn.Run()
//
//	var result Position
//	err := conn.Call(ctx, "textDocument/definition", params, &result)
package jsonrpc

import (
	"bufio"
	"encoding/json"
	"errors"
	"fmt"
	"io"
)

// Version is the only value the protocol's jsonrpc field ever takes.
const Version = "2.0"

// ErrClosed is returned once the connection has been closed or its peer has
// gone away.
var ErrClosed = errors.New("jsonrpc: connection closed")

// Framer marks off one message from the next on a stream.
//
// The two halves are deliberately separate functions rather than a codec
// object: framing is stateless, and a reader and a writer of the same
// connection are used from different goroutines.
//
//	type NewlineFramer struct{}
//
//	func (NewlineFramer) WriteMessage(w io.Writer, body []byte) error {
//		_, err := fmt.Fprintf(w, "%s\n", body)
//		return err
//	}
type Framer interface {
	// ReadMessage reads one whole message and returns its body. It returns
	// io.EOF when the stream ends cleanly between messages, which is how a
	// peer shutting down is told apart from one that died mid-message.
	ReadMessage(r *bufio.Reader) ([]byte, error)

	// WriteMessage frames body and writes it out.
	WriteMessage(w io.Writer, body []byte) error
}

// ResponseError is the error object a response may carry.
//
// It is an error, so a failure at the far end reads like any other one at the
// call site, and a handler may return one to choose the code the peer sees.
//
//	return nil, &jsonrpc.ResponseError{Code: jsonrpc.CodeInvalidParams, Message: "no uri"}
type ResponseError struct {
	Code    int             `json:"code"`
	Message string          `json:"message"`
	Data    json.RawMessage `json:"data,omitempty"`
}

// Error makes ResponseError an error.
func (e *ResponseError) Error() string {
	return fmt.Sprintf("jsonrpc: peer error %d: %s", e.Code, e.Message)
}

// The standard error codes, of which a client normally sends only the first.
const (
	CodeParseError     = -32700
	CodeInvalidRequest = -32600
	CodeMethodNotFound = -32601
	CodeInvalidParams  = -32602
	CodeInternalError  = -32603
)

// message is any frame: a request, a notification or a response. Which one it
// is follows from which fields are present.
type message struct {
	JSONRPC string          `json:"jsonrpc"`
	ID      json.RawMessage `json:"id,omitempty"`
	Method  string          `json:"method,omitempty"`
	Params  json.RawMessage `json:"params,omitempty"`
	Result  json.RawMessage `json:"result,omitempty"`
	Error   *ResponseError  `json:"error,omitempty"`
}

// NotificationFunc handles a notification the peer sent. It is called from the
// connection's read loop, so it must not block for long.
type NotificationFunc func(method string, params json.RawMessage)

// RequestFunc handles a request the peer sent.
//
// It is called from the connection's read loop and must not block: a handler
// whose answer has to come from somewhere else — the user, another goroutine,
// the next turn of an event loop — keeps the Request and calls Reply when it
// has one. Answering twice is harmless; never answering leaves the peer
// waiting until the connection ends.
type RequestFunc func(req *Request)

// asResponseError turns any error into one the protocol can carry.
func asResponseError(err error) *ResponseError {
	var responseErr *ResponseError
	if errors.As(err, &responseErr) {
		return responseErr
	}
	return &ResponseError{Code: CodeInternalError, Message: err.Error()}
}

// encodeParams encodes a parameter object, treating nil as absent.
func encodeParams(params any) (json.RawMessage, error) {
	if params == nil {
		return nil, nil
	}
	encoded, err := json.Marshal(params)
	if err != nil {
		return nil, fmt.Errorf("jsonrpc: encoding the parameters: %w", err)
	}
	return encoded, nil
}

// decodeReply turns a response into an error or a decoded result.
func decodeReply(reply *message, result any) error {
	if reply == nil {
		return ErrClosed
	}
	if reply.Error != nil {
		return reply.Error
	}
	if result == nil || len(reply.Result) == 0 {
		return nil
	}
	if err := json.Unmarshal(reply.Result, result); err != nil {
		return fmt.Errorf("jsonrpc: unreadable result: %w", err)
	}
	return nil
}