Skip to content

Commit bb0e0e5

Browse files
committed
feat #582: use OpenAPI minor version during validation if available
1 parent ca01401 commit bb0e0e5

9 files changed

+996
-470
lines changed

.github/docs/openapi3.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ func Float64Ptr(value float64) *float64
2222
func Int64Ptr(value int64) *int64
2323
func ReadFromFile(loader *Loader, location *url.URL) ([]byte, error)
2424
func RegisterArrayUniqueItemsChecker(fn SliceUniqueItemsChecker)
25+
func RestoreDefaultStringFormats()
26+
func RestoreStringFormats(formatToRestore map[string]*Format)
27+
func SaveStringFormats(map[string]*Format) map[string]*Format
2528
func Uint64Ptr(value uint64) *uint64
2629
func ValidateIdentifier(value string) error
2730
func WithValidationOptions(ctx context.Context, opts ...ValidationOption) context.Context
@@ -102,6 +105,9 @@ type Schema struct{ ... }
102105
func NewDateSchema() *Schema
103106
func NewDateTimeSchema() *Schema
104107
func NewFloat64Schema() *Schema
108+
func NewHostnameSchema() *Schema
109+
func NewIPv4Schema() *Schema
110+
func NewIPv6Schema() *Schema
105111
func NewInt32Schema() *Schema
106112
func NewInt64Schema() *Schema
107113
func NewIntegerSchema() *Schema

