Repository: goadesign/goa
Pull request #3409 (2023-11-05 11:51:11)

Fix generated validation code for gRPC 
The protobuf types do not consider Meta, so the validation code should not too.

raphael: This is really great, thank you for the nice fix and tests!

Patch:
diff --git a/grpc/codegen/service_data.go b/grpc/codegen/service_data.go
index ced49fb4..f356d441 100644
--- a/grpc/codegen/service_data.go
+++ b/grpc/codegen/service_data.go
@@ -744,6 +744,7 @@ func addValidation(att *expr.AttributeExpr, attName string, sd *ServiceData, req
 		}
 	}
 	vtx := protoBufTypeContext(sd.PkgName, sd.Scope, false)
+	removeMeta(att)
 	if def := codegen.ValidationCode(att, ut, vtx, true, expr.IsAlias(att.Type), attName); def != "" {
 		v := &ValidationData{
 			Name:    "Validate" + name,


Test patch:
diff --git a/grpc/codegen/client_types_test.go b/grpc/codegen/client_types_test.go
index cb4ca2d7..d6ed0d54 100644
--- a/grpc/codegen/client_types_test.go
+++ b/grpc/codegen/client_types_test.go
@@ -23,6 +23,7 @@ func TestClientTypeFiles(t *testing.T) {
 		{"client-with-errors", testdata.UnaryRPCWithErrorsDSL, testdata.WithErrorsClientTypeCode},
 		{"client-bidirectional-streaming-same-type", testdata.BidirectionalStreamingRPCSameTypeDSL, testdata.BidirectionalStreamingRPCSameTypeClientTypeCode},
 		{"client-struct-meta-type", testdata.StructMetaTypeDSL, testdata.StructMetaTypeTypeCode},
+		{"client-struct-field-name-meta-type", testdata.StructFieldNameMetaTypeDSL, testdata.StructFieldNameMetaTypeClientTypesCode},
 		{"client-default-fields", testdata.DefaultFieldsDSL, testdata.DefaultFieldsTypeCode},
 	}
 	for _, c := range cases {
diff --git a/grpc/codegen/server_types_test.go b/grpc/codegen/server_types_test.go
index 15a401fd..ea5e87c3 100644
--- a/grpc/codegen/server_types_test.go
+++ b/grpc/codegen/server_types_test.go
@@ -24,6 +24,7 @@ func TestServerTypeFiles(t *testing.T) {
 		{"server-elem-validation", testdata.ElemValidationDSL, testdata.ElemValidationServerTypesFile},
 		{"server-alias-validation", testdata.AliasValidationDSL, testdata.AliasValidationServerTypesFile},
 		{"server-struct-meta-type", testdata.StructMetaTypeDSL, testdata.StructMetaTypeServerTypeCode},
+		{"server-struct-field-name-meta-type", testdata.StructFieldNameMetaTypeDSL, testdata.StructFieldNameMetaTypeServerTypesCode},
 		{"server-default-fields", testdata.DefaultFieldsDSL, testdata.DefaultFieldsServerTypeCode},
 	}
 	for _, c := range cases {
diff --git a/grpc/codegen/testdata/client_type_code.go b/grpc/codegen/testdata/client_type_code.go
index 61a68a09..fdf74726 100644
--- a/grpc/codegen/testdata/client_type_code.go
+++ b/grpc/codegen/testdata/client_type_code.go
@@ -371,6 +371,49 @@ func NewMethodResult(message *using_meta_typespb.MethodResponse) *usingmetatypes
 }
 `
 
+const StructFieldNameMetaTypeClientTypesCode = `// NewProtoMethodRequest builds the gRPC request type from the payload of the
+// "Method" endpoint of the "UsingMetaTypes" service.
+func NewProtoMethodRequest(payload *usingmetatypes.MethodPayload) *using_meta_typespb.MethodRequest {
+	message := &using_meta_typespb.MethodRequest{
+		A: &payload.Foo,
+	}
+	if payload.Bar != nil {
+		message.B = make([]int64, len(payload.Bar))
+		for i, val := range payload.Bar {
+			message.B[i] = val
+		}
+	}
+	return message
+}
+
+// NewMethodResult builds the result type of the "Method" endpoint of the
+// "UsingMetaTypes" service from the gRPC response type.
+func NewMethodResult(message *using_meta_typespb.MethodResponse) *usingmetatypes.MethodResult {
+	result := &usingmetatypes.MethodResult{}
+	if message.A != nil {
+		result.Foo = *message.A
+	}
+	if message.A == nil {
+		result.Foo = 1
+	}
+	if message.B != nil {
+		result.Bar = make([]int64, len(message.B))
+		for i, val := range message.B {
+			result.Bar[i] = val
+		}
+	}
+	return result
+}
+
+// ValidateMethodResponse runs the validations defined on MethodResponse.
+func ValidateMethodResponse(message *using_meta_typespb.MethodResponse) (err error) {
+	if message.B == nil {
+		err = goa.MergeErrors(err, goa.MissingFieldError("b", "message"))
+	}
+	return
+}
+`
+
 const DefaultFieldsTypeCode = `// NewProtoMethodRequest builds the gRPC request type from the payload of the
 // "Method" endpoint of the "DefaultFields" service.
 func NewProtoMethodRequest(payload *defaultfields.MethodPayload) *default_fieldspb.MethodRequest {
diff --git a/grpc/codegen/testdata/dsls.go b/grpc/codegen/testdata/dsls.go
index ffa3b0f8..a54d1b13 100644
--- a/grpc/codegen/testdata/dsls.go
+++ b/grpc/codegen/testdata/dsls.go
@@ -924,6 +924,34 @@ var StructMetaTypeDSL = func() {
 	})
 }
 
+var StructFieldNameMetaTypeDSL = func() {
+	Service("UsingMetaTypes", func() {
+		Method("Method", func() {
+			Payload(func() {
+				Field(1, "a", Int64, func() {
+					Meta("struct:field:name", "Foo")
+					Default(1)
+				})
+				Field(2, "b", ArrayOf(Int64), func() {
+					Meta("struct:field:name", "Bar")
+				})
+				Required("b")
+			})
+			Result(func() {
+				Field(1, "a", Int64, func() {
+					Meta("struct:field:name", "Foo")
+					Default(1)
+				})
+				Field(2, "b", ArrayOf(Int64), func() {
+					Meta("struct:field:name", "Bar")
+				})
+				Required("b")
+			})
+			GRPC(func() {})
+		})
+	})
+}
+
 var DefaultFieldsDSL = func() {
 	Service("DefaultFields", func() {
 		Method("Method", func() {
diff --git a/grpc/codegen/testdata/server_type_code.go b/grpc/codegen/testdata/server_type_code.go
index 923f1e74..c6acec68 100644
--- a/grpc/codegen/testdata/server_type_code.go
+++ b/grpc/codegen/testdata/server_type_code.go
@@ -491,6 +491,49 @@ func NewProtoMethodResponse(result *usingmetatypes.MethodResult) *using_meta_typ
 }
 `
 
+const StructFieldNameMetaTypeServerTypesCode = `// NewMethodPayload builds the payload of the "Method" endpoint of the
+// "UsingMetaTypes" service from the gRPC request type.
+func NewMethodPayload(message *using_meta_typespb.MethodRequest) *usingmetatypes.MethodPayload {
+	v := &usingmetatypes.MethodPayload{}
+	if message.A != nil {
+		v.Foo = *message.A
+	}
+	if message.A == nil {
+		v.Foo = 1
+	}
+	if message.B != nil {
+		v.Bar = make([]int64, len(message.B))
+		for i, val := range message.B {
+			v.Bar[i] = val
+		}
+	}
+	return v
+}
+
+// NewProtoMethodResponse builds the gRPC response type from the result of the
+// "Method" endpoint of the "UsingMetaTypes" service.
+func NewProtoMethodResponse(result *usingmetatypes.MethodResult) *using_meta_typespb.MethodResponse {
+	message := &using_meta_typespb.MethodResponse{
+		A: &result.Foo,
+	}
+	if result.Bar != nil {
+		message.B = make([]int64, len(result.Bar))
+		for i, val := range result.Bar {
+			message.B[i] = val
+		}
+	}
+	return message
+}
+
+// ValidateMethodRequest runs the validations defined on MethodRequest.
+func ValidateMethodRequest(message *using_meta_typespb.MethodRequest) (err error) {
+	if message.B == nil {
+		err = goa.MergeErrors(err, goa.MissingFieldError("b", "message"))
+	}
+	return
+}
+`
+
 const DefaultFieldsServerTypeCode = `// NewMethodPayload builds the payload of the "Method" endpoint of the
 // "DefaultFields" service from the gRPC request type.
 func NewMethodPayload(message *default_fieldspb.MethodRequest) *defaultfields.MethodPayload {