diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 348a86db97..2d24a7fc81 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,7 +32,7 @@ jobs: - name: Upload test coverage for deep source if: matrix.go == '1.21' && matrix.os == 'ubuntu-latest' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: coverage path: cover.out diff --git a/expr/example.go b/expr/example.go index b3f3dab4f1..6eedb51b82 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 82d1b3c5ef..b7d3724dd5 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/v2/files_test.go b/http/codegen/openapi/v2/files_test.go index 4c628b969d..2e8281553f 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 0000000000..8b00cc6867 --- /dev/null +++ b/http/codegen/openapi/v2/testdata/TestSections/not-generate-attribute_file0.golden @@ -0,0 +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 0000000000..0736ec84f8 --- /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 47a46df88a..103682bb84 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 0000000000..e2f92241a4 --- /dev/null +++ b/http/codegen/openapi/v3/testdata/golden/not-generate-attribute_file0.golden @@ -0,0 +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 0000000000..8909aa94da --- /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.go b/http/codegen/openapi/v3/types.go index 7ccc9c1f8a..f8661a51cf 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) diff --git a/http/codegen/openapi/v3/types_test.go b/http/codegen/openapi/v3/types_test.go index e160f8b4fa..f61bffc838 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 c3280424b5..af135c397f 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("/") + }) + }) + }) +}