Repository: goadesign/goa (go)

Issue:
How to define a ResultType for Cycle struct?
# My first time try code 
### My DSL

```
var CategoryResult = ResultType("application/vnd.category", func() {
	Description("category")
	Attributes(func() {
		Field(1, "id", Int, "ID")
		Field(2, "p_category_id", Int, "p_category_id")
		Field(3, "children_category", CollectionOf("application/vnd.category"), "children_category")
		Field(4, "name", String, "name")
	})

	View("default", func() {
		Field(1, "id")
		Field(2, "p_category_id")
		Field(3, "name")
	})

	View("full", func() {
		Field(1, "id")
		Field(2, "p_category_id")
		Field(3, "name")
	})
})
```
### The error info

```
invalid CollectionOf argument: not a result type and not a known result type identifier in attribute
```

# My second time try code

### My DSL

```
var CategoryResult = ResultType("application/vnd.category", func() {
	Description("category")
	Attributes(func() {
		Field(1, "id", Int, "ID")
		Field(2, "p_category_id", Int, "p_category_id")
		Field(3, "children_category", CollectionOf(CategoryResult), "children_category")
		Field(4, "name", String, "name")
	})

	View("default", func() {
		Field(1, "id")
		Field(2, "p_category_id")
		Field(3, "name")
	})

	View("full", func() {
		Field(1, "id")
		Field(2, "p_category_id")
		Field(3, "name")
	})
})
```
### The error info

```
 initialization loop:
        _resources.go:26:5: CategoryResult refers to
       _resources.go:28:13: glob..func39.1 refers to
        _resources.go:26:5: CategoryResult
```

# My question
How to define a struct like the follow code in goa by DSL ?

```
type Category  struct {
	ID int `json:"id"`
	PCategoryID int `json:"p_category_id"`
	ChildrenCategory []Category `json:"children_category"`
	Name string `json:"name"`
}
```








Pull request:
Fix cyclic definition for CollectionOf
Fixes #2514 

Patch:
diff --git a/dsl/result_type.go b/dsl/result_type.go
index e52e493c..68606b4c 100644
--- a/dsl/result_type.go
+++ b/dsl/result_type.go
@@ -67,14 +67,13 @@ func ResultType(identifier string, fn func()) *expr.ResultTypeExpr {
 		return nil
 	}
 
+	identifier, typeName, err := mediaTypeToResultType(identifier)
 	// Validate Result Type
-	identifier, params, err := mime.ParseMediaType(identifier)
 	if err != nil {
 		eval.ReportError("invalid result type identifier %#v: %s",
 			identifier, err)
 		// We don't return so that other errors may be captured in this
 		// one run.
-		identifier = "text/plain"
 	}
 	canonicalID := expr.CanonicalIdentifier(identifier)
 	// Validate that result type identifier doesn't clash
@@ -86,26 +85,6 @@ func ResultType(identifier string, fn func()) *expr.ResultTypeExpr {
 			return nil
 		}
 	}
-	identifier = mime.FormatMediaType(identifier, params)
-	lastPart := identifier
-	lastPartIndex := strings.LastIndex(identifier, "/")
-	if lastPartIndex > -1 {
-		lastPart = identifier[lastPartIndex+1:]
-	}
-	plusIndex := strings.Index(lastPart, "+")
-	if plusIndex > 0 {
-		lastPart = lastPart[:plusIndex]
-	}
-	lastPart = strings.TrimPrefix(lastPart, "vnd.")
-	elems := strings.Split(lastPart, ".")
-	for i, e := range elems {
-		elems[i] = strings.Title(e)
-	}
-	typeName := strings.Join(elems, "")
-	if typeName == "" {
-		resultTypeCount++
-		typeName = fmt.Sprintf("ResultType%d", resultTypeCount)
-	}
 	// Now save the type in the API result types map
 	mt := expr.NewResultTypeExpr(typeName, identifier, fn)
 	expr.Root.ResultTypes = append(expr.Root.ResultTypes, mt)
