<commit_before>require "uri"

module JsonSchema
  class Validator
    TYPE_MAP = {
      "array"   => Array,
      "boolean" => [FalseClass, TrueClass],
      "integer" => Integer,
      "number"  => [Integer, Float],
      "null"    => NilClass,
      "object"  => Hash,
      "string"  => String,
    }

    attr_accessor :errors

    def initialize(schema)
      @schema = schema
    end

    def validate(data)
      @errors = []
      @visits = {}
      validate_data(@schema, data, @errors, ['#'])
      @errors.size == 0
    end

    def validate!(data)
      if !validate(data)
        raise AggregateError.new(@errors)
      end
    end

    private

    def first_visit(schema, errors, path)
      true
# removed until more comprehensive testing can be performed .. this is
# currently causing validation loop detections to go off on all non-trivial
# schemas
=begin
      key = "#{schema.object_id}-#{schema.pointer}-#{path.join("/")}"
      if !@visits.key?(key)
        @visits[key] = true
        true
      else
        message = %{Validation loop detected.}
        errors << ValidationError.new(schema, path, message, :loop_detected)
        false
      end
=end
    end

    # for use with additionalProperties and strictProperties
    def get_extra_keys(schema, data)
      extra = data.keys - schema.properties.keys

      if schema.pattern_properties
        schema.pattern_properties.keys.each do |pattern|
          extra -= extra.select { |k| k =~ pattern }
        end
      end

      extra
    end

    # works around &&'s "lazy" behavior
    def strict_and(valid_old, valid_new)
      valid_old && valid_new
    end

    def validate_data(schema, data, errors, path)
      valid = true
      # detect a validation loop
      if !first_visit(schema, errors, path)
        return false
      end

      # validation: any
      valid = strict_and valid, validate_all_of(schema, data, errors, path)
      valid = strict_and valid, validate_any_of(schema, data, errors, path)
      valid = strict_and valid, validate_enum(schema, data, errors, path)
      valid = strict_and valid, validate_one_of(schema, data, errors, path)
      valid = strict_and valid, validate_not(schema, data, errors, path)
      valid = strict_and valid, validate_type(schema, data, errors, path)

      # validation: array
      if data.is_a?(Array)
        valid = strict_and valid, validate_items(schema, data, errors, path)
        valid = strict_and valid, validate_max_items(schema, data, errors, path)
        valid = strict_and valid, validate_min_items(schema, data, errors, path)
        valid = strict_and valid, validate_unique_items(schema, data, errors, path)
      end

      # validation: integer/number
      if data.is_a?(Float) || data.is_a?(Integer)
        valid = strict_and valid, validate_max(schema, data, errors, path)
        valid = strict_and valid, validate_min(schema, data, errors, path)
        valid = strict_and valid, validate_multiple_of(schema, data, errors, path)
      end

      # validation: object
      if data.is_a?(Hash)
        valid = strict_and valid, validate_additional_properties(schema, data, errors, path)
        valid = strict_and valid, validate_dependencies(schema, data, errors, path)
        valid = strict_and valid, validate_max_properties(schema, data, errors, path)
        valid = strict_and valid, validate_min_properties(schema, data, errors, path)
        valid = strict_and valid, validate_pattern_properties(schema, data, errors, path)
        valid = strict_and valid, validate_properties(schema, data, errors, path)
        valid = strict_and valid, validate_required(schema, data, errors, path, schema.required)
        valid = strict_and valid, validate_strict_properties(schema, data, errors, path)
      end

      # validation: string
      if data.is_a?(String)
        valid = strict_and valid, validate_format(schema, data, errors, path)
        valid = strict_and valid, validate_max_length(schema, data, errors, path)
        valid = strict_and valid, validate_min_length(schema, data, errors, path)
        valid = strict_and valid, validate_pattern(schema, data, errors, path)
      end

      valid
    end

    def validate_additional_properties(schema, data, errors, path)
      return true if schema.additional_properties == true

      # schema indicates that all properties not in `properties` should be
      # validated according to subschema
      if schema.additional_properties.is_a?(Schema)
        extra = get_extra_keys(schema, data)
        validations = extra.map do |key|
          validate_data(schema.additional_properties, data[key], errors, path + [key])
        end

        # true only if all keys validate
        validations.all?

      # boolean indicates whether additional properties are allowed
      else
        validate_extra(schema, data, errors, path)
      end
    end

    def validate_all_of(schema, data, errors, path)
      return true if schema.all_of.empty?
      valid = schema.all_of.all? do |subschema|
        validate_data(subschema, data, errors, path)
      end
      message = %{Not all subschemas of "allOf" matched.}
      errors << ValidationError.new(schema, path, message, :all_of_failed, data: data) if !valid
      valid
    end

    def validate_any_of(schema, data, errors, path)
      return true if schema.any_of.empty?

      sub_errors = []

      subschemata_validity = schema.any_of.map do |subschema|
        current_sub_errors = []
        sub_errors << current_sub_errors
        validate_data(subschema, data, current_sub_errors, path)
      end

      unless subschemata_validity.any? { |valid| valid == true }
        message = %{No subschema in "anyOf" matched.}
        errors << ValidationError.new(schema, path, message, :any_of_failed,
          sub_errors: sub_errors, data: data)
        return false
      end

      true
    end

    def validate_dependencies(schema, data, errors, path)
      return true if schema.dependencies.empty?
      schema.dependencies.each do |key, obj|
        # if the key is not present, the dependency is fulfilled by definition
        next unless data[key]
        if obj.is_a?(Schema)
          validate_data(obj, data, errors, path)
        else
          # if not a schema, value is an array of required fields
          validate_required(schema, data, errors, path, obj)
        end
      end
    end

    def validate_format(schema, data, errors, path)
      return true unless schema.format
      validator = (
        JsonSchema.configuration.custom_formats[schema.format] ||
        DEFAULT_FORMAT_VALIDATORS[schema.format]
      )
      if validator[data]<commit_msg>fix return value of validate_dependencies<commit_after>require "uri"

