From 001aa27b4d110df2cd68ee5f86f17d8e6d1f3b36 Mon Sep 17 00:00:00 2001 From: quint daenen Date: Thu, 14 Nov 2019 14:23:42 +0100 Subject: [PATCH] Support for custom formats on all primitive types (#280) * added support for custom formats on all types * added tests for custom formats * moved format checking to validateCommon * added format comment * moved format tests --- format_checkers_test.go | 117 ++++++++++++++++++++++++++++++++++++++++ validation.go | 36 +++++-------- 2 files changed, 129 insertions(+), 24 deletions(-) diff --git a/format_checkers_test.go b/format_checkers_test.go index ef8edaf..65b00ba 100644 --- a/format_checkers_test.go +++ b/format_checkers_test.go @@ -1,6 +1,7 @@ package gojsonschema import ( + "encoding/json" "github.com/stretchr/testify/assert" "testing" ) @@ -21,3 +22,119 @@ func TestURIReferenceFormatCheckerIsFormat(t *testing.T) { assert.True(t, checker.IsFormat("relative")) assert.True(t, checker.IsFormat("https://dummyhost.com/dummy-path?dummy-qp-name=dummy-qp-value")) } + +const formatSchema = `{ + "type": "object", + "properties": { + "arr": {"type": "array", "items": {"type": "string"}, "format": "ArrayChecker"}, + "bool": {"type": "boolean", "format": "BoolChecker"}, + "int": {"format": "IntegerChecker"}, + "name": {"type": "string"}, + "str": {"type": "string", "format": "StringChecker"} + }, + "format": "ObjectChecker", + "required": ["name"] +}` + +type arrayChecker struct{} + +func (c arrayChecker) IsFormat(input interface{}) bool { + arr, ok := input.([]interface{}) + if !ok { + return true + } + for _, v := range arr { + if v == "x" { + return true + } + } + return false +} + +type boolChecker struct{} + +func (c boolChecker) IsFormat(input interface{}) bool { + b, ok := input.(bool) + if !ok { + return true + } + return b +} + +type integerChecker struct{} + +func (c integerChecker) IsFormat(input interface{}) bool { + number, ok := input.(json.Number) + if !ok { + return true + } + f, _ := number.Float64() + return int(f)%2 == 0 +} + +type objectChecker struct{} + +func (c objectChecker) IsFormat(input interface{}) bool { + obj, ok := input.(map[string]interface{}) + if !ok { + return true + } + return obj["name"] == "x" +} + +type stringChecker struct{} + +func (c stringChecker) IsFormat(input interface{}) bool { + str, ok := input.(string) + if !ok { + return true + } + return str == "o" +} + +func TestCustomFormat(t *testing.T) { + FormatCheckers. + Add("ArrayChecker", arrayChecker{}). + Add("BoolChecker", boolChecker{}). + Add("IntegerChecker", integerChecker{}). + Add("ObjectChecker", objectChecker{}). + Add("StringChecker", stringChecker{}) + + sl := NewStringLoader(formatSchema) + validResult, err := Validate(sl, NewGoLoader(map[string]interface{}{ + "arr": []string{"x", "y", "z"}, + "bool": true, + "int": "2", // format not defined for string + "name": "x", + "str": "o", + })) + if err != nil { + t.Error(err) + } + + if !validResult.Valid() { + for _, desc := range validResult.Errors() { + t.Error(desc) + } + } + + invalidResult, err := Validate(sl, NewGoLoader(map[string]interface{}{ + "arr": []string{"a", "b", "c"}, + "bool": false, + "int": 1, + "name": "z", + "str": "a", + })) + if err != nil { + t.Error(err) + } + + assert.Len(t, invalidResult.Errors(), 5) + + FormatCheckers. + Remove("ArrayChecker"). + Remove("BoolChecker"). + Remove("IntegerChecker"). + Remove("ObjectChecker"). + Remove("StringChecker") +} diff --git a/validation.go b/validation.go index 74091bc..9081bd9 100644 --- a/validation.go +++ b/validation.go @@ -440,6 +440,18 @@ func (v *subSchema) validateCommon(currentSubSchema *subSchema, value interface{ } } + // format: + if currentSubSchema.format != "" { + if !FormatCheckers.IsFormat(currentSubSchema.format, value) { + result.addInternalError( + new(DoesNotMatchFormatError), + context, + value, + ErrorDetails{"format": currentSubSchema.format}, + ) + } + } + result.incrementScore() } @@ -746,18 +758,6 @@ func (v *subSchema) validateString(currentSubSchema *subSchema, value interface{ } } - // format - if currentSubSchema.format != "" { - if !FormatCheckers.IsFormat(currentSubSchema.format, stringValue) { - result.addInternalError( - new(DoesNotMatchFormatError), - context, - value, - ErrorDetails{"format": currentSubSchema.format}, - ) - } - } - result.incrementScore() } @@ -842,17 +842,5 @@ func (v *subSchema) validateNumber(currentSubSchema *subSchema, value interface{ } } - // format - if currentSubSchema.format != "" { - if !FormatCheckers.IsFormat(currentSubSchema.format, float64Value) { - result.addInternalError( - new(DoesNotMatchFormatError), - context, - value, - ErrorDetails{"format": currentSubSchema.format}, - ) - } - } - result.incrementScore() }