@@ -282,10 +261,22 @@ func CollectionOf(v interface{}, adsl ...func()) *expr.ResultTypeExpr {
 	m, ok = v.(*expr.ResultTypeExpr)
 	if !ok {
 		if id, ok := v.(string); ok {
-			if dt := expr.Root.UserType(expr.CanonicalIdentifier(id)); dt != nil {
+			// Check if a result type exists with the given type name
+			if dt := expr.Root.UserType(id); dt != nil {
 				if mt, ok := dt.(*expr.ResultTypeExpr); ok {
 					m = mt
 				}
+			} else {
+				// Check if a result type exists with the given identifier
+				id, typeName, err := mediaTypeToResultType(id)
+				if dt := expr.Root.UserType(typeName); dt != nil {
+					if mt, ok := dt.(*expr.ResultTypeExpr); ok {
+						m = mt
+					}
+				}
+				if err != nil {
+					eval.ReportError("invalid result type identifier %#v in CollectionOf: %s", id, err)
+				}
 			}
 		}
 	}
@@ -447,6 +438,37 @@ func Attributes(fn func()) {
 	eval.Execute(fn, mt)
 }
 
+// mediaTypeToResultType returns the formatted identifier and the result type
+// name from the given identifier string. If the given identifier is invalid it
+// returns text/plain as the identifier and an error.
+func mediaTypeToResultType(identifier string) (string, string, error) {
+	identifier, params, err := mime.ParseMediaType(identifier)
+	if err != nil {
+		identifier = "text/plain"
+	}
+	identifier = mime.FormatMediaType(identifier, params)
+	lastPart := identifier
+	lastPartIndex := strings.LastIndex(identifier, "/")
+	if lastPartIndex > -1 {
+		lastPart = identifier[lastPartIndex+1:]
+	}
+	plusIndex := strings.Index(lastPart, "+")
+	if plusIndex > 0 {
+		lastPart = lastPart[:plusIndex]
+	}
+	lastPart = strings.TrimPrefix(lastPart, "vnd.")
+	elems := strings.Split(lastPart, ".")
+	for i, e := range elems {
+		elems[i] = strings.Title(e)
+	}
+	typeName := strings.Join(elems, "")
+	if typeName == "" {
+		resultTypeCount++
+		typeName = fmt.Sprintf("ResultType%d", resultTypeCount)
+	}
+	return identifier, typeName, err
+}
+
 // buildView builds a view expression given an attribute and a corresponding
 // result type. The attribute must be an object listing the child attributes
 // that make up the view.
diff --git a/expr/grpc_endpoint.go b/expr/grpc_endpoint.go
index 12931f60..88677657 100644
--- a/expr/grpc_endpoint.go
+++ b/expr/grpc_endpoint.go
@@ -319,6 +319,13 @@ func (e *GRPCEndpointExpr) Finalize() {
 	// Finalize streaming payload type if defined
 	if e.MethodExpr.StreamingPayload.Type != Empty {
 		initAttrFromDesign(e.StreamingRequest, e.MethodExpr.StreamingPayload)
+		if msgObj := AsObject(e.StreamingRequest.Type); msgObj != nil {
+			for _, nat := range *msgObj {
+				if e.MethodExpr.StreamingPayload.IsRequired(nat.Name) {
+					e.StreamingRequest.Validation.AddRequired(nat.Name)
+				}
+			}
+		}
 	}
 
 	// Finalize response


Test patch:
diff --git a/codegen/service/testdata/views_code.go b/codegen/service/testdata/views_code.go
index d2496fd2..27ad51cc 100644
--- a/codegen/service/testdata/views_code.go
+++ b/codegen/service/testdata/views_code.go
@@ -547,3 +547,168 @@ func ValidateRTViewTiny(result *RTView) (err error) {
 	return
 }
 `
+
+const ResultWithRecursiveCollectionOfResultTypeCode = `// SomeRT is the viewed result type that is projected based on a view.
+type SomeRT struct {
+	// Type to project
+	Projected *SomeRTView
+	// View to render
+	View string
+}
+
+// AnotherResult is the viewed result type that is projected based on a view.
+type AnotherResult struct {
+	// Type to project
+	Projected *AnotherResultView
+	// View to render
+	View string
+}
+
+// SomeRTView is a type that runs validations on a projected type.
+type SomeRTView struct {
+	A SomeRTCollectionView
+}
+
+// SomeRTCollectionView is a type that runs validations on a projected type.
+type SomeRTCollectionView []*SomeRTView
+
+// AnotherResultView is a type that runs validations on a projected type.
+type AnotherResultView struct {
+	A AnotherResultCollectionView
+}
+
+// AnotherResultCollectionView is a type that runs validations on a projected
+// type.
+type AnotherResultCollectionView []*AnotherResultView
+
+var (
+	// SomeRTMap is a map of attribute names in result type SomeRT indexed by view
+	// name.
+	SomeRTMap = map[string][]string{
+		"default": []string{
+			"a",
+		},
+		"tiny": []string{
+			"a",
+		},
+	}
+	// AnotherResultMap is a map of attribute names in result type AnotherResult
+	// indexed by view name.
+	AnotherResultMap = map[string][]string{
+		"default": []string{
+			"a",
+		},
+	}
+	// SomeRTCollectionMap is a map of attribute names in result type
+	// SomeRTCollection indexed by view name.
+	SomeRTCollectionMap = map[string][]string{
+		"default": []string{
+			"a",
+		},
+		"tiny": []string{
+			"a",
+		},
+	}
+	// AnotherResultCollectionMap is a map of attribute names in result type
+	// AnotherResultCollection indexed by view name.
+	AnotherResultCollectionMap = map[string][]string{
+		"default": []string{
+			"a",
+		},
+	}
+)
+
+// ValidateSomeRT runs the validations defined on the viewed result type SomeRT.
+func ValidateSomeRT(result *SomeRT) (err error) {
+	switch result.View {
+	case "default", "":
+		err = ValidateSomeRTView(result.Projected)
+	case "tiny":
+		err = ValidateSomeRTViewTiny(result.Projected)
+	default:
+		err = goa.InvalidEnumValueError("view", result.View, []interface{}{"default", "tiny"})
+	}
+	return
+}
+
+// ValidateAnotherResult runs the validations defined on the viewed result type
+// AnotherResult.
+func ValidateAnotherResult(result *AnotherResult) (err error) {
+	switch result.View {
+	case "default", "":
+		err = ValidateAnotherResultView(result.Projected)
+	default:
+		err = goa.InvalidEnumValueError("view", result.View, []interface{}{"default"})
+	}
+	return
+}
+
+// ValidateSomeRTView runs the validations defined on SomeRTView using the
+// "default" view.
+func ValidateSomeRTView(result *SomeRTView) (err error) {
+
+	if result.A != nil {
+		if err2 := ValidateSomeRTCollectionViewTiny(result.A); err2 != nil {
+			err = goa.MergeErrors(err, err2)
+		}
+	}
+	return
+}
+
+// ValidateSomeRTViewTiny runs the validations defined on SomeRTView using the
+// "tiny" view.
+func ValidateSomeRTViewTiny(result *SomeRTView) (err error) {
+
+	if result.A != nil {
+		if err2 := ValidateSomeRTCollectionView(result.A); err2 != nil {
+			err = goa.MergeErrors(err, err2)
+		}
+	}
+	return
+}
+
+// ValidateSomeRTCollectionView runs the validations defined on
+// SomeRTCollectionView using the "default" view.
+func ValidateSomeRTCollectionView(result SomeRTCollectionView) (err error) {
+	for _, item := range result {
+		if err2 := ValidateSomeRTView(item); err2 != nil {
+			err = goa.MergeErrors(err, err2)
+		}
+	}
+	return
+}
+
+// ValidateSomeRTCollectionViewTiny runs the validations defined on
+// SomeRTCollectionView using the "tiny" view.
+func ValidateSomeRTCollectionViewTiny(result SomeRTCollectionView) (err error) {
+	for _, item := range result {
+		if err2 := ValidateSomeRTViewTiny(item); err2 != nil {
+			err = goa.MergeErrors(err, err2)
+		}
+	}
+	return
+}
+
+// ValidateAnotherResultView runs the validations defined on AnotherResultView
+// using the "default" view.
+func ValidateAnotherResultView(result *AnotherResultView) (err error) {
+
+	if result.A != nil {
+		if err2 := ValidateAnotherResultCollectionView(result.A); err2 != nil {
+			err = goa.MergeErrors(err, err2)
+		}
+	}
+	return
+}
+
+// ValidateAnotherResultCollectionView runs the validations defined on
+// AnotherResultCollectionView using the "default" view.
+func ValidateAnotherResultCollectionView(result AnotherResultCollectionView) (err error) {
+	for _, item := range result {
+		if err2 := ValidateAnotherResultView(item); err2 != nil {
+			err = goa.MergeErrors(err, err2)
+		}
+	}
+	return
+}
+`
diff --git a/codegen/service/testdata/views_dsls.go b/codegen/service/testdata/views_dsls.go
index fb31f611..475eb500 100644
--- a/codegen/service/testdata/views_dsls.go
+++ b/codegen/service/testdata/views_dsls.go
@@ -169,6 +169,38 @@ var ResultWithRecursiveResultTypeDSL = func() {
 	})
 }
 
+var ResultWithRecursiveCollectionOfResultTypeDSL = func() {
+	var SomeRT = ResultType("application/vnd.some_result", func() {
+		TypeName("SomeRT")
+		Attributes(func() {
+			Attribute("a", CollectionOf("SomeRT"))
+			Required("a")
+		})
+		View("default", func() {
+			Attribute("a", func() {
+				View("tiny")
+			})
+		})
+		View("tiny", func() {
+			Attribute("a")
+		})
+	})
+	var AnotherRT = ResultType("application/vnd.another_result", func() {
+		Attributes(func() {
+			Attribute("a", CollectionOf("application/vnd.another_result"))
+			Required("a")
+		})
+	})
+	Service("ResultWithRecursiveCollectionOfResultType", func() {
+		Method("A", func() {
+			Result(SomeRT)
+		})
+		Method("B", func() {
+			Result(AnotherRT)
+		})
+	})
+}
+
 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 caa5ebd3..bd3fa876 100644
--- a/codegen/service/views_test.go
+++ b/codegen/service/views_test.go
@@ -23,6 +23,7 @@ func TestViews(t *testing.T) {
 		{"result-with-result-type", testdata.ResultWithResultTypeDSL, testdata.ResultWithResultTypeCode},
 		{"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},
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
diff --git a/grpc/codegen/server_types_test.go b/grpc/codegen/server_types_test.go
index 8a1d82ab..1b35897d 100644
--- a/grpc/codegen/server_types_test.go
+++ b/grpc/codegen/server_types_test.go
@@ -17,6 +17,7 @@ func TestServerTypeFiles(t *testing.T) {
 	}{
 		{"payload-with-nested-types", testdata.PayloadWithNestedTypesDSL, testdata.PayloadWithNestedTypesServerTypeCode},
 		{"payload-with-alias-type", testdata.PayloadWithAliasTypeDSL, testdata.PayloadWithAliasTypeServerTypeCode},
+		{"payload-with-mixed-attributes", testdata.PayloadWithMixedAttributesDSL, testdata.PayloadWithMixedAttributesServerTypeCode},
 		{"result-collection", testdata.ResultWithCollectionDSL, testdata.ResultWithCollectionServerTypeCode},
 		{"with-errors", testdata.UnaryRPCWithErrorsDSL, testdata.WithErrorsServerTypeCode},
 	}
diff --git a/grpc/codegen/testdata/dsls.go b/grpc/codegen/testdata/dsls.go
index 91256894..1fbcdc0e 100644
--- a/grpc/codegen/testdata/dsls.go
+++ b/grpc/codegen/testdata/dsls.go
@@ -487,6 +487,34 @@ var ResultWithCollectionDSL = func() {
 	})
 }
 
+var PayloadWithMixedAttributesDSL = func() {
+	var APayload = Type("APayload", func() {
+		Field(1, "optional", Int)
+		Field(2, "required", Int)
+		Field(3, "default", Int, func() {
+			Default(100)
+		})
+		Field(5, "required_default", Int, func() {
+			Default(100000)
+		})
+		Required("required", "required_default")
+	})
+	Service("ServicePayloadWithMixedAttributes", func() {
+		Method("UnaryMethod", func() {
+			Payload(APayload)
+			GRPC(func() {
+				Response(CodeOK)
+			})
+		})
+		Method("StreamingMethod", func() {
+			StreamingPayload(APayload)
+			GRPC(func() {
+				Response(CodeOK)
+			})
+		})
+	})
+}
+
 var PayloadWithNestedTypesDSL = func() {
 	var AParams = Type("AParams", func() {
 		Field(1, "a", MapOf(String, ArrayOf(String)))
diff --git a/grpc/codegen/testdata/server_type_code.go b/grpc/codegen/testdata/server_type_code.go
index 3628b8c4..0039e51e 100644
--- a/grpc/codegen/testdata/server_type_code.go
+++ b/grpc/codegen/testdata/server_type_code.go
@@ -194,6 +194,56 @@ func protobufServiceResultWithCollectionpbResultTToServiceresultwithcollectionRe
 }
 `
 
+const PayloadWithMixedAttributesServerTypeCode = `// NewUnaryMethodPayload builds the payload of the "UnaryMethod" endpoint of
+// the "ServicePayloadWithMixedAttributes" service from the gRPC request type.
+func NewUnaryMethodPayload(message *service_payload_with_mixed_attributespb.UnaryMethodRequest) *servicepayloadwithmixedattributes.APayload {
+	v := &servicepayloadwithmixedattributes.APayload{
+		Required:        int(message.Required),
+		Default:         int(message.Default),
+		RequiredDefault: int(message.RequiredDefault),
+	}
+	if message.Optional != 0 {
+		optionalptr := int(message.Optional)
+		v.Optional = &optionalptr
+	}
+	if message.Default == 0 {
+		v.Default = 100
+	}
+	return v
+}
+
+// NewUnaryMethodResponse builds the gRPC response type from the result of the
+// "UnaryMethod" endpoint of the "ServicePayloadWithMixedAttributes" service.
+func NewUnaryMethodResponse() *service_payload_with_mixed_attributespb.UnaryMethodResponse {
+	message := &service_payload_with_mixed_attributespb.UnaryMethodResponse{}
+	return message
+}
+
+// NewStreamingMethodResponse builds the gRPC response type from the result of
+// the "StreamingMethod" endpoint of the "ServicePayloadWithMixedAttributes"
+// service.
+func NewStreamingMethodResponse() *service_payload_with_mixed_attributespb.StreamingMethodResponse {
+	message := &service_payload_with_mixed_attributespb.StreamingMethodResponse{}
+	return message
+}
+
+func NewAPayload(v *service_payload_with_mixed_attributespb.APayload) *servicepayloadwithmixedattributes.APayload {
+	spayload := &servicepayloadwithmixedattributes.APayload{
+		Required:        int(v.Required),
+		Default:         int(v.Default),
+		RequiredDefault: int(v.RequiredDefault),
+	}
+	if v.Optional != 0 {
+		optionalptr := int(v.Optional)
+		spayload.Optional = &optionalptr
+	}
+	if v.Default == 0 {
+		spayload.Default = 100
+	}
+	return spayload
+}
+`
+
 const WithErrorsServerTypeCode = `// NewMethodUnaryRPCWithErrorsPayload builds the payload of the
 // "MethodUnaryRPCWithErrors" endpoint of the "ServiceUnaryRPCWithErrors"
 // service from the gRPC request type.