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 default: false
client: client:
paths: "client/" paths: "client/"
threshold: 0% # don't allow coverage to drop on client threshold: 2% # don't allow coverage to drop on client
service: service:
paths: "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: ignore:
- "dapr/proto/**" - "dapr/proto/**"
- "example/**" - "example/**"

3
.gitignore vendored
View File

@ -5,6 +5,9 @@
*.so *.so
*.dylib *.dylib
# IDE
.vscode
# Test binary, build with `go test -c` # Test binary, build with `go test -c`
*.test *.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 InvokeOutputBinding(ctx context.Context, in *BindingInvocation) error
// InvokeService invokes service without raw data // 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 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 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 pubishes data onto topic in specific pubsub component.
PublishEvent(ctx context.Context, component, topic string, in []byte) error PublishEvent(ctx context.Context, component, topic string, in []byte) error

View File

@ -3,6 +3,7 @@ package client
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"strings"
v1 "github.com/dapr/go-sdk/dapr/proto/common/v1" v1 "github.com/dapr/go-sdk/dapr/proto/common/v1"
pb "github.com/dapr/go-sdk/dapr/proto/runtime/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 return
} }
// InvokeService invokes service without raw data ([]byte). func verbToHTTPExtension(verb string) *v1.HTTPExtension {
func (c *GRPCClient) InvokeService(ctx context.Context, serviceID, method string) (out []byte, err error) { 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 == "" { if serviceID == "" {
return nil, errors.New("nil serviceID") return errors.New("serviceID")
} }
if method == "" { 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{ req := &pb.InvokeServiceRequest{
Id: serviceID, Id: serviceID,
Message: &v1.InvokeRequest{ Message: &v1.InvokeRequest{
Method: method, Method: method,
HttpExtension: &v1.HTTPExtension{ HttpExtension: verbToHTTPExtension(verb),
Verb: v1.HTTPExtension_POST,
},
}, },
} }
return c.invokeServiceWithRequest(ctx, req) return c.invokeServiceWithRequest(ctx, req)
} }
// InvokeServiceWithContent invokes service without content (data + content type). // InvokeServiceWithContent invokes service without content (data + content type).
func (c *GRPCClient) InvokeServiceWithContent(ctx context.Context, serviceID, method string, content *DataContent) (out []byte, err error) { func (c *GRPCClient) InvokeServiceWithContent(ctx context.Context, serviceID, method, verb string, content *DataContent) (out []byte, err error) {
if serviceID == "" { if err := hasRequiredInvokeArgs(serviceID, method, verb); err != nil {
return nil, errors.New("serviceID is required") return nil, errors.Wrap(err, "missing required parameter")
}
if method == "" {
return nil, errors.New("method name is required")
} }
if content == nil { if content == nil {
return nil, errors.New("content required") return nil, errors.New("content required")
} }
req := &pb.InvokeServiceRequest{ req := &pb.InvokeServiceRequest{
Id: serviceID, Id: serviceID,
Message: &v1.InvokeRequest{ Message: &v1.InvokeRequest{
Method: method, Method: method,
Data: &anypb.Any{Value: content.Data}, Data: &anypb.Any{Value: content.Data},
ContentType: content.ContentType, ContentType: content.ContentType,
HttpExtension: &v1.HTTPExtension{ HttpExtension: verbToHTTPExtension(verb),
Verb: v1.HTTPExtension_POST,
},
}, },
} }
return c.invokeServiceWithRequest(ctx, req) return c.invokeServiceWithRequest(ctx, req)
} }
// InvokeServiceWithCustomContent invokes service with custom content (struct + content type). // 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) { func (c *GRPCClient) InvokeServiceWithCustomContent(ctx context.Context, serviceID, method, verb string, contentType string, content interface{}) (out []byte, err error) {
if serviceID == "" { if err := hasRequiredInvokeArgs(serviceID, method, verb); err != nil {
return nil, errors.New("serviceID is required") return nil, errors.Wrap(err, "missing required parameter")
}
if method == "" {
return nil, errors.New("method name is required")
} }
if contentType == "" { if contentType == "" {
return nil, errors.New("content type required") return nil, errors.New("content type required")
@ -101,7 +107,6 @@ func (c *GRPCClient) InvokeServiceWithCustomContent(ctx context.Context, service
} }
contentData, err := json.Marshal(content) contentData, err := json.Marshal(content)
if err != nil { if err != nil {
return nil, errors.WithMessage(err, "error serializing input struct") return nil, errors.WithMessage(err, "error serializing input struct")
} }
@ -109,12 +114,10 @@ func (c *GRPCClient) InvokeServiceWithCustomContent(ctx context.Context, service
req := &pb.InvokeServiceRequest{ req := &pb.InvokeServiceRequest{
Id: serviceID, Id: serviceID,
Message: &v1.InvokeRequest{ Message: &v1.InvokeRequest{
Method: method, Method: method,
Data: &anypb.Any{Value: contentData}, Data: &anypb.Any{Value: contentData},
ContentType: contentType, ContentType: contentType,
HttpExtension: &v1.HTTPExtension{ HttpExtension: verbToHTTPExtension(verb),
Verb: v1.HTTPExtension_POST,
},
}, },
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"testing" "testing"
v1 "github.com/dapr/go-sdk/dapr/proto/common/v1"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -22,7 +23,6 @@ type _testStructwithSlices struct {
} }
// go test -timeout 30s ./client -count 1 -run ^TestInvokeServiceWithContent$ // go test -timeout 30s ./client -count 1 -run ^TestInvokeServiceWithContent$
func TestInvokeServiceWithContent(t *testing.T) { func TestInvokeServiceWithContent(t *testing.T) {
ctx := context.Background() ctx := context.Background()
data := "ping" data := "ping"
@ -32,7 +32,7 @@ func TestInvokeServiceWithContent(t *testing.T) {
ContentType: "text/plain", ContentType: "text/plain",
Data: []byte(data), 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.Nil(t, err)
assert.NotNil(t, resp) assert.NotNil(t, resp)
assert.Equal(t, string(resp), data) assert.Equal(t, string(resp), data)
@ -40,18 +40,30 @@ func TestInvokeServiceWithContent(t *testing.T) {
}) })
t.Run("without content", func(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, err)
assert.Nil(t, resp) 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) { t.Run("from struct with text", func(t *testing.T) {
testdata := _testCustomContentwithText{ testdata := _testCustomContentwithText{
Key1: "value1", Key1: "value1",
Key2: "value2", 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) assert.Nil(t, err)
}) })
@ -60,7 +72,7 @@ func TestInvokeServiceWithContent(t *testing.T) {
Key1: "value1", Key1: "value1",
Key2: 2500, 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) assert.Nil(t, err)
}) })
@ -69,7 +81,27 @@ func TestInvokeServiceWithContent(t *testing.T) {
Key1: []string{"value1", "value2", "value3"}, Key1: []string{"value1", "value2", "value3"},
Key2: []int{25, 40, 600}, 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) 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 Etag string
} }
// StateItem represents a single state item. // BulkStateItem represents a single state item.
type BulkStateItem struct { type BulkStateItem struct {
Key string Key string
Value []byte Value []byte

View File

@ -71,7 +71,7 @@ func main() {
ContentType: "text/plain", ContentType: "text/plain",
Data: []byte("hellow"), Data: []byte("hellow"),
} }
resp, err := client.InvokeServiceWithContent(ctx, "serving", "echo", content) resp, err := client.InvokeServiceWithContent(ctx, "serving", "echo", "post", content)
if err != nil { if err != nil {
panic(err) panic(err)
} }