rpc: pre-encode JSON-RPC error responses at creation time

This commit is contained in:
jonny rhea 2026-03-10 13:02:45 -05:00
parent d620f9f96a
commit 4a6e13623a
4 changed files with 31 additions and 25 deletions

View file

@ -364,7 +364,7 @@ func (c *Client) CallContext(ctx context.Context, result interface{}, method str
resp := batchresp[0] resp := batchresp[0]
switch { switch {
case resp.Error != nil: case resp.Error != nil:
return resp.Error return resp.jsonErr()
case len(resp.Result) == 0: case len(resp.Result) == 0:
return ErrNoResult return ErrNoResult
default: default:
@ -449,7 +449,7 @@ func (c *Client) BatchCallContext(ctx context.Context, b []BatchElem) error {
elem := &b[index] elem := &b[index]
switch { switch {
case resp.Error != nil: case resp.Error != nil:
elem.Error = resp.Error elem.Error = resp.jsonErr()
case resp.Result == nil: case resp.Result == nil:
elem.Error = ErrNoResult elem.Error = ErrNoResult
default: default:

View file

@ -237,7 +237,7 @@ func (h *handler) handleBatch(msgs []*jsonrpcMessage) {
resp := h.handleCallMsg(cp, msg) resp := h.handleCallMsg(cp, msg)
callBuffer.pushResponse(resp) callBuffer.pushResponse(resp)
if resp != nil && h.batchResponseMaxSize != 0 { if resp != nil && h.batchResponseMaxSize != 0 {
responseBytes += len(resp.Result) responseBytes += len(resp.Result) + len(resp.Error)
if responseBytes > h.batchResponseMaxSize { if responseBytes > h.batchResponseMaxSize {
err := &internalServerError{errcodeResponseTooLarge, errMsgResponseTooLarge} err := &internalServerError{errcodeResponseTooLarge, errMsgResponseTooLarge}
callBuffer.respondWithError(cp.ctx, h.conn, err) callBuffer.respondWithError(cp.ctx, h.conn, err)
@ -415,7 +415,7 @@ func (h *handler) handleResponses(batch []*jsonrpcMessage, handleCall func(*json
// the op.resp channel. // the op.resp channel.
if op.sub != nil { if op.sub != nil {
if msg.Error != nil { if msg.Error != nil {
op.err = msg.Error op.err = msg.jsonErr()
} else { } else {
op.err = json.Unmarshal(msg.Result, &op.sub.subid) op.err = json.Unmarshal(msg.Result, &op.sub.subid)
if op.err == nil { if op.err == nil {
@ -481,9 +481,10 @@ func (h *handler) handleCallMsg(ctx *callProc, msg *jsonrpcMessage) *jsonrpcMess
var logctx []any var logctx []any
logctx = append(logctx, "reqid", idForLog{msg.ID}, "duration", time.Since(start)) logctx = append(logctx, "reqid", idForLog{msg.ID}, "duration", time.Since(start))
if resp.Error != nil { if resp.Error != nil {
logctx = append(logctx, "err", resp.Error.Message) je := resp.jsonErr()
if resp.Error.Data != nil { logctx = append(logctx, "err", je.Message)
logctx = append(logctx, "errdata", formatErrorData(resp.Error.Data)) if je.Data != nil {
logctx = append(logctx, "errdata", formatErrorData(je.Data))
} }
h.log.Warn("Served "+msg.Method, logctx...) h.log.Warn("Served "+msg.Method, logctx...)
} else { } else {
@ -550,7 +551,7 @@ func (h *handler) handleCall(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage
answer := h.runMethod(rctx, msg, callb, args) answer := h.runMethod(rctx, msg, callb, args)
var rErr error var rErr error
if answer.Error != nil { if answer.Error != nil {
rErr = errors.New(answer.Error.Message) rErr = errors.New(answer.jsonErr().Message)
} }
rSpanEnd(&rErr) rSpanEnd(&rErr)
@ -623,7 +624,7 @@ func (h *handler) runMethod(ctx context.Context, msg *jsonrpcMessage, callb *cal
_, _, spanEnd := telemetry.StartSpanWithTracer(ctx, h.tracer(), "rpc.encodeJSONResponse", attributes...) _, _, spanEnd := telemetry.StartSpanWithTracer(ctx, h.tracer(), "rpc.encodeJSONResponse", attributes...)
response := msg.response(result) response := msg.response(result)
if response.Error != nil { if response.Error != nil {
err = errors.New(response.Error.Message) err = errors.New(response.jsonErr().Message)
} }
spanEnd(&err) spanEnd(&err)
return response return response

View file

@ -60,7 +60,7 @@ type jsonrpcMessage struct {
ID json.RawMessage `json:"id,omitempty"` ID json.RawMessage `json:"id,omitempty"`
Method string `json:"method,omitempty"` Method string `json:"method,omitempty"`
Params json.RawMessage `json:"params,omitempty"` Params json.RawMessage `json:"params,omitempty"`
Error *jsonError `json:"error,omitempty"` Error json.RawMessage `json:"error,omitempty"`
Result json.RawMessage `json:"result,omitempty"` Result json.RawMessage `json:"result,omitempty"`
} }
@ -108,6 +108,16 @@ func (msg *jsonrpcMessage) errorResponse(err error) *jsonrpcMessage {
return resp return resp
} }
// jsonErr decodes the Error field into a jsonError struct.
func (msg *jsonrpcMessage) jsonErr() *jsonError {
if msg.Error == nil {
return nil
}
je := new(jsonError)
json.Unmarshal(msg.Error, je)
return je
}
func (msg *jsonrpcMessage) response(result interface{}) *jsonrpcMessage { func (msg *jsonrpcMessage) response(result interface{}) *jsonrpcMessage {
var ( var (
enc []byte enc []byte
@ -125,19 +135,18 @@ func (msg *jsonrpcMessage) response(result interface{}) *jsonrpcMessage {
} }
func errorMessage(err error) *jsonrpcMessage { func errorMessage(err error) *jsonrpcMessage {
msg := &jsonrpcMessage{Version: vsn, ID: null, Error: &jsonError{ je := &jsonError{
Code: errcodeDefault, Code: errcodeDefault,
Message: err.Error(), Message: err.Error(),
}}
ec, ok := err.(Error)
if ok {
msg.Error.Code = ec.ErrorCode()
} }
de, ok := err.(DataError) if ec, ok := err.(Error); ok {
if ok { je.Code = ec.ErrorCode()
msg.Error.Data = de.ErrorData()
} }
return msg if de, ok := err.(DataError); ok {
je.Data = de.ErrorData()
}
enc, _ := json.Marshal(je)
return &jsonrpcMessage{Version: vsn, ID: null, Error: enc}
} }
type jsonError struct { type jsonError struct {
@ -248,12 +257,8 @@ func writeMessage(w io.Writer, msg *jsonrpcMessage) error {
buf = append(buf, msg.Params...) buf = append(buf, msg.Params...)
} }
if msg.Error != nil { if msg.Error != nil {
errBytes, err := json.Marshal(msg.Error)
if err != nil {
return err
}
buf = append(buf, `,"error":`...) buf = append(buf, `,"error":`...)
buf = append(buf, errBytes...) buf = append(buf, msg.Error...)
} }
if msg.Result != nil { if msg.Result != nil {
buf = append(buf, `,"result":`...) buf = append(buf, `,"result":`...)

View file

@ -220,7 +220,7 @@ func readAndValidateMessage(in *json.Decoder) (*subConfirmation, *subscriptionRe
case msg.isResponse(): case msg.isResponse():
var c subConfirmation var c subConfirmation
if msg.Error != nil { if msg.Error != nil {
return nil, nil, msg.Error return nil, nil, msg.jsonErr()
} else if err := json.Unmarshal(msg.Result, &c.subid); err != nil { } else if err := json.Unmarshal(msg.Result, &c.subid); err != nil {
return nil, nil, fmt.Errorf("invalid response: %v", err) return nil, nil, fmt.Errorf("invalid response: %v", err)
} else { } else {