...

Source file src/github.com/xeipuuv/gojsonschema/validation.go

Documentation: github.com/xeipuuv/gojsonschema

     1  // Copyright 2015 xeipuuv ( https://github.com/xeipuuv )
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //   http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // author           xeipuuv
    16  // author-github    https://github.com/xeipuuv
    17  // author-mail      xeipuuv@gmail.com
    18  //
    19  // repository-name  gojsonschema
    20  // repository-desc  An implementation of JSON Schema, based on IETF's draft v4 - Go language.
    21  //
    22  // description      Extends Schema and subSchema, implements the validation phase.
    23  //
    24  // created          28-02-2013
    25  
    26  package gojsonschema
    27  
    28  import (
    29  	"encoding/json"
    30  	"math/big"
    31  	"reflect"
    32  	"regexp"
    33  	"strconv"
    34  	"strings"
    35  	"unicode/utf8"
    36  )
    37  
    38  // Validate loads and validates a JSON schema
    39  func Validate(ls JSONLoader, ld JSONLoader) (*Result, error) {
    40  	// load schema
    41  	schema, err := NewSchema(ls)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  	return schema.Validate(ld)
    46  }
    47  
    48  // Validate loads and validates a JSON document
    49  func (v *Schema) Validate(l JSONLoader) (*Result, error) {
    50  	root, err := l.LoadJSON()
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  	return v.validateDocument(root), nil
    55  }
    56  
    57  func (v *Schema) validateDocument(root interface{}) *Result {
    58  	result := &Result{}
    59  	context := NewJsonContext(STRING_CONTEXT_ROOT, nil)
    60  	v.rootSchema.validateRecursive(v.rootSchema, root, result, context)
    61  	return result
    62  }
    63  
    64  func (v *subSchema) subValidateWithContext(document interface{}, context *JsonContext) *Result {
    65  	result := &Result{}
    66  	v.validateRecursive(v, document, result, context)
    67  	return result
    68  }
    69  
    70  // Walker function to validate the json recursively against the subSchema
    71  func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode interface{}, result *Result, context *JsonContext) {
    72  
    73  	if internalLogEnabled {
    74  		internalLog("validateRecursive %s", context.String())
    75  		internalLog(" %v", currentNode)
    76  	}
    77  
    78  	// Handle true/false schema as early as possible as all other fields will be nil
    79  	if currentSubSchema.pass != nil {
    80  		if !*currentSubSchema.pass {
    81  			result.addInternalError(
    82  				new(FalseError),
    83  				context,
    84  				currentNode,
    85  				ErrorDetails{},
    86  			)
    87  		}
    88  		return
    89  	}
    90  
    91  	// Handle referenced schemas, returns directly when a $ref is found
    92  	if currentSubSchema.refSchema != nil {
    93  		v.validateRecursive(currentSubSchema.refSchema, currentNode, result, context)
    94  		return
    95  	}
    96  
    97  	// Check for null value
    98  	if currentNode == nil {
    99  		if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_NULL) {
   100  			result.addInternalError(
   101  				new(InvalidTypeError),
   102  				context,
   103  				currentNode,
   104  				ErrorDetails{
   105  					"expected": currentSubSchema.types.String(),
   106  					"given":    TYPE_NULL,
   107  				},
   108  			)
   109  			return
   110  		}
   111  
   112  		currentSubSchema.validateSchema(currentSubSchema, currentNode, result, context)
   113  		v.validateCommon(currentSubSchema, currentNode, result, context)
   114  
   115  	} else { // Not a null value
   116  
   117  		if isJSONNumber(currentNode) {
   118  
   119  			value := currentNode.(json.Number)
   120  
   121  			isInt := checkJSONInteger(value)
   122  
   123  			validType := currentSubSchema.types.Contains(TYPE_NUMBER) || (isInt && currentSubSchema.types.Contains(TYPE_INTEGER))
   124  
   125  			if currentSubSchema.types.IsTyped() && !validType {
   126  
   127  				givenType := TYPE_INTEGER
   128  				if !isInt {
   129  					givenType = TYPE_NUMBER
   130  				}
   131  
   132  				result.addInternalError(
   133  					new(InvalidTypeError),
   134  					context,
   135  					currentNode,
   136  					ErrorDetails{
   137  						"expected": currentSubSchema.types.String(),
   138  						"given":    givenType,
   139  					},
   140  				)
   141  				return
   142  			}
   143  
   144  			currentSubSchema.validateSchema(currentSubSchema, value, result, context)
   145  			v.validateNumber(currentSubSchema, value, result, context)
   146  			v.validateCommon(currentSubSchema, value, result, context)
   147  			v.validateString(currentSubSchema, value, result, context)
   148  
   149  		} else {
   150  
   151  			rValue := reflect.ValueOf(currentNode)
   152  			rKind := rValue.Kind()
   153  
   154  			switch rKind {
   155  
   156  			// Slice => JSON array
   157  
   158  			case reflect.Slice:
   159  
   160  				if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_ARRAY) {
   161  					result.addInternalError(
   162  						new(InvalidTypeError),
   163  						context,
   164  						currentNode,
   165  						ErrorDetails{
   166  							"expected": currentSubSchema.types.String(),
   167  							"given":    TYPE_ARRAY,
   168  						},
   169  					)
   170  					return
   171  				}
   172  
   173  				castCurrentNode := currentNode.([]interface{})
   174  
   175  				currentSubSchema.validateSchema(currentSubSchema, castCurrentNode, result, context)
   176  
   177  				v.validateArray(currentSubSchema, castCurrentNode, result, context)
   178  				v.validateCommon(currentSubSchema, castCurrentNode, result, context)
   179  
   180  			// Map => JSON object
   181  
   182  			case reflect.Map:
   183  				if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_OBJECT) {
   184  					result.addInternalError(
   185  						new(InvalidTypeError),
   186  						context,
   187  						currentNode,
   188  						ErrorDetails{
   189  							"expected": currentSubSchema.types.String(),
   190  							"given":    TYPE_OBJECT,
   191  						},
   192  					)
   193  					return
   194  				}
   195  
   196  				castCurrentNode, ok := currentNode.(map[string]interface{})
   197  				if !ok {
   198  					castCurrentNode = convertDocumentNode(currentNode).(map[string]interface{})
   199  				}
   200  
   201  				currentSubSchema.validateSchema(currentSubSchema, castCurrentNode, result, context)
   202  
   203  				v.validateObject(currentSubSchema, castCurrentNode, result, context)
   204  				v.validateCommon(currentSubSchema, castCurrentNode, result, context)
   205  
   206  				for _, pSchema := range currentSubSchema.propertiesChildren {
   207  					nextNode, ok := castCurrentNode[pSchema.property]
   208  					if ok {
   209  						subContext := NewJsonContext(pSchema.property, context)
   210  						v.validateRecursive(pSchema, nextNode, result, subContext)
   211  					}
   212  				}
   213  
   214  			// Simple JSON values : string, number, boolean
   215  
   216  			case reflect.Bool:
   217  
   218  				if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_BOOLEAN) {
   219  					result.addInternalError(
   220  						new(InvalidTypeError),
   221  						context,
   222  						currentNode,
   223  						ErrorDetails{
   224  							"expected": currentSubSchema.types.String(),
   225  							"given":    TYPE_BOOLEAN,
   226  						},
   227  					)
   228  					return
   229  				}
   230  
   231  				value := currentNode.(bool)
   232  
   233  				currentSubSchema.validateSchema(currentSubSchema, value, result, context)
   234  				v.validateNumber(currentSubSchema, value, result, context)
   235  				v.validateCommon(currentSubSchema, value, result, context)
   236  				v.validateString(currentSubSchema, value, result, context)
   237  
   238  			case reflect.String:
   239  
   240  				if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_STRING) {
   241  					result.addInternalError(
   242  						new(InvalidTypeError),
   243  						context,
   244  						currentNode,
   245  						ErrorDetails{
   246  							"expected": currentSubSchema.types.String(),
   247  							"given":    TYPE_STRING,
   248  						},
   249  					)
   250  					return
   251  				}
   252  
   253  				value := currentNode.(string)
   254  
   255  				currentSubSchema.validateSchema(currentSubSchema, value, result, context)
   256  				v.validateNumber(currentSubSchema, value, result, context)
   257  				v.validateCommon(currentSubSchema, value, result, context)
   258  				v.validateString(currentSubSchema, value, result, context)
   259  
   260  			}
   261  
   262  		}
   263  
   264  	}
   265  
   266  	result.incrementScore()
   267  }
   268  
   269  // Different kinds of validation there, subSchema / common / array / object / string...
   270  func (v *subSchema) validateSchema(currentSubSchema *subSchema, currentNode interface{}, result *Result, context *JsonContext) {
   271  
   272  	if internalLogEnabled {
   273  		internalLog("validateSchema %s", context.String())
   274  		internalLog(" %v", currentNode)
   275  	}
   276  
   277  	if len(currentSubSchema.anyOf) > 0 {
   278  
   279  		validatedAnyOf := false
   280  		var bestValidationResult *Result
   281  
   282  		for _, anyOfSchema := range currentSubSchema.anyOf {
   283  			if !validatedAnyOf {
   284  				validationResult := anyOfSchema.subValidateWithContext(currentNode, context)
   285  				validatedAnyOf = validationResult.Valid()
   286  
   287  				if !validatedAnyOf && (bestValidationResult == nil || validationResult.score > bestValidationResult.score) {
   288  					bestValidationResult = validationResult
   289  				}
   290  			}
   291  		}
   292  		if !validatedAnyOf {
   293  
   294  			result.addInternalError(new(NumberAnyOfError), context, currentNode, ErrorDetails{})
   295  
   296  			if bestValidationResult != nil {
   297  				// add error messages of closest matching subSchema as
   298  				// that's probably the one the user was trying to match
   299  				result.mergeErrors(bestValidationResult)
   300  			}
   301  		}
   302  	}
   303  
   304  	if len(currentSubSchema.oneOf) > 0 {
   305  
   306  		nbValidated := 0
   307  		var bestValidationResult *Result
   308  
   309  		for _, oneOfSchema := range currentSubSchema.oneOf {
   310  			validationResult := oneOfSchema.subValidateWithContext(currentNode, context)
   311  			if validationResult.Valid() {
   312  				nbValidated++
   313  			} else if nbValidated == 0 && (bestValidationResult == nil || validationResult.score > bestValidationResult.score) {
   314  				bestValidationResult = validationResult
   315  			}
   316  		}
   317  
   318  		if nbValidated != 1 {
   319  
   320  			result.addInternalError(new(NumberOneOfError), context, currentNode, ErrorDetails{})
   321  
   322  			if nbValidated == 0 {
   323  				// add error messages of closest matching subSchema as
   324  				// that's probably the one the user was trying to match
   325  				result.mergeErrors(bestValidationResult)
   326  			}
   327  		}
   328  
   329  	}
   330  
   331  	if len(currentSubSchema.allOf) > 0 {
   332  		nbValidated := 0
   333  
   334  		for _, allOfSchema := range currentSubSchema.allOf {
   335  			validationResult := allOfSchema.subValidateWithContext(currentNode, context)
   336  			if validationResult.Valid() {
   337  				nbValidated++
   338  			}
   339  			result.mergeErrors(validationResult)
   340  		}
   341  
   342  		if nbValidated != len(currentSubSchema.allOf) {
   343  			result.addInternalError(new(NumberAllOfError), context, currentNode, ErrorDetails{})
   344  		}
   345  	}
   346  
   347  	if currentSubSchema.not != nil {
   348  		validationResult := currentSubSchema.not.subValidateWithContext(currentNode, context)
   349  		if validationResult.Valid() {
   350  			result.addInternalError(new(NumberNotError), context, currentNode, ErrorDetails{})
   351  		}
   352  	}
   353  
   354  	if currentSubSchema.dependencies != nil && len(currentSubSchema.dependencies) > 0 {
   355  		if isKind(currentNode, reflect.Map) {
   356  			for elementKey := range currentNode.(map[string]interface{}) {
   357  				if dependency, ok := currentSubSchema.dependencies[elementKey]; ok {
   358  					switch dependency := dependency.(type) {
   359  
   360  					case []string:
   361  						for _, dependOnKey := range dependency {
   362  							if _, dependencyResolved := currentNode.(map[string]interface{})[dependOnKey]; !dependencyResolved {
   363  								result.addInternalError(
   364  									new(MissingDependencyError),
   365  									context,
   366  									currentNode,
   367  									ErrorDetails{"dependency": dependOnKey},
   368  								)
   369  							}
   370  						}
   371  
   372  					case *subSchema:
   373  						dependency.validateRecursive(dependency, currentNode, result, context)
   374  					}
   375  				}
   376  			}
   377  		}
   378  	}
   379  
   380  	if currentSubSchema._if != nil {
   381  		validationResultIf := currentSubSchema._if.subValidateWithContext(currentNode, context)
   382  		if currentSubSchema._then != nil && validationResultIf.Valid() {
   383  			validationResultThen := currentSubSchema._then.subValidateWithContext(currentNode, context)
   384  			if !validationResultThen.Valid() {
   385  				result.addInternalError(new(ConditionThenError), context, currentNode, ErrorDetails{})
   386  				result.mergeErrors(validationResultThen)
   387  			}
   388  		}
   389  		if currentSubSchema._else != nil && !validationResultIf.Valid() {
   390  			validationResultElse := currentSubSchema._else.subValidateWithContext(currentNode, context)
   391  			if !validationResultElse.Valid() {
   392  				result.addInternalError(new(ConditionElseError), context, currentNode, ErrorDetails{})
   393  				result.mergeErrors(validationResultElse)
   394  			}
   395  		}
   396  	}
   397  
   398  	result.incrementScore()
   399  }
   400  
   401  func (v *subSchema) validateCommon(currentSubSchema *subSchema, value interface{}, result *Result, context *JsonContext) {
   402  
   403  	if internalLogEnabled {
   404  		internalLog("validateCommon %s", context.String())
   405  		internalLog(" %v", value)
   406  	}
   407  
   408  	// const:
   409  	if currentSubSchema._const != nil {
   410  		vString, err := marshalWithoutNumber(value)
   411  		if err != nil {
   412  			result.addInternalError(new(InternalError), context, value, ErrorDetails{"error": err})
   413  		}
   414  		if *vString != *currentSubSchema._const {
   415  			result.addInternalError(new(ConstError),
   416  				context,
   417  				value,
   418  				ErrorDetails{
   419  					"allowed": *currentSubSchema._const,
   420  				},
   421  			)
   422  		}
   423  	}
   424  
   425  	// enum:
   426  	if len(currentSubSchema.enum) > 0 {
   427  		vString, err := marshalWithoutNumber(value)
   428  		if err != nil {
   429  			result.addInternalError(new(InternalError), context, value, ErrorDetails{"error": err})
   430  		}
   431  		if !isStringInSlice(currentSubSchema.enum, *vString) {
   432  			result.addInternalError(
   433  				new(EnumError),
   434  				context,
   435  				value,
   436  				ErrorDetails{
   437  					"allowed": strings.Join(currentSubSchema.enum, ", "),
   438  				},
   439  			)
   440  		}
   441  	}
   442  
   443  	result.incrementScore()
   444  }
   445  
   446  func (v *subSchema) validateArray(currentSubSchema *subSchema, value []interface{}, result *Result, context *JsonContext) {
   447  
   448  	if internalLogEnabled {
   449  		internalLog("validateArray %s", context.String())
   450  		internalLog(" %v", value)
   451  	}
   452  
   453  	nbValues := len(value)
   454  
   455  	// TODO explain
   456  	if currentSubSchema.itemsChildrenIsSingleSchema {
   457  		for i := range value {
   458  			subContext := NewJsonContext(strconv.Itoa(i), context)
   459  			validationResult := currentSubSchema.itemsChildren[0].subValidateWithContext(value[i], subContext)
   460  			result.mergeErrors(validationResult)
   461  		}
   462  	} else {
   463  		if currentSubSchema.itemsChildren != nil && len(currentSubSchema.itemsChildren) > 0 {
   464  
   465  			nbItems := len(currentSubSchema.itemsChildren)
   466  
   467  			// while we have both schemas and values, check them against each other
   468  			for i := 0; i != nbItems && i != nbValues; i++ {
   469  				subContext := NewJsonContext(strconv.Itoa(i), context)
   470  				validationResult := currentSubSchema.itemsChildren[i].subValidateWithContext(value[i], subContext)
   471  				result.mergeErrors(validationResult)
   472  			}
   473  
   474  			if nbItems < nbValues {
   475  				// we have less schemas than elements in the instance array,
   476  				// but that might be ok if "additionalItems" is specified.
   477  
   478  				switch currentSubSchema.additionalItems.(type) {
   479  				case bool:
   480  					if !currentSubSchema.additionalItems.(bool) {
   481  						result.addInternalError(new(ArrayNoAdditionalItemsError), context, value, ErrorDetails{})
   482  					}
   483  				case *subSchema:
   484  					additionalItemSchema := currentSubSchema.additionalItems.(*subSchema)
   485  					for i := nbItems; i != nbValues; i++ {
   486  						subContext := NewJsonContext(strconv.Itoa(i), context)
   487  						validationResult := additionalItemSchema.subValidateWithContext(value[i], subContext)
   488  						result.mergeErrors(validationResult)
   489  					}
   490  				}
   491  			}
   492  		}
   493  	}
   494  
   495  	// minItems & maxItems
   496  	if currentSubSchema.minItems != nil {
   497  		if nbValues < int(*currentSubSchema.minItems) {
   498  			result.addInternalError(
   499  				new(ArrayMinItemsError),
   500  				context,
   501  				value,
   502  				ErrorDetails{"min": *currentSubSchema.minItems},
   503  			)
   504  		}
   505  	}
   506  	if currentSubSchema.maxItems != nil {
   507  		if nbValues > int(*currentSubSchema.maxItems) {
   508  			result.addInternalError(
   509  				new(ArrayMaxItemsError),
   510  				context,
   511  				value,
   512  				ErrorDetails{"max": *currentSubSchema.maxItems},
   513  			)
   514  		}
   515  	}
   516  
   517  	// uniqueItems:
   518  	if currentSubSchema.uniqueItems {
   519  		var stringifiedItems = make(map[string]int)
   520  		for j, v := range value {
   521  			vString, err := marshalWithoutNumber(v)
   522  			if err != nil {
   523  				result.addInternalError(new(InternalError), context, value, ErrorDetails{"err": err})
   524  			}
   525  			if i, ok := stringifiedItems[*vString]; ok {
   526  				result.addInternalError(
   527  					new(ItemsMustBeUniqueError),
   528  					context,
   529  					value,
   530  					ErrorDetails{"type": TYPE_ARRAY, "i": i, "j": j},
   531  				)
   532  			}
   533  			stringifiedItems[*vString] = j
   534  		}
   535  	}
   536  
   537  	// contains:
   538  
   539  	if currentSubSchema.contains != nil {
   540  		validatedOne := false
   541  		var bestValidationResult *Result
   542  
   543  		for i, v := range value {
   544  			subContext := NewJsonContext(strconv.Itoa(i), context)
   545  
   546  			validationResult := currentSubSchema.contains.subValidateWithContext(v, subContext)
   547  			if validationResult.Valid() {
   548  				validatedOne = true
   549  				break
   550  			} else {
   551  				if bestValidationResult == nil || validationResult.score > bestValidationResult.score {
   552  					bestValidationResult = validationResult
   553  				}
   554  			}
   555  		}
   556  		if !validatedOne {
   557  			result.addInternalError(
   558  				new(ArrayContainsError),
   559  				context,
   560  				value,
   561  				ErrorDetails{},
   562  			)
   563  			if bestValidationResult != nil {
   564  				result.mergeErrors(bestValidationResult)
   565  			}
   566  		}
   567  	}
   568  
   569  	result.incrementScore()
   570  }
   571  
   572  func (v *subSchema) validateObject(currentSubSchema *subSchema, value map[string]interface{}, result *Result, context *JsonContext) {
   573  
   574  	if internalLogEnabled {
   575  		internalLog("validateObject %s", context.String())
   576  		internalLog(" %v", value)
   577  	}
   578  
   579  	// minProperties & maxProperties:
   580  	if currentSubSchema.minProperties != nil {
   581  		if len(value) < int(*currentSubSchema.minProperties) {
   582  			result.addInternalError(
   583  				new(ArrayMinPropertiesError),
   584  				context,
   585  				value,
   586  				ErrorDetails{"min": *currentSubSchema.minProperties},
   587  			)
   588  		}
   589  	}
   590  	if currentSubSchema.maxProperties != nil {
   591  		if len(value) > int(*currentSubSchema.maxProperties) {
   592  			result.addInternalError(
   593  				new(ArrayMaxPropertiesError),
   594  				context,
   595  				value,
   596  				ErrorDetails{"max": *currentSubSchema.maxProperties},
   597  			)
   598  		}
   599  	}
   600  
   601  	// required:
   602  	for _, requiredProperty := range currentSubSchema.required {
   603  		_, ok := value[requiredProperty]
   604  		if ok {
   605  			result.incrementScore()
   606  		} else {
   607  			result.addInternalError(
   608  				new(RequiredError),
   609  				context,
   610  				value,
   611  				ErrorDetails{"property": requiredProperty},
   612  			)
   613  		}
   614  	}
   615  
   616  	// additionalProperty & patternProperty:
   617  	for pk := range value {
   618  
   619  		// Check whether this property is described by "properties"
   620  		found := false
   621  		for _, spValue := range currentSubSchema.propertiesChildren {
   622  			if pk == spValue.property {
   623  				found = true
   624  			}
   625  		}
   626  
   627  		//  Check whether this property is described by "patternProperties"
   628  		ppMatch := v.validatePatternProperty(currentSubSchema, pk, value[pk], result, context)
   629  
   630  		// If it is not described by neither "properties" nor "patternProperties" it must pass "additionalProperties"
   631  		if !found && !ppMatch {
   632  			switch ap := currentSubSchema.additionalProperties.(type) {
   633  			case bool:
   634  				// Handle the boolean case separately as it's cleaner to return a specific error than failing to pass the false schema
   635  				if !ap {
   636  					result.addInternalError(
   637  						new(AdditionalPropertyNotAllowedError),
   638  						context,
   639  						value[pk],
   640  						ErrorDetails{"property": pk},
   641  					)
   642  
   643  				}
   644  			case *subSchema:
   645  				validationResult := ap.subValidateWithContext(value[pk], NewJsonContext(pk, context))
   646  				result.mergeErrors(validationResult)
   647  			}
   648  		}
   649  	}
   650  
   651  	// propertyNames:
   652  	if currentSubSchema.propertyNames != nil {
   653  		for pk := range value {
   654  			validationResult := currentSubSchema.propertyNames.subValidateWithContext(pk, context)
   655  			if !validationResult.Valid() {
   656  				result.addInternalError(new(InvalidPropertyNameError),
   657  					context,
   658  					value, ErrorDetails{
   659  						"property": pk,
   660  					})
   661  				result.mergeErrors(validationResult)
   662  			}
   663  		}
   664  	}
   665  
   666  	result.incrementScore()
   667  }
   668  
   669  func (v *subSchema) validatePatternProperty(currentSubSchema *subSchema, key string, value interface{}, result *Result, context *JsonContext) bool {
   670  
   671  	if internalLogEnabled {
   672  		internalLog("validatePatternProperty %s", context.String())
   673  		internalLog(" %s %v", key, value)
   674  	}
   675  
   676  	validated := false
   677  
   678  	for pk, pv := range currentSubSchema.patternProperties {
   679  		if matches, _ := regexp.MatchString(pk, key); matches {
   680  			validated = true
   681  			subContext := NewJsonContext(key, context)
   682  			validationResult := pv.subValidateWithContext(value, subContext)
   683  			result.mergeErrors(validationResult)
   684  		}
   685  	}
   686  
   687  	if !validated {
   688  		return false
   689  	}
   690  
   691  	result.incrementScore()
   692  	return true
   693  }
   694  
   695  func (v *subSchema) validateString(currentSubSchema *subSchema, value interface{}, result *Result, context *JsonContext) {
   696  
   697  	// Ignore JSON numbers
   698  	if isJSONNumber(value) {
   699  		return
   700  	}
   701  
   702  	// Ignore non strings
   703  	if !isKind(value, reflect.String) {
   704  		return
   705  	}
   706  
   707  	if internalLogEnabled {
   708  		internalLog("validateString %s", context.String())
   709  		internalLog(" %v", value)
   710  	}
   711  
   712  	stringValue := value.(string)
   713  
   714  	// minLength & maxLength:
   715  	if currentSubSchema.minLength != nil {
   716  		if utf8.RuneCount([]byte(stringValue)) < int(*currentSubSchema.minLength) {
   717  			result.addInternalError(
   718  				new(StringLengthGTEError),
   719  				context,
   720  				value,
   721  				ErrorDetails{"min": *currentSubSchema.minLength},
   722  			)
   723  		}
   724  	}
   725  	if currentSubSchema.maxLength != nil {
   726  		if utf8.RuneCount([]byte(stringValue)) > int(*currentSubSchema.maxLength) {
   727  			result.addInternalError(
   728  				new(StringLengthLTEError),
   729  				context,
   730  				value,
   731  				ErrorDetails{"max": *currentSubSchema.maxLength},
   732  			)
   733  		}
   734  	}
   735  
   736  	// pattern:
   737  	if currentSubSchema.pattern != nil {
   738  		if !currentSubSchema.pattern.MatchString(stringValue) {
   739  			result.addInternalError(
   740  				new(DoesNotMatchPatternError),
   741  				context,
   742  				value,
   743  				ErrorDetails{"pattern": currentSubSchema.pattern},
   744  			)
   745  
   746  		}
   747  	}
   748  
   749  	// format
   750  	if currentSubSchema.format != "" {
   751  		if !FormatCheckers.IsFormat(currentSubSchema.format, stringValue) {
   752  			result.addInternalError(
   753  				new(DoesNotMatchFormatError),
   754  				context,
   755  				value,
   756  				ErrorDetails{"format": currentSubSchema.format},
   757  			)
   758  		}
   759  	}
   760  
   761  	result.incrementScore()
   762  }
   763  
   764  func (v *subSchema) validateNumber(currentSubSchema *subSchema, value interface{}, result *Result, context *JsonContext) {
   765  
   766  	// Ignore non numbers
   767  	if !isJSONNumber(value) {
   768  		return
   769  	}
   770  
   771  	if internalLogEnabled {
   772  		internalLog("validateNumber %s", context.String())
   773  		internalLog(" %v", value)
   774  	}
   775  
   776  	number := value.(json.Number)
   777  	float64Value, _ := new(big.Rat).SetString(string(number))
   778  
   779  	// multipleOf:
   780  	if currentSubSchema.multipleOf != nil {
   781  		if q := new(big.Rat).Quo(float64Value, currentSubSchema.multipleOf); !q.IsInt() {
   782  			result.addInternalError(
   783  				new(MultipleOfError),
   784  				context,
   785  				number,
   786  				ErrorDetails{
   787  					"multiple": new(big.Float).SetRat(currentSubSchema.multipleOf),
   788  				},
   789  			)
   790  		}
   791  	}
   792  
   793  	//maximum & exclusiveMaximum:
   794  	if currentSubSchema.maximum != nil {
   795  		if float64Value.Cmp(currentSubSchema.maximum) == 1 {
   796  			result.addInternalError(
   797  				new(NumberLTEError),
   798  				context,
   799  				number,
   800  				ErrorDetails{
   801  					"max": new(big.Float).SetRat(currentSubSchema.maximum),
   802  				},
   803  			)
   804  		}
   805  	}
   806  	if currentSubSchema.exclusiveMaximum != nil {
   807  		if float64Value.Cmp(currentSubSchema.exclusiveMaximum) >= 0 {
   808  			result.addInternalError(
   809  				new(NumberLTError),
   810  				context,
   811  				number,
   812  				ErrorDetails{
   813  					"max": new(big.Float).SetRat(currentSubSchema.exclusiveMaximum),
   814  				},
   815  			)
   816  		}
   817  	}
   818  
   819  	//minimum & exclusiveMinimum:
   820  	if currentSubSchema.minimum != nil {
   821  		if float64Value.Cmp(currentSubSchema.minimum) == -1 {
   822  			result.addInternalError(
   823  				new(NumberGTEError),
   824  				context,
   825  				number,
   826  				ErrorDetails{
   827  					"min": new(big.Float).SetRat(currentSubSchema.minimum),
   828  				},
   829  			)
   830  		}
   831  	}
   832  	if currentSubSchema.exclusiveMinimum != nil {
   833  		if float64Value.Cmp(currentSubSchema.exclusiveMinimum) <= 0 {
   834  			result.addInternalError(
   835  				new(NumberGTError),
   836  				context,
   837  				number,
   838  				ErrorDetails{
   839  					"min": new(big.Float).SetRat(currentSubSchema.exclusiveMinimum),
   840  				},
   841  			)
   842  		}
   843  	}
   844  
   845  	// format
   846  	if currentSubSchema.format != "" {
   847  		if !FormatCheckers.IsFormat(currentSubSchema.format, float64Value) {
   848  			result.addInternalError(
   849  				new(DoesNotMatchFormatError),
   850  				context,
   851  				value,
   852  				ErrorDetails{"format": currentSubSchema.format},
   853  			)
   854  		}
   855  	}
   856  
   857  	result.incrementScore()
   858  }
   859  

View as plain text