diff --git a/.codecov.yaml b/.codecov.yaml index 23f8483..92d594e 100644 --- a/.codecov.yaml +++ b/.codecov.yaml @@ -5,10 +5,10 @@ coverage: default: false client: paths: "client/" - threshold: 0% # don't allow coverage to drop on client + threshold: 2% # don't allow coverage to drop on client service: paths: "service" - threshold: 3% # allow coverage to drop on service by 3% (wip) + threshold: 2% # allow coverage to drop on service by 3% (wip) ignore: - "dapr/proto/**" - "example/**" diff --git a/.gitignore b/.gitignore index d0b9e12..d34b90b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ *.so *.dylib +# IDE +.vscode + # Test binary, build with `go test -c` *.test diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index c23774c..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Launch", - "type": "go", - "request": "launch", - "mode": "auto", - "program": "${fileDirname}", - "env": {}, - "args": [] - } - ] -} \ No newline at end of file diff --git a/client/client.go b/client/client.go index 24af6af..831332f 100644 --- a/client/client.go +++ b/client/client.go @@ -40,13 +40,13 @@ type Client interface { InvokeOutputBinding(ctx context.Context, in *BindingInvocation) error // InvokeService invokes service without raw data - InvokeService(ctx context.Context, serviceID, method string) (out []byte, err error) + InvokeService(ctx context.Context, serviceID, method, verb string) (out []byte, err error) // InvokeServiceWithContent invokes service with content - InvokeServiceWithContent(ctx context.Context, serviceID, method string, content *DataContent) (out []byte, err error) + InvokeServiceWithContent(ctx context.Context, serviceID, method, verb string, content *DataContent) (out []byte, err error) // InvokeServiceWithCustomContent invokes service with custom content (struct + content type). - InvokeServiceWithCustomContent(ctx context.Context, serviceID, method string, contentType string, content interface{}) (out []byte, err error) + InvokeServiceWithCustomContent(ctx context.Context, serviceID, method, verb string, contentType string, content interface{}) (out []byte, err error) // PublishEvent pubishes data onto topic in specific pubsub component. PublishEvent(ctx context.Context, component, topic string, in []byte) error diff --git a/client/invoke.go b/client/invoke.go index ee7acc1..b19232e 100644 --- a/client/invoke.go +++ b/client/invoke.go @@ -3,6 +3,7 @@ package client import ( "context" "encoding/json" + "strings" v1 "github.com/dapr/go-sdk/dapr/proto/common/v1" pb "github.com/dapr/go-sdk/dapr/proto/runtime/v1" @@ -38,60 +39,65 @@ func (c *GRPCClient) invokeServiceWithRequest(ctx context.Context, req *pb.Invok return } -// InvokeService invokes service without raw data ([]byte). -func (c *GRPCClient) InvokeService(ctx context.Context, serviceID, method string) (out []byte, err error) { +func verbToHTTPExtension(verb string) *v1.HTTPExtension { + if v, ok := v1.HTTPExtension_Verb_value[strings.ToUpper(verb)]; ok { + return &v1.HTTPExtension{Verb: v1.HTTPExtension_Verb(v)} + } + return &v1.HTTPExtension{Verb: v1.HTTPExtension_NONE} +} + +func hasRequiredInvokeArgs(serviceID, method, verb string) error { if serviceID == "" { - return nil, errors.New("nil serviceID") + return errors.New("serviceID") } if method == "" { - return nil, errors.New("nil method") + return errors.New("method") + } + if verb == "" { + return errors.New("verb") + } + return nil +} + +// InvokeService invokes service without raw data ([]byte). +func (c *GRPCClient) InvokeService(ctx context.Context, serviceID, method, verb string) (out []byte, err error) { + if err := hasRequiredInvokeArgs(serviceID, method, verb); err != nil { + return nil, errors.Wrap(err, "missing required parameter") } req := &pb.InvokeServiceRequest{ Id: serviceID, Message: &v1.InvokeRequest{ - Method: method, - HttpExtension: &v1.HTTPExtension{ - Verb: v1.HTTPExtension_POST, - }, + Method: method, + HttpExtension: verbToHTTPExtension(verb), }, } return c.invokeServiceWithRequest(ctx, req) } // InvokeServiceWithContent invokes service without content (data + content type). -func (c *GRPCClient) InvokeServiceWithContent(ctx context.Context, serviceID, method string, content *DataContent) (out []byte, err error) { - if serviceID == "" { - return nil, errors.New("serviceID is required") - } - if method == "" { - return nil, errors.New("method name is required") +func (c *GRPCClient) InvokeServiceWithContent(ctx context.Context, serviceID, method, verb string, content *DataContent) (out []byte, err error) { + if err := hasRequiredInvokeArgs(serviceID, method, verb); err != nil { + return nil, errors.Wrap(err, "missing required parameter") } if content == nil { return nil, errors.New("content required") } - req := &pb.InvokeServiceRequest{ Id: serviceID, Message: &v1.InvokeRequest{ - Method: method, - Data: &anypb.Any{Value: content.Data}, - ContentType: content.ContentType, - HttpExtension: &v1.HTTPExtension{ - Verb: v1.HTTPExtension_POST, - }, + Method: method, + Data: &anypb.Any{Value: content.Data}, + ContentType: content.ContentType, + HttpExtension: verbToHTTPExtension(verb), }, } - return c.invokeServiceWithRequest(ctx, req) } // InvokeServiceWithCustomContent invokes service with custom content (struct + content type). -func (c *GRPCClient) InvokeServiceWithCustomContent(ctx context.Context, serviceID, method string, contentType string, content interface{}) (out []byte, err error) { - if serviceID == "" { - return nil, errors.New("serviceID is required") - } - if method == "" { - return nil, errors.New("method name is required") +func (c *GRPCClient) InvokeServiceWithCustomContent(ctx context.Context, serviceID, method, verb string, contentType string, content interface{}) (out []byte, err error) { + if err := hasRequiredInvokeArgs(serviceID, method, verb); err != nil { + return nil, errors.Wrap(err, "missing required parameter") } if contentType == "" { return nil, errors.New("content type required") @@ -101,7 +107,6 @@ func (c *GRPCClient) InvokeServiceWithCustomContent(ctx context.Context, service } contentData, err := json.Marshal(content) - if err != nil { return nil, errors.WithMessage(err, "error serializing input struct") } @@ -109,12 +114,10 @@ func (c *GRPCClient) InvokeServiceWithCustomContent(ctx context.Context, service req := &pb.InvokeServiceRequest{ Id: serviceID, Message: &v1.InvokeRequest{ - Method: method, - Data: &anypb.Any{Value: contentData}, - ContentType: contentType, - HttpExtension: &v1.HTTPExtension{ - Verb: v1.HTTPExtension_POST, - }, + Method: method, + Data: &anypb.Any{Value: contentData}, + ContentType: contentType, + HttpExtension: verbToHTTPExtension(verb), }, } diff --git a/client/invoke_test.go b/client/invoke_test.go index ba6fa45..73d95db 100644 --- a/client/invoke_test.go +++ b/client/invoke_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + v1 "github.com/dapr/go-sdk/dapr/proto/common/v1" "github.com/stretchr/testify/assert" ) @@ -22,7 +23,6 @@ type _testStructwithSlices struct { } // go test -timeout 30s ./client -count 1 -run ^TestInvokeServiceWithContent$ - func TestInvokeServiceWithContent(t *testing.T) { ctx := context.Background() data := "ping" @@ -32,7 +32,7 @@ func TestInvokeServiceWithContent(t *testing.T) { ContentType: "text/plain", Data: []byte(data), } - resp, err := testClient.InvokeServiceWithContent(ctx, "test", "fn", content) + resp, err := testClient.InvokeServiceWithContent(ctx, "test", "fn", "post", content) assert.Nil(t, err) assert.NotNil(t, resp) assert.Equal(t, string(resp), data) @@ -40,18 +40,30 @@ func TestInvokeServiceWithContent(t *testing.T) { }) t.Run("without content", func(t *testing.T) { - resp, err := testClient.InvokeService(ctx, "test", "fn") + resp, err := testClient.InvokeService(ctx, "test", "fn", "get") assert.Nil(t, err) assert.Nil(t, resp) }) + t.Run("without service ID", func(t *testing.T) { + _, err := testClient.InvokeService(ctx, "", "fn", "get") + assert.NotNil(t, err) + }) + t.Run("without method", func(t *testing.T) { + _, err := testClient.InvokeService(ctx, "test", "", "get") + assert.NotNil(t, err) + }) + t.Run("without verb", func(t *testing.T) { + _, err := testClient.InvokeService(ctx, "test", "fn", "") + assert.NotNil(t, err) + }) t.Run("from struct with text", func(t *testing.T) { testdata := _testCustomContentwithText{ Key1: "value1", Key2: "value2", } - _, err := testClient.InvokeServiceWithCustomContent(ctx, "test", "fn", "text/plain", testdata) + _, err := testClient.InvokeServiceWithCustomContent(ctx, "test", "fn", "post", "text/plain", testdata) assert.Nil(t, err) }) @@ -60,7 +72,7 @@ func TestInvokeServiceWithContent(t *testing.T) { Key1: "value1", Key2: 2500, } - _, err := testClient.InvokeServiceWithCustomContent(ctx, "test", "fn", "text/plain", testdata) + _, err := testClient.InvokeServiceWithCustomContent(ctx, "test", "fn", "post", "text/plain", testdata) assert.Nil(t, err) }) @@ -69,7 +81,27 @@ func TestInvokeServiceWithContent(t *testing.T) { Key1: []string{"value1", "value2", "value3"}, Key2: []int{25, 40, 600}, } - _, err := testClient.InvokeServiceWithCustomContent(ctx, "test", "fn", "text/plain", testdata) + _, err := testClient.InvokeServiceWithCustomContent(ctx, "test", "fn", "post", "text/plain", testdata) assert.Nil(t, err) }) } + +func TestVerbParsing(t *testing.T) { + t.Run("valid lower case", func(t *testing.T) { + v := verbToHTTPExtension("post") + assert.NotNil(t, v) + assert.Equal(t, v1.HTTPExtension_POST, v.Verb) + }) + + t.Run("valid upper case", func(t *testing.T) { + v := verbToHTTPExtension("GET") + assert.NotNil(t, v) + assert.Equal(t, v1.HTTPExtension_GET, v.Verb) + }) + + t.Run("invalid verb", func(t *testing.T) { + v := verbToHTTPExtension("BAD") + assert.NotNil(t, v) + assert.Equal(t, v1.HTTPExtension_NONE, v.Verb) + }) +} diff --git a/client/state.go b/client/state.go index d43c289..cfa75cd 100644 --- a/client/state.go +++ b/client/state.go @@ -106,7 +106,7 @@ type StateItem struct { Etag string } -// StateItem represents a single state item. +// BulkStateItem represents a single state item. type BulkStateItem struct { Key string Value []byte diff --git a/example/client/main.go b/example/client/main.go index 4e88236..5a7fcca 100644 --- a/example/client/main.go +++ b/example/client/main.go @@ -71,7 +71,7 @@ func main() { ContentType: "text/plain", Data: []byte("hellow"), } - resp, err := client.InvokeServiceWithContent(ctx, "serving", "echo", content) + resp, err := client.InvokeServiceWithContent(ctx, "serving", "echo", "post", content) if err != nil { panic(err) }