Skip to content

Commit

Permalink
feat #582: use OpenAPI minor version during validation if available
Browse files Browse the repository at this point in the history
  • Loading branch information
cboitel committed Jul 30, 2023
1 parent ca01401 commit bb0e0e5
Show file tree
Hide file tree
Showing 9 changed files with 996 additions and 470 deletions.
6 changes: 6 additions & 0 deletions .github/docs/openapi3.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ func Float64Ptr(value float64) *float64
func Int64Ptr(value int64) *int64
func ReadFromFile(loader *Loader, location *url.URL) ([]byte, error)
func RegisterArrayUniqueItemsChecker(fn SliceUniqueItemsChecker)
func RestoreDefaultStringFormats()
func RestoreStringFormats(formatToRestore map[string]*Format)
func SaveStringFormats(map[string]*Format) map[string]*Format
func Uint64Ptr(value uint64) *uint64
func ValidateIdentifier(value string) error
func WithValidationOptions(ctx context.Context, opts ...ValidationOption) context.Context
Expand Down Expand Up @@ -102,6 +105,9 @@ type Schema struct{ ... }
func NewDateSchema() *Schema
func NewDateTimeSchema() *Schema
func NewFloat64Schema() *Schema
func NewHostnameSchema() *Schema
func NewIPv4Schema() *Schema
func NewIPv6Schema() *Schema
func NewInt32Schema() *Schema
func NewInt64Schema() *Schema
func NewIntegerSchema() *Schema
Expand Down
2 changes: 2 additions & 0 deletions openapi3/issue735_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ func TestIssue735(t *testing.T) {
DefineStringFormat("email", FormatOfStringForEmail)
DefineIPv4Format()
DefineIPv6Format()
// restore modified string formats used during this tests
defer RestoreDefaultStringFormats()

testCases := []testCase{
{
Expand Down
21 changes: 21 additions & 0 deletions openapi3/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -593,13 +593,34 @@ func NewDateTimeSchema() *Schema {
}
}

func NewHostnameSchema() *Schema {
return &Schema{
Type: TypeString,
Format: "hostname",
}
}

func NewUUIDSchema() *Schema {
return &Schema{
Type: TypeString,
Format: "uuid",
}
}

func NewIPv4Schema() *Schema {
return &Schema{
Type: TypeString,
Format: "ipv4",
}
}

func NewIPv6Schema() *Schema {
return &Schema{
Type: TypeString,
Format: "ipv6",
}
}

func NewBytesSchema() *Schema {
return &Schema{
Type: TypeString,
Expand Down
60 changes: 60 additions & 0 deletions openapi3/schema_formats.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ func (format Format) DefinedForMinorVersion(minorVersion uint64) bool {

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

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

// SaveStringFormats allows to save (obtain a deep copy) of your current string formats
// so you can later restore it if needed
func SaveStringFormats(map[string]*Format) map[string]*Format {
savedStringFormats := map[string]*Format{}
for name, value := range SchemaStringFormats {
var savedFormat Format
savedFormat.versionedFormats = make([]*versionedFormat, len(value.versionedFormats))
for index, versionedFormatValue := range value.versionedFormats {
if versionedFormatValue != nil {
savedVersionedFormat := versionedFormat{
regexp: versionedFormatValue.regexp,
callback: versionedFormatValue.callback,
}
savedFormat.versionedFormats[index] = &savedVersionedFormat
}
}
savedStringFormats[name] = &savedFormat
}
return savedStringFormats
}

// RestoreStringFormats allows to restore string format back to default values
func RestoreStringFormats(formatToRestore map[string]*Format) {
restoredStringFormats := map[string]*Format{}
for name, value := range formatToRestore {
var restoredFormat Format
restoredFormat.versionedFormats = make([]*versionedFormat, len(value.versionedFormats))
for index, versionedFormatValue := range value.versionedFormats {
if versionedFormatValue != nil {
restoredVersionedFormat := versionedFormat{
regexp: versionedFormatValue.regexp,
callback: versionedFormatValue.callback,
}
restoredFormat.versionedFormats[index] = &restoredVersionedFormat
}
}
restoredStringFormats[name] = &restoredFormat
}
SchemaStringFormats = restoredStringFormats
}

// RestoreDefaultStringFormats allows to restore string format back to default values
func RestoreDefaultStringFormats() {
RestoreStringFormats(defaultSchemaStringFormats)
}
func init() {
// Base64
// The pattern supports base64 and b./ase64url. Padding ('=') is supported.
Expand All @@ -177,6 +223,20 @@ func init() {

// defined as date-time in https://www.rfc-editor.org/rfc/rfc3339#section-5.6
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})?$`)

// defined as uuid in https://www.rfc-editor.org/rfc/rfc4122
DefineStringFormatStartingWithOpenAPIMinorVersion("uuid", 1, FormatOfStringForUUIDOfRFC4122, true)

// defined as ipv4 in
DefineStringFormatCallbackStartingWithOpenAPIMinorVersion("ipv4", 1, validateIPv4, true)

// defined as ipv6 in https://www.rfc-editor.org/rfc/rfc4122
DefineStringFormatCallbackStartingWithOpenAPIMinorVersion("ipv6", 1, validateIPv6, true)

// hostname as defined in https://www.rfc-editor.org/rfc/rfc1123#section-2.1
DefineStringFormatStartingWithOpenAPIMinorVersion(`hostname`, 1, `^[a-zA-Z0-9][a-zA-Z0-9-.]+[a-zA-Z0-9]$`, true)

defaultSchemaStringFormats = SaveStringFormats(SchemaStringFormats)
}

// DefineIPv4Format opts in ipv4 format validation on top of OAS 3 spec
Expand Down
19 changes: 7 additions & 12 deletions openapi3/schema_formats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ func TestIssue430(t *testing.T) {
NewStringSchema().WithFormat("ipv6"),
)

delete(SchemaStringFormats, "ipv4")
delete(SchemaStringFormats, "ipv6")

err := schema.Validate(context.Background())
require.NoError(t, err)

Expand Down Expand Up @@ -46,11 +43,8 @@ func TestIssue430(t *testing.T) {
require.Error(t, err, ErrOneOfConflict.Error())
}

DefineIPv4Format()
DefineIPv6Format()

for datum, isV4 := range data {
err = schema.VisitJSON(datum)
err = schema.VisitJSON(datum, SetOpenAPIMinorVersion(1))
require.NoError(t, err)
if isV4 {
require.Nil(t, validateIPv4(datum), "%q should be IPv4", datum)
Expand Down Expand Up @@ -78,8 +72,6 @@ func TestFormatCallback_WrapError(t *testing.T) {
}

func TestReversePathInMessageSchemaError(t *testing.T) {
DefineIPv4Format()

SchemaErrorDetailsDisabled = true

const spc = `
Expand All @@ -99,11 +91,11 @@ components:

err = doc.Components.Schemas["Something"].Value.VisitJSON(map[string]interface{}{
`ip`: `123.0.0.11111`,
})
}, SetOpenAPIMinorVersion(1))

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

delete(SchemaStringFormats, "ipv4")
SchemaErrorDetailsDisabled = false
}

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

DefineStringFormat("uuid", FormatOfStringForUUIDOfRFC4122)
defer RestoreDefaultStringFormats()
testCases := []testCase{
{
name: "invalid",
Expand Down Expand Up @@ -156,6 +149,7 @@ func TestUuidFormat(t *testing.T) {
}
func TestStringFormatsStartingWithOpenAPIMinorVersion(t *testing.T) {
DefineStringFormatStartingWithOpenAPIMinorVersion("test", 0, "test0", false)
defer RestoreDefaultStringFormats()
for i := uint64(0); i < 10; i++ {
if assert.Contains(t, SchemaStringFormats, "test") &&
assert.NotNilf(t, SchemaStringFormats["test"].get(i), "%d", i) {
Expand Down Expand Up @@ -364,6 +358,7 @@ func createCallBack(callbackError error) FormatCallback {
}

func TestStringFormatsCallbackStartingWithOpenAPIMinorVersion(t *testing.T) {
defer RestoreDefaultStringFormats()
callbackError0 := createCallBackError(0)
DefineStringFormatCallback("testCallback", createCallBack(callbackError0))
for i := uint64(0); i < 10; i++ {
Expand Down
Loading

0 comments on commit bb0e0e5

Please sign in to comment.