Skip to content

Commit

Permalink
#482 integer support broken with yaml (#577)
Browse files Browse the repository at this point in the history
Co-authored-by: Christian Boitel <cboitel@lfdj.com>
  • Loading branch information
cboitel and Christian Boitel authored Aug 31, 2022
1 parent 40bb5a1 commit 14af893
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 33 deletions.
47 changes: 46 additions & 1 deletion openapi3/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ const (
TypeNumber = "number"
TypeObject = "object"
TypeString = "string"

// constants for integer formats
formatMinInt32 = float64(math.MinInt32)
formatMaxInt32 = float64(math.MaxInt32)
formatMinInt64 = float64(math.MinInt64)
formatMaxInt64 = float64(math.MaxInt64)
)

var (
Expand Down Expand Up @@ -808,6 +814,12 @@ func (schema *Schema) visitJSON(settings *schemaValidationSettings, value interf
switch value := value.(type) {
case bool:
return schema.visitJSONBoolean(settings, value)
case int:
return schema.visitJSONNumber(settings, float64(value))
case int32:
return schema.visitJSONNumber(settings, float64(value))
case int64:
return schema.visitJSONNumber(settings, float64(value))
case float64:
return schema.visitJSONNumber(settings, value)
case string:
Expand Down Expand Up @@ -1019,7 +1031,7 @@ func (schema *Schema) VisitJSONNumber(value float64) error {
func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value float64) error {
var me MultiError
schemaType := schema.Type
if schemaType == "integer" {
if schemaType == TypeInteger {
if bigFloat := big.NewFloat(value); !bigFloat.IsInt() {
if settings.failfast {
return errSchema
Expand All @@ -1039,6 +1051,39 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
return schema.expectedType(settings, "number, integer")
}

// formats
if schemaType == TypeInteger && schema.Format != "" {
formatMin := float64(0)
formatMax := float64(0)
switch schema.Format {
case "int32":
formatMin = formatMinInt32
formatMax = formatMaxInt32
case "int64":
formatMin = formatMinInt64
formatMax = formatMaxInt64
default:
if !SchemaFormatValidationDisabled {
return unsupportedFormat(schema.Format)
}
}
if formatMin != 0 && formatMax != 0 && !(formatMin <= value && value <= formatMax) {
if settings.failfast {
return errSchema
}
err := &SchemaError{
Value: value,
Schema: schema,
SchemaField: "format",
Reason: fmt.Sprintf("number must be an %s", schema.Format),
}
if !settings.multiError {
return err
}
me = append(me, err)
}
}

// "exclusiveMinimum"
if v := schema.ExclusiveMin; v && !(*schema.Min < value) {
if settings.failfast {
Expand Down
133 changes: 101 additions & 32 deletions openapi3/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"math"
"reflect"
"strings"
"testing"

"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)

type schemaExample struct {
Expand Down Expand Up @@ -45,23 +45,25 @@ func testSchema(t *testing.T, example schemaExample) func(*testing.T) {
require.NoError(t, err)
require.Equal(t, dataUnserialized, dataSchema)
}
for _, value := range example.AllValid {
err := validateSchema(t, schema, value)
require.NoError(t, err)
}
for _, value := range example.AllInvalid {
err := validateSchema(t, schema, value)
require.Error(t, err)
for validateFuncIndex, validateFunc := range validateSchemaFuncs {
for index, value := range example.AllValid {
err := validateFunc(t, schema, value)
require.NoErrorf(t, err, "ValidateFunc #%d, AllValid #%d: %#v", validateFuncIndex, index, value)
}
for index, value := range example.AllInvalid {
err := validateFunc(t, schema, value)
require.Errorf(t, err, "ValidateFunc #%d, AllInvalid #%d: %#v", validateFuncIndex, index, value)
}
}
// NaN and Inf aren't valid JSON but are handled
for _, value := range []interface{}{math.NaN(), math.Inf(-1), math.Inf(+1)} {
for index, value := range []interface{}{math.NaN(), math.Inf(-1), math.Inf(+1)} {
err := schema.VisitJSON(value)
require.Error(t, err)
require.Errorf(t, err, "NaNAndInf #%d: %#v", index, value)
}
}
}

func validateSchema(t *testing.T, schema *Schema, value interface{}, opts ...SchemaValidationOption) error {
func validateSchemaJSON(t *testing.T, schema *Schema, value interface{}, opts ...SchemaValidationOption) error {
data, err := json.Marshal(value)
require.NoError(t, err)
var val interface{}
Expand All @@ -70,6 +72,22 @@ func validateSchema(t *testing.T, schema *Schema, value interface{}, opts ...Sch
return schema.VisitJSON(val, opts...)
}

func validateSchemaYAML(t *testing.T, schema *Schema, value interface{}, opts ...SchemaValidationOption) error {
data, err := yaml.Marshal(value)
require.NoError(t, err)
var val interface{}
err = yaml.Unmarshal(data, &val)
require.NoError(t, err)
return schema.VisitJSON(val, opts...)
}

type validateSchemaFunc func(t *testing.T, schema *Schema, value interface{}, opts ...SchemaValidationOption) error

var validateSchemaFuncs = []validateSchemaFunc{
validateSchemaJSON,
validateSchemaYAML,
}

var schemaExamples = []schemaExample{
{
Title: "EMPTY SCHEMA",
Expand Down Expand Up @@ -234,7 +252,56 @@ var schemaExamples = []schemaExample{
map[string]interface{}{},
},
},

{
Title: "INTEGER OPTIONAL INT64 FORMAT",
Schema: NewInt64Schema(),
Serialization: map[string]interface{}{
"type": "integer",
"format": "int64",
},
AllValid: []interface{}{
1,
256,
65536,
int64(math.MaxInt32) + 10,
int64(math.MinInt32) - 10,
},
AllInvalid: []interface{}{
nil,
false,
3.5,
true,
"",
[]interface{}{},
map[string]interface{}{},
},
},
{
Title: "INTEGER OPTIONAL INT32 FORMAT",
Schema: NewInt32Schema(),
Serialization: map[string]interface{}{
"type": "integer",
"format": "int32",
},
AllValid: []interface{}{
1,
256,
65536,
int64(math.MaxInt32),
int64(math.MaxInt32),
},
AllInvalid: []interface{}{
nil,
false,
3.5,
int64(math.MaxInt32) + 10,
int64(math.MinInt32) - 10,
true,
"",
[]interface{}{},
map[string]interface{}{},
},
},
{
Title: "STRING",
Schema: NewStringSchema().
Expand Down Expand Up @@ -350,7 +417,7 @@ var schemaExamples = []schemaExample{
AllInvalid: []interface{}{
nil,
" ",
"\n",
"\n\n", // a \n is ok for JSON but not for YAML decoder/encoder
"%",
},
},
Expand Down Expand Up @@ -1074,28 +1141,30 @@ func TestSchemasMultiError(t *testing.T) {
func testSchemaMultiError(t *testing.T, example schemaMultiErrorExample) func(*testing.T) {
return func(t *testing.T) {
schema := example.Schema
for i, value := range example.Values {
err := validateSchema(t, schema, value, MultiErrors())
require.Error(t, err)
require.IsType(t, MultiError{}, err)
for validateFuncIndex, validateFunc := range validateSchemaFuncs {
for i, value := range example.Values {
err := validateFunc(t, schema, value, MultiErrors())
require.Errorf(t, err, "ValidateFunc #%d, value #%d: %#", validateFuncIndex, i, value)
require.IsType(t, MultiError{}, err)

merr, _ := err.(MultiError)
expected := example.ExpectedErrors[i]
require.True(t, len(merr) > 0)
require.Len(t, merr, len(expected))
for _, e := range merr {
require.IsType(t, &SchemaError{}, e)
var found bool
scherr, _ := e.(*SchemaError)
for _, expectedErr := range expected {
expectedScherr, _ := expectedErr.(*SchemaError)
if reflect.DeepEqual(expectedScherr.reversePath, scherr.reversePath) &&
expectedScherr.SchemaField == scherr.SchemaField {
found = true
break
merr, _ := err.(MultiError)
expected := example.ExpectedErrors[i]
require.True(t, len(merr) > 0)
require.Len(t, merr, len(expected))
for _, e := range merr {
require.IsType(t, &SchemaError{}, e)
var found bool
scherr, _ := e.(*SchemaError)
for _, expectedErr := range expected {
expectedScherr, _ := expectedErr.(*SchemaError)
if reflect.DeepEqual(expectedScherr.reversePath, scherr.reversePath) &&
expectedScherr.SchemaField == scherr.SchemaField {
found = true
break
}
}
require.Truef(t, found, "ValidateFunc #%d, value #%d: missing %s error on %s", validateFunc, i, scherr.SchemaField, strings.Join(scherr.JSONPointer(), "."))
}
require.True(t, found, fmt.Sprintf("missing %s error on %s", scherr.SchemaField, strings.Join(scherr.JSONPointer(), ".")))
}
}
}
Expand Down

0 comments on commit 14af893

Please sign in to comment.