package jsonrpc2 import ( "context" "encoding/json" "errors" "io" ) // ReadWriterServer wraps an io.ReadWriter so that it can handle RPC calls. // // The HandleNext method will process a single request and response. It should // be called in a loop until it returns an error. type ReadWriterServer struct { handler Handler enc *json.Encoder dec *json.Decoder } // NewReadWriterServer returns a ReadWriterServer which will read/write on the // given io.ReadWriter. // // All methods exported from rpcInterface which accept a context and one other // value, and return a value and an error, will be considered to be available // RPC methods, and the ReadWriterServer will execute calls to those methods // against the given rpcInterface instance. func NewReadWriterServer(handler Handler, rw io.ReadWriter) *ReadWriterServer { return &ReadWriterServer{ handler: handler, enc: json.NewEncoder(rw), dec: json.NewDecoder(rw), } } // HandleNext blocks until a single RPC call has been handled. If an error is // returned, including context errors, then the ReadWriterServer should be // considered in an unusable state and discarded. func (h *ReadWriterServer) HandleNext(ctx context.Context) error { req, decErr := decodeRequest(h.dec) if decErr.Code != 0 { encErr := encodeErrorResponse(h.enc, req.ID, decErr) return errors.Join(decErr, encErr) } var rpcErr Error res, err := h.handler.ServeRPC(ctx, req) if err != nil && !errors.As(err, &rpcErr) { // We deliberately don't include the actual error in the response. If // doing so is desired this can be achieved using a middleware which // translates the non-Error into an Error prior to it reaching this // point. rpcErr = NewError(errCodeServerError, "unexpected server-side error") } if rpcErr.Code != 0 { return encodeErrorResponse(h.enc, req.ID, rpcErr) } return encodeResponse(h.enc, req.ID, res) }