openapi3/issue735_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ func TestIssue735(t *testing.T) {
2020
DefineStringFormat("email", FormatOfStringForEmail)
2121
DefineIPv4Format()
2222
DefineIPv6Format()
23+
// restore modified string formats used during this tests
24+
defer RestoreDefaultStringFormats()
2325

2426
testCases := []testCase{
2527
{

openapi3/schema.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,13 +593,34 @@ func NewDateTimeSchema() *Schema {
593593
}
594594
}
595595

596+
func NewHostnameSchema() *Schema {
597+
return &Schema{
598+
Type: TypeString,
599+
Format: "hostname",
600+
}
601+
}
602+
596603
func NewUUIDSchema() *Schema {
597604
return &Schema{
598605
Type: TypeString,
599606
Format: "uuid",
600607
}
601608
}
602609

610+
func NewIPv4Schema() *Schema {
611+
return &Schema{
612+
Type: TypeString,
613+
Format: "ipv4",
614+
}
615+
}
616+
617+
func NewIPv6Schema() *Schema {
618+
return &Schema{
619+
Type: TypeString,
620+
Format: "ipv6",
621+
}
622+
}
623+
603624
func NewBytesSchema() *Schema {
604625
return &Schema{
605626
Type: TypeString,

openapi3/schema_formats.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ func (format Format) DefinedForMinorVersion(minorVersion uint64) bool {
8181

8282
// SchemaStringFormats allows for validating string formats
8383
var SchemaStringFormats = make(map[string]*Format, 4)
84+
var defaultSchemaStringFormats map[string]*Format
8485

8586
// DefineStringFormat defines a new regexp pattern for a given format
8687
// Will enforce regexp usage for minor versions of OpenAPI (3.Y.Z)
@@ -167,6 +168,51 @@ func validateIPv6(ip string) error {
167168
return nil
168169
}
169170

171+
// SaveStringFormats allows to save (obtain a deep copy) of your current string formats
172+
// so you can later restore it if needed
173+
func SaveStringFormats(map[string]*Format) map[string]*Format {
174+
savedStringFormats := map[string]*Format{}
175+
for name, value := range SchemaStringFormats {
176+
var savedFormat Format
177+
savedFormat.versionedFormats = make([]*versionedFormat, len(value.versionedFormats))
178+
for index, versionedFormatValue := range value.versionedFormats {
179+
if versionedFormatValue != nil {
180+
savedVersionedFormat := versionedFormat{
181+
regexp: versionedFormatValue.regexp,
182+
callback: versionedFormatValue.callback,
183+
}
184+
savedFormat.versionedFormats[index] = &savedVersionedFormat
185+
}
186+
}
187+
savedStringFormats[name] = &savedFormat
188+
}
189+
return savedStringFormats
190+
}
191+
192+
// RestoreStringFormats allows to restore string format back to default values
193+
func RestoreStringFormats(formatToRestore map[string]*Format) {
194+
restoredStringFormats := map[string]*Format{}
195+
for name, value := range formatToRestore {
196+
var restoredFormat Format
197+
restoredFormat.versionedFormats = make([]*versionedFormat, len(value.versionedFormats))
198+
for index, versionedFormatValue := range value.versionedFormats {
199+
if versionedFormatValue != nil {
200+
restoredVersionedFormat := versionedFormat{
201+
regexp: versionedFormatValue.regexp,
202+
callback: versionedFormatValue.callback,
203+
}
204+
restoredFormat.versionedFormats[index] = &restoredVersionedFormat
205+
}
206+
}
207+
restoredStringFormats[name] = &restoredFormat
208+
}
209+
SchemaStringFormats = restoredStringFormats
210+
}
211+
212+
// RestoreDefaultStringFormats allows to restore string format back to default values
213+
func RestoreDefaultStringFormats() {
214+
RestoreStringFormats(defaultSchemaStringFormats)
215+
}
170216
func init() {
171217
// Base64
172218
// The pattern supports base64 and b./ase64url. Padding ('=') is supported.
@@ -177,6 +223,20 @@ func init() {
177223

178224
// defined as date-time in https://www.rfc-editor.org/rfc/rfc3339#section-5.6
179225
DefineStringFormat("date-time", `^[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T(23:59:60|(([01][0-9]|2[0-3])(:[0-5][0-9]){2}))(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})?$`)
226+
227+
// defined as uuid in https://www.rfc-editor.org/rfc/rfc4122
228+
DefineStringFormatStartingWithOpenAPIMinorVersion("uuid", 1, FormatOfStringForUUIDOfRFC4122, true)
229+
230+
// defined as ipv4 in
231+
DefineStringFormatCallbackStartingWithOpenAPIMinorVersion("ipv4", 1, validateIPv4, true)
232+
233+
// defined as ipv6 in https://www.rfc-editor.org/rfc/rfc4122
234+
DefineStringFormatCallbackStartingWithOpenAPIMinorVersion("ipv6", 1, validateIPv6, true)
235+
236+
// hostname as defined in https://www.rfc-editor.org/rfc/rfc1123#section-2.1
237+
DefineStringFormatStartingWithOpenAPIMinorVersion(`hostname`, 1, `^[a-zA-Z0-9][a-zA-Z0-9-.]+[a-zA-Z0-9]$`, true)
238+
239+
defaultSchemaStringFormats = SaveStringFormats(SchemaStringFormats)
180240
}
181241

182242
// DefineIPv4Format opts in ipv4 format validation on top of OAS 3 spec

openapi3/schema_formats_test.go

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@ func TestIssue430(t *testing.T) {
1616
NewStringSchema().WithFormat("ipv6"),
1717
)
1818

19-
delete(SchemaStringFormats, "ipv4")
20-
delete(SchemaStringFormats, "ipv6")
21-
2219
err := schema.Validate(context.Background())
2320
require.NoError(t, err)
2421

@@ -46,11 +43,8 @@ func TestIssue430(t *testing.T) {
4643
require.Error(t, err, ErrOneOfConflict.Error())
4744
}
4845

49-
DefineIPv4Format()
50-
DefineIPv6Format()
51-
5246
for datum, isV4 := range data {
53-
err = schema.VisitJSON(datum)
47+
err = schema.VisitJSON(datum, SetOpenAPIMinorVersion(1))
5448
require.NoError(t, err)
5549
if isV4 {
5650
require.Nil(t, validateIPv4(datum), "%q should be IPv4", datum)
@@ -78,8 +72,6 @@ func TestFormatCallback_WrapError(t *testing.T) {
7872
}
7973

8074
func TestReversePathInMessageSchemaError(t *testing.T) {
81-
DefineIPv4Format()
82-
8375
SchemaErrorDetailsDisabled = true
8476

8577
const spc = `
@@ -99,11 +91,11 @@ components:
9991

10092
err = doc.Components.Schemas["Something"].Value.VisitJSON(map[string]interface{}{
10193
`ip`: `123.0.0.11111`,
102-
})
94+
}, SetOpenAPIMinorVersion(1))
10395

104-
require.EqualError(t, err, `Error at "/ip": Not an IP address`)
96+
// assert, do not require to ensure SchemaErrorDetailsDisabled can be set to false
97+
assert.ErrorContains(t, err, `Error at "/ip"`)
10598

106-
delete(SchemaStringFormats, "ipv4")
10799
SchemaErrorDetailsDisabled = false
108100
}
109101

@@ -116,6 +108,7 @@ func TestUuidFormat(t *testing.T) {
116108
}
117109

118110
DefineStringFormat("uuid", FormatOfStringForUUIDOfRFC4122)
111+
defer RestoreDefaultStringFormats()
119112
testCases := []testCase{
120113
{
121114
name: "invalid",
@@ -156,6 +149,7 @@ func TestUuidFormat(t *testing.T) {
156149
}
157150
func TestStringFormatsStartingWithOpenAPIMinorVersion(t *testing.T) {
158151
DefineStringFormatStartingWithOpenAPIMinorVersion("test", 0, "test0", false)
152+
defer RestoreDefaultStringFormats()
159153
for i := uint64(0); i < 10; i++ {
160154
if assert.Contains(t, SchemaStringFormats, "test") &&
161155
assert.NotNilf(t, SchemaStringFormats["test"].get(i), "%d", i) {
@@ -364,6 +358,7 @@ func createCallBack(callbackError error) FormatCallback {
364358
}
365359

366360
func TestStringFormatsCallbackStartingWithOpenAPIMinorVersion(t *testing.T) {
361+
defer RestoreDefaultStringFormats()
367362
callbackError0 := createCallBackError(0)
368363
DefineStringFormatCallback("testCallback", createCallBack(callbackError0))
369364
for i := uint64(0); i < 10; i++ {

0 commit comments

Comments
 (0)