Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add 'Data' field to 'respError' struct #123

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion client.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ type clientResponse struct {
Jsonrpc string `json:"jsonrpc"`
Result json.RawMessage `json:"result"`
ID interface{} `json:"id"`
Error *respError `json:"error,omitempty"`
Error *JSONRPCError `json:"error,omitempty"`
}

type makeChanSink func() (context.Context, func([]byte, bool))
Expand Down
5 changes: 5 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,8 @@ type marshalable interface {
json.Marshaler
json.Unmarshaler
}

type RPCErrorCodec interface {
FromJSONRPCError(JSONRPCError) error
ToJSONRPCError() (JSONRPCError, error)
}
99 changes: 28 additions & 71 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,71 +65,6 @@ type request struct {
// Configured by WithMaxRequestSize.
const DEFAULT_MAX_REQUEST_SIZE = 100 << 20 // 100 MiB

type respError struct {
Code ErrorCode `json:"code"`
Message string `json:"message"`
Meta json.RawMessage `json:"meta,omitempty"`
}

func (e *respError) Error() string {
if e.Code >= -32768 && e.Code <= -32000 {
return fmt.Sprintf("RPC error (%d): %s", e.Code, e.Message)
}
return e.Message
}

var marshalableRT = reflect.TypeOf(new(marshalable)).Elem()

func (e *respError) val(errors *Errors) reflect.Value {
if errors != nil {
t, ok := errors.byCode[e.Code]
if ok {
var v reflect.Value
if t.Kind() == reflect.Ptr {
v = reflect.New(t.Elem())
} else {
v = reflect.New(t)
}
if len(e.Meta) > 0 && v.Type().Implements(marshalableRT) {
_ = v.Interface().(marshalable).UnmarshalJSON(e.Meta)
}
if t.Kind() != reflect.Ptr {
v = v.Elem()
}
return v
}
}

return reflect.ValueOf(e)
}

type response struct {
Jsonrpc string
Result interface{}
ID interface{}
Error *respError
}

func (r response) MarshalJSON() ([]byte, error) {
// Custom marshal logic as per JSON-RPC 2.0 spec:
// > `result`:
// > This member is REQUIRED on success.
// > This member MUST NOT exist if there was an error invoking the method.
//
// > `error`:
// > This member is REQUIRED on error.
// > This member MUST NOT exist if there was no error triggered during invocation.
data := make(map[string]interface{})
data["jsonrpc"] = r.Jsonrpc
data["id"] = r.ID
if r.Error != nil {
data["error"] = r.Error
} else {
data["result"] = r.Result
}
return json.Marshal(data)
}

type handler struct {
methods map[string]methodHandler
errors *Errors
Expand Down Expand Up @@ -334,7 +269,7 @@ func (s *handler) getSpan(ctx context.Context, req request) (context.Context, *t
return ctx, span
}

func (s *handler) createError(err error) *respError {
func (s *handler) createError(err error) *JSONRPCError {
var code ErrorCode = 1
if s.errors != nil {
c, ok := s.errors.byType[reflect.TypeOf(err)]
Expand All @@ -343,15 +278,25 @@ func (s *handler) createError(err error) *respError {
}
}

out := &respError{
out := &JSONRPCError{
Code: code,
Message: err.Error(),
}

if m, ok := err.(marshalable); ok {
meta, err := m.MarshalJSON()
if err == nil {
switch m := err.(type) {
case RPCErrorCodec:
o, err := m.ToJSONRPCError()
if err != nil {
log.Warnf("Failed to convert error to JSONRPCError: %v", err)
} else {
out = &o
}
case marshalable:
meta, marshalErr := m.MarshalJSON()
if marshalErr == nil {
out.Meta = meta
} else {
log.Warnf("Failed to marshal error metadata: %v", marshalErr)
}
}

Expand Down Expand Up @@ -504,10 +449,22 @@ func (s *handler) handle(ctx context.Context, req request, w func(func(io.Writer

log.Warnf("failed to setup channel in RPC call to '%s': %+v", req.Method, err)
stats.Record(ctx, metrics.RPCResponseError.M(1))
resp.Error = &respError{

respErr := &JSONRPCError{
Code: 1,
Message: err.Error(),
}

if m, ok := err.(RPCErrorCodec); ok {
rpcErr, err := m.ToJSONRPCError()
if err != nil {
log.Warnf("Failed to convert error to JSONRPCError: %v", err)
} else {
respErr.Data = rpcErr.Data
}
}

resp.Error = respErr
} else {
resp.Result = res
}
Expand Down
Loading