| 🛟 Updated. 28d5985 k33g 18h ago | 1 | // Package jsonrpc is a JSON-RPC 2.0 connection over a framed byte stream. |
| 2 | // |
| 3 | // It holds everything the protocol says and nothing about any particular use |
| 4 | // of it: requests and their answers, notifications, requests arriving the |
| 5 | // other way, and the error object a failure travels in. How a message is |
| 6 | // marked off from the next one is the caller's, through a Framer — the |
| 7 | // Language Server Protocol puts an HTTP-style header in front of each message, |
| 8 | // the Agent Client Protocol separates them with newlines, and everything above |
| 9 | // that layer is the same. |
| 10 | // |
| 11 | // A connection is safe for concurrent use: several goroutines may call and |
| 12 | // notify at once, and answers are matched back to their callers by request id. |
| 13 | // The ids a peer chooses are its own and never collide with the ones sent from |
| 14 | // here, because the two directions are tracked separately. |
| 15 | // |
| 16 | // conn := jsonrpc.NewConn(stream, framer, onNotify, onRequest) |
| 17 | // go conn.Run() |
| 18 | // |
| 19 | // var result Position |
| 20 | // err := conn.Call(ctx, "textDocument/definition", params, &result) |
| 21 | package jsonrpc |
| 22 | |
| 23 | import ( |
| 24 | "bufio" |
| 25 | "encoding/json" |
| 26 | "errors" |
| 27 | "fmt" |
| 28 | "io" |
| 29 | ) |
| 30 | |
| 31 | // Version is the only value the protocol's jsonrpc field ever takes. |
| 32 | const Version = "2.0" |
| 33 | |
| 34 | // ErrClosed is returned once the connection has been closed or its peer has |
| 35 | // gone away. |
| 36 | var ErrClosed = errors.New("jsonrpc: connection closed") |
| 37 | |
| 38 | // Framer marks off one message from the next on a stream. |
| 39 | // |
| 40 | // The two halves are deliberately separate functions rather than a codec |
| 41 | // object: framing is stateless, and a reader and a writer of the same |
| 42 | // connection are used from different goroutines. |
| 43 | // |
| 44 | // type NewlineFramer struct{} |
| 45 | // |
| 46 | // func (NewlineFramer) WriteMessage(w io.Writer, body []byte) error { |
| 47 | // _, err := fmt.Fprintf(w, "%s\n", body) |
| 48 | // return err |
| 49 | // } |
| 50 | type Framer interface { |
| 51 | // ReadMessage reads one whole message and returns its body. It returns |
| 52 | // io.EOF when the stream ends cleanly between messages, which is how a |
| 53 | // peer shutting down is told apart from one that died mid-message. |
| 54 | ReadMessage(r *bufio.Reader) ([]byte, error) |
| 55 | |
| 56 | // WriteMessage frames body and writes it out. |
| 57 | WriteMessage(w io.Writer, body []byte) error |
| 58 | } |
| 59 | |
| 60 | // ResponseError is the error object a response may carry. |
| 61 | // |
| 62 | // It is an error, so a failure at the far end reads like any other one at the |
| 63 | // call site, and a handler may return one to choose the code the peer sees. |
| 64 | // |
| 65 | // return nil, &jsonrpc.ResponseError{Code: jsonrpc.CodeInvalidParams, Message: "no uri"} |
| 66 | type ResponseError struct { |
| 67 | Code int `json:"code"` |
| 68 | Message string `json:"message"` |
| 69 | Data json.RawMessage `json:"data,omitempty"` |
| 70 | } |
| 71 | |
| 72 | // Error makes ResponseError an error. |
| 73 | func (e *ResponseError) Error() string { |
| 74 | return fmt.Sprintf("jsonrpc: peer error %d: %s", e.Code, e.Message) |
| 75 | } |
| 76 | |
| 77 | // The standard error codes, of which a client normally sends only the first. |
| 78 | const ( |
| 79 | CodeParseError = -32700 |
| 80 | CodeInvalidRequest = -32600 |
| 81 | CodeMethodNotFound = -32601 |
| 82 | CodeInvalidParams = -32602 |
| 83 | CodeInternalError = -32603 |
| 84 | ) |
| 85 | |
| 86 | // message is any frame: a request, a notification or a response. Which one it |
| 87 | // is follows from which fields are present. |
| 88 | type message struct { |
| 89 | JSONRPC string `json:"jsonrpc"` |
| 90 | ID json.RawMessage `json:"id,omitempty"` |
| 91 | Method string `json:"method,omitempty"` |
| 92 | Params json.RawMessage `json:"params,omitempty"` |
| 93 | Result json.RawMessage `json:"result,omitempty"` |
| 94 | Error *ResponseError `json:"error,omitempty"` |
| 95 | } |
| 96 | |
| 97 | // NotificationFunc handles a notification the peer sent. It is called from the |
| 98 | // connection's read loop, so it must not block for long. |
| 99 | type NotificationFunc func(method string, params json.RawMessage) |
| 100 | |
| 101 | // RequestFunc handles a request the peer sent. |
| 102 | // |
| 103 | // It is called from the connection's read loop and must not block: a handler |
| 104 | // whose answer has to come from somewhere else — the user, another goroutine, |
| 105 | // the next turn of an event loop — keeps the Request and calls Reply when it |
| 106 | // has one. Answering twice is harmless; never answering leaves the peer |
| 107 | // waiting until the connection ends. |
| 108 | type RequestFunc func(req *Request) |
| 109 | |
| 110 | // asResponseError turns any error into one the protocol can carry. |
| 111 | func asResponseError(err error) *ResponseError { |
| 112 | var responseErr *ResponseError |
| 113 | if errors.As(err, &responseErr) { |
| 114 | return responseErr |
| 115 | } |
| 116 | return &ResponseError{Code: CodeInternalError, Message: err.Error()} |
| 117 | } |
| 118 | |
| 119 | // encodeParams encodes a parameter object, treating nil as absent. |
| 120 | func encodeParams(params any) (json.RawMessage, error) { |
| 121 | if params == nil { |
| 122 | return nil, nil |
| 123 | } |
| 124 | encoded, err := json.Marshal(params) |
| 125 | if err != nil { |
| 126 | return nil, fmt.Errorf("jsonrpc: encoding the parameters: %w", err) |
| 127 | } |
| 128 | return encoded, nil |
| 129 | } |
| 130 | |
| 131 | // decodeReply turns a response into an error or a decoded result. |
| 132 | func decodeReply(reply *message, result any) error { |
| 133 | if reply == nil { |
| 134 | return ErrClosed |
| 135 | } |
| 136 | if reply.Error != nil { |
| 137 | return reply.Error |
| 138 | } |
| 139 | if result == nil || len(reply.Result) == 0 { |
| 140 | return nil |
| 141 | } |
| 142 | if err := json.Unmarshal(reply.Result, result); err != nil { |
| 143 | return fmt.Errorf("jsonrpc: unreadable result: %w", err) |
| 144 | } |
| 145 | return nil |
| 146 | } |