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

     1  package jsonschema
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  	"slices"
     7  	"strings"
     8  )
     9  
    10  // Initialize or Load Schema
    11  func (s *Schema) compilePatterns() {
    12  	if s.PatternProperties == nil {
    13  		return // No patterns to compile if the map is nil
    14  	}
    15  
    16  	s.compiledPatterns = make(map[string]*regexp.Regexp)
    17  	// Since s.PatternProperties is a pointer to a SchemaMap, we dereference it here
    18  	for pattern := range *s.PatternProperties {
    19  		regex, err := regexp.Compile(pattern)
    20  		if err == nil {
    21  			s.compiledPatterns[pattern] = regex
    22  		}
    23  	}
    24  }
    25  
    26  // EvaluatePatternProperties checks if properties in the data object that match regex patterns conform to the schemas specified in the schema's patternProperties attribute.
    27  // According to the JSON Schema Draft 2020-12:
    28  //   - Each property name in "patternProperties" must be a valid regex and each property value must be a valid JSON Schema.
    29  //   - Validation succeeds for each instance name that matches any regular expressions, and the child instance for that name validates against the corresponding schema.
    30  //
    31  // This function ensures that properties which match the patterns validate accordingly and aids the behavior of "additionalProperties" and "unevaluatedProperties".
    32  //
    33  // Reference: https://json-schema.org/draft/2020-12/json-schema-core#name-patternproperties
    34  func evaluatePatternProperties(schema *Schema, object map[string]interface{}, evaluatedProps map[string]bool, _ map[int]bool, dynamicScope *DynamicScope) ([]*EvaluationResult, *EvaluationError) {
    35  	if schema.PatternProperties == nil {
    36  		return nil, nil // No patternProperties defined, nothing to do.
    37  	}
    38  
    39  	// invalid_regex  := []string{}
    40  	invalid_properties := []string{}
    41  	results := []*EvaluationResult{}
    42  
    43  	// Loop over each pattern in the PatternProperties map.
    44  	for patternKey, patternSchema := range *schema.PatternProperties {
    45  		// Get from the compiled patterns map, if not found, compile the pattern
    46  		regex, ok := schema.compiledPatterns[patternKey]
    47  		if !ok {
    48  			var err error
    49  			regex, err = regexp.Compile(patternKey)
    50  			if err != nil {
    51  				// invalid_regex = append(invalid_regex, patternKey)
    52  				continue
    53  			}
    54  			schema.compiledPatterns[patternKey] = regex
    55  		}
    56  
    57  		// Check each property in the object against the compiled regex.
    58  		for propName, propValue := range object {
    59  			if regex.MatchString(propName) {
    60  				evaluatedProps[propName] = true
    61  
    62  				// Evaluate the property value directly using the associated schema or boolean.
    63  				result, _, _ := patternSchema.evaluate(propValue, dynamicScope)
    64  				if result != nil {
    65  					//nolint:errcheck
    66  					result.SetEvaluationPath(fmt.Sprintf("/patternProperties/%s", propName)).
    67  						SetSchemaLocation(schema.GetSchemaLocation(fmt.Sprintf("/patternProperties/%s", propName))).
    68  						SetInstanceLocation(fmt.Sprintf("/%s", propName))
    69  
    70  					results = append(results, result)
    71  
    72  					if !result.IsValid() && !slices.Contains(invalid_properties, propName) {
    73  						invalid_properties = append(invalid_properties, propName)
    74  					}
    75  				}
    76  			}
    77  		}
    78  	}
    79  
    80  	if len(invalid_properties) == 1 {
    81  		return results, NewEvaluationError("properties", "pattern_property_mismatch", "Property {property} does not match the pattern schema", map[string]interface{}{
    82  			"property": fmt.Sprintf("'%s'", invalid_properties[0]),
    83  		})
    84  	} else if len(invalid_properties) > 1 {
    85  		quotedProperties := make([]string, len(invalid_properties))
    86  		for i, prop := range invalid_properties {
    87  			quotedProperties[i] = fmt.Sprintf("'%s'", prop)
    88  		}
    89  		return results, NewEvaluationError("properties", "pattern_properties_mismatch", "Properties {properties} do not match their pattern schemas", map[string]interface{}{
    90  			"properties": strings.Join(quotedProperties, ", "),
    91  		})
    92  	}
    93  
    94  	return results, nil
    95  }