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

     1  package jsonschema
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  )
     8  
     9  // EvaluateUnevaluatedItems checks if the data's array items that have not been evaluated by 'items', 'prefixItems', or 'contains'
    10  // conform to the subschema specified in the 'unevaluatedItems' attribute of the schema.
    11  // According to the JSON Schema Draft 2020-12:
    12  //   - The value of "unevaluatedItems" MUST be a valid JSON Schema.
    13  //   - Validation depends on the annotation results of "prefixItems", "items", and "contains".
    14  //   - If no relevant annotations are present, "unevaluatedItems" must apply to all locations in the array.
    15  //   - If a boolean true value is present from any annotations, "unevaluatedItems" must be ignored.
    16  //   - Otherwise, the subschema must be applied to any index greater than the largest evaluated index.
    17  //
    18  // This method ensures that any unevaluated array elements conform to the constraints defined in the unevaluatedItems attribute.
    19  // If an unevaluated array element does not conform, it returns a EvaluationError detailing the issue.
    20  //
    21  // Reference: https://json-schema.org/draft/2020-12/json-schema-core#name-unevaluateditems
    22  func evaluateUnevaluatedItems(schema *Schema, data interface{}, _ map[string]bool, evaluatedItems map[int]bool, dynamicScope *DynamicScope) ([]*EvaluationResult, *EvaluationError) {
    23  	items, ok := data.([]interface{})
    24  	if !ok {
    25  		return nil, nil // If data is not an array, then skip the array-specific validations.
    26  	}
    27  
    28  	// If UnevaluatedItems is not set, all items are considered evaluated
    29  	if schema.UnevaluatedItems == nil {
    30  		return nil, nil
    31  	}
    32  
    33  	// If UnevaluatedItems is a boolean type
    34  	if schema.UnevaluatedItems.Boolean != nil {
    35  		if *schema.UnevaluatedItems.Boolean {
    36  			// If true, all unevaluated items are valid
    37  			for i := range items {
    38  				evaluatedItems[i] = true
    39  			}
    40  			return nil, nil
    41  		}
    42  		// If false, any unevaluated item is invalid
    43  		var unevaluatedIndexes []string
    44  		for i := range items {
    45  			if _, evaluated := evaluatedItems[i]; !evaluated {
    46  				unevaluatedIndexes = append(unevaluatedIndexes, strconv.Itoa(i))
    47  			}
    48  		}
    49  		if len(unevaluatedIndexes) > 0 {
    50  			return nil, NewEvaluationError("unevaluatedItems", "unevaluated_items_not_allowed", "Unevaluated items are not allowed at indexes: {indexes}", map[string]interface{}{
    51  				"indexes": strings.Join(unevaluatedIndexes, ", "),
    52  			})
    53  		}
    54  		return nil, nil
    55  	}
    56  
    57  	results := []*EvaluationResult{}
    58  	invalid_indexes := []string{}
    59  
    60  	// Evaluate unevaluated items
    61  	for i, item := range items {
    62  		if _, evaluated := evaluatedItems[i]; !evaluated {
    63  			result, _, evaluatedMap := schema.UnevaluatedItems.evaluate(item, dynamicScope)
    64  			if result != nil {
    65  				//nolint:errcheck
    66  				result.SetEvaluationPath(fmt.Sprintf("/unevaluatedItems/%d", i)).
    67  					SetSchemaLocation(schema.GetSchemaLocation(fmt.Sprintf("/unevaluatedItems/%d", i))).
    68  					SetInstanceLocation(fmt.Sprintf("/%d", i))
    69  
    70  				results = append(results, result)
    71  				if result.IsValid() {
    72  					evaluatedItems[i] = true
    73  				} else {
    74  					invalid_indexes = append(invalid_indexes, strconv.Itoa(i))
    75  				}
    76  			}
    77  			// Merge evaluation states
    78  			for k, v := range evaluatedMap {
    79  				evaluatedItems[k] = v
    80  			}
    81  		}
    82  	}
    83  
    84  	if len(invalid_indexes) == 1 {
    85  		return results, NewEvaluationError("unevaluatedItems", "unevaluated_item_mismatch", "Item at index {index} does not match the unevaluatedItems schema", map[string]interface{}{
    86  			"index": invalid_indexes[0],
    87  		})
    88  	} else if len(invalid_indexes) > 1 {
    89  		return results, NewEvaluationError("unevaluatedItems", "unevaluated_items_mismatch", "Items at indexes {indexes} do not match the unevaluatedItems schema", map[string]interface{}{
    90  			"indexes": strings.Join(invalid_indexes, ", "),
    91  		})
    92  	}
    93  
    94  	return results, nil
    95  }