module JsonSchema
  class Validator
    TYPE_MAP = {
      "array"   => Array,
      "boolean" => [FalseClass, TrueClass],
      "integer" => Integer,
      "number"  => [Integer, Float],
      "null"    => NilClass,
      "object"  => Hash,
      "string"  => String,
    }

    attr_accessor :errors

    def initialize(schema)
      @schema = schema
    end

    def validate(data)
      @errors = []
      @visits = {}
      validate_data(@schema, data, @errors, ['#'])
      @errors.size == 0
    end

    def validate!(data)
      if !validate(data)
        raise AggregateError.new(@errors)
      end
    end

    private

    def first_visit(schema, errors, path)
      true
# removed until more comprehensive testing can be performed .. this is
# currently causing validation loop detections to go off on all non-trivial
# schemas
=begin
      key = "#{schema.object_id}-#{schema.pointer}-#{path.join("/")}"
      if !@visits.key?(key)
        @visits[key] = true
        true
      else
        message = %{Validation loop detected.}
        errors << ValidationError.new(schema, path, message, :loop_detected)
        false
      end
=end
    end

    # for use with additionalProperties and strictProperties
    def get_extra_keys(schema, data)
      extra = data.keys - schema.properties.keys

      if schema.pattern_properties
        schema.pattern_properties.keys.each do |pattern|
          extra -= extra.select { |k| k =~ pattern }
        end
      end

      extra
    end

    # works around &&'s "lazy" behavior
    def strict_and(valid_old, valid_new)
      valid_old && valid_new
    end

    def validate_data(schema, data, errors, path)
      valid = true
      # detect a validation loop
      if !first_visit(schema, errors, path)
        return false
      end

      # validation: any
      valid = strict_and valid, validate_all_of(schema, data, errors, path)
      valid = strict_and valid, validate_any_of(schema, data, errors, path)
      valid = strict_and valid, validate_enum(schema, data, errors, path)
      valid = strict_and valid, validate_one_of(schema, data, errors, path)
      valid = strict_and valid, validate_not(schema, data, errors, path)
      valid = strict_and valid, validate_type(schema, data, errors, path)

      # validation: array
      if data.is_a?(Array)
        valid = strict_and valid, validate_items(schema, data, errors, path)
        valid = strict_and valid, validate_max_items(schema, data, errors, path)
        valid = strict_and valid, validate_min_items(schema, data, errors, path)
        valid = strict_and valid, validate_unique_items(schema, data, errors, path)
      end

      # validation: integer/number
      if data.is_a?(Float) || data.is_a?(Integer)
        valid = strict_and valid, validate_max(schema, data, errors, path)
        valid = strict_and valid, validate_min(schema, data, errors, path)
        valid = strict_and valid, validate_multiple_of(schema, data, errors, path)
      end

      # validation: object
      if data.is_a?(Hash)
        valid = strict_and valid, validate_additional_properties(schema, data, errors, path)
        valid = strict_and valid, validate_dependencies(schema, data, errors, path)
        valid = strict_and valid, validate_max_properties(schema, data, errors, path)
        valid = strict_and valid, validate_min_properties(schema, data, errors, path)
        valid = strict_and valid, validate_pattern_properties(schema, data, errors, path)
        valid = strict_and valid, validate_properties(schema, data, errors, path)
        valid = strict_and valid, validate_required(schema, data, errors, path, schema.required)
        valid = strict_and valid, validate_strict_properties(schema, data, errors, path)
      end

      # validation: string
      if data.is_a?(String)
        valid = strict_and valid, validate_format(schema, data, errors, path)
        valid = strict_and valid, validate_max_length(schema, data, errors, path)
        valid = strict_and valid, validate_min_length(schema, data, errors, path)
        valid = strict_and valid, validate_pattern(schema, data, errors, path)
      end

      valid
    end

    def validate_additional_properties(schema, data, errors, path)
      return true if schema.additional_properties == true

      # schema indicates that all properties not in `properties` should be
      # validated according to subschema
      if schema.additional_properties.is_a?(Schema)
        extra = get_extra_keys(schema, data)
        validations = extra.map do |key|
          validate_data(schema.additional_properties, data[key], errors, path + [key])
        end

        # true only if all keys validate
        validations.all?

      # boolean indicates whether additional properties are allowed
      else
        validate_extra(schema, data, errors, path)
      end
    end

    def validate_all_of(schema, data, errors, path)
      return true if schema.all_of.empty?
      valid = schema.all_of.all? do |subschema|
        validate_data(subschema, data, errors, path)
      end
      message = %{Not all subschemas of "allOf" matched.}
      errors << ValidationError.new(schema, path, message, :all_of_failed, data: data) if !valid
      valid
    end

    def validate_any_of(schema, data, errors, path)
      return true if schema.any_of.empty?

      sub_errors = []

      subschemata_validity = schema.any_of.map do |subschema|
        current_sub_errors = []
        sub_errors << current_sub_errors
        validate_data(subschema, data, current_sub_errors, path)
      end

      unless subschemata_validity.any? { |valid| valid == true }
        message = %{No subschema in "anyOf" matched.}
        errors << ValidationError.new(schema, path, message, :any_of_failed,
          sub_errors: sub_errors, data: data)
        return false
      end

      true
    end

    def validate_dependencies(schema, data, errors, path)
      return true if schema.dependencies.empty?
      result = schema.dependencies.map do |key, obj|
        # if the key is not present, the dependency is fulfilled by definition
        next true unless data[key]
        if obj.is_a?(Schema)
          validate_data(obj, data, errors, path)
        else
          # if not a schema, value is an array of required fields
          validate_required(schema, data, errors, path, obj)
        end
      end
      result.all?
    end

    def validate_format(schema, data, errors, path)
      return true unless schema.format
      validator = (
        JsonSchema.configuration.custom_formats[schema.format] ||
        DEFAULT_FORMAT_VALIDATORS[schema.format]
      )
      if validator[data]
{'fraction_non_alphanumeric': 0.0893216924110141, 'fraction_numerical': 0.00014924259383628088, 'mean_word_length': 2.770962296004502, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2106786', 'n_tokens_mistral': 3844, 'n_tokens_neox': 3756, 'n_words': 1136}