diff --git a/.github/docs/openapi3.txt b/.github/docs/openapi3.txt index 4729e0c3..47aaeadf 100644 --- a/.github/docs/openapi3.txt +++ b/.github/docs/openapi3.txt @@ -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" diff --git a/openapi3/datetime_schema_test.go b/openapi3/datetime_schema_test.go new file mode 100644 index 00000000..fec60576 --- /dev/null +++ b/openapi3/datetime_schema_test.go @@ -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+`"`) +} diff --git a/openapi3/schema_formats.go b/openapi3/schema_formats.go index 1b76707f..489105b4 100644 --- a/openapi3/schema_formats.go +++ b/openapi3/schema_formats.go @@ -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() { diff --git a/openapi3/schema_issue492_test.go b/openapi3/schema_issue492_test.go index 2fdfcbce..15156722 100644 --- a/openapi3/schema_issue492_test.go +++ b/openapi3/schema_issue492_test.go @@ -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+`"`) }