Repository: goadesign/goa
Pull request #3437 (2023-12-29 07:35:56)

Add openapi:generate Meta support to dsl.Attribute
Related to #3405 #3406

tchssk: It's ready to merge.

Patch:
diff --git a/expr/example.go b/expr/example.go
index b3f3dab4..6eedb51b 100644
--- a/expr/example.go
+++ b/expr/example.go
@@ -30,7 +30,15 @@ func (a *AttributeExpr) Example(r *ExampleGenerator) any {
 		return nil
 	}
 
-	value, ok := a.Meta.Last("openapi:example")
+	value, ok := a.Meta.Last("openapi:generate")
+	if !ok {
+		value, ok = a.Meta.Last("swagger:generate")
+	}
+	if ok && value == "false" {
+		return nil
+	}
+
+	value, ok = a.Meta.Last("openapi:example")
 	if !ok {
 		value, ok = a.Meta.Last("swagger:example")
 	}
diff --git a/http/codegen/openapi/json_schema.go b/http/codegen/openapi/json_schema.go
index 82d1b3c5..b7d3724d 100644
--- a/http/codegen/openapi/json_schema.go
+++ b/http/codegen/openapi/json_schema.go
@@ -344,6 +344,9 @@ func TypeSchemaWithPrefix(api *expr.APIExpr, t expr.DataType, prefix string) *Sc
 	case *expr.Object:
 		s.Type = Object
 		for _, nat := range *actual {
+			if !mustGenerate(nat.Attribute.Meta) {
+				continue
+			}
 			prop := NewSchema()
 			buildAttributeSchema(api, prop, nat.Attribute)
 			s.Properties[nat.Name] = prop
@@ -565,3 +568,16 @@ func buildResultTypeSchema(api *expr.APIExpr, mt *expr.ResultTypeExpr, view stri
 	}
 	buildAttributeSchema(api, s, projected.AttributeExpr)
 }
