diff --git a/.gitignore b/.gitignore index d34b90b..2dd770c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ # IDE .vscode +.idea # Test binary, build with `go test -c` *.test diff --git a/client/invoke.go b/client/invoke.go index 29348fa..a373ab5 100644 --- a/client/invoke.go +++ b/client/invoke.go @@ -39,9 +39,16 @@ func (c *GRPCClient) invokeServiceWithRequest(ctx context.Context, req *pb.Invok return } -func verbToHTTPExtension(verb string) *v1.HTTPExtension { +func queryAndVerbToHTTPExtension(query string, verb string) *v1.HTTPExtension { + var queryMap = map[string]string{} + for _, item := range strings.Split(query, "&") { + kv := strings.Split(item, "=") + if len(kv) == 2 { + queryMap[kv[0]] = kv[1] + } + } 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_Verb(v), Querystring: queryMap} } return &v1.HTTPExtension{Verb: v1.HTTPExtension_NONE} } @@ -64,11 +71,12 @@ func (c *GRPCClient) InvokeMethod(ctx context.Context, appID, methodName, verb s if err := hasRequiredInvokeArgs(appID, methodName, verb); err != nil { return nil, errors.Wrap(err, "missing required parameter") } + method, query := extractMethodAndQuery(methodName) req := &pb.InvokeServiceRequest{ Id: appID, Message: &v1.InvokeRequest{ - Method: methodName, - HttpExtension: verbToHTTPExtension(verb), + Method: method, + HttpExtension: queryAndVerbToHTTPExtension(query, verb), }, } return c.invokeServiceWithRequest(ctx, req) @@ -82,13 +90,14 @@ func (c *GRPCClient) InvokeMethodWithContent(ctx context.Context, appID, methodN if content == nil { return nil, errors.New("content required") } + method, query := extractMethodAndQuery(methodName) req := &pb.InvokeServiceRequest{ Id: appID, Message: &v1.InvokeRequest{ - Method: methodName, + Method: method, Data: &anypb.Any{Value: content.Data}, ContentType: content.ContentType, - HttpExtension: verbToHTTPExtension(verb), + HttpExtension: queryAndVerbToHTTPExtension(query, verb), }, } return c.invokeServiceWithRequest(ctx, req) @@ -111,15 +120,26 @@ func (c *GRPCClient) InvokeMethodWithCustomContent(ctx context.Context, appID, m return nil, errors.WithMessage(err, "error serializing input struct") } + method, query := extractMethodAndQuery(methodName) + req := &pb.InvokeServiceRequest{ Id: appID, Message: &v1.InvokeRequest{ - Method: methodName, + Method: method, Data: &anypb.Any{Value: contentData}, ContentType: contentType, - HttpExtension: verbToHTTPExtension(verb), + HttpExtension: queryAndVerbToHTTPExtension(query, verb), }, } return c.invokeServiceWithRequest(ctx, req) } + +func extractMethodAndQuery(name string) (method, query string) { + splitStr := strings.SplitN(name, "?", 2) + method = splitStr[0] + if len(splitStr) == 2 { + query = splitStr[1] + } + return +} diff --git a/client/invoke_test.go b/client/invoke_test.go index 31d0ef5..63bc1ca 100644 --- a/client/invoke_test.go +++ b/client/invoke_test.go @@ -35,14 +35,23 @@ func TestInvokeMethodWithContent(t *testing.T) { assert.Nil(t, err) assert.NotNil(t, resp) assert.Equal(t, string(resp), data) + }) + t.Run("with content, method contains querystring", func(t *testing.T) { + content := &DataContent{ + ContentType: "text/plain", + Data: []byte(data), + } + resp, err := testClient.InvokeMethodWithContent(ctx, "test", "fn?foo=bar&url=http://dapr.io", "get", content) + assert.Nil(t, err) + assert.NotNil(t, resp) + assert.Equal(t, string(resp), data) }) t.Run("without content", func(t *testing.T) { resp, err := testClient.InvokeMethod(ctx, "test", "fn", "get") assert.Nil(t, err) assert.Nil(t, resp) - }) t.Run("without service ID", func(t *testing.T) { @@ -87,20 +96,78 @@ func TestInvokeMethodWithContent(t *testing.T) { func TestVerbParsing(t *testing.T) { t.Run("valid lower case", func(t *testing.T) { - v := verbToHTTPExtension("post") + v := queryAndVerbToHTTPExtension("", "post") assert.NotNil(t, v) assert.Equal(t, v1.HTTPExtension_POST, v.Verb) + assert.Len(t, v.Querystring, 0) }) t.Run("valid upper case", func(t *testing.T) { - v := verbToHTTPExtension("GET") + v := queryAndVerbToHTTPExtension("", "GET") assert.NotNil(t, v) assert.Equal(t, v1.HTTPExtension_GET, v.Verb) }) t.Run("invalid verb", func(t *testing.T) { - v := verbToHTTPExtension("BAD") + v := queryAndVerbToHTTPExtension("", "BAD") assert.NotNil(t, v) assert.Equal(t, v1.HTTPExtension_NONE, v.Verb) }) + + t.Run("valid query", func(t *testing.T) { + v := queryAndVerbToHTTPExtension("foo=bar&url=http://dapr.io", "post") + assert.NotNil(t, v) + assert.Equal(t, v1.HTTPExtension_POST, v.Verb) + assert.Len(t, v.Querystring, 2) + assert.Equal(t, "bar", v.Querystring["foo"]) + assert.Equal(t, "http://dapr.io", v.Querystring["url"]) + }) +} + +func TestExtractMethodAndQuery(t *testing.T) { + type args struct { + name string + } + tests := []struct { + name string + args args + wantMethod string + wantQuery string + }{ + { + "pure uri", + args{name: "method"}, + "method", + "", + }, + { + "root route method", + args{name: "/"}, + "/", + "", + }, + { + "uri with one query", + args{name: "method?foo=bar"}, + "method", + "foo=bar", + }, + { + "uri with two query", + args{name: "method?foo=bar&url=http://dapr.io"}, + "method", + "foo=bar&url=http://dapr.io", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotMethod, gotQuery := extractMethodAndQuery(tt.args.name) + if gotMethod != tt.wantMethod { + t.Errorf("extractMethodAndQuery() gotMethod = %v, want %v", gotMethod, tt.wantMethod) + } + if gotQuery != tt.wantQuery { + t.Errorf("extractMethodAndQuery() gotQuery = %v, want %v", gotQuery, tt.wantQuery) + } + }) + } }