Skip to content

Commit

Permalink
openapi3: update date schema formats to not match months or days of '…
Browse files Browse the repository at this point in the history
…00' (#1042)

* Update date schema formats to not match months or days of '00'

* Update schema_issue492_test.go to look for correct DateTime format in error output

* Add test cases for '00' months and days in date and date-time objects

* Change date/time validation test cases to use EqualError

* Fix hour/minute/second matches as well - also update tests
  • Loading branch information
RulerOfTheQueendom authored Dec 11, 2024
1 parent d819171 commit b82c647
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 5 deletions.
4 changes: 2 additions & 2 deletions .github/docs/openapi3.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ const (
FormatOfStringByte = `(^$|^[a-zA-Z0-9+/\-_]*=*$)`

// FormatOfStringDate is a RFC3339 date format regexp, for example "2017-07-21".
FormatOfStringDate = `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)$`
FormatOfStringDate = `^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])$`

// FormatOfStringDateTime is a RFC3339 date-time format regexp, for example "2017-07-21T17:32:28Z".
FormatOfStringDateTime = `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})?$`
FormatOfStringDateTime = `^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})?$`
)
const (
SerializationSimple = "simple"
Expand Down
167 changes: 167 additions & 0 deletions openapi3/datetime_schema_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package openapi3

import (
"testing"

"github.com/stretchr/testify/require"
)

var DateSpec = []byte(`
components:
schemas:
Server:
properties:
date:
$ref: "#/components/schemas/timestamp"
name:
type: string
type: object
timestamp:
type: string
format: date
openapi: "3.0.1"
paths: {}
info:
version: 1.1.1
title: title
`[1:])

var DateTimeSpec = []byte(`
components:
schemas:
Server:
properties:
datetime:
$ref: "#/components/schemas/timestamp"
name:
type: string
type: object
timestamp:
type: string
format: date-time
openapi: "3.0.1"
paths: {}
info:
version: 1.1.1
title: title
`[1:])

func TestDateZeroMonth(t *testing.T) {
loader := NewLoader()
doc, err := loader.LoadFromData(DateSpec)
require.NoError(t, err)

err = doc.Validate(loader.Context)
require.NoError(t, err)

err = doc.Components.Schemas["Server"].Value.VisitJSON(map[string]any{
"name": "kin-openapi",
"date": "2001-00-03",
})
require.EqualError(t, err, `Error at "/date": string doesn't match the format "date": string doesn't match pattern "`+FormatOfStringDate+`"`)
}

func TestDateZeroDay(t *testing.T) {
loader := NewLoader()
doc, err := loader.LoadFromData(DateSpec)
require.NoError(t, err)

err = doc.Validate(loader.Context)
require.NoError(t, err)

err = doc.Components.Schemas["Server"].Value.VisitJSON(map[string]any{
"name": "kin-openapi",
"date": "2001-02-00",
})
require.EqualError(t, err, `Error at "/date": string doesn't match the format "date": string doesn't match pattern "`+FormatOfStringDate+`"`)
}

func TestDateTimeZeroMonth(t *testing.T) {
loader := NewLoader()
doc, err := loader.LoadFromData(DateTimeSpec)
require.NoError(t, err)

err = doc.Validate(loader.Context)
require.NoError(t, err)

err = doc.Components.Schemas["Server"].Value.VisitJSON(map[string]any{
"name": "kin-openapi",
"datetime": "2001-00-03T04:05:06.789Z",
})
require.EqualError(t, err, `Error at "/datetime": string doesn't match the format "date-time": string doesn't match pattern "`+FormatOfStringDateTime+`"`)
}

func TestDateTimeZeroDay(t *testing.T) {
loader := NewLoader()
doc, err := loader.LoadFromData(DateTimeSpec)
require.NoError(t, err)

err = doc.Validate(loader.Context)
require.NoError(t, err)

err = doc.Components.Schemas["Server"].Value.VisitJSON(map[string]any{
"name": "kin-openapi",
"datetime": "2001-02-00T04:05:06.789Z",
})
require.EqualError(t, err, `Error at "/datetime": string doesn't match the format "date-time": string doesn't match pattern "`+FormatOfStringDateTime+`"`)
}

func TestDateTimeLeapSecond(t *testing.T) {
loader := NewLoader()
doc, err := loader.LoadFromData(DateTimeSpec)
require.NoError(t, err)

err = doc.Validate(loader.Context)
require.NoError(t, err)

err = doc.Components.Schemas["Server"].Value.VisitJSON(map[string]any{
"name": "kin-openapi",
"datetime": "2016-12-31T23:59:60.000Z", // exact time of the most recent leap second
})
require.NoError(t, err)
}

func TestDateTimeHourOutOfBounds(t *testing.T) {
loader := NewLoader()
doc, err := loader.LoadFromData(DateTimeSpec)
require.NoError(t, err)

err = doc.Validate(loader.Context)
require.NoError(t, err)

err = doc.Components.Schemas["Server"].Value.VisitJSON(map[string]any{
"name": "kin-openapi",
"datetime": "2016-12-31T24:00:00.000Z",
})
require.EqualError(t, err, `Error at "/datetime": string doesn't match the format "date-time": string doesn't match pattern "`+FormatOfStringDateTime+`"`)
}

func TestDateTimeMinuteOutOfBounds(t *testing.T) {
loader := NewLoader()
doc, err := loader.LoadFromData(DateTimeSpec)
require.NoError(t, err)

err = doc.Validate(loader.Context)
require.NoError(t, err)

err = doc.Components.Schemas["Server"].Value.VisitJSON(map[string]any{
"name": "kin-openapi",
"datetime": "2016-12-31T23:60:00.000Z",
})
require.EqualError(t, err, `Error at "/datetime": string doesn't match the format "date-time": string doesn't match pattern "`+FormatOfStringDateTime+`"`)
}

func TestDateTimeSecondOutOfBounds(t *testing.T) {
loader := NewLoader()
doc, err := loader.LoadFromData(DateTimeSpec)
require.NoError(t, err)

err = doc.Validate(loader.Context)
require.NoError(t, err)

err = doc.Components.Schemas["Server"].Value.VisitJSON(map[string]any{
"name": "kin-openapi",
"datetime": "2016-12-31T23:59:61.000Z",
})
require.EqualError(t, err, `Error at "/datetime": string doesn't match the format "date-time": string doesn't match pattern "`+FormatOfStringDateTime+`"`)
}
4 changes: 2 additions & 2 deletions openapi3/schema_formats.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ const (
FormatOfStringByte = `(^$|^[a-zA-Z0-9+/\-_]*=*$)`

// FormatOfStringDate is a RFC3339 date format regexp, for example "2017-07-21".
FormatOfStringDate = `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)$`
FormatOfStringDate = `^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])$`

// FormatOfStringDateTime is a RFC3339 date-time format regexp, for example "2017-07-21T17:32:28Z".
FormatOfStringDateTime = `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})?$`
FormatOfStringDateTime = `^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})?$`
)

func init() {
Expand Down
2 changes: 1 addition & 1 deletion openapi3/schema_issue492_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,5 @@ info:
"name": "kin-openapi",
"time": "2001-02-03T04:05:06:789Z",
})
require.ErrorContains(t, err, `Error at "/time": string doesn't match the format "date-time": string doesn't match pattern "^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})?$"`)
require.EqualError(t, err, `Error at "/time": string doesn't match the format "date-time": string doesn't match pattern "`+FormatOfStringDateTime+`"`)
}

0 comments on commit b82c647

Please sign in to comment.