Skip to content

Commit

Permalink
feat(type): support additional number types (#72)
Browse files Browse the repository at this point in the history
At present, only Go float64 types are supported. Validating an integer in an
interface{} of another type currently displays an "unknown" type error.

This adds support for additional Go numeric types: uint, ints and float32s,
and converts them to float64s where required.
  • Loading branch information
saracen committed Jul 23, 2020
1 parent 0995c6b commit 9874480
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 12 deletions.
53 changes: 43 additions & 10 deletions keywords_numeric.go
Expand Up @@ -26,10 +26,10 @@ func (m *MultipleOf) Resolve(pointer jptr.Pointer, uri string) *Schema {
// ValidateKeyword implements the Keyword interface for MultipleOf
func (m MultipleOf) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
schemaDebug("[MultipleOf] Validating")
if num, ok := data.(float64); ok {
if num, ok := convertNumberToFloat(data); ok {
div := num / float64(m)
if float64(int(div)) != div {
currentState.AddError(data, fmt.Sprintf("must be a multiple of %f", m))
currentState.AddError(data, fmt.Sprintf("must be a multiple of %v", m))
}
}
}
Expand All @@ -53,9 +53,9 @@ func (m *Maximum) Resolve(pointer jptr.Pointer, uri string) *Schema {
// ValidateKeyword implements the Keyword interface for Maximum
func (m Maximum) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
schemaDebug("[Maximum] Validating")
if num, ok := data.(float64); ok {
if num, ok := convertNumberToFloat(data); ok {
if num > float64(m) {
currentState.AddError(data, fmt.Sprintf("must be less than or equal to %f", m))
currentState.AddError(data, fmt.Sprintf("must be less than or equal to %v", m))
}
}
}
Expand All @@ -79,9 +79,9 @@ func (m *ExclusiveMaximum) Resolve(pointer jptr.Pointer, uri string) *Schema {
// ValidateKeyword implements the Keyword interface for ExclusiveMaximum
func (m ExclusiveMaximum) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
schemaDebug("[ExclusiveMaximum] Validating")
if num, ok := data.(float64); ok {
if num, ok := convertNumberToFloat(data); ok {
if num >= float64(m) {
currentState.AddError(data, fmt.Sprintf("%f must be less than %f", num, m))
currentState.AddError(data, fmt.Sprintf("%v must be less than %v", num, m))
}
}
}
Expand All @@ -105,9 +105,9 @@ func (m *Minimum) Resolve(pointer jptr.Pointer, uri string) *Schema {
// ValidateKeyword implements the Keyword interface for Minimum
func (m Minimum) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
schemaDebug("[Minimum] Validating")
if num, ok := data.(float64); ok {
if num, ok := convertNumberToFloat(data); ok {
if num < float64(m) {
currentState.AddError(data, fmt.Sprintf("must be less than or equal to %f", m))
currentState.AddError(data, fmt.Sprintf("must be less than or equal to %v", m))
}
}
}
Expand All @@ -131,9 +131,42 @@ func (m *ExclusiveMinimum) Resolve(pointer jptr.Pointer, uri string) *Schema {
// ValidateKeyword implements the Keyword interface for ExclusiveMinimum
func (m ExclusiveMinimum) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
schemaDebug("[ExclusiveMinimum] Validating")
if num, ok := data.(float64); ok {
if num, ok := convertNumberToFloat(data); ok {
if num <= float64(m) {
currentState.AddError(data, fmt.Sprintf("%f must be less than %f", num, m))
currentState.AddError(data, fmt.Sprintf("%v must be less than %v", num, m))
}
}
}

func convertNumberToFloat(data interface{}) (float64, bool) {
switch v := data.(type) {
case uint:
return float64(v), true
case uint8:
return float64(v), true
case uint16:
return float64(v), true
case uint32:
return float64(v), true
case uint64:
return float64(v), true
case int:
return float64(v), true
case int8:
return float64(v), true
case int16:
return float64(v), true
case int32:
return float64(v), true
case int64:
return float64(v), true
case float32:
return float64(v), true
case float64:
return float64(v), true
case uintptr:
return float64(v), true
}

return 0, false
}
6 changes: 5 additions & 1 deletion keywords_standard.go
Expand Up @@ -145,7 +145,11 @@ func DataType(data interface{}) string {
switch reflect.TypeOf(data).Kind() {
case reflect.Bool:
return "boolean"
case reflect.Float64:

case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uintptr:
return "integer"
case reflect.Float32, reflect.Float64:
number := reflect.ValueOf(data).Float()
if float64(int(number)) == number {
return "integer"
Expand Down
12 changes: 11 additions & 1 deletion schema_test.go
Expand Up @@ -579,7 +579,17 @@ func TestDataType(t *testing.T) {
{map[string]interface{}{}, "object"},
{struct{}{}, "object"},
{customObject{}, "object"},
{int8(42), "unknown"},
{uint8(42), "integer"},
{uint16(42), "integer"},
{uint32(42), "integer"},
{uint64(42), "integer"},
{int8(42), "integer"},
{int16(42), "integer"},
{int32(42), "integer"},
{int64(42), "integer"},
{float32(42), "integer"},
{float32(42.0), "integer"},
{float32(42.5), "number"},
// special cases which should pass with type hints
{"true", "boolean"},
{4.0, "number"},
Expand Down

0 comments on commit 9874480

Please sign in to comment.