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 }