-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f5cb0c5
commit b1407ec
Showing
2 changed files
with
208 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package cachemdw | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
) | ||
|
||
type JsonRpcError struct { | ||
Code int `json:"code"` | ||
Message string `json:"message"` | ||
} | ||
|
||
// String returns the string representation of the error | ||
func (e *JsonRpcError) String() string { | ||
return fmt.Sprintf("%s (code: %d)", e.Message, e.Code) | ||
} | ||
|
||
// JsonRpcResponse is a EVM JSON-RPC response | ||
type JsonRpcResponse struct { | ||
Version string `json:"jsonrpc,omitempty"` | ||
ID json.RawMessage `json:"id,omitempty"` | ||
Result json.RawMessage `json:"result,omitempty"` | ||
JsonRpcError *JsonRpcError `json:"error,omitempty"` | ||
} | ||
|
||
// UnmarshalJsonRpcResponse unmarshals a JSON-RPC response | ||
func UnmarshalJsonRpcResponse(data []byte) (*JsonRpcResponse, error) { | ||
var msg JsonRpcResponse | ||
err := json.Unmarshal(data, &msg) | ||
return &msg, err | ||
} | ||
|
||
// Marshal marshals a JSON-RPC response to JSON | ||
func (resp *JsonRpcResponse) Marshal() ([]byte, error) { | ||
return json.Marshal(resp) | ||
} | ||
|
||
// Error returns the json-rpc error if any | ||
func (resp *JsonRpcResponse) Error() error { | ||
if resp.JsonRpcError == nil { | ||
return nil | ||
} | ||
|
||
return errors.New(resp.JsonRpcError.String()) | ||
} | ||
|
||
// IsResultEmpty checks if the response's result is empty | ||
func (resp *JsonRpcResponse) IsResultEmpty() bool { | ||
if len(resp.Result) == 0 { | ||
// empty response's result | ||
return true | ||
} | ||
|
||
var result interface{} | ||
err := json.Unmarshal(resp.Result, &result) | ||
if err != nil { | ||
// consider result as empty if it's malformed | ||
return true | ||
} | ||
|
||
switch r := result.(type) { | ||
case []interface{}: | ||
// consider result as empty if it's empty slice | ||
return len(r) == 0 | ||
case string: | ||
// Matches: | ||
// - "" - Empty string | ||
// - "0x0" - Represents zero in official json-rpc conventions. See: | ||
// https://ethereum.org/en/developers/docs/apis/json-rpc/#conventions | ||
// | ||
// - "0x" - Empty response from some endpoints like getCode | ||
|
||
return r == "" || r == "0x0" || r == "0x" | ||
case bool: | ||
// consider result as empty if it's false | ||
return !r | ||
case nil: | ||
// consider result as empty if it's null | ||
return true | ||
default: | ||
return false | ||
} | ||
} | ||
|
||
// IsCacheable returns true in case of: | ||
// - json-rpc response doesn't contain an error | ||
// - json-rpc response's result isn't empty | ||
func (resp *JsonRpcResponse) IsCacheable() bool { | ||
if err := resp.Error(); err != nil { | ||
return false | ||
} | ||
|
||
if resp.IsResultEmpty() { | ||
return false | ||
} | ||
|
||
return true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package cachemdw_test | ||
|
||
import ( | ||
"encoding/json" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/kava-labs/kava-proxy-service/service/cachemdw" | ||
) | ||
|
||
func TestUnitTestJsonRpcResponse_IsEmpty(t *testing.T) { | ||
toJSON := func(t *testing.T, result any) []byte { | ||
resultInJSON, err := json.Marshal(result) | ||
require.NoError(t, err) | ||
|
||
return resultInJSON | ||
} | ||
|
||
mkResp := func(result []byte) *cachemdw.JsonRpcResponse { | ||
return &cachemdw.JsonRpcResponse{ | ||
Version: "2.0", | ||
ID: []byte("1"), | ||
Result: result, | ||
} | ||
} | ||
|
||
tests := []struct { | ||
name string | ||
resp *cachemdw.JsonRpcResponse | ||
isEmpty bool | ||
}{ | ||
{ | ||
name: "empty result", | ||
resp: mkResp([]byte("")), | ||
isEmpty: true, | ||
}, | ||
{ | ||
name: "invalid json", | ||
resp: mkResp([]byte("invalid json")), | ||
isEmpty: true, | ||
}, | ||
{ | ||
name: "empty slice", | ||
resp: mkResp(toJSON(t, []interface{}{})), | ||
isEmpty: true, | ||
}, | ||
{ | ||
name: "empty string", | ||
resp: mkResp(toJSON(t, "")), | ||
isEmpty: true, | ||
}, | ||
{ | ||
name: "0x0 string", | ||
resp: mkResp(toJSON(t, "0x0")), | ||
isEmpty: true, | ||
}, | ||
{ | ||
name: "0x string", | ||
resp: mkResp(toJSON(t, "0x")), | ||
isEmpty: true, | ||
}, | ||
{ | ||
name: "empty bool", | ||
resp: mkResp(toJSON(t, false)), | ||
isEmpty: true, | ||
}, | ||
{ | ||
name: "nil", | ||
resp: mkResp(nil), | ||
isEmpty: true, | ||
}, | ||
{ | ||
name: "null", | ||
resp: mkResp(toJSON(t, nil)), | ||
isEmpty: true, | ||
}, | ||
{ | ||
name: "non-empty slice", | ||
resp: mkResp(toJSON(t, []interface{}{1})), | ||
isEmpty: false, | ||
}, | ||
{ | ||
name: "non-empty string", | ||
resp: mkResp(toJSON(t, "0x1234")), | ||
isEmpty: false, | ||
}, | ||
{ | ||
name: "non-empty bool", | ||
resp: mkResp(toJSON(t, true)), | ||
isEmpty: false, | ||
}, | ||
{ | ||
name: "unsupported empty object", | ||
resp: mkResp(toJSON(t, map[string]interface{}{})), | ||
isEmpty: false, | ||
}, | ||
} | ||
|
||
for _, tc := range tests { | ||
t.Run(tc.name, func(t *testing.T) { | ||
require.Equal( | ||
t, | ||
tc.isEmpty, | ||
tc.resp.IsResultEmpty(), | ||
) | ||
}) | ||
} | ||
} |