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 }