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

     1  package jsonschema
     2  
     3  import (
     4  	"fmt"
     5  	"slices"
     6  	"strings"
     7  )
     8  
     9  // EvaluateProperties checks if the properties in the data object conform to the schemas specified in the schema's properties attribute.
    10  // According to the JSON Schema Draft 2020-12:
    11  //   - The value of "properties" must be an object, with each value being a valid JSON Schema.
    12  //   - Validation succeeds if, for each name that appears in both the instance and as a name within this keyword's value, the child instance for that name successfully validates against the corresponding schema.
    13  //   - This function also affects the validation of "additionalProperties" and "unevaluatedProperties" by determining which properties have been evaluated.
    14  //
    15  // This method ensures that each property in the data matches its defined schema.
    16  // If a property does not conform, it returns a EvaluationError detailing the issue with that property.
    17  //
    18  // Reference: https://json-schema.org/draft/2020-12/json-schema-core#name-properties
    19  func evaluateProperties(schema *Schema, object map[string]interface{}, evaluatedProps map[string]bool, _ map[int]bool, dynamicScope *DynamicScope) ([]*EvaluationResult, *EvaluationError) {
    20  	if schema.Properties == nil {
    21  		return nil, nil // No properties defined, nothing to do.
    22  	}
    23  
    24  	invalid_properties := []string{}
    25  	results := []*EvaluationResult{}
    26  
    27  	for propName, propSchema := range *schema.Properties {
    28  		evaluatedProps[propName] = true
    29  		propValue, exists := object[propName]
    30  
    31  		if exists {
    32  			result, _, _ := propSchema.evaluate(propValue, dynamicScope)
    33  			if result != nil {
    34  				//nolint:errcheck
    35  				result.SetEvaluationPath(fmt.Sprintf("/properties/%s", propName)).
    36  					SetSchemaLocation(schema.GetSchemaLocation(fmt.Sprintf("/properties/%s", propName))).
    37  					SetInstanceLocation(fmt.Sprintf("/%s", propName))
    38  
    39  				results = append(results, result)
    40  
    41  				if !result.IsValid() {
    42  					invalid_properties = append(invalid_properties, propName)
    43  				}
    44  			}
    45  		} else if isRequired(schema, propName) && !defaultIsSpecified(propSchema) {
    46  			// Handle properties that are expected but not provided
    47  			result, _, _ := propSchema.evaluate(nil, dynamicScope)
    48  
    49  			if result != nil {
    50  				//nolint:errcheck
    51  				result.SetEvaluationPath(fmt.Sprintf("/properties/%s", propName)).
    52  					SetSchemaLocation(schema.GetSchemaLocation(fmt.Sprintf("/properties/%s", propName))).
    53  					SetInstanceLocation(fmt.Sprintf("/%s", propName))
    54  
    55  				results = append(results, result)
    56  
    57  				if !result.IsValid() {
    58  					invalid_properties = append(invalid_properties, propName)
    59  				}
    60  			}
    61  		}
    62  	}
    63  
    64  	if len(invalid_properties) == 1 {
    65  		return results, NewEvaluationError("properties", "property_mismatch", "Property {property} does not match the schema", map[string]interface{}{
    66  			"property": fmt.Sprintf("'%s'", invalid_properties[0]),
    67  		})
    68  	} else if len(invalid_properties) > 1 {
    69  		slices.Sort(invalid_properties)
    70  		quotedProperties := make([]string, len(invalid_properties))
    71  		for i, prop := range invalid_properties {
    72  			quotedProperties[i] = fmt.Sprintf("'%s'", prop)
    73  		}
    74  		return results, NewEvaluationError("properties", "properties_mismatch", "Properties {properties} do not match their schemas", map[string]interface{}{
    75  			"properties": strings.Join(quotedProperties, ", "),
    76  		})
    77  	}
    78  
    79  	return results, nil
    80  }
    81  
    82  // isRequired checks if a property is required.
    83  func isRequired(schema *Schema, propName string) bool {
    84  	for _, reqProp := range schema.Required {
    85  		if reqProp == propName {
    86  			return true
    87  		}
    88  	}
    89  	return false
    90  }
    91  
    92  // defaultIsSpecified checks if a default value is specified for a property schema.
    93  func defaultIsSpecified(propSchema *Schema) bool {
    94  	return propSchema != nil && propSchema.Default != nil
    95  }