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

     1  package jsonschema
     2  
     3  import "fmt"
     4  
     5  // EvaluateContains checks if at least one element in an array meets the conditions specified by the 'contains' keyword.
     6  // It follows the JSON Schema Draft 2020-12:
     7  //   - "contains" must be associated with a valid JSON Schema.
     8  //   - An array is valid if at least one of its elements matches the given schema, unless "minContains" is 0.
     9  //   - When "minContains" is 0, the array is considered valid even if no elements match the schema.
    10  //   - Produces annotations of indexes where the schema validates successfully.
    11  //   - If every element validates, it produces a boolean "true".
    12  //   - If the array is empty, annotations reflect the validation state.
    13  //   - Influences "unevaluatedItems" and may be used to implement "minContains" and "maxContains".
    14  //
    15  // This function provides detailed feedback on element validation and gathers comprehensive annotations.
    16  //
    17  // Reference: https://json-schema.org/draft/2020-12/json-schema-core#name-contains
    18  func evaluateContains(schema *Schema, data []interface{}, _ map[string]bool, evaluatedItems map[int]bool, dynamicScope *DynamicScope) ([]*EvaluationResult, *EvaluationError) {
    19  	if schema.Contains == nil {
    20  		// No 'contains' constraint is defined, skip further checks.
    21  		return nil, nil
    22  	}
    23  
    24  	results := []*EvaluationResult{}
    25  
    26  	var validCount int
    27  	for i, item := range data {
    28  		result, _, _ := schema.Contains.evaluate(item, dynamicScope)
    29  
    30  		if result != nil {
    31  			//nolint:errcheck
    32  			result.SetEvaluationPath("/contains").
    33  				SetSchemaLocation(schema.GetSchemaLocation("/contains")).
    34  				SetInstanceLocation(fmt.Sprintf("/%d", i))
    35  
    36  			if result.IsValid() {
    37  				validCount++
    38  				evaluatedItems[i] = true // Mark this item as evaluated
    39  			}
    40  		}
    41  	}
    42  
    43  	// Handle 'minContains' logic
    44  	minContains := 1 // Default value if 'minContains' is not specified
    45  	if schema.MinContains != nil {
    46  		minContains = int(*schema.MinContains)
    47  	}
    48  
    49  	if minContains == 0 && validCount == 0 {
    50  		// Valid scenario when minContains is 0. Still need to check maxContains.
    51  	} else if validCount < minContains {
    52  		return results, NewEvaluationError("minContains", "contains_too_few_items", "Value should contain at least {min_contains} matching items", map[string]interface{}{
    53  			"min_contains": minContains,
    54  			"count":        validCount,
    55  		})
    56  	}
    57  
    58  	// Handle 'maxContains' logic
    59  	if schema.MaxContains != nil && validCount > int(*schema.MaxContains) {
    60  		return results, NewEvaluationError("maxContains", "contains_too_many_items", "Value should contain no more than {max_contains} matching items", map[string]interface{}{
    61  			"max_contains": *schema.MaxContains,
    62  			"count":        validCount,
    63  		})
    64  	}
    65  
    66  	return results, nil
    67  }