package jsonrpc import ( "encoding/json" "fmt" "testing" "github.com/stretchr/testify/require" ) type ComplexData struct { Foo string `json:"foo"` Bar int `json:"bar"` } type StaticError struct{} func (e *StaticError) Error() string { return "static error" } // Define the error types type SimpleError struct { Message string } func (e *SimpleError) Error() string { return e.Message } func (e *SimpleError) FromJSONRPCError(jerr JSONRPCError) error { e.Message = jerr.Message return nil } func (e *SimpleError) ToJSONRPCError() (JSONRPCError, error) { return JSONRPCError{Message: e.Message}, nil } var _ RPCErrorCodec = (*SimpleError)(nil) type DataStringError struct { Message string `json:"message"` Data string `json:"data"` } func (e *DataStringError) Error() string { return e.Message } func (e *DataStringError) FromJSONRPCError(jerr JSONRPCError) error { e.Message = jerr.Message data, ok := jerr.Data.(string) if !ok { return fmt.Errorf("expected string data, got %T", jerr.Data) } e.Data = data return nil } func (e *DataStringError) ToJSONRPCError() (JSONRPCError, error) { return JSONRPCError{Message: e.Message, Data: e.Data}, nil } var _ RPCErrorCodec = (*DataStringError)(nil) type DataComplexError struct { Message string internalData ComplexData } func (e *DataComplexError) Error() string { return e.Message } func (e *DataComplexError) FromJSONRPCError(jerr JSONRPCError) error { e.Message = jerr.Message data, ok := jerr.Data.(json.RawMessage) if !ok { return fmt.Errorf("expected string data, got %T", jerr.Data) } if err := json.Unmarshal(data, &e.internalData); err != nil { return err } return nil } func (e *DataComplexError) ToJSONRPCError() (JSONRPCError, error) { data, err := json.Marshal(e.internalData) if err != nil { return JSONRPCError{}, err } return JSONRPCError{Message: e.Message, Data: data}, nil } var _ RPCErrorCodec = (*DataComplexError)(nil) type MetaError struct { Message string Details string } func (e *MetaError) Error() string { return e.Message } func (e *MetaError) MarshalJSON() ([]byte, error) { return json.Marshal(struct { Message string `json:"message"` Details string `json:"details"` }{ Message: e.Message, Details: e.Details, }) } func (e *MetaError) UnmarshalJSON(data []byte) error { var temp struct { Message string `json:"message"` Details string `json:"details"` } if err := json.Unmarshal(data, &temp); err != nil { return err } e.Message = temp.Message e.Details = temp.Details return nil } type ComplexError struct { Message string Data ComplexData Details string } func (e *ComplexError) Error() string { return e.Message } func (e *ComplexError) MarshalJSON() ([]byte, error) { return json.Marshal(struct { Message string `json:"message"` Details string `json:"details"` Data any `json:"data"` }{ Details: e.Details, Message: e.Message, Data: e.Data, }) } func (e *ComplexError) UnmarshalJSON(data []byte) error { var temp struct { Message string `json:"message"` Details string `json:"details"` Data ComplexData `json:"data"` } if err := json.Unmarshal(data, &temp); err != nil { return err } e.Details = temp.Details e.Message = temp.Message e.Data = temp.Data return nil } func TestRespErrorVal(t *testing.T) { // Initialize the Errors struct and register error types errorsMap := NewErrors() errorsMap.Register(1000, new(*StaticError)) errorsMap.Register(1001, new(*SimpleError)) errorsMap.Register(1002, new(*DataStringError)) errorsMap.Register(1003, new(*DataComplexError)) errorsMap.Register(1004, new(*MetaError)) errorsMap.Register(1005, new(*ComplexError)) // Define test cases testCases := []struct { name string respError *JSONRPCError expectedType interface{} expectedMessage string verify func(t *testing.T, err error) }{ { name: "StaticError", respError: &JSONRPCError{ Code: 1000, Message: "this is ignored", }, expectedType: &StaticError{}, expectedMessage: "static error", }, { name: "SimpleError", respError: &JSONRPCError{ Code: 1001, Message: "simple error occurred", }, expectedType: &SimpleError{}, expectedMessage: "simple error occurred", }, { name: "DataStringError", respError: &JSONRPCError{ Code: 1002, Message: "data error occurred", Data: "additional data", }, expectedType: &DataStringError{}, expectedMessage: "data error occurred", verify: func(t *testing.T, err error) { require.IsType(t, &DataStringError{}, err) require.Equal(t, "data error occurred", err.Error()) require.Equal(t, "additional data", err.(*DataStringError).Data) }, }, { name: "DataComplexError", respError: &JSONRPCError{ Code: 1003, Message: "data error occurred", Data: json.RawMessage(`{"foo":"boop","bar":101}`), }, expectedType: &DataComplexError{}, expectedMessage: "data error occurred", verify: func(t *testing.T, err error) { require.Equal(t, ComplexData{Foo: "boop", Bar: 101}, err.(*DataComplexError).internalData) }, }, { name: "MetaError", respError: &JSONRPCError{ Code: 1004, Message: "meta error occurred", Meta: func() json.RawMessage { me := &MetaError{ Message: "meta error occurred", Details: "meta details", } metaData, _ := me.MarshalJSON() return metaData }(), }, expectedType: &MetaError{}, expectedMessage: "meta error occurred", verify: func(t *testing.T, err error) { // details will also be included in the error message since it implements the marshable interface require.Equal(t, "meta details", err.(*MetaError).Details) }, }, { name: "ComplexError", respError: &JSONRPCError{ Code: 1005, Message: "complex error occurred", Data: json.RawMessage(`"complex data"`), Meta: func() json.RawMessage { ce := &ComplexError{ Message: "complex error occurred", Details: "complex details", Data: ComplexData{Foo: "foo", Bar: 42}, } metaData, _ := ce.MarshalJSON() return metaData }(), }, expectedType: &ComplexError{}, expectedMessage: "complex error occurred", verify: func(t *testing.T, err error) { require.Equal(t, ComplexData{Foo: "foo", Bar: 42}, err.(*ComplexError).Data) require.Equal(t, "complex details", err.(*ComplexError).Details) }, }, { name: "UnregisteredError", respError: &JSONRPCError{ Code: 9999, Message: "unregistered error occurred", Data: json.RawMessage(`"some data"`), }, expectedType: &JSONRPCError{}, expectedMessage: "unregistered error occurred", verify: func(t *testing.T, err error) { require.Equal(t, json.RawMessage(`"some data"`), err.(*JSONRPCError).Data) }, }, } for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { errValue := tc.respError.val(&errorsMap) errInterface := errValue.Interface() err, ok := errInterface.(error) require.True(t, ok, "returned value does not implement error interface") require.IsType(t, tc.expectedType, err) require.Equal(t, tc.expectedMessage, err.Error()) if tc.verify != nil { tc.verify(t, err) } }) } }