diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 842585f..f921225 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -11,32 +11,24 @@ on: jobs: lint: name: Lint - runs-on: ubuntu-latest + runs-on: ubuntu-18.04 steps: - - name: Check out code - uses: actions/checkout@v2 - - name: Download golangci-lint command - run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.21.0 - - name: Lint Go Code - run: PATH=$PATH:$(go env GOPATH)/bin golangci-lint run ./... + - uses: actions/checkout@v3 + - uses: golangci/golangci-lint-action@v3 + with: + version: v1.49.0 test: name: Test - runs-on: ubuntu-latest + runs-on: ubuntu-18.04 strategy: matrix: - go: [ '1.15.x', '1.16.x' ] + go: [ '1.18.x', '1.19.x' ] steps: - - name: Set up Go - uses: actions/setup-go@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-go@v2 with: go-version: ${{ matrix.go }} - - name: Check out code - uses: actions/checkout@v2 - - name: Test Go Code - run: PATH=$PATH:$(go env GOPATH)/bin go test -covermode=atomic -coverprofile=coverage.txt ./... - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 - with: - token: ${{ secrets.CODECOV_TOKEN }} - file: coverage.txt - yml: codecov.yml + - run: go test -race -covermode=atomic -coverprofile=coverage.txt ./... + env: + KENALL_AUTHORIZATION_TOKEN: ${{ secrets.KENALL_AUTHORIZATION_TOKEN }} + - uses: codecov/codecov-action@v2 diff --git a/.golangci.yml b/.golangci.yml index 268677a..72c4bb2 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -8,12 +8,19 @@ linters-settings: linters: enable-all: true disable: - - whitespace + - varnamelen - wsl + - exhaustruct + - exhaustivestruct issues: exclude-rules: + - path: _test\.go + text: "does not use range value in test Run" + linters: + - paralleltest - path: _test\.go linters: - lll - funlen + - dupword diff --git a/README.md b/README.md index 3d57882..b8589e9 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ ## Install ``` -$ go get -u github.com/osamingo/jsonrpc/v2 +$ go get github.com/osamingo/jsonrpc/v2@latest ``` ## Usage @@ -49,7 +49,7 @@ type ( } ) -func (h EchoHandler) ServeJSONRPC(c context.Context, params *json.RawMessage) (interface{}, *jsonrpc.Error) { +func (h EchoHandler) ServeJSONRPC(c context.Context, params *json.RawMessage) (any, *jsonrpc.Error) { var p EchoParams if err := jsonrpc.Unmarshal(params, &p); err != nil { @@ -61,7 +61,7 @@ func (h EchoHandler) ServeJSONRPC(c context.Context, params *json.RawMessage) (i }, nil } -func (h PositionalHandler) ServeJSONRPC(c context.Context, params *json.RawMessage) (interface{}, **jsonrpc.Error) { +func (h PositionalHandler) ServeJSONRPC(c context.Context, params *json.RawMessage) (any, **jsonrpc.Error) { var p PositionalParams if err := jsonrpc.Unmarshal(params, &p); err != nil { @@ -110,8 +110,8 @@ type ( HandleParamsResulter interface { jsonrpc.Handler Name() string - Params() interface{} - Result() interface{} + Params() any + Result() any } Servicer interface { MethodName(HandleParamsResulter) string diff --git a/context.go b/context.go index fc09944..c0fefa1 100644 --- a/context.go +++ b/context.go @@ -14,7 +14,9 @@ type ( // RequestID takes request id from context. func RequestID(c context.Context) *json.RawMessage { - return c.Value(requestIDKey{}).(*json.RawMessage) + v, _ := c.Value(requestIDKey{}).(*json.RawMessage) + + return v } // WithRequestID adds request id to context. @@ -24,7 +26,9 @@ func WithRequestID(c context.Context, id *json.RawMessage) context.Context { // GetMetadata takes jsonrpc metadata from context. func GetMetadata(c context.Context) Metadata { - return c.Value(metadataIDKey{}).(Metadata) + v, _ := c.Value(metadataIDKey{}).(Metadata) + + return v } // WithMetadata adds jsonrpc metadata to context. @@ -34,7 +38,9 @@ func WithMetadata(c context.Context, md Metadata) context.Context { // MethodName takes method name from context. func MethodName(c context.Context) string { - return c.Value(methodNameKey{}).(string) + v, _ := c.Value(methodNameKey{}).(string) + + return v } // WithMethodName adds method name to context. diff --git a/context_test.go b/context_test.go index cb7c887..a106f9d 100644 --- a/context_test.go +++ b/context_test.go @@ -1,41 +1,48 @@ -package jsonrpc +package jsonrpc_test import ( "context" "testing" "github.com/goccy/go-json" + "github.com/osamingo/jsonrpc/v2" "github.com/stretchr/testify/require" ) func TestRequestID(t *testing.T) { + t.Parallel() + c := context.Background() id := json.RawMessage("1") - c = WithRequestID(c, &id) + c = jsonrpc.WithRequestID(c, &id) var pick *json.RawMessage require.NotPanics(t, func() { - pick = RequestID(c) + pick = jsonrpc.RequestID(c) }) require.Equal(t, &id, pick) } func TestMetadata(t *testing.T) { + t.Parallel() + c := context.Background() - md := Metadata{Params: Metadata{}} - c = WithMetadata(c, md) - var pick Metadata + md := jsonrpc.Metadata{Params: jsonrpc.Metadata{}} + c = jsonrpc.WithMetadata(c, md) + var pick jsonrpc.Metadata require.NotPanics(t, func() { - pick = GetMetadata(c) + pick = jsonrpc.GetMetadata(c) }) require.Equal(t, md, pick) } func TestMethodName(t *testing.T) { + t.Parallel() + c := context.Background() - c = WithMethodName(c, t.Name()) + c = jsonrpc.WithMethodName(c, t.Name()) var pick string require.NotPanics(t, func() { - pick = MethodName(c) + pick = jsonrpc.MethodName(c) }) require.Equal(t, t.Name(), pick) } diff --git a/debug.go b/debug.go index 0450ce8..d507404 100644 --- a/debug.go +++ b/debug.go @@ -4,8 +4,8 @@ import ( "net/http" "reflect" - "github.com/alecthomas/jsonschema" "github.com/goccy/go-json" + "github.com/invopop/jsonschema" ) // A MethodReference is a reference of JSON-RPC method. @@ -17,10 +17,11 @@ type MethodReference struct { } // ServeDebug views registered method list. -func (mr *MethodRepository) ServeDebug(w http.ResponseWriter, r *http.Request) { // nolint: unparam +func (mr *MethodRepository) ServeDebug(w http.ResponseWriter, r *http.Request) { ms := mr.Methods() if len(ms) == 0 { w.WriteHeader(http.StatusNotFound) + return } l := make([]*MethodReference, 0, len(ms)) @@ -28,8 +29,9 @@ func (mr *MethodRepository) ServeDebug(w http.ResponseWriter, r *http.Request) { l = append(l, makeMethodReference(k, md)) } w.Header().Set(contentTypeKey, contentTypeValue) - if err := json.NewEncoder(w).Encode(l); err != nil { + if err := json.NewEncoder(w).EncodeContext(r.Context(), l); err != nil { w.WriteHeader(http.StatusInternalServerError) + return } } @@ -49,5 +51,6 @@ func makeMethodReference(k string, md Metadata) *MethodReference { if md.Result != nil { mr.Result = jsonschema.Reflect(md.Result) } + return mr } diff --git a/debug_test.go b/debug_test.go index f386e49..61930eb 100644 --- a/debug_test.go +++ b/debug_test.go @@ -1,19 +1,23 @@ -package jsonrpc +package jsonrpc_test import ( + "context" "net/http" "net/http/httptest" "testing" + "github.com/osamingo/jsonrpc/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestDebugHandler(t *testing.T) { - mr := NewMethodRepository() + t.Parallel() + + mr := jsonrpc.NewMethodRepository() rec := httptest.NewRecorder() - r, err := http.NewRequest("", "", nil) + r, err := http.NewRequestWithContext(context.Background(), "", "", nil) require.NoError(t, err) mr.ServeDebug(rec, r) @@ -27,7 +31,7 @@ func TestDebugHandler(t *testing.T) { }{})) rec = httptest.NewRecorder() - r, err = http.NewRequest("", "", nil) + r, err = http.NewRequestWithContext(context.Background(), "", "", nil) require.NoError(t, err) mr.ServeDebug(rec, r) diff --git a/error.go b/error.go index dad717d..b546e6a 100644 --- a/error.go +++ b/error.go @@ -21,9 +21,9 @@ type ( // An Error is a wrapper for a JSON interface value. Error struct { - Code ErrorCode `json:"code"` - Message string `json:"message"` - Data interface{} `json:"data,omitempty"` + Code ErrorCode `json:"code"` + Message string `json:"message"` + Data any `json:"data,omitempty"` } ) diff --git a/error_test.go b/error_test.go index 8593239..427745e 100644 --- a/error_test.go +++ b/error_test.go @@ -1,21 +1,26 @@ -package jsonrpc +package jsonrpc_test import ( "testing" + "github.com/osamingo/jsonrpc/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestError(t *testing.T) { - var err interface{} = &Error{} + t.Parallel() + + var err any = &jsonrpc.Error{} _, ok := err.(error) require.True(t, ok) } func TestError_Error(t *testing.T) { - err := &Error{ - Code: ErrorCode(100), + t.Parallel() + + err := &jsonrpc.Error{ + Code: jsonrpc.ErrorCode(100), Message: "test", Data: map[string]string{ "test": "test", @@ -26,26 +31,36 @@ func TestError_Error(t *testing.T) { } func TestErrParse(t *testing.T) { - err := ErrParse() - require.Equal(t, ErrorCodeParse, err.Code) + t.Parallel() + + err := jsonrpc.ErrParse() + require.Equal(t, jsonrpc.ErrorCodeParse, err.Code) } func TestErrInvalidRequest(t *testing.T) { - err := ErrInvalidRequest() - require.Equal(t, ErrorCodeInvalidRequest, err.Code) + t.Parallel() + + err := jsonrpc.ErrInvalidRequest() + require.Equal(t, jsonrpc.ErrorCodeInvalidRequest, err.Code) } func TestErrMethodNotFound(t *testing.T) { - err := ErrMethodNotFound() - require.Equal(t, ErrorCodeMethodNotFound, err.Code) + t.Parallel() + + err := jsonrpc.ErrMethodNotFound() + require.Equal(t, jsonrpc.ErrorCodeMethodNotFound, err.Code) } func TestErrInvalidParams(t *testing.T) { - err := ErrInvalidParams() - require.Equal(t, ErrorCodeInvalidParams, err.Code) + t.Parallel() + + err := jsonrpc.ErrInvalidParams() + require.Equal(t, jsonrpc.ErrorCodeInvalidParams, err.Code) } func TestErrInternal(t *testing.T) { - err := ErrInternal() - require.Equal(t, ErrorCodeInternal, err.Code) + t.Parallel() + + err := jsonrpc.ErrInternal() + require.Equal(t, jsonrpc.ErrorCodeInternal, err.Code) } diff --git a/example_test.go b/example_test.go index fd338b0..996a4dd 100644 --- a/example_test.go +++ b/example_test.go @@ -1,4 +1,4 @@ -package jsonrpc +package jsonrpc_test import ( "bytes" @@ -10,6 +10,7 @@ import ( "os" "github.com/goccy/go-json" + "github.com/osamingo/jsonrpc/v2" ) type ( @@ -28,9 +29,9 @@ type ( } ) -func (h EchoHandler) ServeJSONRPC(c context.Context, params *json.RawMessage) (interface{}, *Error) { +func (h EchoHandler) ServeJSONRPC(c context.Context, params *json.RawMessage) (interface{}, *jsonrpc.Error) { var p EchoParams - if err := Unmarshal(params, &p); err != nil { + if err := jsonrpc.Unmarshal(params, &p); err != nil { return nil, err } @@ -39,9 +40,9 @@ func (h EchoHandler) ServeJSONRPC(c context.Context, params *json.RawMessage) (i }, nil } -func (h PositionalHandler) ServeJSONRPC(c context.Context, params *json.RawMessage) (interface{}, *Error) { +func (h PositionalHandler) ServeJSONRPC(c context.Context, params *json.RawMessage) (interface{}, *jsonrpc.Error) { var p PositionalParams - if err := Unmarshal(params, &p); err != nil { + if err := jsonrpc.Unmarshal(params, &p); err != nil { return nil, err } @@ -50,15 +51,19 @@ func (h PositionalHandler) ServeJSONRPC(c context.Context, params *json.RawMessa }, nil } -func ExampleEchoHandler_ServeJSONRPC() { - mr := NewMethodRepository() +func ExampleMethodRepository_ServeHTTP() { //nolint: nosnakecase + mr := jsonrpc.NewMethodRepository() if err := mr.RegisterMethod("Main.Echo", EchoHandler{}, EchoParams{}, EchoResult{}); err != nil { - log.Fatalln(err) + log.Println(err) + + return } if err := mr.RegisterMethod("Main.Positional", PositionalHandler{}, PositionalParams{}, PositionalResult{}); err != nil { - log.Fatalln(err) + log.Println(err) + + return } http.Handle("/jrpc", mr) @@ -67,34 +72,63 @@ func ExampleEchoHandler_ServeJSONRPC() { srv := httptest.NewServer(http.DefaultServeMux) defer srv.Close() - resp, err := http.Post(srv.URL+"/jrpc", "application/json", bytes.NewBufferString(`{ + contextType := "application/json" + echoVal := `{ "jsonrpc": "2.0", "method": "Main.Echo", "params": { "name": "John Doe" }, "id": "243a718a-2ebb-4e32-8cc8-210c39e8a14b" - }`)) + }` + + req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, srv.URL+"/jrpc", bytes.NewBufferString(echoVal)) + if err != nil { + log.Println(err) + + return + } + + req.Header.Add("Content-Type", contextType) + resp, err := http.DefaultClient.Do(req) if err != nil { - log.Fatalln(err) + log.Println(err) + + return } defer resp.Body.Close() if _, err := io.Copy(os.Stdout, resp.Body); err != nil { - log.Fatalln(err) + log.Println(err) + + return } - resp, err = http.Post(srv.URL+"/jrpc", "application/json", bytes.NewBufferString(`{ + positionalVal := `{ "jsonrpc": "2.0", "method": "Main.Positional", "params": [3, 1, 1, 3, 5, 3], "id": "243a718a-2ebb-4e32-8cc8-210c39e8a14b" - }`)) + }` + + req, err = http.NewRequestWithContext(context.Background(), http.MethodPost, srv.URL+"/jrpc", bytes.NewBufferString(positionalVal)) + if err != nil { + log.Println(err) + + return + } + + req.Header.Add("Content-Type", contextType) + resp, err = http.DefaultClient.Do(req) if err != nil { - log.Fatalln(err) + log.Println(err) + + return } defer resp.Body.Close() if _, err := io.Copy(os.Stdout, resp.Body); err != nil { - log.Fatalln(err) + log.Println(err) + + return } // Output: diff --git a/go.mod b/go.mod index 6711825..d9870ba 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,16 @@ module github.com/osamingo/jsonrpc/v2 -go 1.15 +go 1.18 + +require ( + github.com/goccy/go-json v0.9.11 + github.com/invopop/jsonschema v0.6.0 + github.com/stretchr/testify v1.8.0 +) require ( - github.com/alecthomas/jsonschema v0.0.0-20201129101101-7b852d451add github.com/davecgh/go-spew v1.1.1 // indirect - github.com/goccy/go-json v0.4.11 github.com/iancoleman/orderedmap v0.1.0 // indirect - github.com/stretchr/testify v1.7.0 - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 961fc83..ef25bfe 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,23 @@ -github.com/alecthomas/jsonschema v0.0.0-20201129101101-7b852d451add h1:UWj2AVW7oygpatGgJRy6rvNBcl+J7u8vl6qTV+XNWzA= -github.com/alecthomas/jsonschema v0.0.0-20201129101101-7b852d451add/go.mod h1:/n6+1/DWPltRLWL/VKyUxg6tzsl5kHUCcraimt4vr60= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/goccy/go-json v0.4.11 h1:92nyX606ZN/cUFwctfxwDWm8YWSA38Zlv9s7taFeLyo= -github.com/goccy/go-json v0.4.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/iancoleman/orderedmap v0.1.0 h1:2orAxZBJsvimgEBmMWfXaFlzSG2fbQil5qzP3F6cCkg= github.com/iancoleman/orderedmap v0.1.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= +github.com/invopop/jsonschema v0.6.0 h1:8e+xY8ZEn8gDHUYylSlLHy22P+SLeIRIHv3nM3hCbmY= +github.com/invopop/jsonschema v0.6.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/handler.go b/handler.go index c748e74..3903f15 100644 --- a/handler.go +++ b/handler.go @@ -10,17 +10,17 @@ import ( // Handler links a method of JSON-RPC request. type Handler interface { - ServeJSONRPC(c context.Context, params *json.RawMessage) (result interface{}, err *Error) + ServeJSONRPC(c context.Context, params *json.RawMessage) (result any, err *Error) } // HandlerFunc type is an adapter to allow the use of // ordinary functions as JSONRPC handlers. If f is a function // with the appropriate signature, HandlerFunc(f) is a // jsonrpc.Handler that calls f. -type HandlerFunc func(c context.Context, params *json.RawMessage) (result interface{}, err *Error) +type HandlerFunc func(c context.Context, params *json.RawMessage) (result any, err *Error) // ServeJSONRPC calls f(w, r). -func (f HandlerFunc) ServeJSONRPC(c context.Context, params *json.RawMessage) (result interface{}, err *Error) { +func (f HandlerFunc) ServeJSONRPC(c context.Context, params *json.RawMessage) (any, *Error) { return f(c, params) } @@ -28,6 +28,7 @@ func (f HandlerFunc) ServeJSONRPC(c context.Context, params *json.RawMessage) (r func (mr *MethodRepository) ServeHTTP(w http.ResponseWriter, r *http.Request) { rs, batch, err := ParseRequest(r) if err != nil { + //nolint: contextcheck err := SendResponse(w, []*Response{ { Version: Version, @@ -38,6 +39,7 @@ func (mr *MethodRepository) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Failed to encode error objects") w.WriteHeader(http.StatusInternalServerError) } + return } @@ -46,6 +48,7 @@ func (mr *MethodRepository) ServeHTTP(w http.ResponseWriter, r *http.Request) { resp[i] = mr.InvokeMethod(r.Context(), rs[i]) } + //nolint: contextcheck if err := SendResponse(w, resp, batch); err != nil { fmt.Fprint(w, "Failed to encode result objects") w.WriteHeader(http.StatusInternalServerError) @@ -68,5 +71,6 @@ func (mr *MethodRepository) InvokeMethod(c context.Context, r *Request) *Respons if res.Error != nil { res.Result = nil } + return res } diff --git a/handler_test.go b/handler_test.go index c098ad7..acf4d75 100644 --- a/handler_test.go +++ b/handler_test.go @@ -1,4 +1,4 @@ -package jsonrpc +package jsonrpc_test import ( "bytes" @@ -8,63 +8,66 @@ import ( "testing" "github.com/goccy/go-json" + "github.com/osamingo/jsonrpc/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestHandler(t *testing.T) { - mr := NewMethodRepository() + t.Parallel() + + mr := jsonrpc.NewMethodRepository() rec := httptest.NewRecorder() - r, err := http.NewRequest("", "", nil) + r, err := http.NewRequestWithContext(context.Background(), "", "", nil) require.NoError(t, err) mr.ServeHTTP(rec, r) - res := Response{} + res := jsonrpc.Response{} err = json.NewDecoder(rec.Body).Decode(&res) require.NoError(t, err) assert.NotNil(t, res.Error) rec = httptest.NewRecorder() - r, err = http.NewRequest("", "", bytes.NewReader([]byte(`{"jsonrpc":"2.0","id":"test","method":"hello","params":{}}`))) + r, err = http.NewRequestWithContext(context.Background(), "", "", bytes.NewReader([]byte(`{"jsonrpc":"2.0","id":"test","method":"hello","params":{}}`))) require.NoError(t, err) r.Header.Set("Content-Type", "application/json") mr.ServeHTTP(rec, r) - res = Response{} + res = jsonrpc.Response{} err = json.NewDecoder(rec.Body).Decode(&res) require.NoError(t, err) assert.NotNil(t, res.Error) - h1 := HandlerFunc(func(c context.Context, params *json.RawMessage) (interface{}, *Error) { + h1 := jsonrpc.HandlerFunc(func(c context.Context, params *json.RawMessage) (any, *jsonrpc.Error) { return "hello", nil }) require.NoError(t, mr.RegisterMethod("hello", h1, nil, nil)) - h2 := HandlerFunc(func(c context.Context, params *json.RawMessage) (interface{}, *Error) { - return nil, ErrInternal() + h2 := jsonrpc.HandlerFunc(func(c context.Context, params *json.RawMessage) (any, *jsonrpc.Error) { + return nil, jsonrpc.ErrInternal() }) require.NoError(t, mr.RegisterMethod("bye", h2, nil, nil)) rec = httptest.NewRecorder() - r, err = http.NewRequest("", "", bytes.NewReader([]byte(`{"jsonrpc":"2.0","id":"test","method":"hello","params":{}}`))) + r, err = http.NewRequestWithContext(context.Background(), "", "", bytes.NewReader([]byte(`{"jsonrpc":"2.0","id":"test","method":"hello","params":{}}`))) require.NoError(t, err) r.Header.Set("Content-Type", "application/json") mr.ServeHTTP(rec, r) - res = Response{} + res = jsonrpc.Response{} err = json.NewDecoder(rec.Body).Decode(&res) require.NoError(t, err) assert.Nil(t, res.Error) assert.Equal(t, "hello", res.Result) rec = httptest.NewRecorder() - r, err = http.NewRequest("", "", bytes.NewReader([]byte(`{"jsonrpc":"2.0","id":"test","method":"bye","params":{}}`))) + r, err = http.NewRequestWithContext(context.Background(), "", "", bytes.NewReader([]byte(`{"jsonrpc":"2.0","id":"test","method":"bye","params":{}}`))) require.NoError(t, err) r.Header.Set("Content-Type", "application/json") mr.ServeHTTP(rec, r) - res = Response{} + res = jsonrpc.Response{} err = json.NewDecoder(rec.Body).Decode(&res) require.NoError(t, err) assert.NotNil(t, res.Error) diff --git a/jsonrpc.go b/jsonrpc.go index d82215d..f5330b1 100644 --- a/jsonrpc.go +++ b/jsonrpc.go @@ -2,6 +2,7 @@ package jsonrpc import ( "bytes" + "fmt" "net/http" "strings" @@ -29,7 +30,7 @@ type ( // A Response represents a JSON-RPC response returned by the server. Response struct { Version string `json:"jsonrpc"` - Result interface{} `json:"result,omitempty"` + Result any `json:"result,omitempty"` Error *Error `json:"error,omitempty"` ID *json.RawMessage `json:"id,omitempty"` } @@ -72,6 +73,7 @@ func ParseRequest(r *http.Request) ([]*Request, bool, *Error) { if err := json.NewDecoder(buf).Decode(&req); err != nil { return nil, false, ErrParse() } + return append(rs, req), false, nil } @@ -94,9 +96,14 @@ func NewResponse(r *Request) *Response { func SendResponse(w http.ResponseWriter, resp []*Response, batch bool) error { w.Header().Set(contentTypeKey, contentTypeValue) if batch || len(resp) > 1 { - return json.NewEncoder(w).Encode(resp) + if err := json.NewEncoder(w).Encode(resp); err != nil { + return fmt.Errorf("jsonrpc: failed to encode: %w", err) + } } else if len(resp) == 1 { - return json.NewEncoder(w).Encode(resp[0]) + if err := json.NewEncoder(w).Encode(resp[0]); err != nil { + return fmt.Errorf("jsonrpc: failed to encode: %w", err) + } } + return nil } diff --git a/jsonrpc_test.go b/jsonrpc_test.go index d6e20cb..f3d42c0 100644 --- a/jsonrpc_test.go +++ b/jsonrpc_test.go @@ -1,83 +1,89 @@ -package jsonrpc +package jsonrpc_test import ( "bytes" + "context" "net/http" "net/http/httptest" "testing" "github.com/goccy/go-json" + "github.com/osamingo/jsonrpc/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestParseRequest(t *testing.T) { - r, rerr := http.NewRequest("", "", bytes.NewReader(nil)) + t.Parallel() + + r, rerr := http.NewRequestWithContext(context.Background(), "", "", bytes.NewReader(nil)) require.NoError(t, rerr) - _, _, err := ParseRequest(r) - require.IsType(t, &Error{}, err) - assert.Equal(t, ErrorCodeInvalidRequest, err.Code) + _, _, err := jsonrpc.ParseRequest(r) + require.IsType(t, &jsonrpc.Error{}, err) + assert.Equal(t, jsonrpc.ErrorCodeInvalidRequest, err.Code) r.Header.Set("Content-Type", "application/json") - _, _, err = ParseRequest(r) - require.IsType(t, &Error{}, err) - assert.Equal(t, ErrorCodeInvalidRequest, err.Code) + _, _, err = jsonrpc.ParseRequest(r) + require.IsType(t, &jsonrpc.Error{}, err) + assert.Equal(t, jsonrpc.ErrorCodeInvalidRequest, err.Code) - r, rerr = http.NewRequest("", "", bytes.NewReader([]byte(""))) + r, rerr = http.NewRequestWithContext(context.Background(), "", "", bytes.NewReader([]byte(""))) require.NoError(t, rerr) r.Header.Set("Content-Type", "application/json") - _, _, err = ParseRequest(r) - require.IsType(t, &Error{}, err) - assert.Equal(t, ErrorCodeInvalidRequest, err.Code) + _, _, err = jsonrpc.ParseRequest(r) + require.IsType(t, &jsonrpc.Error{}, err) + assert.Equal(t, jsonrpc.ErrorCodeInvalidRequest, err.Code) - r, rerr = http.NewRequest("", "", bytes.NewReader([]byte("test"))) + r, rerr = http.NewRequestWithContext(context.Background(), "", "", bytes.NewReader([]byte("test"))) require.NoError(t, rerr) r.Header.Set("Content-Type", "application/json") - _, _, err = ParseRequest(r) - require.IsType(t, &Error{}, err) - assert.Equal(t, ErrorCodeParse, err.Code) + _, _, err = jsonrpc.ParseRequest(r) + require.IsType(t, &jsonrpc.Error{}, err) + assert.Equal(t, jsonrpc.ErrorCodeParse, err.Code) - r, rerr = http.NewRequest("", "", bytes.NewReader([]byte("{}"))) + r, rerr = http.NewRequestWithContext(context.Background(), "", "", bytes.NewReader([]byte("{}"))) require.NoError(t, rerr) r.Header.Set("Content-Type", "application/json") - rs, batch, err := ParseRequest(r) + rs, batch, err := jsonrpc.ParseRequest(r) require.Nil(t, err) require.NotEmpty(t, rs) assert.False(t, batch) - r, rerr = http.NewRequest("", "", bytes.NewReader([]byte("["))) + r, rerr = http.NewRequestWithContext(context.Background(), "", "", bytes.NewReader([]byte("["))) require.NoError(t, rerr) r.Header.Set("Content-Type", "application/json") - _, _, err = ParseRequest(r) - require.IsType(t, &Error{}, err) - assert.Equal(t, ErrorCodeParse, err.Code) + _, _, err = jsonrpc.ParseRequest(r) + require.IsType(t, &jsonrpc.Error{}, err) + assert.Equal(t, jsonrpc.ErrorCodeParse, err.Code) - r, rerr = http.NewRequest("", "", bytes.NewReader([]byte("[test]"))) + r, rerr = http.NewRequestWithContext(context.Background(), "", "", bytes.NewReader([]byte("[test]"))) require.NoError(t, rerr) r.Header.Set("Content-Type", "application/json") - _, _, err = ParseRequest(r) - require.IsType(t, &Error{}, err) - assert.Equal(t, ErrorCodeParse, err.Code) + _, _, err = jsonrpc.ParseRequest(r) + require.IsType(t, &jsonrpc.Error{}, err) + assert.Equal(t, jsonrpc.ErrorCodeParse, err.Code) - r, rerr = http.NewRequest("", "", bytes.NewReader([]byte("[{}]"))) + r, rerr = http.NewRequestWithContext(context.Background(), "", "", bytes.NewReader([]byte("[{}]"))) require.NoError(t, rerr) r.Header.Set("Content-Type", "application/json") - rs, batch, err = ParseRequest(r) + rs, batch, err = jsonrpc.ParseRequest(r) require.Nil(t, err) require.NotEmpty(t, rs) assert.True(t, batch) } func TestNewResponse(t *testing.T) { + t.Parallel() + id := json.RawMessage("test") - r := NewResponse(&Request{ + r := jsonrpc.NewResponse(&jsonrpc.Request{ Version: "2.0", ID: &id, }) @@ -86,13 +92,15 @@ func TestNewResponse(t *testing.T) { } func TestSendResponse(t *testing.T) { + t.Parallel() + rec := httptest.NewRecorder() - err := SendResponse(rec, []*Response{}, false) + err := jsonrpc.SendResponse(rec, []*jsonrpc.Response{}, false) require.NoError(t, err) assert.Empty(t, rec.Body.String()) id := json.RawMessage([]byte(`"test"`)) - r := &Response{ + r := &jsonrpc.Response{ ID: &id, Version: "2.0", Result: struct { @@ -103,19 +111,19 @@ func TestSendResponse(t *testing.T) { } rec = httptest.NewRecorder() - err = SendResponse(rec, []*Response{r}, false) + err = jsonrpc.SendResponse(rec, []*jsonrpc.Response{r}, false) require.NoError(t, err) assert.Equal(t, `{"jsonrpc":"2.0","result":{"name":"john"},"id":"test"} `, rec.Body.String()) rec = httptest.NewRecorder() - err = SendResponse(rec, []*Response{r}, true) + err = jsonrpc.SendResponse(rec, []*jsonrpc.Response{r}, true) require.NoError(t, err) assert.Equal(t, `[{"jsonrpc":"2.0","result":{"name":"john"},"id":"test"}] `, rec.Body.String()) rec = httptest.NewRecorder() - err = SendResponse(rec, []*Response{r, r}, false) + err = jsonrpc.SendResponse(rec, []*jsonrpc.Response{r, r}, false) require.NoError(t, err) assert.Equal(t, `[{"jsonrpc":"2.0","result":{"name":"john"},"id":"test"},{"jsonrpc":"2.0","result":{"name":"john"},"id":"test"}] `, rec.Body.String()) diff --git a/method.go b/method.go index 241d824..e799c27 100644 --- a/method.go +++ b/method.go @@ -14,8 +14,8 @@ type ( // Metadata has method meta data. Metadata struct { Handler Handler - Params interface{} - Result interface{} + Params any + Result any } ) @@ -44,18 +44,19 @@ func (mr *MethodRepository) TakeMethodMetadata(r *Request) (Metadata, *Error) { } // TakeMethod takes jsonrpc.Func in MethodRepository. -func (mr *MethodRepository) TakeMethod(r *Request) (Handler, *Error) { +func (mr *MethodRepository) TakeMethod(r *Request) (Handler, *Error) { //nolint: ireturn md, err := mr.TakeMethodMetadata(r) if err != nil { return nil, err } + return md.Handler, nil } // RegisterMethod registers jsonrpc.Func to MethodRepository. -func (mr *MethodRepository) RegisterMethod(method string, h Handler, params, result interface{}) error { +func (mr *MethodRepository) RegisterMethod(method string, h Handler, params, result any) error { if method == "" || h == nil { - return errors.New("jsonrpc: method name and function should not be empty") + return errors.New("jsonrpc: method name and function should not be empty") //nolint: goerr113 } mr.m.Lock() mr.r[method] = Metadata{ @@ -64,6 +65,7 @@ func (mr *MethodRepository) RegisterMethod(method string, h Handler, params, res Result: result, } mr.m.Unlock() + return nil } @@ -75,5 +77,6 @@ func (mr *MethodRepository) Methods() map[string]Metadata { ml[k] = md } mr.m.RUnlock() + return ml } diff --git a/method_test.go b/method_test.go index fb41c9d..a838529 100644 --- a/method_test.go +++ b/method_test.go @@ -1,31 +1,34 @@ -package jsonrpc +package jsonrpc_test import ( "context" "testing" "github.com/goccy/go-json" + "github.com/osamingo/jsonrpc/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestTakeMethod(t *testing.T) { - mr := NewMethodRepository() + t.Parallel() - r := &Request{} + mr := jsonrpc.NewMethodRepository() + + r := &jsonrpc.Request{} _, err := mr.TakeMethod(r) - require.IsType(t, &Error{}, err) - assert.Equal(t, ErrorCodeInvalidParams, err.Code) + require.IsType(t, &jsonrpc.Error{}, err) + assert.Equal(t, jsonrpc.ErrorCodeInvalidParams, err.Code) r.Method = "test" _, err = mr.TakeMethod(r) - require.IsType(t, &Error{}, err) - assert.Equal(t, ErrorCodeInvalidParams, err.Code) + require.IsType(t, &jsonrpc.Error{}, err) + assert.Equal(t, jsonrpc.ErrorCodeInvalidParams, err.Code) r.Version = "2.0" _, err = mr.TakeMethod(r) - require.IsType(t, &Error{}, err) - assert.Equal(t, ErrorCodeMethodNotFound, err.Code) + require.IsType(t, &jsonrpc.Error{}, err) + assert.Equal(t, jsonrpc.ErrorCodeMethodNotFound, err.Code) require.NoError(t, mr.RegisterMethod("test", SampleHandler(), nil, nil)) @@ -35,7 +38,9 @@ func TestTakeMethod(t *testing.T) { } func TestRegisterMethod(t *testing.T) { - mr := NewMethodRepository() + t.Parallel() + + mr := jsonrpc.NewMethodRepository() err := mr.RegisterMethod("", nil, nil, nil) require.Error(t, err) @@ -48,7 +53,9 @@ func TestRegisterMethod(t *testing.T) { } func TestMethods(t *testing.T) { - mr := NewMethodRepository() + t.Parallel() + + mr := jsonrpc.NewMethodRepository() err := mr.RegisterMethod("JsonRpc.Sample", SampleHandler(), nil, nil) require.NoError(t, err) @@ -58,9 +65,10 @@ func TestMethods(t *testing.T) { assert.NotEmpty(t, ml["JsonRpc.Sample"].Handler) } -func SampleHandler() Handler { - h := HandlerFunc(func(c context.Context, params *json.RawMessage) (result interface{}, err *Error) { - return nil, nil +func SampleHandler() *jsonrpc.HandlerFunc { + h := jsonrpc.HandlerFunc(func(c context.Context, params *json.RawMessage) (any, *jsonrpc.Error) { + return (any)(nil), nil }) + return &h } diff --git a/unmarshal.go b/unmarshal.go index 5cd84b8..277149e 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -3,12 +3,13 @@ package jsonrpc import "github.com/goccy/go-json" // Unmarshal decodes JSON-RPC params. -func Unmarshal(params *json.RawMessage, dst interface{}) *Error { +func Unmarshal(params *json.RawMessage, dst any) *Error { if params == nil { return ErrInvalidParams() } if err := json.Unmarshal(*params, dst); err != nil { return ErrInvalidParams() } + return nil } diff --git a/unmarshal_test.go b/unmarshal_test.go index 17e399d..15873af 100644 --- a/unmarshal_test.go +++ b/unmarshal_test.go @@ -1,29 +1,32 @@ -package jsonrpc +package jsonrpc_test import ( "testing" "github.com/goccy/go-json" + "github.com/osamingo/jsonrpc/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestUnmarshal(t *testing.T) { - err := Unmarshal(nil, nil) - require.IsType(t, &Error{}, err) - assert.Equal(t, ErrorCodeInvalidParams, err.Code) + t.Parallel() + + err := jsonrpc.Unmarshal(nil, nil) + require.IsType(t, &jsonrpc.Error{}, err) + assert.Equal(t, jsonrpc.ErrorCodeInvalidParams, err.Code) src := json.RawMessage([]byte(`{"name":"john"}`)) - err = Unmarshal(&src, nil) - require.IsType(t, &Error{}, err) - assert.Equal(t, ErrorCodeInvalidParams, err.Code) + err = jsonrpc.Unmarshal(&src, nil) + require.IsType(t, &jsonrpc.Error{}, err) + assert.Equal(t, jsonrpc.ErrorCodeInvalidParams, err.Code) dst := struct { Name string `json:"name"` }{} - err = Unmarshal(&src, &dst) + err = jsonrpc.Unmarshal(&src, &dst) require.Nil(t, err) assert.Equal(t, "john", dst.Name) }