Repository: goadesign/goa (go)

Issue:
Union attributes are not described by the OpenAPI specification
Types that are only used as union attributes are not being described by Goa in the OpenAPI specification.
That makes sense given that they map as JSON-encoded strings, but it would be useful to have a mechanism that can be used to force a type to appear in the spec. I have tried both `type:generate:force` and `openapi:generate` with no luck.

Link to [conversation in Slack](https://gophers.slack.com/team/U043DU7LM).

Thank you!

Pull request description:
Generate OpenAPI spec for forced types
Fixes #3129

Patch:
diff --git a/http/codegen/openapi/v3/types.go b/http/codegen/openapi/v3/types.go
index f25cbb42..dfdb1c98 100644
--- a/http/codegen/openapi/v3/types.go
+++ b/http/codegen/openapi/v3/types.go
@@ -130,6 +130,13 @@ func buildBodyTypes(api *expr.APIExpr) (map[string]map[string]*EndpointBodies, m
 		}
 		bodies[s.Name()] = sbodies
 	}
+	for _, t := range expr.Root.Types {
+		_, ok := t.Attribute().Meta["type:generate:force"]
+		if !ok {
+			continue
+		}
+		sf.schemafy(&expr.AttributeExpr{Type: t})
+	}
 	return bodies, sf.schemas
 }
 


Test patch:
diff --git a/http/codegen/openapi/v3/testdata/dsls/types.go b/http/codegen/openapi/v3/testdata/dsls/types.go
index 54aa847e..ba9b387f 100644
--- a/http/codegen/openapi/v3/testdata/dsls/types.go
+++ b/http/codegen/openapi/v3/testdata/dsls/types.go
@@ -150,3 +150,19 @@ func ObjectErrorResponseBodyDSL(svcName, metName string) func() {
 		})
 	}
 }
+
+func ForcedTypeDSL(svcName, metName string) func() {
+	return func() {
+		var _ = Type("Forced", func() {
+			Attribute("foo")
+			Meta("type:generate:force")
+		})
+		var _ = Service(svcName, func() {
+			Method(metName, func() {
+				HTTP(func() {
+					POST("/")
+				})
+			})
+		})
+	}
+}
diff --git a/http/codegen/openapi/v3/types_test.go b/http/codegen/openapi/v3/types_test.go
index ace4e4a3..8a0407ad 100644
--- a/http/codegen/openapi/v3/types_test.go
+++ b/http/codegen/openapi/v3/types_test.go
@@ -70,6 +70,7 @@ func TestBuildBodyTypes(t *testing.T) {
 		ExpectedType          typ
 		ExpectedFormat        string
 		ExpectedResponseTypes rt
+		ExpectedExtraTypes    map[string]typ
 	}{{
 		Name: "string_body",
 		DSL:  dsls.StringBodyDSL(svcName, "string_body"),
@@ -142,6 +143,13 @@ func TestBuildBodyTypes(t *testing.T) {
 
 		ExpectedType:          tempty,
 		ExpectedResponseTypes: rt{204: tempty, 400: tobj("name", tstring, "age", tint)},
+	}, {
+		Name: "forced_type",
+		DSL:  dsls.ForcedTypeDSL(svcName, "forced_type"),
+
+		ExpectedType:          tempty,
+		ExpectedResponseTypes: rt{204: tempty},
+		ExpectedExtraTypes:    map[string]typ{"Forced": tobj("foo", tstring)},
 	}}
 
 	for _, c := range cases {
@@ -180,6 +188,14 @@ func TestBuildBodyTypes(t *testing.T) {
 				}
 				matchesSchema(t, "response", met.ResponseBodies[s][0], types, r)
 			}
+			for name, forced := range c.ExpectedExtraTypes {
+				got, ok := types[name]
+				if !ok {
+					t.Errorf("missing forced type %q", name)
+					continue
+				}
+				matchesSchema(t, "extra type", got, types, forced)
+			}
 		})
 	}
 }