request method verb on service invocation (#119)

* request method verb on service invocation

* missing args tests

* merge custom serialize

* removes IDE specific stuff from project
This commit is contained in:
Mark Chmarny 2020-12-09 11:35:43 -08:00 committed by GitHub
parent bd7d126b4f
commit ec13ce3483
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 86 additions and 65 deletions

View File

@ -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/**"

3
.gitignore vendored
View File

@ -5,6 +5,9 @@
*.so
*.dylib
# IDE
.vscode
# Test binary, build with `go test -c`
*.test

17
.vscode/launch.json vendored
View File

@ -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": []
}
]
}

View File

@ -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

View File

@ -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),
},
}

View File

@ -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)
})
}

View File

@ -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

View File

@ -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)
}