github.com/kaptinlin/jsonschema@v0.4.6/additionalProperties.go (about)

     1  package jsonschema
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  )
     7  
     8  // EvaluateAdditionalProperties checks if properties not explicitly defined or matched by patternProperties conform to the schema specified in additionalProperties.
     9  // According to the JSON Schema Draft 2020-12:
    10  //   - The value of "additionalProperties" must be a valid JSON Schema.
    11  //   - This keyword validates child values of instance names that do not appear in the annotation results of either "properties" or "patternProperties".
    12  //   - Validation succeeds for these properties if the child instance validates against the "additionalProperties" schema.
    13  //   - Omitting "additionalProperties" has the same assertion behavior as an empty schema, which allows any type of value.
    14  //
    15  // This function ensures that all properties not explicitly mentioned or matched are validated according to a default schema or constraints.
    16  //
    17  // Reference: https://json-schema.org/draft/2020-12/json-schema-core#name-additionalproperties
    18  func evaluateAdditionalProperties(schema *Schema, object map[string]interface{}, evaluatedProps map[string]bool, _ map[int]bool, dynamicScope *DynamicScope) ([]*EvaluationResult, *EvaluationError) {
    19  	results := []*EvaluationResult{}
    20  	invalid_properties := []string{}
    21  
    22  	properties := make(map[string]bool)
    23  	if schema.Properties != nil {
    24  		for propName := range *schema.Properties {
    25  			properties[propName] = true
    26  		}
    27  	}
    28  	if schema.PatternProperties != nil {
    29  		for _, regex := range schema.compiledPatterns {
    30  			for propName := range object {
    31  				if regex.MatchString(propName) {
    32  					properties[propName] = true
    33  				}
    34  			}
    35  		}
    36  	}
    37  
    38  	// Evaluate additional properties
    39  	if schema.AdditionalProperties != nil {
    40  		for propName, propValue := range object {
    41  			if !properties[propName] {
    42  				result, _, _ := schema.AdditionalProperties.evaluate(propValue, dynamicScope)
    43  				if result != nil {
    44  					//nolint:errcheck
    45  					result.SetEvaluationPath(fmt.Sprintf("/additionalProperties/%s", propName)).
    46  						SetSchemaLocation(schema.GetSchemaLocation(fmt.Sprintf("/additionalProperties/%s", propName))).
    47  						SetInstanceLocation(fmt.Sprintf("/%s", propName))
    48  
    49  					results = append(results, result)
    50  					if !result.IsValid() {
    51  						invalid_properties = append(invalid_properties, propName)
    52  					}
    53  				}
    54  
    55  				// Mark property as evaluated
    56  				evaluatedProps[propName] = true
    57  			}
    58  		}
    59  	}
    60  
    61  	if len(invalid_properties) == 1 {
    62  		return results, NewEvaluationError("additionalProperties", "additional_property_mismatch", "Additional property {property} does not match the schema", map[string]interface{}{
    63  			"property": fmt.Sprintf("'%s'", invalid_properties[0]),
    64  		})
    65  	} else if len(invalid_properties) > 1 {
    66  		quotedProperties := make([]string, len(invalid_properties))
    67  		for i, prop := range invalid_properties {
    68  			quotedProperties[i] = fmt.Sprintf("'%s'", prop)
    69  		}
    70  		return results, NewEvaluationError("additionalProperties", "additional_properties_mismatch", "Additional properties {properties} do not match the schema", map[string]interface{}{
    71  			"properties": strings.Join(quotedProperties, ", "),
    72  		})
    73  	}
    74  
    75  	return results, nil
    76  }