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

     1  package jsonschema
     2  
     3  import (
     4  	"reflect"
     5  )
     6  
     7  // Validate checks if the given instance conforms to the schema.
     8  // This method automatically detects the input type and delegates to the appropriate validation method.
     9  func (s *Schema) Validate(instance interface{}) *EvaluationResult {
    10  	switch data := instance.(type) {
    11  	case []byte:
    12  		return s.ValidateJSON(data)
    13  	case map[string]interface{}:
    14  		return s.ValidateMap(data)
    15  	default:
    16  		return s.ValidateStruct(instance)
    17  	}
    18  }
    19  
    20  // ValidateJSON validates JSON data provided as []byte.
    21  // The input is guaranteed to be treated as JSON data and parsed accordingly.
    22  func (s *Schema) ValidateJSON(data []byte) *EvaluationResult {
    23  	parsed, err := s.parseJSONData(data)
    24  	if err != nil {
    25  		result := NewEvaluationResult(s)
    26  		//nolint:errcheck
    27  		result.AddError(NewEvaluationError("format", "invalid_json", "Invalid JSON format"))
    28  		return result
    29  	}
    30  
    31  	dynamicScope := NewDynamicScope()
    32  	result, _, _ := s.evaluate(parsed, dynamicScope)
    33  	return result
    34  }
    35  
    36  // ValidateStruct validates Go struct data directly using reflection.
    37  // This method uses cached reflection data for optimal performance.
    38  func (s *Schema) ValidateStruct(instance interface{}) *EvaluationResult {
    39  	dynamicScope := NewDynamicScope()
    40  	result, _, _ := s.evaluate(instance, dynamicScope)
    41  	return result
    42  }
    43  
    44  // ValidateMap validates map[string]interface{} data directly.
    45  // This method provides optimal performance for pre-parsed JSON data.
    46  func (s *Schema) ValidateMap(data map[string]interface{}) *EvaluationResult {
    47  	dynamicScope := NewDynamicScope()
    48  	result, _, _ := s.evaluate(data, dynamicScope)
    49  	return result
    50  }
    51  
    52  // parseJSONData safely parses []byte data as JSON
    53  func (s *Schema) parseJSONData(data []byte) (interface{}, error) {
    54  	var parsed interface{}
    55  	return parsed, s.GetCompiler().jsonDecoder(data, &parsed)
    56  }
    57  
    58  // processJSONBytes handles []byte input with smart JSON parsing
    59  func (s *Schema) processJSONBytes(jsonBytes []byte) (interface{}, error) {
    60  	var parsed interface{}
    61  	if err := s.GetCompiler().jsonDecoder(jsonBytes, &parsed); err == nil {
    62  		return parsed, nil
    63  	}
    64  
    65  	// Only return error if it looks like intended JSON
    66  	if len(jsonBytes) > 0 && (jsonBytes[0] == '{' || jsonBytes[0] == '[') {
    67  		return nil, s.GetCompiler().jsonDecoder(jsonBytes, &parsed)
    68  	}
    69  
    70  	// Otherwise, keep original bytes for validation as byte array
    71  	return jsonBytes, nil
    72  }
    73  
    74  func (s *Schema) evaluate(instance interface{}, dynamicScope *DynamicScope) (*EvaluationResult, map[string]bool, map[int]bool) {
    75  	// Handle []byte input
    76  	instance = s.preprocessByteInput(instance)
    77  
    78  	dynamicScope.Push(s)
    79  	defer dynamicScope.Pop()
    80  
    81  	result := NewEvaluationResult(s)
    82  	evaluatedProps := make(map[string]bool)
    83  	evaluatedItems := make(map[int]bool)
    84  
    85  	// Process schema types
    86  	if s.Boolean != nil {
    87  		if err := s.evaluateBoolean(instance, evaluatedProps, evaluatedItems); err != nil {
    88  			//nolint:errcheck
    89  			result.AddError(err)
    90  		}
    91  		return result, evaluatedProps, evaluatedItems
    92  	}
    93  
    94  	// Compile patterns if needed
    95  	if s.PatternProperties != nil {
    96  		s.compilePatterns()
    97  	}
    98  
    99  	// Process references
   100  	s.processReferences(instance, dynamicScope, result, evaluatedProps, evaluatedItems)
   101  
   102  	// Process validation keywords
   103  	s.processValidationKeywords(instance, dynamicScope, result, evaluatedProps, evaluatedItems)
   104  
   105  	return result, evaluatedProps, evaluatedItems
   106  }
   107  
   108  // preprocessByteInput handles []byte input intelligently
   109  func (s *Schema) preprocessByteInput(instance interface{}) interface{} {
   110  	jsonBytes, ok := instance.([]byte)
   111  	if !ok {
   112  		return instance
   113  	}
   114  
   115  	parsed, err := s.processJSONBytes(jsonBytes)
   116  	if err != nil {
   117  		// Create a temporary result to hold the JSON parsing error
   118  		// Return the error as part of the instance for downstream handling
   119  		return &jsonParseError{data: jsonBytes, err: err}
   120  	}
   121  
   122  	return parsed
   123  }
   124  
   125  // jsonParseError wraps JSON parsing errors for downstream handling
   126  type jsonParseError struct {
   127  	data []byte
   128  	err  error
   129  }
   130  
   131  // processReferences handles $ref and $dynamicRef evaluation
   132  func (s *Schema) processReferences(instance interface{}, dynamicScope *DynamicScope, result *EvaluationResult, evaluatedProps map[string]bool, evaluatedItems map[int]bool) {
   133  	// Handle JSON parse errors
   134  	if _, ok := instance.(*jsonParseError); ok {
   135  		//nolint:errcheck
   136  		result.AddError(NewEvaluationError("format", "invalid_json", "Invalid JSON format in byte array"))
   137  		return
   138  	}
   139  
   140  	// Process $ref
   141  	if s.ResolvedRef != nil {
   142  		refResult, props, items := s.ResolvedRef.evaluate(instance, dynamicScope)
   143  		if refResult != nil {
   144  			//nolint:errcheck
   145  			result.AddDetail(refResult)
   146  			if !refResult.IsValid() {
   147  				//nolint:errcheck
   148  				result.AddError(NewEvaluationError("$ref", "ref_mismatch", "Value does not match the reference schema"))
   149  			}
   150  		}
   151  		mergeStringMaps(evaluatedProps, props)
   152  		mergeIntMaps(evaluatedItems, items)
   153  	}
   154  
   155  	// Process $dynamicRef
   156  	if s.ResolvedDynamicRef != nil {
   157  		s.processDynamicRef(instance, dynamicScope, result, evaluatedProps, evaluatedItems)
   158  	}
   159  }
   160  
   161  // processDynamicRef handles $dynamicRef evaluation
   162  func (s *Schema) processDynamicRef(instance interface{}, dynamicScope *DynamicScope, result *EvaluationResult, evaluatedProps map[string]bool, evaluatedItems map[int]bool) {
   163  	anchorSchema := s.ResolvedDynamicRef
   164  	_, anchor := splitRef(s.DynamicRef)
   165  
   166  	if !isJSONPointer(anchor) {
   167  		if dynamicAnchor := s.ResolvedDynamicRef.DynamicAnchor; dynamicAnchor != "" {
   168  			if schema := dynamicScope.LookupDynamicAnchor(dynamicAnchor); schema != nil {
   169  				anchorSchema = schema
   170  			}
   171  		}
   172  	}
   173  
   174  	dynamicRefResult, props, items := anchorSchema.evaluate(instance, dynamicScope)
   175  	if dynamicRefResult != nil {
   176  		//nolint:errcheck
   177  		result.AddDetail(dynamicRefResult)
   178  		if !dynamicRefResult.IsValid() {
   179  			//nolint:errcheck
   180  			result.AddError(NewEvaluationError("$dynamicRef", "dynamic_ref_mismatch", "Value does not match the dynamic reference schema"))
   181  		}
   182  	}
   183  
   184  	mergeStringMaps(evaluatedProps, props)
   185  	mergeIntMaps(evaluatedItems, items)
   186  }
   187  
   188  // processValidationKeywords handles all validation keywords
   189  func (s *Schema) processValidationKeywords(instance interface{}, dynamicScope *DynamicScope, result *EvaluationResult, evaluatedProps map[string]bool, evaluatedItems map[int]bool) {
   190  	// Basic type validation
   191  	s.processBasicValidation(instance, result)
   192  
   193  	// Logical operations
   194  	s.processLogicalOperations(instance, dynamicScope, result, evaluatedProps, evaluatedItems)
   195  
   196  	// Conditional logic
   197  	s.processConditionalLogic(instance, dynamicScope, result, evaluatedProps, evaluatedItems)
   198  
   199  	// Type-specific validation
   200  	s.processTypeSpecificValidation(instance, dynamicScope, result, evaluatedProps, evaluatedItems)
   201  
   202  	// Content validation
   203  	s.processContentValidation(instance, dynamicScope, result, evaluatedProps, evaluatedItems)
   204  }
   205  
   206  // processBasicValidation handles basic validation keywords
   207  func (s *Schema) processBasicValidation(instance interface{}, result *EvaluationResult) {
   208  	if s.Type != nil {
   209  		if err := evaluateType(s, instance); err != nil {
   210  			//nolint:errcheck
   211  			result.AddError(err)
   212  		}
   213  	}
   214  
   215  	if s.Enum != nil {
   216  		if err := evaluateEnum(s, instance); err != nil {
   217  			//nolint:errcheck
   218  			result.AddError(err)
   219  		}
   220  	}
   221  
   222  	if s.Const != nil {
   223  		if err := evaluateConst(s, instance); err != nil {
   224  			//nolint:errcheck
   225  			result.AddError(err)
   226  		}
   227  	}
   228  }
   229  
   230  // processLogicalOperations handles allOf, anyOf, oneOf, not
   231  func (s *Schema) processLogicalOperations(instance interface{}, dynamicScope *DynamicScope, result *EvaluationResult, evaluatedProps map[string]bool, evaluatedItems map[int]bool) {
   232  	if s.AllOf != nil {
   233  		results, err := evaluateAllOf(s, instance, evaluatedProps, evaluatedItems, dynamicScope)
   234  		s.addResultsAndError(result, results, err)
   235  	}
   236  
   237  	if s.AnyOf != nil {
   238  		results, err := evaluateAnyOf(s, instance, evaluatedProps, evaluatedItems, dynamicScope)
   239  		s.addResultsAndError(result, results, err)
   240  	}
   241  
   242  	if s.OneOf != nil {
   243  		results, err := evaluateOneOf(s, instance, evaluatedProps, evaluatedItems, dynamicScope)
   244  		s.addResultsAndError(result, results, err)
   245  	}
   246  
   247  	if s.Not != nil {
   248  		evalResult, err := evaluateNot(s, instance, evaluatedProps, evaluatedItems, dynamicScope)
   249  		if evalResult != nil {
   250  			//nolint:errcheck
   251  			result.AddDetail(evalResult)
   252  		}
   253  		if err != nil {
   254  			//nolint:errcheck
   255  			result.AddError(err)
   256  		}
   257  	}
   258  }
   259  
   260  // processConditionalLogic handles if/then/else
   261  func (s *Schema) processConditionalLogic(instance interface{}, dynamicScope *DynamicScope, result *EvaluationResult, evaluatedProps map[string]bool, evaluatedItems map[int]bool) {
   262  	if s.If != nil || s.Then != nil || s.Else != nil {
   263  		results, err := evaluateConditional(s, instance, evaluatedProps, evaluatedItems, dynamicScope)
   264  		s.addResultsAndError(result, results, err)
   265  	}
   266  }
   267  
   268  // processTypeSpecificValidation handles array, object, string, and numeric validation
   269  func (s *Schema) processTypeSpecificValidation(instance interface{}, dynamicScope *DynamicScope, result *EvaluationResult, evaluatedProps map[string]bool, evaluatedItems map[int]bool) {
   270  	// Array validation
   271  	if s.hasArrayValidation() {
   272  		results, errors := evaluateArray(s, instance, evaluatedProps, evaluatedItems, dynamicScope)
   273  		s.addResultsAndErrors(result, results, errors)
   274  	}
   275  
   276  	// Numeric validation
   277  	if s.hasNumericValidation() {
   278  		errors := evaluateNumeric(s, instance)
   279  		s.addErrors(result, errors)
   280  	}
   281  
   282  	// String validation
   283  	if s.hasStringValidation() {
   284  		errors := evaluateString(s, instance)
   285  		s.addErrors(result, errors)
   286  	}
   287  
   288  	if s.Format != nil {
   289  		if err := evaluateFormat(s, instance); err != nil {
   290  			//nolint:errcheck
   291  			result.AddError(err)
   292  		}
   293  	}
   294  
   295  	// Object validation
   296  	if s.hasObjectValidation() {
   297  		results, errors := evaluateObject(s, instance, evaluatedProps, evaluatedItems, dynamicScope)
   298  		s.addResultsAndErrors(result, results, errors)
   299  	}
   300  
   301  	// Dependent schemas
   302  	if s.DependentSchemas != nil {
   303  		results, err := evaluateDependentSchemas(s, instance, evaluatedProps, evaluatedItems, dynamicScope)
   304  		s.addResultsAndError(result, results, err)
   305  	}
   306  
   307  	// Unevaluated properties and items
   308  	s.processUnevaluatedValidation(instance, dynamicScope, result, evaluatedProps, evaluatedItems)
   309  }
   310  
   311  // processContentValidation handles content encoding/media type/schema
   312  func (s *Schema) processContentValidation(instance interface{}, dynamicScope *DynamicScope, result *EvaluationResult, evaluatedProps map[string]bool, evaluatedItems map[int]bool) {
   313  	if s.ContentEncoding != nil || s.ContentMediaType != nil || s.ContentSchema != nil {
   314  		contentResult, err := evaluateContent(s, instance, evaluatedProps, evaluatedItems, dynamicScope)
   315  		if contentResult != nil {
   316  			//nolint:errcheck
   317  			result.AddDetail(contentResult)
   318  		}
   319  		if err != nil {
   320  			//nolint:errcheck
   321  			result.AddError(err)
   322  		}
   323  	}
   324  }
   325  
   326  // processUnevaluatedValidation handles unevaluated properties and items
   327  func (s *Schema) processUnevaluatedValidation(instance interface{}, dynamicScope *DynamicScope, result *EvaluationResult, evaluatedProps map[string]bool, evaluatedItems map[int]bool) {
   328  	if s.UnevaluatedProperties != nil {
   329  		results, err := evaluateUnevaluatedProperties(s, instance, evaluatedProps, evaluatedItems, dynamicScope)
   330  		s.addResultsAndError(result, results, err)
   331  	}
   332  
   333  	if s.UnevaluatedItems != nil {
   334  		results, err := evaluateUnevaluatedItems(s, instance, evaluatedProps, evaluatedItems, dynamicScope)
   335  		s.addResultsAndError(result, results, err)
   336  	}
   337  }
   338  
   339  // Helper methods for checking if schema has specific validation types
   340  func (s *Schema) hasArrayValidation() bool {
   341  	return len(s.PrefixItems) > 0 || s.Items != nil || s.Contains != nil ||
   342  		s.MaxContains != nil || s.MinContains != nil || s.MaxItems != nil ||
   343  		s.MinItems != nil || s.UniqueItems != nil
   344  }
   345  
   346  func (s *Schema) hasNumericValidation() bool {
   347  	return s.MultipleOf != nil || s.Maximum != nil || s.ExclusiveMaximum != nil ||
   348  		s.Minimum != nil || s.ExclusiveMinimum != nil
   349  }
   350  
   351  func (s *Schema) hasStringValidation() bool {
   352  	return s.MaxLength != nil || s.MinLength != nil || s.Pattern != nil
   353  }
   354  
   355  func (s *Schema) hasObjectValidation() bool {
   356  	return s.Properties != nil || s.PatternProperties != nil || s.AdditionalProperties != nil ||
   357  		s.PropertyNames != nil || s.MaxProperties != nil || s.MinProperties != nil ||
   358  		len(s.Required) > 0 || len(s.DependentRequired) > 0
   359  }
   360  
   361  // Helper methods for adding results and errors
   362  func (s *Schema) addResultsAndError(result *EvaluationResult, results []*EvaluationResult, err *EvaluationError) {
   363  	for _, res := range results {
   364  		//nolint:errcheck
   365  		result.AddDetail(res)
   366  	}
   367  	if err != nil {
   368  		//nolint:errcheck
   369  		result.AddError(err)
   370  	}
   371  }
   372  
   373  func (s *Schema) addResultsAndErrors(result *EvaluationResult, results []*EvaluationResult, errors []*EvaluationError) {
   374  	for _, res := range results {
   375  		//nolint:errcheck
   376  		result.AddDetail(res)
   377  	}
   378  	s.addErrors(result, errors)
   379  }
   380  
   381  func (s *Schema) addErrors(result *EvaluationResult, errors []*EvaluationError) {
   382  	for _, err := range errors {
   383  		//nolint:errcheck
   384  		result.AddError(err)
   385  	}
   386  }
   387  
   388  func (s *Schema) evaluateBoolean(instance interface{}, evaluatedProps map[string]bool, evaluatedItems map[int]bool) *EvaluationError {
   389  	if s.Boolean == nil {
   390  		return nil
   391  	}
   392  
   393  	if *s.Boolean {
   394  		switch v := instance.(type) {
   395  		case map[string]interface{}:
   396  			for key := range v {
   397  				evaluatedProps[key] = true
   398  			}
   399  		case []interface{}:
   400  			for index := range v {
   401  				evaluatedItems[index] = true
   402  			}
   403  		}
   404  		return nil
   405  	}
   406  
   407  	return NewEvaluationError("schema", "false_schema_mismatch", "No values are allowed because the schema is set to 'false'")
   408  }
   409  
   410  // evaluateObject groups the validation of all object-specific keywords.
   411  func evaluateObject(schema *Schema, data interface{}, evaluatedProps map[string]bool, evaluatedItems map[int]bool, dynamicScope *DynamicScope) ([]*EvaluationResult, []*EvaluationError) {
   412  	// Fast path: direct map[string]interface{} type
   413  	if object, ok := data.(map[string]interface{}); ok {
   414  		return evaluateObjectMap(schema, object, evaluatedProps, evaluatedItems, dynamicScope)
   415  	}
   416  
   417  	// Reflection path: handle structs and other object types
   418  	rv := reflect.ValueOf(data)
   419  	for rv.Kind() == reflect.Ptr {
   420  		if rv.IsNil() {
   421  			return nil, nil
   422  		}
   423  		rv = rv.Elem()
   424  	}
   425  
   426  	//nolint:exhaustive // Only handling Struct and Map kinds - other types use default fallback
   427  	switch rv.Kind() {
   428  	case reflect.Struct:
   429  		return evaluateObjectStruct(schema, rv, evaluatedProps, evaluatedItems, dynamicScope)
   430  	case reflect.Map:
   431  		if rv.Type().Key().Kind() == reflect.String {
   432  			return evaluateObjectReflectMap(schema, rv, evaluatedProps, evaluatedItems, dynamicScope)
   433  		}
   434  	default:
   435  		// Handle other kinds by returning nil
   436  	}
   437  
   438  	return nil, nil
   439  }
   440  
   441  // evaluateObjectMap handles validation for map[string]interface{} (original implementation)
   442  func evaluateObjectMap(schema *Schema, object map[string]interface{}, evaluatedProps map[string]bool, evaluatedItems map[int]bool, dynamicScope *DynamicScope) ([]*EvaluationResult, []*EvaluationError) {
   443  	var results []*EvaluationResult
   444  	var errors []*EvaluationError
   445  
   446  	// Properties validation
   447  	if schema.Properties != nil {
   448  		if propResults, propError := evaluateProperties(schema, object, evaluatedProps, evaluatedItems, dynamicScope); propResults != nil || propError != nil {
   449  			results = append(results, propResults...)
   450  			if propError != nil {
   451  				errors = append(errors, propError)
   452  			}
   453  		}
   454  	}
   455  
   456  	// Pattern properties validation
   457  	if schema.PatternProperties != nil {
   458  		if patResults, patError := evaluatePatternProperties(schema, object, evaluatedProps, evaluatedItems, dynamicScope); patResults != nil || patError != nil {
   459  			results = append(results, patResults...)
   460  			if patError != nil {
   461  				errors = append(errors, patError)
   462  			}
   463  		}
   464  	}
   465  
   466  	// Additional properties validation
   467  	if schema.AdditionalProperties != nil {
   468  		if addResults, addError := evaluateAdditionalProperties(schema, object, evaluatedProps, evaluatedItems, dynamicScope); addResults != nil || addError != nil {
   469  			results = append(results, addResults...)
   470  			if addError != nil {
   471  				errors = append(errors, addError)
   472  			}
   473  		}
   474  	}
   475  
   476  	// Property names validation
   477  	if schema.PropertyNames != nil {
   478  		if nameResults, nameError := evaluatePropertyNames(schema, object, evaluatedProps, evaluatedItems, dynamicScope); nameResults != nil || nameError != nil {
   479  			results = append(results, nameResults...)
   480  			if nameError != nil {
   481  				errors = append(errors, nameError)
   482  			}
   483  		}
   484  	}
   485  
   486  	// Object constraint validation
   487  	errors = append(errors, validateObjectConstraints(schema, object)...)
   488  
   489  	return results, errors
   490  }
   491  
   492  // validateObjectConstraints validates object-specific constraints
   493  func validateObjectConstraints(schema *Schema, object map[string]interface{}) []*EvaluationError {
   494  	var errors []*EvaluationError
   495  
   496  	if schema.MaxProperties != nil {
   497  		if err := evaluateMaxProperties(schema, object); err != nil {
   498  			errors = append(errors, err)
   499  		}
   500  	}
   501  
   502  	if schema.MinProperties != nil {
   503  		if err := evaluateMinProperties(schema, object); err != nil {
   504  			errors = append(errors, err)
   505  		}
   506  	}
   507  
   508  	if len(schema.Required) > 0 {
   509  		if err := evaluateRequired(schema, object); err != nil {
   510  			errors = append(errors, err)
   511  		}
   512  	}
   513  
   514  	if len(schema.DependentRequired) > 0 {
   515  		if err := evaluateDependentRequired(schema, object); err != nil {
   516  			errors = append(errors, err)
   517  		}
   518  	}
   519  
   520  	return errors
   521  }
   522  
   523  // validateNumeric groups the validation of all numeric-specific keywords.
   524  func evaluateNumeric(schema *Schema, data interface{}) []*EvaluationError {
   525  	dataType := getDataType(data)
   526  	if dataType != "number" && dataType != "integer" {
   527  		return nil
   528  	}
   529  
   530  	value := NewRat(data)
   531  	if value == nil {
   532  		return []*EvaluationError{
   533  			NewEvaluationError("type", "invalid_numberic", "Value is {received} but should be numeric", map[string]interface{}{
   534  				"actual_type": dataType,
   535  			}),
   536  		}
   537  	}
   538  
   539  	var errors []*EvaluationError
   540  
   541  	// Collect all numeric validation errors
   542  	if schema.MultipleOf != nil {
   543  		if err := evaluateMultipleOf(schema, value); err != nil {
   544  			errors = append(errors, err)
   545  		}
   546  	}
   547  
   548  	if schema.Maximum != nil {
   549  		if err := evaluateMaximum(schema, value); err != nil {
   550  			errors = append(errors, err)
   551  		}
   552  	}
   553  
   554  	if schema.ExclusiveMaximum != nil {
   555  		if err := evaluateExclusiveMaximum(schema, value); err != nil {
   556  			errors = append(errors, err)
   557  		}
   558  	}
   559  
   560  	if schema.Minimum != nil {
   561  		if err := evaluateMinimum(schema, value); err != nil {
   562  			errors = append(errors, err)
   563  		}
   564  	}
   565  
   566  	if schema.ExclusiveMinimum != nil {
   567  		if err := evaluateExclusiveMinimum(schema, value); err != nil {
   568  			errors = append(errors, err)
   569  		}
   570  	}
   571  
   572  	return errors
   573  }
   574  
   575  // validateString groups the validation of all string-specific keywords.
   576  func evaluateString(schema *Schema, data interface{}) []*EvaluationError {
   577  	value, ok := data.(string)
   578  	if !ok {
   579  		return nil
   580  	}
   581  
   582  	var errors []*EvaluationError
   583  
   584  	// Collect all string validation errors
   585  	if schema.MaxLength != nil {
   586  		if err := evaluateMaxLength(schema, value); err != nil {
   587  			errors = append(errors, err)
   588  		}
   589  	}
   590  
   591  	if schema.MinLength != nil {
   592  		if err := evaluateMinLength(schema, value); err != nil {
   593  			errors = append(errors, err)
   594  		}
   595  	}
   596  
   597  	if schema.Pattern != nil {
   598  		if err := evaluatePattern(schema, value); err != nil {
   599  			errors = append(errors, err)
   600  		}
   601  	}
   602  
   603  	return errors
   604  }
   605  
   606  // validateArray groups the validation of all array-specific keywords.
   607  func evaluateArray(schema *Schema, data interface{}, evaluatedProps map[string]bool, evaluatedItems map[int]bool, dynamicScope *DynamicScope) ([]*EvaluationResult, []*EvaluationError) {
   608  	items, ok := data.([]interface{})
   609  	if !ok {
   610  		return nil, nil
   611  	}
   612  
   613  	var results []*EvaluationResult
   614  	var errors []*EvaluationError
   615  
   616  	// Process array schema validations
   617  	arrayValidations := []func(*Schema, []interface{}, map[string]bool, map[int]bool, *DynamicScope) ([]*EvaluationResult, *EvaluationError){
   618  		evaluatePrefixItems,
   619  		evaluateItems,
   620  		evaluateContains,
   621  	}
   622  
   623  	for _, validate := range arrayValidations {
   624  		if res, err := validate(schema, items, evaluatedProps, evaluatedItems, dynamicScope); res != nil || err != nil {
   625  			if res != nil {
   626  				results = append(results, res...)
   627  			}
   628  			if err != nil {
   629  				errors = append(errors, err)
   630  			}
   631  		}
   632  	}
   633  
   634  	// Array constraint validation
   635  	errors = append(errors, validateArrayConstraints(schema, items)...)
   636  
   637  	return results, errors
   638  }
   639  
   640  // validateArrayConstraints validates array-specific constraints
   641  func validateArrayConstraints(schema *Schema, items []interface{}) []*EvaluationError {
   642  	var errors []*EvaluationError
   643  
   644  	if schema.MaxItems != nil {
   645  		if err := evaluateMaxItems(schema, items); err != nil {
   646  			errors = append(errors, err)
   647  		}
   648  	}
   649  
   650  	if schema.MinItems != nil {
   651  		if err := evaluateMinItems(schema, items); err != nil {
   652  			errors = append(errors, err)
   653  		}
   654  	}
   655  
   656  	if schema.UniqueItems != nil && *schema.UniqueItems {
   657  		if err := evaluateUniqueItems(schema, items); err != nil {
   658  			errors = append(errors, err)
   659  		}
   660  	}
   661  
   662  	return errors
   663  }
   664  
   665  // DynamicScope struct defines a stack specifically for handling Schema types
   666  type DynamicScope struct {
   667  	schemas []*Schema // Slice storing pointers to Schema
   668  }
   669  
   670  // NewDynamicScope creates and returns a new empty DynamicScope
   671  func NewDynamicScope() *DynamicScope {
   672  	return &DynamicScope{schemas: make([]*Schema, 0)}
   673  }
   674  
   675  // Push adds a Schema to the dynamic scope
   676  func (ds *DynamicScope) Push(schema *Schema) {
   677  	ds.schemas = append(ds.schemas, schema)
   678  }
   679  
   680  // Pop removes and returns the top Schema from the dynamic scope
   681  func (ds *DynamicScope) Pop() *Schema {
   682  	if len(ds.schemas) == 0 {
   683  		return nil
   684  	}
   685  	lastIndex := len(ds.schemas) - 1
   686  	schema := ds.schemas[lastIndex]
   687  	ds.schemas = ds.schemas[:lastIndex]
   688  	return schema
   689  }
   690  
   691  // Peek returns the top Schema without removing it
   692  func (ds *DynamicScope) Peek() *Schema {
   693  	if len(ds.schemas) == 0 {
   694  		return nil
   695  	}
   696  	return ds.schemas[len(ds.schemas)-1]
   697  }
   698  
   699  // IsEmpty checks if the dynamic scope is empty
   700  func (ds *DynamicScope) IsEmpty() bool {
   701  	return len(ds.schemas) == 0
   702  }
   703  
   704  // Size returns the number of Schemas in the dynamic scope
   705  func (ds *DynamicScope) Size() int {
   706  	return len(ds.schemas)
   707  }
   708  
   709  // LookupDynamicAnchor searches for a dynamic anchor in the dynamic scope
   710  func (ds *DynamicScope) LookupDynamicAnchor(anchor string) *Schema {
   711  	// use the first schema dynamic anchor matching the anchor
   712  	for i := 0; i < len(ds.schemas); i++ {
   713  		schema := ds.schemas[i]
   714  
   715  		if schema.dynamicAnchors != nil && schema.dynamicAnchors[anchor] != nil {
   716  			return schema.dynamicAnchors[anchor]
   717  		}
   718  	}
   719  
   720  	return nil
   721  }