diff --git a/pkg/codegen/typemapping.go b/pkg/codegen/typemapping.go index 2a54695f6..b47b63bf6 100644 --- a/pkg/codegen/typemapping.go +++ b/pkg/codegen/typemapping.go @@ -99,6 +99,7 @@ var DefaultTypeMapping = TypeMapping{ "email": {Type: "openapi_types.Email"}, "date": {Type: "openapi_types.Date"}, "date-time": {Type: "time.Time", Import: "time"}, + "duration": {Type: "openapi_types.Duration"}, "json": {Type: "json.RawMessage", Import: "encoding/json"}, "uuid": {Type: "openapi_types.UUID"}, "binary": {Type: "openapi_types.File"}, diff --git a/pkg/codegen/typemapping_test.go b/pkg/codegen/typemapping_test.go index ca593cfd5..cb54d543f 100644 --- a/pkg/codegen/typemapping_test.go +++ b/pkg/codegen/typemapping_test.go @@ -3,7 +3,9 @@ package codegen import ( "testing" + "github.com/getkin/kin-openapi/openapi3" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestFormatMapping_Resolve(t *testing.T) { @@ -81,8 +83,61 @@ func TestDefaultTypeMapping_Completeness(t *testing.T) { assert.Equal(t, "openapi_types.Email", dm.String.Resolve("email").Type) assert.Equal(t, "openapi_types.Date", dm.String.Resolve("date").Type) assert.Equal(t, "time.Time", dm.String.Resolve("date-time").Type) + assert.Equal(t, "openapi_types.Duration", dm.String.Resolve("duration").Type) assert.Equal(t, "json.RawMessage", dm.String.Resolve("json").Type) assert.Equal(t, "openapi_types.UUID", dm.String.Resolve("uuid").Type) assert.Equal(t, "openapi_types.File", dm.String.Resolve("binary").Type) assert.Equal(t, "string", dm.String.Resolve("unknown").Type) } + +// TestDurationFormatMapping verifies that `type: string, format: duration` +// generates the openapi_types.Duration runtime type by default, and that the +// historical plain-string behavior is restored by explicitly mapping the +// format back to string via type-mapping. +// Please see https://github.com/oapi-codegen/oapi-codegen/issues/2456 +func TestDurationFormatMapping(t *testing.T) { + const spec = ` +openapi: "3.0.0" +info: + version: 1.0.0 + title: Durations +paths: {} +components: + schemas: + RetryPolicy: + type: object + required: [backoff] + properties: + backoff: + type: string + format: duration +` + loader := openapi3.NewLoader() + swagger, err := loader.LoadFromData([]byte(spec)) + require.NoError(t, err) + + opts := Configuration{ + PackageName: "api", + Generate: GenerateOptions{Models: true}, + OutputOptions: OutputOptions{SkipPrune: true}, + } + + code, err := Generate(swagger, opts) + require.NoError(t, err) + assert.Contains(t, code, "Backoff openapi_types.Duration `json:\"backoff\"`") + assert.Contains(t, code, `openapi_types "github.com/oapi-codegen/runtime/types"`) + + // The pre-duration behavior is a plain string; restore it by mapping + // the format back explicitly. + opts.OutputOptions.TypeMapping = &TypeMapping{ + String: FormatMapping{ + Formats: map[string]SimpleTypeSpec{ + "duration": {Type: "string"}, + }, + }, + } + code, err = Generate(swagger, opts) + require.NoError(t, err) + assert.Contains(t, code, "Backoff string `json:\"backoff\"`") + assert.NotContains(t, code, "openapi_types.Duration") +}