Skip to content

Commit b82c647

Browse files
openapi3: update date schema formats to not match months or days of '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
1 parent d819171 commit b82c647

File tree

4 files changed

+172
-5
lines changed

4 files changed

+172
-5
lines changed

.github/docs/openapi3.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ const (
3535
FormatOfStringByte = `(^$|^[a-zA-Z0-9+/\-_]*=*$)`
3636

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

4040
// FormatOfStringDateTime is a RFC3339 date-time format regexp, for example "2017-07-21T17:32:28Z".
41-
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})?$`
41+
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})?$`
4242
)
4343
const (
4444
SerializationSimple = "simple"

openapi3/datetime_schema_test.go

+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package openapi3
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
var DateSpec = []byte(`
10+
components:
11+
schemas:
12+
Server:
13+
properties:
14+
date:
15+
$ref: "#/components/schemas/timestamp"
16+
name:
17+
type: string
18+
type: object
19+
timestamp:
20+
type: string
21+
format: date
22+
openapi: "3.0.1"
23+
paths: {}
24+
info:
25+
version: 1.1.1
26+
title: title
27+
`[1:])
28+
29+
var DateTimeSpec = []byte(`
30+
components:
31+
schemas:
32+
Server:
33+
properties:
34+
datetime:
35+
$ref: "#/components/schemas/timestamp"
36+
name:
37+
type: string
38+
type: object
39+
timestamp:
40+
type: string
41+
format: date-time
42+
openapi: "3.0.1"
43+
paths: {}
44+
info:
45+
version: 1.1.1
46+
title: title
47+
`[1:])
48+
49+
func TestDateZeroMonth(t *testing.T) {
50+
loader := NewLoader()
51+
doc, err := loader.LoadFromData(DateSpec)
52+
require.NoError(t, err)
53+
54+
err = doc.Validate(loader.Context)
55+
require.NoError(t, err)
56+
57+
err = doc.Components.Schemas["Server"].Value.VisitJSON(map[string]any{
58+
"name": "kin-openapi",
59+
"date": "2001-00-03",
60+
})
61+
require.EqualError(t, err, `Error at "/date": string doesn't match the format "date": string doesn't match pattern "`+FormatOfStringDate+`"`)
62+
}
63+
64+
func TestDateZeroDay(t *testing.T) {
65+
loader := NewLoader()
66+
doc, err := loader.LoadFromData(DateSpec)
67+
require.NoError(t, err)
68+
69+
err = doc.Validate(loader.Context)
70+
require.NoError(t, err)
71+
72+
err = doc.Components.Schemas["Server"].Value.VisitJSON(map[string]any{
73+
"name": "kin-openapi",
74+
"date": "2001-02-00",
75+
})
76+
require.EqualError(t, err, `Error at "/date": string doesn't match the format "date": string doesn't match pattern "`+FormatOfStringDate+`"`)
77+
}
78+
79+
func TestDateTimeZeroMonth(t *testing.T) {
80+
loader := NewLoader()
81+
doc, err := loader.LoadFromData(DateTimeSpec)
82+
require.NoError(t, err)
83+
84+
err = doc.Validate(loader.Context)
85+
require.NoError(t, err)
86+
87+
err = doc.Components.Schemas["Server"].Value.VisitJSON(map[string]any{
88+
"name": "kin-openapi",
89+
"datetime": "2001-00-03T04:05:06.789Z",
90+
})
91+
require.EqualError(t, err, `Error at "/datetime": string doesn't match the format "date-time": string doesn't match pattern "`+FormatOfStringDateTime+`"`)
92+
}
93+
94+
func TestDateTimeZeroDay(t *testing.T) {
95+
loader := NewLoader()
96+
doc, err := loader.LoadFromData(DateTimeSpec)
97+
require.NoError(t, err)
98+
99+
err = doc.Validate(loader.Context)
100+
require.NoError(t, err)
101+
102+
err = doc.Components.Schemas["Server"].Value.VisitJSON(map[string]any{
103+
"name": "kin-openapi",
104+
"datetime": "2001-02-00T04:05:06.789Z",
105+
})
106+
require.EqualError(t, err, `Error at "/datetime": string doesn't match the format "date-time": string doesn't match pattern "`+FormatOfStringDateTime+`"`)
107+
}
108+
109+
func TestDateTimeLeapSecond(t *testing.T) {
110+
loader := NewLoader()
111+
doc, err := loader.LoadFromData(DateTimeSpec)
112+
require.NoError(t, err)
113+
114+
err = doc.Validate(loader.Context)
115+
require.NoError(t, err)
116+
117+
err = doc.Components.Schemas["Server"].Value.VisitJSON(map[string]any{
118+
"name": "kin-openapi",
119+
"datetime": "2016-12-31T23:59:60.000Z", // exact time of the most recent leap second
120+
})
121+
require.NoError(t, err)
122+
}
123+
124+
func TestDateTimeHourOutOfBounds(t *testing.T) {
125+
loader := NewLoader()
126+
doc, err := loader.LoadFromData(DateTimeSpec)
127+
require.NoError(t, err)
128+
129+
err = doc.Validate(loader.Context)
130+
require.NoError(t, err)
131+
132+
err = doc.Components.Schemas["Server"].Value.VisitJSON(map[string]any{
133+
"name": "kin-openapi",
134+
"datetime": "2016-12-31T24:00:00.000Z",
135+
})
136+
require.EqualError(t, err, `Error at "/datetime": string doesn't match the format "date-time": string doesn't match pattern "`+FormatOfStringDateTime+`"`)
137+
}
138+
139+
func TestDateTimeMinuteOutOfBounds(t *testing.T) {
140+
loader := NewLoader()
141+
doc, err := loader.LoadFromData(DateTimeSpec)
142+
require.NoError(t, err)
143+
144+
err = doc.Validate(loader.Context)
145+
require.NoError(t, err)
146+
147+
err = doc.Components.Schemas["Server"].Value.VisitJSON(map[string]any{
148+
"name": "kin-openapi",
149+
"datetime": "2016-12-31T23:60:00.000Z",
150+
})
151+
require.EqualError(t, err, `Error at "/datetime": string doesn't match the format "date-time": string doesn't match pattern "`+FormatOfStringDateTime+`"`)
152+
}
153+
154+
func TestDateTimeSecondOutOfBounds(t *testing.T) {
155+
loader := NewLoader()
156+
doc, err := loader.LoadFromData(DateTimeSpec)
157+
require.NoError(t, err)
158+
159+
err = doc.Validate(loader.Context)
160+
require.NoError(t, err)
161+
162+
err = doc.Components.Schemas["Server"].Value.VisitJSON(map[string]any{
163+
"name": "kin-openapi",
164+
"datetime": "2016-12-31T23:59:61.000Z",
165+
})
166+
require.EqualError(t, err, `Error at "/datetime": string doesn't match the format "date-time": string doesn't match pattern "`+FormatOfStringDateTime+`"`)
167+
}

openapi3/schema_formats.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ const (
4141
FormatOfStringByte = `(^$|^[a-zA-Z0-9+/\-_]*=*$)`
4242

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

4646
// FormatOfStringDateTime is a RFC3339 date-time format regexp, for example "2017-07-21T17:32:28Z".
47-
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})?$`
47+
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})?$`
4848
)
4949

5050
func init() {

openapi3/schema_issue492_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,5 @@ info:
4646
"name": "kin-openapi",
4747
"time": "2001-02-03T04:05:06:789Z",
4848
})
49-
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})?$"`)
49+
require.EqualError(t, err, `Error at "/time": string doesn't match the format "date-time": string doesn't match pattern "`+FormatOfStringDateTime+`"`)
5050
}

0 commit comments

Comments
 (0)