From 0eb0a270239d745c5275fc05233707960261e94b Mon Sep 17 00:00:00 2001 From: "hazem.borham" Date: Thu, 19 Jan 2023 15:54:25 -0800 Subject: [PATCH] feat: add support to MatchV2 structs generated by proto-gen-go allows for reuse of structs generated by the client code for reuse with protobuf or json. the protobuf portion of the struct def was not compatible with the MatchV2 due to some field types not supported, and in some cases the json information is only an attribute of protobuf tag. this enhancement opts for 1. json tag. 2. protobuf tag. 3. skips field --- matchers/matcher.go | 24 +++++++++++++++++++++++- matchers/matcher_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/matchers/matcher.go b/matchers/matcher.go index c550ee5e2..f7a49455c 100644 --- a/matchers/matcher.go +++ b/matchers/matcher.go @@ -316,7 +316,11 @@ func match(srcType reflect.Type, params params) Matcher { for i := 0; i < srcType.NumField(); i++ { field := srcType.Field(i) - result[strings.Split(field.Tag.Get("json"), ",")[0]] = match(field.Type, pluckParams(field.Type, field.Tag.Get("pact"))) + fieldName := fieldNameByTagStrategy(field) + if fieldName == "" { + continue + } + result[fieldName] = match(field.Type, pluckParams(field.Type, field.Tag.Get("pact"))) } return result case reflect.String: @@ -349,6 +353,24 @@ func match(srcType reflect.Type, params params) Matcher { } } +func fieldNameByTagStrategy(field reflect.StructField) string { + var v string + var ok bool + if v, ok = field.Tag.Lookup("json"); ok { + return strings.Split(v, ",")[0] + } else if v, ok = field.Tag.Lookup("protobuf"); ok { + // parsing tag value like such. need to find spec for protobuf field tag + // "varint,1,opt,name=proto_with_json_tag,json=protoWithJsonTag,proto3" + arr := strings.Split(v, ",") + for i := 0; i < len(arr); i++ { + if strings.HasPrefix(arr[i], "json=") { + return strings.Split(arr[i], "=")[1] + } + } + } + return "" +} + // params are plucked from 'pact' struct tags as match() traverses // struct fields. They are passed back into match() along with their // associated type to serve as parameters for the dsl functions. diff --git a/matchers/matcher_test.go b/matchers/matcher_test.go index 923881522..de229315d 100644 --- a/matchers/matcher_test.go +++ b/matchers/matcher_test.go @@ -582,6 +582,21 @@ func TestMatch(t *testing.T) { Integer int `json:"integer" pact:"example=42"` Float float32 `json:"float" pact:"example=6.66"` } + // mixedDTO in order to reuse protoc-gen-go where structs are compatible with protobuf and json + type mixedDTO struct { + // has tag and should be in output + OnlyJsonTag string `json:"onlyJsonTag"` + // no tag, skip + NoTagString string + // no tag, skip - this covers case of proto compatible structs that contain func fields + NoTagFunc func() + BothUseJsonTag int32 `protobuf:"varint,1,opt,name=both_use_json_tag,json=bothNameFromProtobufTag,proto3" json:"bothNameFromJsonTag,omitempty"` + ProtoWithoutJsonTag *struct { + OnlyJsonTag string `json:"onlyJsonTagNested"` + // no tag, skip + NoTag func() + } `protobuf:"bytes,7,opt,name=proto_without_json_tag,json=onlyProtobufTag,proto3,oneof"` + } str := "str" type args struct { src interface{} @@ -774,6 +789,17 @@ func TestMatch(t *testing.T) { }, wantPanic: true, }, + { + name: "structs mixed for compatibility with proto3 and json types", + args: args{ + src: mixedDTO{}, + }, + want: StructMatcher{ + "onlyJsonTag": Like("string"), + "bothNameFromJsonTag": Like(1), + "onlyProtobufTag": StructMatcher{"onlyJsonTagNested": Like("string")}, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {