diff --git a/http/codegen/openapi/v2/builder.go b/http/codegen/openapi/v2/builder.go index 0fa7735295..26921dcdd9 100644 --- a/http/codegen/openapi/v2/builder.go +++ b/http/codegen/openapi/v2/builder.go @@ -311,56 +311,31 @@ func paramsFromHeaders(endpoint *expr.HTTPEndpointExpr) []*Parameter { func paramFor(at *expr.AttributeExpr, name, in string, required bool) *Parameter { alias := at - if expr.IsAlias(at.Type) { - at = at.Type.(expr.UserType).Attribute() - } + at = resolvedAliasAttribute(at) p := &Parameter{ In: in, Name: name, Default: openapi.ToStringMap(at.DefaultValue), Description: at.Description, Required: required, - Type: at.Type.Name(), } + p.Type, p.Format = openAPITypeFormat(at) if expr.IsArray(at.Type) { p.Items = itemsFromExpr(expr.AsArray(at.Type).ElemType) p.CollectionFormat = "multi" } - switch at.Type { - case expr.Int, expr.UInt, expr.UInt32, expr.UInt64: - p.Type = "integer" - case expr.Int32, expr.Int64: - p.Type = "integer" - p.Format = at.Type.Name() - case expr.Float32: - p.Type = "number" - p.Format = "float" - case expr.Float64: - p.Type = "number" - p.Format = "double" - case expr.Bytes: - p.Type = "string" - p.Format = "byte" - } p.Extensions = openapi.ExtensionsFromExpr(at.Meta) - initValidations(alias, p) + initAttributeValidations(alias, p) return p } func itemsFromExpr(at *expr.AttributeExpr) *Items { - items := &Items{Type: at.Type.Name()} - p, ok := at.Type.(expr.Primitive) - if ok { - switch p.Kind() { - case expr.IntKind, expr.Int64Kind, expr.UIntKind, expr.UInt64Kind, expr.Int32Kind, expr.UInt32Kind: - items.Type = "integer" - case expr.Float32Kind, expr.Float64Kind: - items.Type = "number" - case expr.BytesKind: - items.Type = "string" - } + itemType, itemFormat := openAPITypeFormat(at) + items := &Items{ + Type: itemType, + Format: itemFormat, } - initValidations(at, items) + initAttributeValidations(at, items) if expr.IsArray(at.Type) { items.Items = itemsFromExpr(expr.AsArray(at.Type).ElemType) } @@ -401,12 +376,17 @@ func headersFromExpr(headers *expr.MappedAttributeExpr) map[string]*Header { } res := make(map[string]*Header) codegen.WalkMappedAttr(headers, func(_, n string, _ bool, at *expr.AttributeExpr) error { // nolint: errcheck + headerType, headerFormat := openAPITypeFormat(at) header := &Header{ Default: at.DefaultValue, Description: at.Description, - Type: at.Type.Name(), + Type: headerType, + Format: headerFormat, } - initValidations(at, header) + if expr.IsArray(at.Type) { + header.Items = itemsFromExpr(expr.AsArray(at.Type).ElemType) + } + initAttributeValidations(at, header) res[n] = header return nil }) @@ -416,6 +396,45 @@ func headersFromExpr(headers *expr.MappedAttributeExpr) map[string]*Header { return res } +func openAPITypeFormat(at *expr.AttributeExpr) (string, string) { + at = resolvedAliasAttribute(at) + p, ok := at.Type.(expr.Primitive) + if !ok { + return at.Type.Name(), "" + } + switch p.Kind() { + case expr.IntKind, expr.UIntKind, expr.Int64Kind, expr.UInt64Kind: + return "integer", "int64" + case expr.Int32Kind, expr.UInt32Kind: + return "integer", "int32" + case expr.Float32Kind: + return "number", "float" + case expr.Float64Kind: + return "number", "double" + case expr.BytesKind: + return "string", "byte" + case expr.AnyKind: + return "", "" + default: + return p.Name(), "" + } +} + +func resolvedAliasAttribute(at *expr.AttributeExpr) *expr.AttributeExpr { + if expr.IsAlias(at.Type) { + return at.Type.(expr.UserType).Attribute() + } + return at +} + +func initAttributeValidations(at *expr.AttributeExpr, def any) { + resolved := resolvedAliasAttribute(at) + if resolved != at { + initValidations(resolved, def) + } + initValidations(at, def) +} + func buildPathFromFileServer(s *V2, root *expr.RootExpr, fs *expr.HTTPFileServerExpr) { for _, path := range fs.RequestPaths { wcs := expr.ExtractHTTPWildcards(path) diff --git a/http/codegen/openapi/v2/files_test.go b/http/codegen/openapi/v2/files_test.go index 9f13d5eb40..256719c53c 100644 --- a/http/codegen/openapi/v2/files_test.go +++ b/http/codegen/openapi/v2/files_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "goa.design/goa/v3/codegen/testutil" + "goa.design/goa/v3/dsl" httpgen "goa.design/goa/v3/http/codegen" "goa.design/goa/v3/http/codegen/openapi" openapiv2 "goa.design/goa/v3/http/codegen/openapi/v2" @@ -187,6 +188,71 @@ func TestExtensions(t *testing.T) { } } +func TestNamedPrimitiveParamsAndHeadersUseOpenAPIBaseTypes(t *testing.T) { + // Reset global variables + openapi.Definitions = make(map[string]*openapi.Schema) + + root := httpgen.RunHTTPDSL(t, func() { + var UUID = dsl.Type("UUID", dsl.String, func() { + dsl.Format(dsl.FormatUUID) + }) + var Time = dsl.Type("Time", dsl.String, func() { + dsl.Format(dsl.FormatDateTime) + }) + var UploadStatus = dsl.ResultType("application/vnd.upload-status", func() { + dsl.Attributes(func() { + dsl.Attribute("expires", Time, "RFC3339 expiration timestamp.") + dsl.Attribute("offset", dsl.Int64, "Current upload offset in bytes.") + }) + }) + + dsl.API("test", func() { + dsl.Server("test", func() { + dsl.Host("localhost", func() { + dsl.URI("http://localhost:80") + }) + }) + }) + + dsl.Service("repro", func() { + dsl.Method("show", func() { + dsl.Payload(func() { + dsl.Attribute("ids", dsl.ArrayOf(UUID), "UUID filter values.") + }) + dsl.Result(UploadStatus) + dsl.HTTP(func() { + dsl.GET("/repro") + dsl.Param("ids") + dsl.Response(dsl.StatusOK, func() { + dsl.Header("expires:Upload-Expires") + dsl.Header("offset:Upload-Offset") + dsl.Body(dsl.Empty) + }) + }) + }) + }) + }) + + spec, err := openapiv2.NewV2(root, root.API.Servers[0].Hosts[0]) + require.NoError(t, err) + + path, ok := spec.Paths["/repro"] + require.True(t, ok) + + get := path.(*openapiv2.Path).Get + require.Len(t, get.Parameters, 1) + require.Equal(t, "array", get.Parameters[0].Type) + require.NotNil(t, get.Parameters[0].Items) + require.Equal(t, "string", get.Parameters[0].Items.Type) + require.Equal(t, "uuid", get.Parameters[0].Items.Format) + + headers := get.Responses["200"].Headers + require.Equal(t, "string", headers["Upload-Expires"].Type) + require.Equal(t, "date-time", headers["Upload-Expires"].Format) + require.Equal(t, "integer", headers["Upload-Offset"].Type) + require.Equal(t, "int64", headers["Upload-Offset"].Format) +} + // validateSwagger asserts that the given bytes contain a valid Swagger spec. func validateSwagger(b []byte) error { doc := &openapi2.T{} diff --git a/http/codegen/openapi/v2/testdata/TestSections/headers_file0.golden b/http/codegen/openapi/v2/testdata/TestSections/headers_file0.golden index e870f8cc3b..3b301effec 100644 --- a/http/codegen/openapi/v2/testdata/TestSections/headers_file0.golden +++ b/http/codegen/openapi/v2/testdata/TestSections/headers_file0.golden @@ -15,12 +15,14 @@ "operationId": "test service#test endpoint", "parameters": [ { + "format": "int64", "in": "header", "name": "foo", "required": false, "type": "integer" }, { + "format": "int64", "in": "header", "name": "bar", "required": false, diff --git a/http/codegen/openapi/v2/testdata/TestSections/headers_file1.golden b/http/codegen/openapi/v2/testdata/TestSections/headers_file1.golden index d26977bdf2..50deb7f40d 100644 --- a/http/codegen/openapi/v2/testdata/TestSections/headers_file1.golden +++ b/http/codegen/openapi/v2/testdata/TestSections/headers_file1.golden @@ -23,10 +23,12 @@ paths: in: header required: false type: integer + format: int64 - name: bar in: header required: false type: integer + format: int64 responses: "204": description: No Content response. diff --git a/http/codegen/openapi/v2/testdata/TestSections/path-with-multiple-explicit-wildcards_file0.golden b/http/codegen/openapi/v2/testdata/TestSections/path-with-multiple-explicit-wildcards_file0.golden index b6fba2b823..1453e7a59d 100644 --- a/http/codegen/openapi/v2/testdata/TestSections/path-with-multiple-explicit-wildcards_file0.golden +++ b/http/codegen/openapi/v2/testdata/TestSections/path-with-multiple-explicit-wildcards_file0.golden @@ -15,12 +15,14 @@ "operationId": "test service#test endpoint", "parameters": [ { + "format": "int64", "in": "path", "name": "foo", "required": true, "type": "integer" }, { + "format": "int64", "in": "path", "name": "bar", "required": true, diff --git a/http/codegen/openapi/v2/testdata/TestSections/path-with-multiple-explicit-wildcards_file1.golden b/http/codegen/openapi/v2/testdata/TestSections/path-with-multiple-explicit-wildcards_file1.golden index c0b317c33f..74b38f830a 100644 --- a/http/codegen/openapi/v2/testdata/TestSections/path-with-multiple-explicit-wildcards_file1.golden +++ b/http/codegen/openapi/v2/testdata/TestSections/path-with-multiple-explicit-wildcards_file1.golden @@ -23,10 +23,12 @@ paths: in: path required: true type: integer + format: int64 - name: bar in: path required: true type: integer + format: int64 responses: "204": description: No Content response. diff --git a/http/codegen/openapi/v2/testdata/TestSections/path-with-multiple-wildcards_file0.golden b/http/codegen/openapi/v2/testdata/TestSections/path-with-multiple-wildcards_file0.golden index b6fba2b823..1453e7a59d 100644 --- a/http/codegen/openapi/v2/testdata/TestSections/path-with-multiple-wildcards_file0.golden +++ b/http/codegen/openapi/v2/testdata/TestSections/path-with-multiple-wildcards_file0.golden @@ -15,12 +15,14 @@ "operationId": "test service#test endpoint", "parameters": [ { + "format": "int64", "in": "path", "name": "foo", "required": true, "type": "integer" }, { + "format": "int64", "in": "path", "name": "bar", "required": true, diff --git a/http/codegen/openapi/v2/testdata/TestSections/path-with-multiple-wildcards_file1.golden b/http/codegen/openapi/v2/testdata/TestSections/path-with-multiple-wildcards_file1.golden index c0b317c33f..74b38f830a 100644 --- a/http/codegen/openapi/v2/testdata/TestSections/path-with-multiple-wildcards_file1.golden +++ b/http/codegen/openapi/v2/testdata/TestSections/path-with-multiple-wildcards_file1.golden @@ -23,10 +23,12 @@ paths: in: path required: true type: integer + format: int64 - name: bar in: path required: true type: integer + format: int64 responses: "204": description: No Content response. diff --git a/http/codegen/openapi/v2/testdata/TestSections/path-with-wildcards_file0.golden b/http/codegen/openapi/v2/testdata/TestSections/path-with-wildcards_file0.golden index cb6de81c95..6918e8bcf5 100644 --- a/http/codegen/openapi/v2/testdata/TestSections/path-with-wildcards_file0.golden +++ b/http/codegen/openapi/v2/testdata/TestSections/path-with-wildcards_file0.golden @@ -15,6 +15,7 @@ "operationId": "test service#test endpoint", "parameters": [ { + "format": "int64", "in": "path", "name": "int_map", "required": true, diff --git a/http/codegen/openapi/v2/testdata/TestSections/path-with-wildcards_file1.golden b/http/codegen/openapi/v2/testdata/TestSections/path-with-wildcards_file1.golden index 9d48ad686c..800a66627a 100644 --- a/http/codegen/openapi/v2/testdata/TestSections/path-with-wildcards_file1.golden +++ b/http/codegen/openapi/v2/testdata/TestSections/path-with-wildcards_file1.golden @@ -23,6 +23,7 @@ paths: in: path required: true type: integer + format: int64 responses: "204": description: No Content response.