+
+// mustGenerate returns true if the meta indicates that a OpenAPI specification should be
+// generated, false otherwise.
+func mustGenerate(meta expr.MetaExpr) bool {
+	m, ok := meta.Last("openapi:generate")
+	if !ok {
+		m, ok = meta.Last("swagger:generate")
+	}
+	if ok && m == "false" {
+		return false
+	}
+	return true
+}
diff --git a/http/codegen/openapi/v3/types.go b/http/codegen/openapi/v3/types.go
index 7ccc9c1f..f8661a51 100644
--- a/http/codegen/openapi/v3/types.go
+++ b/http/codegen/openapi/v3/types.go
@@ -187,6 +187,9 @@ func (sf *schemafier) schemafy(attr *expr.AttributeExpr, noref ...bool) *openapi
 		s.Type = openapi.Object
 		var itemNotes []string
 		for _, nat := range *t {
+			if !mustGenerate(nat.Attribute.Meta) {
+				continue
+			}
 			s.Properties[nat.Name] = sf.schemafy(nat.Attribute)
 		}
 		if len(itemNotes) > 0 {
@@ -381,6 +384,9 @@ func hashAttribute(att *expr.AttributeExpr, h hash.Hash64, seen map[string]*uint
 	case expr.ObjectKind:
 		o := expr.AsObject(t)
 		for _, m := range *o {
+			if !mustGenerate(m.Attribute.Meta) {
+				continue
+			}
 			kh := hashString(m.Name, h)
 			vh := hashAttribute(m.Attribute, h, seen)
 			*res = *res ^ orderedHash(kh, *vh, h)


Test patch:
diff --git a/http/codegen/openapi/v2/files_test.go b/http/codegen/openapi/v2/files_test.go
index 4c628b96..2e828155 100644
--- a/http/codegen/openapi/v2/files_test.go
+++ b/http/codegen/openapi/v2/files_test.go
@@ -43,6 +43,7 @@ func TestSections(t *testing.T) {
 		{"typename", testdata.TypenameDSL},
 		{"not-generate-server", testdata.NotGenerateServerDSL},
 		{"not-generate-host", testdata.NotGenerateHostDSL},
+		{"not-generate-attribute", testdata.NotGenerateAttributeDSL},
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
diff --git a/http/codegen/openapi/v2/testdata/TestSections/not-generate-attribute_file0.golden b/http/codegen/openapi/v2/testdata/TestSections/not-generate-attribute_file0.golden
new file mode 100644
index 00000000..8b00cc68
--- /dev/null
+++ b/http/codegen/openapi/v2/testdata/TestSections/not-generate-attribute_file0.golden
@@ -0,0 +1,1 @@
+{"swagger":"2.0","info":{"title":"","version":""},"host":"goa.design","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/":{"get":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","parameters":[{"name":"TestEndpointRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/TestServiceTestEndpointRequestBody"}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/TestServiceTestEndpointResponseBody"}}},"schemes":["https"]}}},"definitions":{"TestServiceTestEndpointRequestBody":{"title":"TestServiceTestEndpointRequestBody","type":"object","properties":{"string":{"type":"string","example":""}},"example":{"string":""}},"TestServiceTestEndpointResponseBody":{"title":"TestServiceTestEndpointResponseBody","type":"object","properties":{"int":{"type":"integer","example":0,"format":"int64"}},"example":{"int":0}}}}
\ No newline at end of file
diff --git a/http/codegen/openapi/v2/testdata/TestSections/not-generate-attribute_file1.golden b/http/codegen/openapi/v2/testdata/TestSections/not-generate-attribute_file1.golden
new file mode 100644
index 00000000..0736ec84
--- /dev/null
+++ b/http/codegen/openapi/v2/testdata/TestSections/not-generate-attribute_file1.golden
@@ -0,0 +1,53 @@
+swagger: "2.0"
+info:
+    title: ""
+    version: ""
+host: goa.design
+consumes:
+    - application/json
+    - application/xml
+    - application/gob
+produces:
+    - application/json
+    - application/xml
+    - application/gob
+paths:
+    /:
+        get:
+            tags:
+                - testService
+            summary: testEndpoint testService
+            operationId: testService#testEndpoint
+            parameters:
+                - name: TestEndpointRequestBody
+                  in: body
+                  required: true
+                  schema:
+                    $ref: '#/definitions/TestServiceTestEndpointRequestBody'
+            responses:
+                "200":
+                    description: OK response.
+                    schema:
+                        $ref: '#/definitions/TestServiceTestEndpointResponseBody'
+            schemes:
+                - https
+definitions:
+    TestServiceTestEndpointRequestBody:
+        title: TestServiceTestEndpointRequestBody
+        type: object
+        properties:
+            string:
+                type: string
+                example: ""
+        example:
+            string: ""
+    TestServiceTestEndpointResponseBody:
+        title: TestServiceTestEndpointResponseBody
+        type: object
+        properties:
+            int:
+                type: integer
+                example: 0
+                format: int64
+        example:
+            int: 0
diff --git a/http/codegen/openapi/v3/files_test.go b/http/codegen/openapi/v3/files_test.go
index 47a46df8..103682bb 100644
--- a/http/codegen/openapi/v3/files_test.go
+++ b/http/codegen/openapi/v3/files_test.go
@@ -47,6 +47,7 @@ func TestFiles(t *testing.T) {
 		{"typename", testdata.TypenameDSL},
 		{"not-generate-server", testdata.NotGenerateServerDSL},
 		{"not-generate-host", testdata.NotGenerateHostDSL},
+		{"not-generate-attribute", testdata.NotGenerateAttributeDSL},
 		// TestEndpoints
 		{"endpoint", testdata.ExtensionDSL},
 		{"endpoint-swagger", testdata.ExtensionSwaggerDSL},
diff --git a/http/codegen/openapi/v3/testdata/golden/not-generate-attribute_file0.golden b/http/codegen/openapi/v3/testdata/golden/not-generate-attribute_file0.golden
new file mode 100644
index 00000000..e2f92241
--- /dev/null
+++ b/http/codegen/openapi/v3/testdata/golden/not-generate-attribute_file0.golden
@@ -0,0 +1,1 @@
+{"openapi":"3.0.3","info":{"title":"Goa API","version":"1.0"},"servers":[{"url":"https://goa.design"}],"paths":{"/":{"get":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestEndpointRequestBody"},"example":{"string":""}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"},"example":{"int":0}}}}}}}},"components":{"schemas":{"Result":{"type":"object","properties":{"int":{"type":"integer","example":0,"format":"int64"}},"example":{"int":0}},"TestEndpointRequestBody":{"type":"object","properties":{"string":{"type":"string","example":""}},"example":{"string":""}}}},"tags":[{"name":"testService"}]}
\ No newline at end of file
diff --git a/http/codegen/openapi/v3/testdata/golden/not-generate-attribute_file1.golden b/http/codegen/openapi/v3/testdata/golden/not-generate-attribute_file1.golden
new file mode 100644
index 00000000..8909aa94
--- /dev/null
+++ b/http/codegen/openapi/v3/testdata/golden/not-generate-attribute_file1.golden
@@ -0,0 +1,51 @@
+openapi: 3.0.3
+info:
+    title: Goa API
+    version: "1.0"
+servers:
+    - url: https://goa.design
+paths:
+    /:
+        get:
+            tags:
+                - testService
+            summary: testEndpoint testService
+            operationId: testService#testEndpoint
+            requestBody:
+                required: true
+                content:
+                    application/json:
+                        schema:
+                            $ref: '#/components/schemas/TestEndpointRequestBody'
+                        example:
+                            string: ""
+            responses:
+                "200":
+                    description: OK response.
+                    content:
+                        application/json:
+                            schema:
+                                $ref: '#/components/schemas/Result'
+                            example:
+                                int: 0
+components:
+    schemas:
+        Result:
+            type: object
+            properties:
+                int:
+                    type: integer
+                    example: 0
+                    format: int64
+            example:
+                int: 0
+        TestEndpointRequestBody:
+            type: object
+            properties:
+                string:
+                    type: string
+                    example: ""
+            example:
+                string: ""
+tags:
+    - name: testService
diff --git a/http/codegen/openapi/v3/types_test.go b/http/codegen/openapi/v3/types_test.go
index e160f8b4..f61bffc8 100644
--- a/http/codegen/openapi/v3/types_test.go
+++ b/http/codegen/openapi/v3/types_test.go
@@ -262,6 +262,11 @@ func TestHashAttribute(t *testing.T) {
 		h3 = uint64(7729867354446285276)
 		h4 = uint64(12938215553621425391)
 		h5 = uint64(590638987843676710)
+		h6 = uint64(2958992150570065940)
+		h7 = uint64(17427721879237743911)
+
+		metaNotGenerate = expr.MetaExpr{"openapi:generate": []string{"false"}}
+		metaEmpty       = expr.MetaExpr{}
 	)
 	cases := []struct {
 		Name string
@@ -286,8 +291,8 @@ func TestHashAttribute(t *testing.T) {
 		{"map-str-str", &expr.AttributeExpr{Type: &expr.Map{KeyType: &expr.AttributeExpr{Type: expr.String}, ElemType: &expr.AttributeExpr{Type: expr.String}}}, 10408036596908747853},
 		{"map-int-str", &expr.AttributeExpr{Type: &expr.Map{KeyType: &expr.AttributeExpr{Type: expr.Int}, ElemType: &expr.AttributeExpr{Type: expr.String}}}, 16377853221392883275},
 		{"map-int-int", &expr.AttributeExpr{Type: &expr.Map{KeyType: &expr.AttributeExpr{Type: expr.Int}, ElemType: &expr.AttributeExpr{Type: expr.Int}}}, 3290208366554661977},
-		{"obj-str-req", newObj("foo", expr.String, true), 2958992150570065940},
-		{"obj-str-noreq", newObj("foo", expr.String, false), 17427721879237743911},
+		{"obj-str-req", newObj("foo", expr.String, true), h6},
+		{"obj-str-noreq", newObj("foo", expr.String, false), h7},
 		{"obj-int-req", newObj("foo", expr.Int, true), 8915021286725901502},
 		{"obj-int-noreq", newObj("foo", expr.Int, false), 11777831908257753485},
 		{"obj-other", newObj("bar", expr.Int, false), 12868551315046025641},
@@ -295,6 +300,8 @@ func TestHashAttribute(t *testing.T) {
 		{"obj-str-str-req1", newObj2("foo", "bar", expr.String, expr.String, "foo"), h2},
 		{"obj-str-str-req2", newObj2("foo", "bar", expr.String, expr.String, "bar"), h3},
 		{"obj-str-str-req3", newObj2("foo", "bar", expr.String, expr.String, "foo", "bar"), h4},
+		{"obj-str-str-notgen-req", newObj2Meta("foo", "bar", expr.String, expr.String, metaEmpty, metaNotGenerate, "foo"), h6},
+		{"obj-str-str-notgen-noreq", newObj2Meta("foo", "bar", expr.String, expr.String, metaEmpty, metaNotGenerate), h7},
 		{"obj-int-str-noreq", newObj2("foo", "bar", expr.Int, expr.String), 16228531529443692022},
 		{"obj1-str-str-noreq", newObj2("bar", "foo", expr.String, expr.String), h1},
 		{"obj1-str-str-req1", newObj2("bar", "foo", expr.String, expr.String, "foo"), h2},
@@ -339,6 +346,18 @@ func newObj2(n, o string, t, u expr.DataType, reqs ...string) *expr.AttributeExp
 	return attr
 }
 
+func newObj2Meta(n, o string, t, u expr.DataType, l, m expr.MetaExpr, reqs ...string) *expr.AttributeExpr {
+	attr := &expr.AttributeExpr{
+		Type: &expr.Object{
+			{Name: n, Attribute: &expr.AttributeExpr{Type: t, Meta: l}},
+			{Name: o, Attribute: &expr.AttributeExpr{Type: u, Meta: m}},
+		},
+		Validation: &expr.ValidationExpr{},
+	}
+	attr.Validation.Required = append(attr.Validation.Required, reqs...)
+	return attr
+}
+
 func newRT(id string, att *expr.AttributeExpr) *expr.AttributeExpr {
 	return &expr.AttributeExpr{
 		Type: &expr.ResultTypeExpr{
diff --git a/http/codegen/testdata/openapi_dsls.go b/http/codegen/testdata/openapi_dsls.go
index c3280424..af135c39 100644
--- a/http/codegen/testdata/openapi_dsls.go
+++ b/http/codegen/testdata/openapi_dsls.go
@@ -756,3 +756,38 @@ var NotGenerateHostDSL = func() {
 		})
 	})
 }
+
+var NotGenerateAttributeDSL = func() {
+	var _ = API("test", func() {
+		Server("test", func() {
+			Host("localhost", func() {
+				URI("https://goa.design")
+			})
+		})
+	})
+	var PayloadT = Type("Payload", func() {
+		Attribute("int", Int, func() {
+			Meta("openapi:generate", "false")
+		})
+		Attribute("string", String, func() {
+			Example("")
+		})
+	})
+	var ResultT = Type("Result", func() {
+		Attribute("int", Int, func() {
+			Example(0)
+		})
+		Attribute("string", String, func() {
+			Meta("openapi:generate", "false")
+		})
+	})
+	Service("testService", func() {
+		Method("testEndpoint", func() {
+			Payload(PayloadT)
+			Result(ResultT)
+			HTTP(func() {
+				GET("/")
+			})
+		})
+	})
+}