Repository: goadesign/goa
Pull request #3569 (2024-08-01 23:51:37)

Generate view types for embedded user types
The previous commit was too aggressive and removed user types that are used by result types. These user types need corresponding types in the views package so that result types can be projected. This commit changes the check to be at the root of the type: if a type has no result type attribute recursively then it does not need to have corresponding view types. If a type contains result types then the type and all user type it uses are projected.


Patch:
diff --git a/codegen/service/service_data.go b/codegen/service/service_data.go
index 54173088..f0a62f81 100644
--- a/codegen/service/service_data.go
+++ b/codegen/service/service_data.go
@@ -613,9 +613,11 @@ func (d ServicesData) analyze(service *expr.ServiceExpr) *Data {
 			collectUserTypes(m.StreamingPayload)
 			collectUserTypes(m.Result)
 			// Collect projected types
-			types, umeths := collectProjectedTypes(expr.DupAtt(m.Result), m.Result, viewspkg, scope, viewScope, seenProj)
-			projTypes = append(projTypes, types...)
-			viewedUnionMeths = append(viewedUnionMeths, umeths...)
+			if hasResultType(m.Result) {
+				types, umeths := collectProjectedTypes(expr.DupAtt(m.Result), m.Result, viewspkg, scope, viewScope, seenProj)
+				projTypes = append(projTypes, types...)
+				viewedUnionMeths = append(viewedUnionMeths, umeths...)
+			}
 			for _, er := range m.Errors {
 				recordError(er)
 			}
@@ -1189,9 +1191,6 @@ func BuildSchemeData(s *expr.SchemeExpr, m *expr.MethodExpr) *SchemeData {
 // just result types - because user types make contain result types and thus may
 // need to be marshalled in different ways depending on the view being used.
 func collectProjectedTypes(projected, att *expr.AttributeExpr, viewspkg string, scope, viewScope *codegen.NameScope, seen map[string]*ProjectedTypeData) (data []*ProjectedTypeData, umeths []*UnionValueMethodData) {
-	if !hasResultType(att) {
-		return
-	}
 	collect := func(projected, att *expr.AttributeExpr) ([]*ProjectedTypeData, []*UnionValueMethodData) {
 		return collectProjectedTypes(projected, att, viewspkg, scope, viewScope, seen)
 	}


Test patch:
diff --git a/codegen/service/testdata/service_code.go b/codegen/service/testdata/service_code.go
index 8dc4fd23..f6a9b5bf 100644
--- a/codegen/service/testdata/service_code.go
+++ b/codegen/service/testdata/service_code.go
@@ -1537,15 +1537,15 @@ func newResultOneof(vres *resultwithoneoftypeviews.ResultOneofView) *ResultOneof
 	res := &ResultOneof{}
 	if vres.Result != nil {
 		switch actual := vres.Result.(type) {
-		case *resultwithoneoftypeviews.T:
+		case *resultwithoneoftypeviews.TView:
 			obj := &T{
 				Message: actual.Message,
 			}
 			res.Result = obj
-		case *resultwithoneoftypeviews.U:
+		case *resultwithoneoftypeviews.UView:
 			obj := &U{}
 			if actual.Item != nil {
-				obj.(*U).Item = transformResultwithoneoftypeviewsItemToItem(actual.Item)
+				obj.(*U).Item = transformResultwithoneoftypeviewsItemViewToItem(actual.Item)
 			}
 			res.Result = obj
 		}
@@ -1560,14 +1560,14 @@ func newResultOneofView(res *ResultOneof) *resultwithoneoftypeviews.ResultOneofV
 	if res.Result != nil {
 		switch actual := res.Result.(type) {
 		case *T:
-			obj := &resultwithoneoftypeviews.T{
+			obj := &resultwithoneoftypeviews.TView{
 				Message: actual.Message,
 			}
 			vres.Result = obj
 		case *U:
-			obj := &resultwithoneoftypeviews.U{}
+			obj := &resultwithoneoftypeviews.UView{}
 			if actual.Item != nil {
-				obj.(*resultwithoneoftypeviews.U).Item = transformItemToResultwithoneoftypeviewsItem(actual.Item)
+				obj.(*resultwithoneoftypeviews.UView).Item = transformItemToResultwithoneoftypeviewsItemView(actual.Item)
 			}
 			vres.Result = obj
 		}
@@ -1575,9 +1575,9 @@ func newResultOneofView(res *ResultOneof) *resultwithoneoftypeviews.ResultOneofV
 	return vres
 }
 
-// transformResultwithoneoftypeviewsTToT builds a value of type *T from a value
-// of type *resultwithoneoftypeviews.T.
-func transformResultwithoneoftypeviewsTToT(v *resultwithoneoftypeviews.T) *T {
+// transformResultwithoneoftypeviewsTViewToT builds a value of type *T from a
+// value of type *resultwithoneoftypeviews.TView.
+func transformResultwithoneoftypeviewsTViewToT(v *resultwithoneoftypeviews.TView) *T {
 	if v == nil {
 		return nil
 	}
@@ -1588,23 +1588,23 @@ func transformResultwithoneoftypeviewsTToT(v *resultwithoneoftypeviews.T) *T {
 	return res
 }
 
-// transformResultwithoneoftypeviewsUToU builds a value of type *U from a value
-// of type *resultwithoneoftypeviews.U.
-func transformResultwithoneoftypeviewsUToU(v *resultwithoneoftypeviews.U) *U {
+// transformResultwithoneoftypeviewsUViewToU builds a value of type *U from a
+// value of type *resultwithoneoftypeviews.UView.
+func transformResultwithoneoftypeviewsUViewToU(v *resultwithoneoftypeviews.UView) *U {
 	if v == nil {
 		return nil
 	}
 	res := &U{}
 	if v.Item != nil {
-		res.Item = transformResultwithoneoftypeviewsItemToItem(v.Item)
+		res.Item = transformResultwithoneoftypeviewsItemViewToItem(v.Item)
 	}
 
 	return res
 }
 
-// transformResultwithoneoftypeviewsItemToItem builds a value of type *Item
-// from a value of type *resultwithoneoftypeviews.Item.
-func transformResultwithoneoftypeviewsItemToItem(v *resultwithoneoftypeviews.Item) *Item {
+// transformResultwithoneoftypeviewsItemViewToItem builds a value of type *Item
+// from a value of type *resultwithoneoftypeviews.ItemView.
+func transformResultwithoneoftypeviewsItemViewToItem(v *resultwithoneoftypeviews.ItemView) *Item {
 	if v == nil {
 		return nil
 	}
@@ -1615,40 +1615,40 @@ func transformResultwithoneoftypeviewsItemToItem(v *resultwithoneoftypeviews.Ite
 	return res
 }
 
-// transformTToResultwithoneoftypeviewsT builds a value of type
-// *resultwithoneoftypeviews.T from a value of type *T.
-func transformTToResultwithoneoftypeviewsT(v *T) *resultwithoneoftypeviews.T {
+// transformTToResultwithoneoftypeviewsTView builds a value of type
+// *resultwithoneoftypeviews.TView from a value of type *T.
+func transformTToResultwithoneoftypeviewsTView(v *T) *resultwithoneoftypeviews.TView {
 	if v == nil {
 		return nil
 	}
-	res := &resultwithoneoftypeviews.T{
+	res := &resultwithoneoftypeviews.TView{
 		Message: v.Message,
 	}
 
 	return res
 }
 
-// transformUToResultwithoneoftypeviewsU builds a value of type
-// *resultwithoneoftypeviews.U from a value of type *U.
-func transformUToResultwithoneoftypeviewsU(v *U) *resultwithoneoftypeviews.U {
+// transformUToResultwithoneoftypeviewsUView builds a value of type
+// *resultwithoneoftypeviews.UView from a value of type *U.
+func transformUToResultwithoneoftypeviewsUView(v *U) *resultwithoneoftypeviews.UView {
 	if v == nil {
 		return nil
 	}
-	res := &resultwithoneoftypeviews.U{}
+	res := &resultwithoneoftypeviews.UView{}
 	if v.Item != nil {
-		res.Item = transformItemToResultwithoneoftypeviewsItem(v.Item)
+		res.Item = transformItemToResultwithoneoftypeviewsItemView(v.Item)
 	}
 
 	return res
 }
 
-// transformItemToResultwithoneoftypeviewsItem builds a value of type
-// *resultwithoneoftypeviews.Item from a value of type *Item.
-func transformItemToResultwithoneoftypeviewsItem(v *Item) *resultwithoneoftypeviews.Item {
+// transformItemToResultwithoneoftypeviewsItemView builds a value of type
+// *resultwithoneoftypeviews.ItemView from a value of type *Item.
+func transformItemToResultwithoneoftypeviewsItemView(v *Item) *resultwithoneoftypeviews.ItemView {
 	if v == nil {
 		return nil
 	}
-	res := &resultwithoneoftypeviews.Item{
+	res := &resultwithoneoftypeviews.ItemView{
 		A: v.A,
 	}
 
diff --git a/codegen/service/testdata/views_code.go b/codegen/service/testdata/views_code.go
index 8b0767a0..6c2c0ff3 100644
--- a/codegen/service/testdata/views_code.go
+++ b/codegen/service/testdata/views_code.go
@@ -175,10 +175,15 @@ type ResultType struct {
 
 // ResultTypeView is a type that runs validations on a projected type.
 type ResultTypeView struct {
-	A *UserType
+	A *UserTypeView
 	B *string
 }
 
+// UserTypeView is a type that runs validations on a projected type.
+type UserTypeView struct {
+	A *string
+}
+
 var (
 	// ResultTypeMap is a map indexing the attribute names of ResultType by view
 	// name.
@@ -224,6 +229,12 @@ func ValidateResultTypeViewTiny(result *ResultTypeView) (err error) {
 	}
 	return
 }
+
+// ValidateUserTypeView runs the validations defined on UserTypeView.
+func ValidateUserTypeView(result *UserTypeView) (err error) {
+
+	return
+}
 `
 
 const ResultWithResultTypeCode = `// RT is the viewed result type that is projected based on a view.
@@ -244,14 +255,19 @@ type RTView struct {
 // RT2View is a type that runs validations on a projected type.
 type RT2View struct {
 	C *string
-	D *UserType
+	D *UserTypeView
 	E *string
 }
 
+// UserTypeView is a type that runs validations on a projected type.
+type UserTypeView struct {
+	P *string
+}
+
 // RT3View is a type that runs validations on a projected type.
 type RT3View struct {
 	X []string
-	Y map[int]*UserType
+	Y map[int]*UserTypeView
 	Z *string
 }
 
@@ -375,6 +391,12 @@ func ValidateRT2ViewTiny(result *RT2View) (err error) {
 	return
 }
 
+// ValidateUserTypeView runs the validations defined on UserTypeView.
+func ValidateUserTypeView(result *UserTypeView) (err error) {
+
+	return
+}
+
 // ValidateRT3View runs the validations defined on RT3View using the "default"
 // view.
 func ValidateRT3View(result *RT3View) (err error) {
@@ -741,9 +763,12 @@ type Result struct {
 
 // ResultView is a type that runs validations on a projected type.
 type ResultView struct {
-	T []UserType
+	T []UserTypeView
 }
 
+// UserTypeView is a type that runs validations on a projected type.
+type UserTypeView string
+
 var (
 	// ResultMap is a map indexing the attribute names of Result by view name.
 	ResultMap = map[string][]string{
@@ -774,6 +799,14 @@ func ValidateResultView(result *ResultView) (err error) {
 	}
 	return
 }
+
+// ValidateUserTypeView runs the validations defined on UserTypeView.
+func ValidateUserTypeView(result UserTypeView) (err error) {
+	if !(string(result) == "a" || string(result) == "b") {
+		err = goa.MergeErrors(err, goa.InvalidEnumValueError("result", string(result), []any{"a", "b"}))
+	}
+	return
+}
 `
 
 const ResultWithPkgPathCode = `// RT is the viewed result type that is projected based on a view.
@@ -786,7 +819,12 @@ type RT struct {
 
 // RTView is a type that runs validations on a projected type.
 type RTView struct {
-	A *UserType
+	A *UserTypeView
+}
+
+// UserTypeView is a type that runs validations on a projected type.
+type UserTypeView struct {
+	A *string
 }
 
 var (
@@ -815,4 +853,10 @@ func ValidateRTView(result *RTView) (err error) {
 
 	return
 }
+
+// ValidateUserTypeView runs the validations defined on UserTypeView.
+func ValidateUserTypeView(result *UserTypeView) (err error) {
+
+	return
+}
 `
diff --git a/http/codegen/client_body_types_test.go b/http/codegen/client_body_types_test.go
index f5519bff..1abad7d8 100644
--- a/http/codegen/client_body_types_test.go
+++ b/http/codegen/client_body_types_test.go
@@ -236,7 +236,7 @@ const ExplicitBodyUserResultMultipleViewsInitCode = `// NewMethodExplicitBodyUse
 // "MethodExplicitBodyUserResultMultipleView" endpoint result from a HTTP "OK"
 // response.
 func NewMethodExplicitBodyUserResultMultipleViewResulttypemultipleviewsOK(body *MethodExplicitBodyUserResultMultipleViewResponseBody, c *string) *serviceexplicitbodyuserresultmultipleviewviews.ResulttypemultipleviewsView {
-	v := &serviceexplicitbodyuserresultmultipleviewviews.UserType{
+	v := &serviceexplicitbodyuserresultmultipleviewviews.UserTypeView{
 		X: body.X,
 		Y: body.Y,
 	}
@@ -256,7 +256,7 @@ const ExplicitBodyObjectInitCode = `// NewMethodExplicitBodyUserResultObjectResu
 func NewMethodExplicitBodyUserResultObjectResulttypeOK(body *MethodExplicitBodyUserResultObjectResponseBody, c *string, b *string) *serviceexplicitbodyuserresultobjectviews.ResulttypeView {
 	v := &serviceexplicitbodyuserresultobjectviews.ResulttypeView{}
 	if body.A != nil {
-		v.A = unmarshalUserTypeResponseBodyToServiceexplicitbodyuserresultobjectviewsUserType(body.A)
+		v.A = unmarshalUserTypeResponseBodyToServiceexplicitbodyuserresultobjectviewsUserTypeView(body.A)
 	}
 	v.C = c
 	v.B = b
@@ -272,7 +272,7 @@ const ExplicitBodyObjectViewsInitCode = `// NewMethodExplicitBodyUserResultObjec
 func NewMethodExplicitBodyUserResultObjectMultipleViewResulttypemultipleviewsOK(body *MethodExplicitBodyUserResultObjectMultipleViewResponseBody, c *string) *serviceexplicitbodyuserresultobjectmultipleviewviews.ResulttypemultipleviewsView {
 	v := &serviceexplicitbodyuserresultobjectmultipleviewviews.ResulttypemultipleviewsView{}
 	if body.A != nil {
-		v.A = unmarshalUserTypeResponseBodyToServiceexplicitbodyuserresultobjectmultipleviewviewsUserType(body.A)
+		v.A = unmarshalUserTypeResponseBodyToServiceexplicitbodyuserresultobjectmultipleviewviewsUserTypeView(body.A)
 	}
 	v.C = c