Repository: goadesign/goa
Pull request #2698 (2020-10-28 18:22:20)

Set default value in client request


Patch:
diff --git a/codegen/go_transform.go b/codegen/go_transform.go
index c74f5ee8..a033158f 100644
--- a/codegen/go_transform.go
+++ b/codegen/go_transform.go
@@ -147,6 +147,10 @@ func transformObject(source, target *expr.AttributeExpr, sourceVar, targetVar st
 						}
 						postInitCode += "}\n"
 						return
+					} else if tgtPtr {
+						tmp := Goify(tgtMatt.ElemName(n), false)
+						postInitCode += fmt.Sprintf("%s := %s\n%s.%s = &%s\n", tmp, exp, targetVar, tgtField, tmp)
+						return
 					}
 				case srcPtr && !tgtPtr:
 					exp = "*" + srcField
@@ -232,8 +236,10 @@ func transformObject(source, target *expr.AttributeExpr, sourceVar, targetVar st
 		// Default value handling. We need to handle default values if the target
 		// type uses default values (i.e. attributes with default values are
 		// non-pointers) and has a default value set.
-		if tdef := tgtMatt.GetDefault(n); tdef != nil && ta.TargetCtx.UseDefault {
-			if (ta.SourceCtx.IsPrimitivePointer(n, srcMatt.AttributeExpr) || !expr.IsPrimitive(srcc.Type)) && !srcMatt.IsRequired(n) {
+		if tdef := tgtMatt.GetDefault(n); tdef != nil && ta.TargetCtx.UseDefault && !srcMatt.IsRequired(n) {
+			switch {
+			case ta.SourceCtx.IsPrimitivePointer(n, srcMatt.AttributeExpr) || !expr.IsPrimitive(srcc.Type):
+				// source attribute is a primitive pointer or not a primitive
 				code += fmt.Sprintf("if %s == nil {\n\t", srcVar)
 				if ta.TargetCtx.IsPrimitivePointer(n, tgtMatt.AttributeExpr) && expr.IsPrimitive(tgtc.Type) {
 					code += fmt.Sprintf("var tmp %s = %#v\n\t%s = &tmp\n", GoNativeTypeName(tgtc.Type), tdef, tgtVar)
@@ -241,6 +247,18 @@ func transformObject(source, target *expr.AttributeExpr, sourceVar, targetVar st
 					code += fmt.Sprintf("%s = %#v\n", tgtVar, tdef)
 				}
 				code += "}\n"
+			case expr.IsPrimitive(srcc.Type) && srcMatt.HasDefaultValue(n) && ta.SourceCtx.UseDefault:
+				// source attribute is a primitive with default value
+				// (the field is not a pointer in this case)
+				code += "{\n\t"
+				if _, ok := tgtc.Type.(expr.UserType); ok {
+					// aliased primitive
+					code += fmt.Sprintf("var zero %s\n\t", ta.TargetCtx.Scope.Ref(tgtc, ta.TargetCtx.Pkg))
+				} else {
+					code += fmt.Sprintf("var zero %s\n\t", GoNativeTypeName(tgtc.Type))
+				}
+				code += fmt.Sprintf("if %s == zero {\n\t%s = %#v\n}\n", tgtVar, tgtVar, tdef)
+				code += "}\n"
 			}
 		}
 		buffer.WriteString(code)
diff --git a/codegen/service/service_data.go b/codegen/service/service_data.go
index 6729307a..7d927832 100644
--- a/codegen/service/service_data.go
+++ b/codegen/service/service_data.go
@@ -486,6 +486,7 @@ func (d ServicesData) analyze(service *expr.ServiceExpr) *Data {
 		seenErrors map[string]struct{}
 		seen       map[string]struct{}
 		seenProj   map[string]*ProjectedTypeData
+		seenViewed map[string]*ViewedResultTypeData
 	)
 	{
 		scope = codegen.NewNameScope()
@@ -496,6 +497,7 @@ func (d ServicesData) analyze(service *expr.ServiceExpr) *Data {
 		seen = make(map[string]struct{})
 		seenErrors = make(map[string]struct{})
 		seenProj = make(map[string]*ProjectedTypeData)
+		seenViewed = make(map[string]*ViewedResultTypeData)
 
 		// A function to collect user types from an error expression
 		recordError := func(er *expr.ErrorExpr) {
@@ -586,11 +588,20 @@ func (d ServicesData) analyze(service *expr.ServiceExpr) *Data {
 		for i, e := range service.Methods {
 			m := buildMethodData(e, pkgName, service, scope)
 			if rt, ok := e.Result.Type.(*expr.ResultTypeExpr); ok {
-				projected := seenProj[rt.ID()]
-				projAtt := &expr.AttributeExpr{Type: projected.Type}
-				vrt := buildViewedResultType(e.Result, projAtt, viewspkg, scope, viewScope)
-				viewedRTs = append(viewedRTs, vrt)
-				m.ViewedResult = vrt
+				var view string
+				if v, ok := e.Result.Meta["view"]; ok {
+					view = v[0]
+				}
+				if vrt, ok := seenViewed[m.Result+"::"+view]; ok {
+					m.ViewedResult = vrt
+				} else {
+					projected := seenProj[rt.ID()]
+					projAtt := &expr.AttributeExpr{Type: projected.Type}
+					vrt := buildViewedResultType(e.Result, projAtt, viewspkg, scope, viewScope)
+					viewedRTs = append(viewedRTs, vrt)
+					m.ViewedResult = vrt
+					seenViewed[vrt.Name+"::"+view] = vrt
+				}
 			}
 			methods[i] = m
 			for _, s := range m.Schemes {
@@ -974,8 +985,11 @@ func BuildSchemeData(s *expr.SchemeExpr, m *expr.MethodExpr) *SchemeData {
 }
 
 // collectProjectedTypes builds a projected type for every user type found
-// when recursing through the attributes. It stores the projected types in
-// data.
+// when recursing through the attributes. The projected types live in the views
+// package and support the marshaling and unmarshalling of result types that
+// make use of views. We need to build projected types for all user types - not
+// 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) {
 	collect := func(projected, att *expr.AttributeExpr) []*ProjectedTypeData {
 		return collectProjectedTypes(projected, att, viewspkg, scope, viewScope, seen)
diff --git a/expr/attribute.go b/expr/attribute.go
index 6ca46ef4..538d02cc 100644
--- a/expr/attribute.go
+++ b/expr/attribute.go
@@ -541,6 +541,17 @@ func (a *AttributeExpr) debug(prefix string, seen map[*AttributeExpr]int, indent
 		m.KeyType.debug("key", seen, indent+1)
 		m.ElemType.debug("elem", seen, indent+1)
 	}
+	if rt, ok := a.Type.(*ResultTypeExpr); ok {
+		fmt.Printf("%sviews\n", tab)
+		for _, v := range rt.Views {
+			nats := *AsObject(v.AttributeExpr.Type)
+			keys := make([]string, len(nats))
+			for i, n := range nats {
+				keys[i] = n.Name
+			}
+			fmt.Printf("%s- %s: %v\n", tab+"  ", v.Name, keys)
+		}
+	}
 	if d := a.DefaultValue; d != nil {
 		fmt.Printf("%sdefault\n", tab)
 		fmt.Printf("%s%#v", tab+"  ", a.DefaultValue)
diff --git a/expr/http_body_types.go b/expr/http_body_types.go
index f484707b..5e7d3c29 100644
--- a/expr/http_body_types.go
+++ b/expr/http_body_types.go
@@ -268,18 +268,19 @@ func buildHTTPResponseBody(name string, attr *AttributeExpr, resp *HTTPResponseE
 	}
 }
 
-// buildBodyTypeName concatenates the given strings to generate the
-// endpoint's body type name.
-//
+// concat concatenates the given strings with "smart(?) casing".
 // The concatenation algorithm is:
+//
 // 1) If the first string contains underscores and starts with a lower case,
 // the rest of the strings are converted to lower case and concatenated with
 // underscores.
 // e.g. concat("my_endpoint", "Request", "BODY") => "my_endpoint_request_body"
+//
 // 2) If the first string contains underscores and starts with a upper case,
 // the rest of the strings are converted to title case and concatenated with
 // underscores.
 // e.g. concat("My_endpoint", "response", "body") => "My_endpoint_Response_Body"
+//
 // 3) If the first string is a single word or camelcased, the rest of the
 // strings are concatenated to form a valid upper camelcase.
 // e.g. concat("myEndpoint", "streaming", "Body") => "MyEndpointStreamingBody"
diff --git a/expr/result_type.go b/expr/result_type.go
index 1dcda6eb..647e1caf 100644
--- a/expr/result_type.go
+++ b/expr/result_type.go
@@ -181,6 +181,24 @@ func (m *ResultTypeExpr) ViewHasAttribute(view, attr string) bool {
 // Finalize builds the default view if not explicitly defined and finalizes
 // the underlying UserTypeExpr.
 func (m *ResultTypeExpr) Finalize() {
+	if m.View("default") == nil {
+		m.ensureDefaultView()
+	}
+	m.UserTypeExpr.Finalize()
+	seen := make(map[string]struct{})
+	walkAttribute(m.AttributeExpr, func(_ string, att *AttributeExpr) error {
+		if rt, ok := att.Type.(*ResultTypeExpr); ok {
+			if _, ok := seen[rt.Identifier]; !ok {
+				seen[rt.Identifier] = struct{}{}
+				rt.ensureDefaultView()
+			}
+		}
+		return nil
+	})
+}
+
+// ensureDefaultView builds the default view if not explicitly defined.
+func (m *ResultTypeExpr) ensureDefaultView() {
 	if m.View("default") == nil {
 		att := DupAtt(m.AttributeExpr)
 		if arr := AsArray(att.Type); arr != nil {
@@ -193,7 +211,6 @@ func (m *ResultTypeExpr) Finalize() {
 		}
 		m.Views = append(m.Views, v)
 	}
-	m.UserTypeExpr.Finalize()
 }
 
 // Project creates a ResultTypeExpr containing the fields defined in the view
diff --git a/http/codegen/service_data.go b/http/codegen/service_data.go
index f1b191fd..ebde3f8c 100644
--- a/http/codegen/service_data.go
+++ b/http/codegen/service_data.go
@@ -898,6 +898,7 @@ func makeHTTPType(att *expr.AttributeExpr, seen ...map[string]struct{}) {
 					att.Validation.Merge(v)
 				}
 			}
+			att.DefaultValue = dt.Attribute().DefaultValue
 		}
 		var s map[string]struct{}
 		if len(seen) > 0 {


Test patch:
diff --git a/codegen/go_transform_test.go b/codegen/go_transform_test.go
index 9c5c3f2d..1461212a 100644
--- a/codegen/go_transform_test.go
+++ b/codegen/go_transform_test.go
@@ -161,6 +161,10 @@ func TestGoTransform(t *testing.T) {
 			// arrays
 			{"array-to-default-array", simpleArray, defaultArray, defaultCtx, pointerCtx, srcUseDefaultTgtAllPtrsArrayToDefaultArrayCode},
 
+			// alias
+			{"simple-alias-to-simple", simpleAlias, simple, defaultCtx, pointerCtx, srcUseDefaultTgtAllPtrsSimpleAliasToSimpleCode},
+			{"simple-to-simple-alias", simple, simpleAlias, defaultCtx, pointerCtx, srcUseDefaultTgtAllPtrsSimpleToSimpleAliasCode},
+
 			// others
 			{"recursive-to-recursive", recursive, recursive, defaultCtx, pointerCtx, srcUseDefaultTgtAllPtrsRecursiveToRecursiveCode},
 			{"composite-to-custom-field", composite, customField, defaultCtx, pointerCtx, srcUseDefaultTgtAllPtrsCompositeToCustomFieldCode},
@@ -197,6 +201,12 @@ const (
 		DefaultBool:    source.DefaultBool,
 		Integer:        source.Integer,
 	}
+	{
+		var zero bool
+		if target.DefaultBool == zero {
+			target.DefaultBool = true
+		}
+	}
 }
 `
 
@@ -208,6 +218,12 @@ const (
 	if source.Integer != nil {
 		target.Integer = *source.Integer
 	}
+	{
+		var zero bool
+		if target.DefaultBool == zero {
+			target.DefaultBool = true
+		}
+	}
 }
 `
 
@@ -226,6 +242,12 @@ const (
 		DefaultBool:    source.DefaultBool,
 		Integer:        source.Integer,
 	}
+	{
+		var zero bool
+		if target.DefaultBool == zero {
+			target.DefaultBool = true
+		}
+	}
 }
 `
 
@@ -235,6 +257,12 @@ const (
 		DefaultBool:    source.DefaultBool,
 		Integer:        source.Integer,
 	}
+	{
+		var zero bool
+		if target.DefaultBool == zero {
+			target.DefaultBool = true
+		}
+	}
 }
 `
 
@@ -246,6 +274,12 @@ const (
 	if source.Integer != nil {
 		target.Integer = *source.Integer
 	}
+	{
+		var zero bool
+		if target.DefaultBool == zero {
+			target.DefaultBool = true
+		}
+	}
 	if source.Integer == nil {
 		target.Integer = 1
 	}
@@ -258,6 +292,12 @@ const (
 		DefaultBool:    source.DefaultBool,
 		Integer:        &source.Integer,
 	}
+	{
+		var zero bool
+		if target.DefaultBool == zero {
+			target.DefaultBool = true
+		}
+	}
 }
 `
 
@@ -689,6 +729,12 @@ const (
 		integer := int(*source.Integer)
 		target.Integer = &integer
 	}
+	{
+		var zero bool
+		if target.DefaultBool == zero {
+			target.DefaultBool = true
+		}
+	}
 }
 `
 
@@ -701,6 +747,12 @@ const (
 		integer := IntAlias(*source.Integer)
 		target.Integer = &integer
 	}
+	{
+		var zero BoolAlias
+		if target.DefaultBool == zero {
+			target.DefaultBool = true
+		}
+	}
 }
 `
 
@@ -1098,6 +1150,32 @@ const (
 		}
 	}
 }
+`
+
+	srcUseDefaultTgtAllPtrsSimpleAliasToSimpleCode = `func transform() {
+	target := &Simple{}
+	requiredString := string(source.RequiredString)
+	target.RequiredString = &requiredString
+	defaultBool := bool(source.DefaultBool)
+	target.DefaultBool = &defaultBool
+	if source.Integer != nil {
+		integer := int(*source.Integer)
+		target.Integer = &integer
+	}
+}
+`
+
+	srcUseDefaultTgtAllPtrsSimpleToSimpleAliasCode = `func transform() {
+	target := &SimpleAlias{}
+	requiredString := StringAlias(source.RequiredString)
+	target.RequiredString = &requiredString
+	defaultBool := BoolAlias(source.DefaultBool)
+	target.DefaultBool = &defaultBool
+	if source.Integer != nil {
+		integer := IntAlias(*source.Integer)
+		target.Integer = &integer
+	}
+}
 `
 
 	srcUseDefaultTgtAllPtrsRecursiveToRecursiveCode = `func transform() {
diff --git a/codegen/service/testdata/views_code.go b/codegen/service/testdata/views_code.go
index 27ad51cc..a9a3f389 100644
--- a/codegen/service/testdata/views_code.go
+++ b/codegen/service/testdata/views_code.go
@@ -712,3 +712,44 @@ func ValidateAnotherResultCollectionView(result AnotherResultCollectionView) (er
 	return
 }
 `
+
+const ResultWithMultipleMethodsCode = `// RT is the viewed result type that is projected based on a view.
+type RT struct {
+	// Type to project
+	Projected *RTView
+	// View to render
+	View string
+}
+
+// RTView is a type that runs validations on a projected type.
+type RTView struct {
+	A *string
+}
+
+var (
+	// RTMap is a map of attribute names in result type RT indexed by view name.
+	RTMap = map[string][]string{
+		"default": []string{
+			"a",
+		},
+	}
+)
+
+// ValidateRT runs the validations defined on the viewed result type RT.
+func ValidateRT(result *RT) (err error) {
+	switch result.View {
+	case "default", "":
+		err = ValidateRTView(result.Projected)
+	default:
+		err = goa.InvalidEnumValueError("view", result.View, []interface{}{"default"})
+	}
+	return
+}
+
+// ValidateRTView runs the validations defined on RTView using the "default"
+// view.
+func ValidateRTView(result *RTView) (err error) {
+
+	return
+}
+`
diff --git a/codegen/service/testdata/views_dsls.go b/codegen/service/testdata/views_dsls.go
index 475eb500..32cc5e25 100644
--- a/codegen/service/testdata/views_dsls.go
+++ b/codegen/service/testdata/views_dsls.go
@@ -201,6 +201,23 @@ var ResultWithRecursiveCollectionOfResultTypeDSL = func() {
 	})
 }
 
+var ResultWithMultipleMethodsDSL = func() {
+	var RT = ResultType("application/vnd.some_result", func() {
+		TypeName("RT")
+		Attributes(func() {
+			Attribute("a")
+		})
+	})
+	Service("ResultWithMultipleMethods", func() {
+		Method("A", func() {
+			Result(RT)
+		})
+		Method("B", func() {
+			Result(RT)
+		})
+	})
+}
+
 var ResultWithCustomFieldsDSL = func() {
 	var RT = ResultType("application/vnd.result", func() {
 		TypeName("RT")
diff --git a/codegen/service/views_test.go b/codegen/service/views_test.go
index bd3fa876..a07b101a 100644
--- a/codegen/service/views_test.go
+++ b/codegen/service/views_test.go
@@ -24,6 +24,7 @@ func TestViews(t *testing.T) {
 		{"result-with-recursive-result-type", testdata.ResultWithRecursiveResultTypeDSL, testdata.ResultWithRecursiveResultTypeCode},
 		{"result-type-with-custom-fields", testdata.ResultWithCustomFieldsDSL, testdata.ResultWithCustomFieldsCode},
 		{"result-with-recursive-collection-of-result-type", testdata.ResultWithRecursiveCollectionOfResultTypeDSL, testdata.ResultWithRecursiveCollectionOfResultTypeCode},
+		{"result-with-multiple-methods", testdata.ResultWithMultipleMethodsDSL, testdata.ResultWithMultipleMethodsCode},
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
diff --git a/http/codegen/server_types_test.go b/http/codegen/server_types_test.go
index 49fad0f7..c78fe7e9 100644
--- a/http/codegen/server_types_test.go
+++ b/http/codegen/server_types_test.go
@@ -21,6 +21,7 @@ func TestServerTypes(t *testing.T) {
 		{"payload-extend-validate", testdata.PayloadExtendedValidateDSL, PayloadExtendedValidateServerTypesFile},
 		{"result-type-validate", testdata.ResultTypeValidateDSL, ResultTypeValidateServerTypesFile},
 		{"with-result-collection", testdata.ResultWithResultCollectionDSL, ResultWithResultCollectionServerTypesFile},
+		{"with-result-view", testdata.ResultWithResultViewDSL, ResultWithResultViewServerTypesFile},
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
@@ -273,3 +274,30 @@ func NewMethodResultWithResultCollectionResponseBody(res *serviceresultwithresul
 	return body
 }
 `
+
+const ResultWithResultViewServerTypesFile = `// MethodResultWithResultViewResponseBodyFull is the type of the
+// "ServiceResultWithResultView" service "MethodResultWithResultView" endpoint
+// HTTP response body.
+type MethodResultWithResultViewResponseBodyFull struct {
+	Name *string         ` + "`" + `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` + "`" + `
+	Rt   *RtResponseBody ` + "`" + `form:"rt,omitempty" json:"rt,omitempty" xml:"rt,omitempty"` + "`" + `
+}
+
+// RtResponseBody is used to define fields on response body types.
+type RtResponseBody struct {
+	X *string ` + "`" + `form:"x,omitempty" json:"x,omitempty" xml:"x,omitempty"` + "`" + `
+}
+
+// NewMethodResultWithResultViewResponseBodyFull builds the HTTP response body
+// from the result of the "MethodResultWithResultView" endpoint of the
+// "ServiceResultWithResultView" service.
+func NewMethodResultWithResultViewResponseBodyFull(res *serviceresultwithresultviewviews.ResulttypeView) *MethodResultWithResultViewResponseBodyFull {
+	body := &MethodResultWithResultViewResponseBodyFull{
+		Name: res.Name,
+	}
+	if res.Rt != nil {
+		body.Rt = marshalServiceresultwithresultviewviewsRtViewToRtResponseBody(res.Rt)
+	}
+	return body
+}
+`
diff --git a/http/codegen/testdata/result_dsls.go b/http/codegen/testdata/result_dsls.go
index e1acbd59..8b91c920 100644
--- a/http/codegen/testdata/result_dsls.go
+++ b/http/codegen/testdata/result_dsls.go
@@ -744,6 +744,38 @@ var ResultWithResultCollectionDSL = func() {
 	})
 }
 
+var ResultWithResultViewDSL = func() {
+	var RT = ResultType("RT", func() {
+		Attributes(func() {
+			Attribute("x")
+		})
+	})
+	var ResultType = ResultType("ResultType", func() {
+		Attributes(func() {
+			Attribute("name")
+			Attribute("rt", RT)
+		})
+		View("full", func() {
+			Attribute("name")
+			Attribute("rt")
+		})
+		View("default", func() {
+			Attribute("name")
+		})
+	})
+	Service("ServiceResultWithResultView", func() {
+		Method("MethodResultWithResultView", func() {
+			Result(ResultType, func() {
+				View("full")
+			})
+			HTTP(func() {
+				POST("/")
+				Response(StatusOK)
+			})
+		})
+	})
+}
+
 var EmptyBodyResultMultipleViewsDSL = func() {
 	var ResultType = ResultType("ResultTypeMultipleViews", func() {
 		Attribute("a", String)
diff --git a/http/codegen/testdata/transform_helper_functions.go b/http/codegen/testdata/transform_helper_functions.go
index 0c0a3ffa..0d4d709a 100644
--- a/http/codegen/testdata/transform_helper_functions.go
+++ b/http/codegen/testdata/transform_helper_functions.go
@@ -46,6 +46,12 @@ func marshalInnerTypeRequestBodyToServicebodyuserinnerdefaultInnerType(v *InnerT
 		A: v.A,
 		B: v.B,
 	}
+	{
+		var zero string
+		if res.B == zero {
+			res.B = "defaultb"
+		}
+	}
 
 	return res
 }
@@ -62,6 +68,12 @@ func marshalServicebodyuserinnerdefaultInnerTypeToInnerTypeRequestBody(v *servic
 		A: v.A,
 		B: v.B,
 	}
+	{
+		var zero string
+		if res.B == zero {
+			res.B = "defaultb"
+		}
+	}
 
 	return res
 }