Module: JSI::Schema::Elements

Defined in:
lib/jsi/schema/elements/numeric.rb,
lib/jsi/schema/elements.rb,
lib/jsi/schema/elements.rb,
lib/jsi/schema/elements/id.rb,
lib/jsi/schema/elements/not.rb,
lib/jsi/schema/elements/ref.rb,
lib/jsi/schema/elements/enum.rb,
lib/jsi/schema/elements/self.rb,
lib/jsi/schema/elements/type.rb,
lib/jsi/schema/elements/const.rb,
lib/jsi/schema/elements/items.rb,
lib/jsi/schema/elements/anchor.rb,
lib/jsi/schema/elements/format.rb,
lib/jsi/schema/elements/comment.rb,
lib/jsi/schema/elements/default.rb,
lib/jsi/schema/elements/numeric.rb,
lib/jsi/schema/elements/numeric.rb,
lib/jsi/schema/elements/numeric.rb,
lib/jsi/schema/elements/numeric.rb,
lib/jsi/schema/elements/pattern.rb,
lib/jsi/schema/elements/some_of.rb,
lib/jsi/schema/elements/some_of.rb,
lib/jsi/schema/elements/some_of.rb,
lib/jsi/schema/elements/xschema.rb,
lib/jsi/schema/elements/contains.rb,
lib/jsi/schema/elements/examples.rb,
lib/jsi/schema/elements/required.rb,
lib/jsi/schema/elements/info_bool.rb,
lib/jsi/schema/elements/properties.rb,
lib/jsi/schema/elements/definitions.rb,
lib/jsi/schema/elements/dynamic_ref.rb,
lib/jsi/schema/elements/info_string.rb,
lib/jsi/schema/elements/xvocabulary.rb,
lib/jsi/schema/elements/dependencies.rb,
lib/jsi/schema/elements/if_then_else.rb,
lib/jsi/schema/elements/content_schema.rb,
lib/jsi/schema/elements/items_prefixed.rb,
lib/jsi/schema/elements/property_names.rb,
lib/jsi/schema/elements/contains_minmax.rb,
lib/jsi/schema/elements/numeric_draft04.rb,
lib/jsi/schema/elements/numeric_draft04.rb,
lib/jsi/schema/elements/array_validation.rb,
lib/jsi/schema/elements/array_validation.rb,
lib/jsi/schema/elements/array_validation.rb,
lib/jsi/schema/elements/content_encoding.rb,
lib/jsi/schema/elements/dependent_schemas.rb,
lib/jsi/schema/elements/object_validation.rb,
lib/jsi/schema/elements/object_validation.rb,
lib/jsi/schema/elements/string_validation.rb,
lib/jsi/schema/elements/string_validation.rb,
lib/jsi/schema/elements/unevaluated_items.rb,
lib/jsi/schema/elements/content_media_type.rb,
lib/jsi/schema/elements/dependent_required.rb,
lib/jsi/schema/elements/unevaluated_properties.rb

Overview

module Schema::Elements

Constant Summary collapse

ID =
element_map do |keyword: , fragment_is_anchor: |
  Schema::Element.new(keyword: keyword) do |element|
    element.add_action(:id) { cxt_yield(schema_content[keyword]) if keyword_value_str?(keyword) }

    element.add_action(:id_without_fragment) do
      next if !keyword_value_str?(keyword)
      id_without_fragment = Util.uri(schema_content[keyword]).merge(fragment: nil)

      if !id_without_fragment.empty?
        cxt_yield(id_without_fragment)
      end
    end

    if fragment_is_anchor
      element.add_action(:anchor) do
        next if !keyword_value_str?(keyword)
        id_fragment = Util.uri(schema_content[keyword]).fragment
        if id_fragment && !id_fragment.empty?
          cxt_yield(id_fragment)
        end
      end
    end
  end
end
NOT =
element_map do
  Schema::Element.new(keyword: 'not') do |element|
    element.add_action(:subschema) do
      if keyword?('not')
        #> This keyword's value MUST be a valid JSON Schema.
        cxt_yield(['not'])
      end
    end # element.add_action(:subschema)

    element.add_action(:validate) do
  if keyword?('not')
    # This keyword's value MUST be a valid JSON Schema.
    # An instance is valid against this keyword if it fails to validate successfully against the schema
    # defined by this keyword.
    not_valid = inplace_subschema_validate(['not']).valid?
    validate(
      !not_valid,
      'validation.keyword.not.valid',
      "instance is valid against `not` schema",
      keyword: 'not',
    )
  end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
REF =

exclusive [Boolean]: whether to abort invocation of subsequent actions when a $ref is evaluated

element_map do |exclusive: |
  Schema::Element.new(keyword: '$ref') do |element|
    if exclusive
      # $ref must come before all other elements to abort evaluation
      element.required_before_elements { |_| true }

      actions_to_abort = [
        :id,
        :id_without_fragment,
        :anchor,
      ]
      actions_to_abort.each do |action|
        element.add_action(action) { self.abort = true if keyword?('$ref') }
      end
    end

    resolve_ref = proc do
      next if !keyword_value_str?('$ref')
      ref = schema.schema_ref(schema_content['$ref'])
      resolved_schema = ref.resolve.with_dynamic_scope_from(schema)
      [resolved_schema, ref]
    end

    element.add_action(:inplace_applicate) do
      resolved_schema, ref = *instance_exec(&resolve_ref) || next

      inplace_schema_applicate(resolved_schema, ref: ref)

      if exclusive
        self.abort = true
      end
    end # element.add_action(:inplace_applicate)

    element.add_action(:validate) do
            resolved_schema, schema_ref = *instance_exec(&resolve_ref) || next

            ref_result = resolved_schema.internal_validate_instance(
              instance_ptr,
              instance_document,
              validate_only: validate_only,
              visited_refs: Util.add_visited_ref(visited_refs, schema_ref),
            )
            inplace_results_validate(
              ref_result.valid?,
              'validation.keyword.$ref.invalid',
              "instance is not valid against the schema referenced by `$ref`",
              keyword: '$ref',
              results: [ref_result],
            )
            if exclusive
              self.abort = true
            end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
ENUM =
element_map do
  Schema::Element.new(keyword: 'enum') do |element|
    element.add_action(:validate) do
  if keyword?('enum')
    value = schema_content['enum']
    #> The value of this keyword MUST be an array.
    if value.respond_to?(:to_ary)
      # An instance validates successfully against this keyword if its value is equal to one of the
      # elements in this keyword's array value.
      validate(
        value.include?(instance),
        'validation.keyword.enum.none_equal',
        "instance is not equal to any `enum` item",
        keyword: 'enum',
      )
    end
  end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
SELF =
element_map do
  Schema::Element.new do |element|
    element.add_action(:inplace_applicate) do
      inplace_schema_applicate(schema)
    end

    element.add_action(:validate) do
      #> boolean schemas are equivalent to the following behaviors:
      #>
      #> true: Always passes validation, as if the empty schema {}
      #>
      #> false: Always fails validation, as if the schema { "not":{} }
      if schema_content == false
        validate(false, 'validation.false_schema', "instance is not valid against `false` schema")
      end
    end
  end
end
TYPE =
element_map do
  Schema::Element.new(keyword: 'type') do |element|
    element.add_action(:validate) do
      next if !keyword?('type')
      value = schema_content['type']
      #> The value of this keyword MUST be either a string or an array. If it is an array, elements of
      #> the array MUST be strings and MUST be unique.
      types = value.respond_to?(:to_ary) ? value : [value]
      matched_type = types.any? do |type|
          if instance_types.key?(type)
            instance_exec(&instance_types[type])
          else
            false
          end
      end
      validate(
        matched_type,
        'validation.keyword.type.not_match',
        'instance type does not match `type` value',
        keyword: 'type',
      )
    end # element.add_action(:validate)
  end # Schema::Element.new
end
CONST =
element_map do
  Schema::Element.new(keyword: 'const') do |element|
    element.add_action(:validate) do
  if keyword?('const')
    value = schema_content['const']
    # The value of this keyword MAY be of any type, including null.
    # An instance validates successfully against this keyword if its value is equal to the value of
    # the keyword.
    validate(
      instance == value,
      'validation.keyword.const.not_equal',
      'instance is not equal to `const` value',
      keyword: 'const',
    )
  end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
ITEMS =
element_map do
  Schema::Element.new(keywords: %w(items additionalItems)) do |element|
    element.add_action(:subschema) do
      if keyword?('items')
        #> The value of "items" MUST be either a valid JSON Schema or an array of valid JSON Schemas.
        if keyword_value_ary?('items')
          schema_content['items'].each_index do |i|
            cxt_yield(['items', i])
          end
        else
          cxt_yield(['items'])
        end
      end

      if keyword?('additionalItems')
        #> The value of "additionalItems" MUST be a valid JSON Schema.
        cxt_yield(['additionalItems'])
      end
    end # element.add_action(:subschema)

    element.add_action(:child_applicate) do
if instance.respond_to?(:to_ary)
  if keyword?('items') && schema_content['items'].respond_to?(:to_ary)
    if schema_content['items'].size > token
      child_subschema_applicate(['items', token])
    elsif keyword?('additionalItems')
      child_subschema_applicate(['additionalItems'])
    end
  elsif keyword?('items')
    child_subschema_applicate(['items'])
  end
end # if instance.respond_to?(:to_ary)
    end # element.add_action(:child_applicate)

    element.add_action(:validate) do
      if instance.respond_to?(:to_ary)
        if keyword?('items')
          #> The value of "items" MUST be either a valid JSON Schema or an array of valid JSON Schemas.
          if schema_content['items'].respond_to?(:to_ary)
            #> If "items" is an array of schemas, validation succeeds if each element of the instance
            #> validates against the schema at the same position, if any.
            items_results = {}
            additionalItems_results = {}
            instance.each_index do |i|
              if i < schema_content['items'].size
                items_results[i] = child_subschema_validate(i, ['items', i])
              elsif keyword?('additionalItems')
                additionalItems_results[i] = child_subschema_validate(i, ['additionalItems'])
              end
            end
            child_results_validate(
              items_results.each_value.all?(&:valid?),
              'validation.keyword.items.array.invalid',
              "instance array items are not all valid against corresponding `items` schemas",
              keyword: 'items',
              child_results: items_results,
              instance_indexes_valid: items_results.inject({}) { |h, (i, r)| h.update({i => r.valid?}) }.freeze,
            )
            child_results_validate(
              additionalItems_results.each_value.all?(&:valid?),
              'validation.keyword.additionalItems.invalid',
              "instance array items after `items` schemas are not all valid against `additionalItems` schema",
              keyword: 'additionalItems',
              child_results: additionalItems_results,
              instance_indexes_valid: additionalItems_results.inject({}) { |h, (i, r)| h.update({i => r.valid?}) }.freeze,
            )
          else
            #> If "items" is a schema, validation succeeds if all elements in the array successfully
            #> validate against that schema.
            items_results = {}
            instance.each_index do |i|
              items_results[i] = child_subschema_validate(i, ['items'])
            end
            child_results_validate(
              items_results.each_value.all?(&:valid?),
              'validation.keyword.items.schema.invalid',
              "instance array items are not all valid against `items` schema",
              keyword: 'items',
              child_results: items_results,
              instance_indexes_valid: items_results.inject({}) { |h, (i, r)| h.update({i => r.valid?}) }.freeze,
            )
          end
        end
      end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
ANCHOR =
element_map do |keyword: , actions: |
  Schema::Element.new(keyword: keyword) do |element|
    actions.each do |action|
      element.add_action(action) { cxt_yield(schema_content[keyword]) if keyword_value_str?(keyword) }
    end
  end
end
FORMAT =
element_map do
  Schema::Element.new(keyword: 'format') do |element|
  end # Schema::Element.new
end
COMMENT =
element_map do
  Schema::Element.new(keyword: '$comment') do |element|
  end # Schema::Element.new
end
DEFAULT =
element_map do
  Schema::Element.new(keyword: 'default') do |element|
    element.add_action(:default) { cxt_yield(schema_content['default']) if keyword?('default') }
  end # Schema::Element.new
end
MULTIPLE_OF =
element_map do
  Schema::Element.new(keyword: 'multipleOf') do |element|
    element.add_action(:validate) do
  if keyword?('multipleOf')
    value = schema_content['multipleOf']
    # The value of "multipleOf" MUST be a number, strictly greater than 0.
    if value.is_a?(Numeric) && value > 0
      # A numeric instance is valid only if division by this keyword's value results in an integer.
      if instance.is_a?(Numeric)
        if instance.is_a?(Integer) && value.is_a?(Integer)
          valid = instance % value == 0
        else
          quotient = instance / value
          if quotient.finite?
            valid = quotient % 1.0 == 0.0
          else
            valid = BigDecimal(instance, Float::DIG) % BigDecimal(value, Float::DIG) == 0
          end
        end
        validate(
          valid,
          'validation.keyword.multipleOf.not_multiple',
          'instance is not a multiple of `multipleOf` value',
          keyword: 'multipleOf',
        )
      end
    end
  end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
MAXIMUM =
element_map do
  Schema::Element.new(keyword: 'maximum') do |element|
    element.add_action(:validate) do
  if keyword?('maximum')
    value = schema_content['maximum']
    # The value of "maximum" MUST be a number, representing an inclusive upper limit for a numeric instance.
    if value.is_a?(Numeric)
      # If the instance is a number, then this keyword validates only if the instance is less than or
      # exactly equal to "maximum".
      if instance.is_a?(Numeric)
        validate(
          instance <= value,
          'validation.keyword.maximum.greater',
          "instance is greater than `maximum` value",
          keyword: 'maximum',
        )
      end
    end
  end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
EXCLUSIVE_MAXIMUM =
element_map do
  Schema::Element.new(keyword: 'exclusiveMaximum') do |element|
    element.add_action(:validate) do
  if keyword?('exclusiveMaximum')
    value = schema_content['exclusiveMaximum']
    # The value of "exclusiveMaximum" MUST be number, representing an exclusive upper limit for a numeric instance.
    if value.is_a?(Numeric)
      # If the instance is a number, then the instance is valid only if it has a value strictly less than
      # (not equal to) "exclusiveMaximum".
      if instance.is_a?(Numeric)
        validate(
          instance < value,
          'validation.keyword.exclusiveMaximum.greater_or_equal',
          "instance is greater than or equal to `exclusiveMaximum` value",
          keyword: 'exclusiveMaximum',
        )
      end
    end
  end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
MINIMUM =
element_map do
  Schema::Element.new(keyword: 'minimum') do |element|
    element.add_action(:validate) do
  if keyword?('minimum')
    value = schema_content['minimum']
    # The value of "minimum" MUST be a number, representing an inclusive lower limit for a numeric instance.
    if value.is_a?(Numeric)
      # If the instance is a number, then this keyword validates only if the instance is greater than or
      # exactly equal to "minimum".
      if instance.is_a?(Numeric)
        validate(
          instance >= value,
          'validation.keyword.minimum.less',
          "instance is less than `minimum` value",
          keyword: 'minimum',
        )
      end
    end
  end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
EXCLUSIVE_MINIMUM =
element_map do
  Schema::Element.new(keyword: 'exclusiveMinimum') do |element|
    element.add_action(:validate) do
  if keyword?('exclusiveMinimum')
    value = schema_content['exclusiveMinimum']
    # The value of "exclusiveMinimum" MUST be number, representing an exclusive lower limit for a numeric instance.
    if value.is_a?(Numeric)
      # If the instance is a number, then the instance is valid only if it has a value strictly greater
      # than (not equal to) "exclusiveMinimum".
      if instance.is_a?(Numeric)
        validate(
          instance > value,
          'validation.keyword.exclusiveMaximum.less_or_equal',
          "instance is less than or equal to `exclusiveMinimum` value",
          keyword: 'exclusiveMinimum',
        )
      end
    end
  end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
PATTERN =
element_map do
  Schema::Element.new(keyword: 'pattern') do |element|
    element.add_action(:validate) do
  if keyword?('pattern')
    value = schema_content['pattern']
    # The value of this keyword MUST be a string.
    if value.respond_to?(:to_str)
      if instance.respond_to?(:to_str)
        begin
          # This string SHOULD be a valid regular expression, according to the ECMA 262 regular expression
          # dialect.
          # TODO
          regexp = Regexp.new(value)
          #> A string instance is considered valid if the regular expression matches the instance successfully.
          validate(
            regexp.match(instance),
            'validation.keyword.pattern.not_match',
            'instance string does not match `pattern` regular expression value',
            keyword: 'pattern',
          )
        rescue RegexpError
          # cannot validate
        end
      end
    end
  end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
ALL_OF =
element_map do
  Schema::Element.new(keyword: 'allOf') do |element|
    element.add_action(:subschema) do
      #> This keyword's value MUST be a non-empty array.
      if keyword_value_ary?('allOf')
        schema_content['allOf'].each_index do |i|
          #> Each item of the array MUST be a valid JSON Schema.
          cxt_yield(['allOf', i])
        end
      end
    end # element.add_action(:subschema)

    element.add_action(:inplace_applicate) do
  if keyword?('allOf') && schema_content['allOf'].respond_to?(:to_ary)
    schema_content['allOf'].each_index do |i|
      inplace_subschema_applicate(['allOf', i])
    end
  end
    end # element.add_action(:inplace_applicate)

    element.add_action(:validate) do
      if keyword?('allOf')
        value = schema_content['allOf']
        # This keyword's value MUST be a non-empty array. Each item of the array MUST be a valid JSON Schema.
        if value.respond_to?(:to_ary)
          # An instance validates successfully against this keyword if it validates successfully against all
          # schemas defined by this keyword's value.
          allOf_results = value.each_index.map do |i|
            inplace_subschema_validate(['allOf', i])
          end
          inplace_results_validate(
            allOf_results.all?(&:valid?),
            'validation.keyword.allOf.not_all_valid',
            "instance is not valid against all `allOf` schemas",
            keyword: 'allOf',
            results: allOf_results,
            allOf_indexes_valid: allOf_results.each_with_index.inject({}) { |h, (r, i)| h.update({i => r.valid?}) }.freeze,
          )
        end
      end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
ANY_OF =
element_map do
  Schema::Element.new(keyword: 'anyOf') do |element|
    element.add_action(:subschema) do
      #> This keyword's value MUST be a non-empty array.
      if keyword_value_ary?('anyOf')
        schema_content['anyOf'].each_index do |i|
          #> Each item of the array MUST be a valid JSON Schema.
          cxt_yield(['anyOf', i])
        end
      end
    end # element.add_action(:subschema)

    element.add_action(:inplace_application_requires_instance) { cxt_yield(true) if keyword?('anyOf') }

    element.add_action(:inplace_applicate) do
  if keyword?('anyOf') && schema_content['anyOf'].respond_to?(:to_ary)
    anyOf = schema_content['anyOf'].each_index.map { |i| subschema(['anyOf', i]) }
    validOf = anyOf.select { |schema| schema.instance_valid?(instance) }
    if !validOf.empty?
      applicators = validOf
    else
      # invalid application: if none of the anyOf were valid, we apply them all
      applicators = anyOf
    end

    applicators.each do |applicator|
      inplace_schema_applicate(applicator)
    end
  end
    end # element.add_action(:inplace_applicate)

    element.add_action(:validate) do
      if keyword?('anyOf')
        value = schema_content['anyOf']
        # This keyword's value MUST be a non-empty array. Each item of the array MUST be a valid JSON Schema.
        if value.respond_to?(:to_ary)
          # An instance validates successfully against this keyword if it validates successfully against at
          # least one schema defined by this keyword's value.
          # Note that when annotations are being collected, all subschemas MUST be examined so that
          # annotations are collected from each subschema that validates successfully.
          anyOf_results = value.each_index.map do |i|
            inplace_subschema_validate(['anyOf', i])
          end
          inplace_results_validate(
            anyOf_results.any?(&:valid?),
            'validation.keyword.anyOf.not_any_valid',
            "instance is not valid against any `anyOf` schema",
            keyword: 'anyOf',
            results: anyOf_results,
            # when invalid these are all false, but included for consistency with allOf/oneOf
            anyOf_indexes_valid: anyOf_results.each_with_index.inject({}) { |h, (r, i)| h.update({i => r.valid?}) }.freeze,
          )
        end
      end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
ONE_OF =
element_map do
  Schema::Element.new(keyword: 'oneOf') do |element|
    element.add_action(:subschema) do
      #> This keyword's value MUST be a non-empty array.
      if keyword_value_ary?('oneOf')
        schema_content['oneOf'].each_index do |i|
          #> Each item of the array MUST be a valid JSON Schema.
          cxt_yield(['oneOf', i])
        end
      end
    end # element.add_action(:subschema)

    element.add_action(:inplace_application_requires_instance) { cxt_yield(true) if keyword?('oneOf') }

    element.add_action(:inplace_applicate) do
  if keyword?('oneOf') && schema_content['oneOf'].respond_to?(:to_ary)
    oneOf_idxs = schema_content['oneOf'].each_index
    subschema_idx_valid = Hash.new { |h, i| h[i] = subschema(['oneOf', i]).instance_valid?(instance) }
    # count up to 2 `oneOf` subschemas which `instance` validates against
    nvalid = oneOf_idxs.inject(0) { |n, i| n <= 1 && subschema_idx_valid[i] ? n + 1 : n }
    if nvalid != 0
      applicator_idxs = oneOf_idxs.select { |i| subschema_idx_valid[i] }
    else
      # invalid application: if none of the oneOf were valid, we apply them all.
      # note: invalid application does not apply when multiple oneOfs validate.
      applicator_idxs = oneOf_idxs
    end

    applicator_idxs.each do |i|
      inplace_subschema_applicate(['oneOf', i])
    end
  end
    end # element.add_action(:inplace_applicate)

    element.add_action(:validate) do
      if keyword?('oneOf')
        value = schema_content['oneOf']
        # This keyword's value MUST be a non-empty array. Each item of the array MUST be a valid JSON Schema.
        if value.respond_to?(:to_ary)
          # An instance validates successfully against this keyword if it validates successfully against
          # exactly one schema defined by this keyword's value.
          oneOf_results = value.each_index.map do |i|
            inplace_subschema_validate(['oneOf', i])
          end
          if oneOf_results.none?(&:valid?)
            inplace_results_validate(
              false,
              'validation.keyword.oneOf.not_any_valid',
              "instance is not valid against any `oneOf` schema",
              keyword: 'oneOf',
              results: oneOf_results,
              oneOf_indexes_valid: oneOf_results.each_with_index.inject({}) { |h, (r, i)| h.update({i => r.valid?}) }.freeze,
            )
          else
            inplace_results_validate(
              oneOf_results.select(&:valid?).size == 1,
              'validation.keyword.oneOf.multiple_valid',
              "instance is valid against multiple `oneOf` schemas",
              keyword: 'oneOf',
              results: oneOf_results,
              oneOf_indexes_valid: oneOf_results.each_with_index.inject({}) { |h, (r, i)| h.update({i => r.valid?}) }.freeze,
            )
          end
        end
      end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
XSCHEMA =
element_map do
  Schema::Element.new(keyword: '$schema') do |element|
  end # Schema::Element.new
end
CONTAINS =
element_map do
  Schema::Element.new(keyword: 'contains') do |element|
    element.add_action(:subschema) do
      if keyword?('contains')
        #> The value of this keyword MUST be a valid JSON Schema.
        cxt_yield(['contains'])
      end
    end # element.add_action(:subschema)

    element.add_action(:child_applicate) do
if instance.respond_to?(:to_ary)
  if keyword?('contains')
    contains_schema = subschema(['contains'])

    child_idx_valid = Hash.new { |h, i| h[i] = contains_schema.instance_valid?(instance[i]) }

    if child_idx_valid[token]
      child_schema_applicate(contains_schema)
    else
      instance_valid = instance.each_index.any? { |i| child_idx_valid[i] }

      unless instance_valid
        # invalid application: if contains_schema does not validate against any child, it applies to every child
        child_schema_applicate(contains_schema)
      end
    end
  end
end # if instance.respond_to?(:to_ary)
    end # element.add_action(:child_applicate)

    element.add_action(:validate) do
      if keyword?('contains')
        # An array instance is valid against "contains" if at least one of its elements is valid against
        # the given schema.
        if instance.respond_to?(:to_ary)
          results = {}
          instance.each_index do |i|
            results[i] = child_subschema_validate(i, ['contains'])
          end
          child_results_validate(
            results.each_value.any?(&:valid?),
            'validation.keyword.contains.none',
            "instance array does not contain any items valid against `contains` schema",
            keyword: 'contains',
            child_results: results,
            # when invalid these are all false, but included for consistency with `contains` with min/max
            instance_indexes_valid: results.inject({}) { |h, (i, r)| h.update({i => r.valid?}) }.freeze,
          )
        end
      end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
EXAMPLES =
element_map do
  Schema::Element.new(keyword: 'examples') do |element|
  end # Schema::Element.new
end
REQUIRED =
element_map do
  Schema::Element.new(keyword: 'required') do |element|
    element.add_action(:described_object_property_names) do
      next if !keyword_value_ary?('required')
      schema_content['required'].each(&block)
    end

    element.add_action(:validate) do
  if keyword?('required')
    value = schema_content['required']
    # The value of this keyword MUST be an array. Elements of this array, if any, MUST be strings, and MUST be unique.
    if value.respond_to?(:to_ary)
      if instance.respond_to?(:to_hash)
        # An object instance is valid against this keyword if every item in the array is the name of a property in the instance.
        missing_required = value.reject { |property_name| instance.key?(property_name) }.freeze
        validate(
          missing_required.empty?,
          'validation.keyword.required.missing_property_names',
          'instance object does not contain all property names specified by `required` value',
          keyword: 'required',
          missing_required_property_names: missing_required,
        )
      end
    end
  end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
INFO_BOOL =
element_map do |keyword: |
  Schema::Element.new(keyword: keyword) do |element|
  end # Schema::Element.new
end
PROPERTIES =
element_map do
  Schema::Element.new(keywords: %w(properties patternProperties additionalProperties)) do |element|
    element.add_action(:subschema) do
      #> The value of "properties" MUST be an object.
      if keyword_value_hash?('properties')
        schema_content['properties'].each_key do |property_name|
          #> Each value of this object MUST be a valid JSON Schema.
          cxt_yield(['properties', property_name])
        end
      end

      #> The value of "patternProperties" MUST be an object.
      if keyword_value_hash?('patternProperties')
        schema_content['patternProperties'].each_key do |pattern|
          #> Each property value of this object MUST be a valid JSON Schema.
          cxt_yield(['patternProperties', pattern])
        end
      end

      if keyword?('additionalProperties')
        #> The value of "additionalProperties" MUST be a valid JSON Schema.
        cxt_yield(['additionalProperties'])
      end
    end # element.add_action(:subschema)

    element.add_action(:described_object_property_names) do
      next if !keyword_value_hash?('properties')
      schema_content['properties'].each_key(&block)
    end

    element.add_action(:child_applicate) do
if instance.respond_to?(:to_hash)
  apply_additional = true
  if keyword?('properties') && schema_content['properties'].respond_to?(:to_hash) && schema_content['properties'].key?(token)
    apply_additional = false
    child_subschema_applicate(['properties', token])
  end
  if keyword?('patternProperties') && schema_content['patternProperties'].respond_to?(:to_hash)
    schema_content['patternProperties'].each_key do |pattern|
      if pattern.respond_to?(:to_str) && token.to_s =~ Regexp.new(pattern) # TODO map pattern to ruby syntax
        apply_additional = false
        child_subschema_applicate(['patternProperties', pattern])
      end
    end
  end
  if apply_additional && keyword?('additionalProperties')
    child_subschema_applicate(['additionalProperties'])
  end
end # if instance.respond_to?(:to_hash)
    end # element.add_action(:child_applicate)

    element.add_action(:validate) do
      evaluated_property_names = Set[]

      if keyword?('properties')
        value = schema_content['properties']
        # The value of "properties" MUST be an object. Each value of this object MUST be a valid JSON Schema.
        if value.respond_to?(:to_hash)
          # Validation succeeds if, for each name that appears in both the instance and as a name within this
          # keyword's value, the child instance for that name successfully validates against the corresponding
          # schema.
          if instance.respond_to?(:to_hash)
            results = {}
            instance.each_key do |property_name|
              if value.key?(property_name)
                evaluated_property_names << property_name
                results[property_name] = child_subschema_validate(property_name, ['properties', property_name])
              end
            end
            child_results_validate(
              results.each_value.all?(&:valid?),
              'validation.keyword.properties.invalid',
              "instance object properties are not all valid against corresponding `properties` schemas",
              keyword: 'properties',
              child_results: results,
              instance_properties_valid: results.inject({}) { |h, (k, r)| h.update({k => r.valid?}) }.freeze,
            )
          end
        end
      end

      if keyword?('patternProperties')
        value = schema_content['patternProperties']
        # The value of "patternProperties" MUST be an object. Each property name of this object SHOULD be a
        # valid regular expression, according to the ECMA 262 regular expression dialect. Each property value
        # of this object MUST be a valid JSON Schema.
        if value.respond_to?(:to_hash)
          # Validation succeeds if, for each instance name that matches any regular expressions that appear as
          # a property name in this keyword's value, the child instance for that name successfully validates
          # against each schema that corresponds to a matching regular expression.
          if instance.respond_to?(:to_hash)
            results = {}
            instance.each_key do |property_name|
              value.each_key do |value_property_pattern|
                begin
                  # TODO ECMA 262
                  if value_property_pattern.respond_to?(:to_str) && Regexp.new(value_property_pattern).match(property_name.to_s)
                    evaluated_property_names << property_name
                    results[property_name] = child_subschema_validate(property_name, ['patternProperties', value_property_pattern])
                  end
                rescue ::RegexpError
                  # cannot validate
                end
              end
            end
            child_results_validate(
              results.each_value.all?(&:valid?),
              'validation.keyword.patternProperties.invalid',
              "instance object properties are not all valid against matching `patternProperties` schemas",
              keyword: 'patternProperties',
              child_results: results,
              instance_properties_valid: results.inject({}) { |h, (k, r)| h.update({k => r.valid?}) }.freeze,
            )
          end
        end
      end

      if keyword?('additionalProperties')
        # The value of "additionalProperties" MUST be a valid JSON Schema.
        if instance.respond_to?(:to_hash)
          results = {}
          instance.each_key do |property_name|
            if !evaluated_property_names.include?(property_name)
              results[property_name] = child_subschema_validate(property_name, ['additionalProperties'])
            end
          end
          child_results_validate(
            results.each_value.all?(&:valid?),
            'validation.keyword.additionalProperties.invalid',
            "instance object additional properties are not all valid against `additionalProperties` schema",
            keyword: 'additionalProperties',
            child_results: results,
            instance_properties_valid: results.inject({}) { |h, (k, r)| h.update({k => r.valid?}) }.freeze,
          )
        end
      end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
DEFINITIONS =
element_map do |keyword: |
  Schema::Element.new(keyword: keyword) do |element|
    element.add_action(:subschema) do
      #> This keyword's value MUST be an object.
      if keyword_value_hash?(keyword)
        schema_content[keyword].each_key do |property_name|
          #> Each member value of this object MUST be a valid JSON Schema.
          cxt_yield([keyword, property_name])
        end
      end
    end # element.add_action(:subschema)
  end
end
DYNAMIC_REF =
element_map do
  Schema::Element.new(keyword: '$dynamicRef') do |element|
    resolve_dynamicRef = proc do
      next unless keyword_value_str?('$dynamicRef')

      dynamic_anchor_map = schema.jsi_next_schema_dynamic_anchor_map

      #> Resolved against the current URI base, it produces the URI used as the starting point for runtime resolution.
      #> This initial resolution is safe to perform on schema load.
      ref = schema.schema_ref(schema_content['$dynamicRef'])

      initial_resolution = ref.resolve

      #> If the initially resolved starting point URI includes a
      #> fragment that was created by the "$dynamicAnchor" keyword,
      resolve_dynamically = \
        # did resolution resolve a fragment?
        ref.ref_uri.fragment &&
        # does the fragment correspond to a dynamicAnchor? (not a regular anchor, not a pointer)
        initial_resolution.dialect_invoke_each(:dynamicAnchor).include?(ref.ref_uri.fragment) &&
        # is the anchor in our dynamic_anchor_map?
        dynamic_anchor_map.key?(ref.ref_uri.fragment)
      if resolve_dynamically
        #> the initial URI MUST be replaced by the URI (including the fragment)
        #> for the outermost schema resource in the dynamic scope (Section 7.1)
        #> that defines an identically named fragment with "$dynamicAnchor".

        # our dynamic resolution doesn't use a stack of dynamic scope URIs.
        # we replace the initially resolved resource with the resource from dynamic_anchor_map.

        scope_schema, subptrs = dynamic_anchor_map[ref.ref_uri.fragment]
        resolved_schema = subptrs.inject(scope_schema, &:subschema)
      else
        #> Otherwise, its behavior is identical to "$ref", and no runtime resolution is needed.
        resolved_schema = initial_resolution
      end

      [resolved_schema.with_dynamic_scope_from(schema), ref]
    end

    element.add_action(:inplace_applicate) do
      resolved_schema, ref = *instance_exec(&resolve_dynamicRef) || next
      inplace_schema_applicate(resolved_schema, ref: ref)
    end

    element.add_action(:validate) do
      resolved_schema, ref = *instance_exec(&resolve_dynamicRef) || next
      ref_result = resolved_schema.internal_validate_instance(
        instance_ptr,
        instance_document,
        validate_only: validate_only,
        visited_refs: Util.add_visited_ref(visited_refs, ref),
      )
      inplace_results_validate(
        ref_result.valid?,
        'validation.keyword.$dynamicRef.invalid',
        "instance is not valid against the schema referenced by `$dynamicRef`",
        keyword: '$dynamicRef',
        results: [ref_result],
      )
    end
  end
end
INFO_STRING =
element_map do |keyword: |
  Schema::Element.new(keyword: keyword) do |element|
  end # Schema::Element.new
end
XVOCABULARY =
element_map do
  Schema::Element.new(keyword: '$vocabulary') do |element|
  end
end
DEPENDENCIES =
element_map do
  Schema::Element.new(keyword: 'dependencies') do |element|
    element.add_action(:subschema) do
      #> This keyword's value MUST be an object.
      if keyword_value_hash?('dependencies')
        schema_content['dependencies'].each_pair do |property_name, dependency|
          #> Each property specifies a dependency.
          #> Each dependency value MUST be an array or a valid JSON Schema.
          if !dependency.respond_to?(:to_ary)
            cxt_yield(['dependencies', property_name])
          end
        end
      end
    end # element.add_action(:subschema)

    element.add_action(:described_object_property_names) do
      next if !keyword_value_hash?('dependencies')
      schema_content['dependencies'].each do |property_name, dependency|
        cxt_yield(property_name)
        if dependency.respond_to?(:to_ary)
          dependency.each(&block)
        end
      end
    end

    element.add_action(:inplace_application_requires_instance) do
      next if !keyword_value_hash?('dependencies')
      next if schema_content['dependencies'].each_value.all? { |dependency| dependency.respond_to?(:to_ary) }
      cxt_yield(true)
    end

    element.add_action(:inplace_applicate) do
      next if !keyword_value_hash?('dependencies')
      next if schema_content['dependencies'].each_value.all? { |dependency| dependency.respond_to?(:to_ary) }
      next if !instance.respond_to?(:to_hash)
      #> This keyword's value MUST be an object. Each property specifies a dependency.  Each dependency
      #> value MUST be an array or a valid JSON Schema.
      schema_content['dependencies'].each_pair do |property_name, dependency|
        if dependency.respond_to?(:to_ary)
          # noop: array-form dependencies has no in-place applicator schema
        else
          # If the dependency value is a subschema, and the dependency key is a
          # property in the instance, the entire instance must validate against
          # the dependency value.
          if instance.key?(property_name)
            inplace_subschema_applicate(['dependencies', property_name])
          end
        end
      end
    end # element.add_action(:inplace_applicate)

    element.add_action(:validate) do
          #> This keyword's value MUST be an object. Each property specifies a dependency.  Each dependency
          #> value MUST be an array or a valid JSON Schema.
          next if !keyword_value_hash?('dependencies')
          next if !instance.respond_to?(:to_hash)
          schema_content['dependencies'].each_pair do |property_name, dependency|
            if dependency.respond_to?(:to_ary)
              # If the dependency value is an array, each element in the array, if
              # any, MUST be a string, and MUST be unique.  If the dependency key is
              # a property in the instance, each of the items in the dependency value
              # must be a property that exists in the instance.
              if instance.respond_to?(:to_hash) && instance.key?(property_name)
                missing_required = dependency.reject { |name| instance.key?(name) }.freeze
                validate(
                  missing_required.empty?,
                  'validation.keyword.dependencies.dependent_required.missing_property_names',
                  'instance object does not contain all dependent required property names specified by `dependencies` value',
                  keyword: 'dependencies',
                  property_name: property_name,
                  missing_dependent_required_property_names: missing_required,
                )
              end
            else
              # If the dependency value is a subschema, and the dependency key is a
              # property in the instance, the entire instance must validate against
              # the dependency value.
              if instance.key?(property_name)
                dependency_result = inplace_subschema_validate(['dependencies', property_name])
                inplace_results_validate(
                  dependency_result.valid?,
                  'validation.keyword.dependencies.dependent_schema.invalid',
                  'instance object is not valid against the schema corresponding to a matched property name specified by `dependencies` value',
                  keyword: 'dependencies',
                  results: [dependency_result],
                  property_name: property_name,
                )
              end
            end
          end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
IF_THEN_ELSE =
element_map do
  Schema::Element.new(keywords: %w(if then else)) do |element|
    element.add_action(:subschema) do
      if keyword?('if')
        #> This keyword's value MUST be a valid JSON Schema.
        cxt_yield(['if'])
      end

      if keyword?('then')
        #> This keyword's value MUST be a valid JSON Schema.
        cxt_yield(['then'])
      end

      if keyword?('else')
        #> This keyword's value MUST be a valid JSON Schema.
        cxt_yield(['else'])
      end
    end # element.add_action(:subschema)

    element.add_action(:inplace_application_requires_instance) { cxt_yield(true) if keyword?('if') }

    element.add_action(:inplace_applicate) do
  if keyword?('if')
    if subschema(['if']).instance_valid?(instance)
      if collect_evaluated
        inplace_subschema_applicate(['if'], applicate: false)
      end
      if keyword?('then')
        inplace_subschema_applicate(['then'])
      end
    else
      if keyword?('else')
        inplace_subschema_applicate(['else'])
      end
    end
  end
    end # element.add_action(:inplace_applicate)

    element.add_action(:validate) do
      if keyword?('if')
        # This keyword's value MUST be a valid JSON Schema.
        # This validation outcome of this keyword's subschema has no direct effect on the overall validation
        # result. Rather, it controls which of the "then" or "else" keywords are evaluated.
        if_result = inplace_subschema_validate(['if'])

        if if_result.valid?
          result.evaluated_tokens.merge(if_result.evaluated_tokens)
        end

        if if_result.valid?
          if keyword?('then')
            then_result = inplace_subschema_validate(['then'])
            inplace_results_validate(
              then_result.valid?,
              'validation.keyword.then.invalid',
              "instance is not valid against `then` schema after validating against `if` schema",
              keyword: 'if',
              results: [then_result],
            )
          end
        else
          if keyword?('else')
            else_result = inplace_subschema_validate(['else'])
            inplace_results_validate(
              else_result.valid?,
              'validation.keyword.else.invalid',
              "instance is not valid against `else` schema after not validating against `if` schema",
              keyword: 'if',
              results: [else_result],
            )
          end
        end
      end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
CONTENT_SCHEMA =
element_map do
  Schema::Element.new(keyword: 'contentSchema') do |element|
    element.add_action(:subschema) do
      if keyword?('contentSchema')
        #> The value of this property MUST be a valid JSON schema.
        cxt_yield(['contentSchema'])
      end
    end # element.add_action(:subschema)
  end # Schema::Element.new
end
ITEMS_PREFIXED =
element_map do
  Schema::Element.new(keywords: %w(items prefixItems)) do |element|
    element.add_action(:subschema) do
      # https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-01#name-prefixitems
      #> The value of "prefixItems" MUST be a non-empty array of valid JSON Schemas.
      if keyword_value_ary?('prefixItems')
        schema_content['prefixItems'].each_index do |i|
          cxt_yield(['prefixItems', i])
        end
      end

      # https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-01#name-items
      #> The value of "items" MUST be a valid JSON Schema.
      if keyword?('items')
        cxt_yield(['items'])
      end
    end

    element.add_action(:child_applicate) do
      next if !instance.respond_to?(:to_ary)
      if keyword_value_ary?('prefixItems') && schema_content['prefixItems'].size > token
        child_subschema_applicate(['prefixItems', token])
      elsif keyword?('items')
        child_subschema_applicate(['items'])
      end
    end

    element.add_action(:validate) do
      next if !instance.respond_to?(:to_ary)
      i = 0
      if keyword_value_ary?('prefixItems')
        prefixItems_results = {}

        while i < schema_content['prefixItems'].size && i < instance.size
          prefixItems_results[i] = child_subschema_validate(i, ['prefixItems', i])

          i += 1
        end

        #> Validation succeeds if each element of the instance validates
        #> against the schema at the same position, if any.
        #> This keyword does not constrain the length of the array.
        #> If the array is longer than this keyword's value, this keyword
        #> validates only the prefix of matching length.
        child_results_validate(
          prefixItems_results.each_value.all?(&:valid?),
          'validation.keyword.prefixItems.invalid',
          "instance array items are not all valid against corresponding `prefixItems` schemas",
          keyword: 'prefixItems',
          child_results: prefixItems_results,
          instance_indexes_valid: prefixItems_results.inject({}) { |h, (ri, r)| h.update({ri => r.valid?}) }.freeze,
        )
      end

      if keyword?('items')
        items_results = {}

        while i < instance.size
          #> This keyword applies its subschema to all instance elements at indexes
          #> greater than the length of the "prefixItems" array in the same schema
          #> object, as reported by the annotation result of that "prefixItems" keyword.
          #> If no such annotation result exists, "items" applies its subschema to all instance array elements.
          #> Note that the behavior of "items" without "prefixItems" is identical
          #> to that of the schema form of "items" in prior drafts.
          #> When "prefixItems" is present, the behavior of "items" is identical
          #> to the former "additionalItems" keyword.
          items_results[i] = child_subschema_validate(i, ['items'])

          i += 1
        end

        if keyword_value_ary?('prefixItems')
          error_key = 'validation.keyword.items.after_prefixItems.invalid'
          error_msg = "instance array items after `prefixItems` are not all valid against `items` schema"
        else
          error_key = 'validation.keyword.items.invalid'
          error_msg = "instance array items are not all valid against `items` schema"
        end
        child_results_validate(
          items_results.each_value.all?(&:valid?),
          error_key,
          error_msg,
          keyword: 'items',
          child_results: items_results,
          instance_indexes_valid: items_results.inject({}) { |h, (ri, r)| h.update({ri => r.valid?}) }.freeze,
        )
      end
    end
  end
end
PROPERTY_NAMES =
element_map do
  Schema::Element.new(keyword: 'propertyNames') do |element|
    element.add_action(:subschema) do
      if keyword?('propertyNames')
        #> The value of "propertyNames" MUST be a valid JSON Schema.
        cxt_yield(['propertyNames'])
      end
    end # element.add_action(:subschema)

    element.add_action(:propertyNames) do
      cxt_yield(subschema(['propertyNames'])) if keyword?('propertyNames')
    end

    element.add_action(:validate) do
  if keyword?('propertyNames')
    # The value of "propertyNames" MUST be a valid JSON Schema.
    #
    # If the instance is an object, this keyword validates if every property name in the instance
    # validates against the provided schema. Note the property name that the schema is testing will
    # always be a string.
    if instance.respond_to?(:to_hash)
      results = {}
      instance.each_key do |property_name|
        results[property_name] = subschema(['propertyNames']).internal_validate_instance(
          Ptr[],
          property_name,
          validate_only: validate_only,
        )
      end
      validate(
        results.each_value.all?(&:valid?),
        'validation.keyword.propertyNames.invalid',
        "instance object property names are not all valid against `propertyNames` schema",
        keyword: 'propertyNames',
        results: results.each_value,
        instance_property_names_valid: results.inject({}) { |h, (k, r)| h.update({k => r.valid?}) }.freeze,
      )
    end
  end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
CONTAINS_MINMAX =
element_map do
  Schema::Element.new(keywords: ['contains', 'minContains', 'maxContains']) do |element|
    element.add_action(:subschema) do
      if keyword?('contains')
        #> The value of this keyword MUST be a valid JSON Schema.
        cxt_yield(['contains'])
      end
    end # element.add_action(:subschema)

    element.add_action(:child_applicate) do
if instance.respond_to?(:to_ary)
  if keyword?('contains')
    contains_schema = subschema(['contains'])

    child_idx_valid = Hash.new { |h, i| h[i] = contains_schema.instance_valid?(instance[i]) }

    if child_idx_valid[token]
      child_schema_applicate(contains_schema)
    else
      minContains = keyword_value_numeric?('minContains') ? schema_content['minContains'] : 1
      child_valid_count = instance.each_index.inject(0) { |n, i| n < minContains && child_idx_valid[i] ? n + 1 : n }

      if child_valid_count < minContains
        # invalid application: if contains_schema does not validate against `minContains` children,
        # it applies to every child.
        # note: this does not consider maxContains; a validation error from maxContains is not from any
        # validation error of child application (which invalid application is intended to preserve), but
        # rather from too few failures in child application. children that don't validate against contains
        # are correctly not described by contains when contains validation failure comes from maxContains.
        child_schema_applicate(contains_schema)
      end
    end
  end
end # if instance.respond_to?(:to_ary)
    end # element.add_action(:child_applicate)

    element.add_action(:validate) do
      if keyword?('contains')
        # https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-01#name-contains
        #> An array instance is valid against "contains" if at least one of its elements is valid against
        #> the given schema, except when "minContains" is present and has a value of 0,
        #> in which case an array instance MUST be considered valid against the "contains" keyword,
        #> even if none of its elements is valid against the given schema.
        if instance.respond_to?(:to_ary)
          results = {}
          instance.each_index do |i|
            results[i] = child_subschema_validate(i, ['contains'])
          end
          child_valid_count = instance.each_index.count { |i| results[i].valid? }

          minContains = keyword_value_numeric?('minContains') ? schema_content['minContains'] : nil
          if minContains
            # https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#name-mincontains
            child_results_validate(
              child_valid_count >= minContains,
              'validation.keyword.contains.fewer_than_minContains',
              "instance array contains fewer items that are valid against `contains` schema than `minContains` value",
              keyword: 'contains',
              child_results: results,
            )
          else
            child_results_validate(
              child_valid_count > 0,
              'validation.keyword.contains.none',
              "instance array does not contain any items that are valid against `contains` schema",
              keyword: 'contains',
              child_results: results,
            )
          end

          maxContains = keyword_value_numeric?('maxContains') ? schema_content['maxContains'] : nil
          if maxContains
            # https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#name-maxcontains
            validate(
              child_valid_count <= maxContains,
              'validation.keyword.maxContains.more_than_maxContains',
              "instance array contains more items that are valid against `contains` schema than `maxContains` value",
              keyword: 'maxContains',
            )
          end
        end
      end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
MAXIMUM_BOOLEAN_EXCLUSIVE =
element_map do
  Schema::Element.new(keywords: %w(maximum exclusiveMaximum)) do |element|
    element.add_action(:validate) do
  if keyword?('maximum')
    value = schema_content['maximum']
    # The value of "maximum" MUST be a JSON number.
    if value.is_a?(Numeric)
      if instance.is_a?(Numeric)
        # Successful validation depends on the presence and value of "exclusiveMaximum":
        if schema_content['exclusiveMaximum'] == true
          # if "exclusiveMaximum" has boolean value true, the instance is valid if it is strictly lower
          # than the value of "maximum".
          validate(
            instance < value,
            'validation.keyword.maximum.with_exclusiveMaximum.greater_or_equal',
            "instance is greater than or equal to `maximum` value with `exclusiveMaximum` = true",
            keyword: 'maximum',
          )
        else
          # if "exclusiveMaximum" is not present, or has boolean value false, then the instance is
          # valid if it is lower than, or equal to, the value of "maximum"
          validate(
            instance <= value,
            'validation.keyword.maximum.greater',
            "instance is greater than `maximum` value",
            keyword: 'maximum',
          )
        end
      end
    end
  end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
MINIMUM_BOOLEAN_EXCLUSIVE =
element_map do
  Schema::Element.new(keywords: %w(minimum exclusiveMinimum)) do |element|
    element.add_action(:validate) do
  if keyword?('minimum')
    value = schema_content['minimum']
    # The value of "minimum" MUST be a JSON number.
    if value.is_a?(Numeric)
      if instance.is_a?(Numeric)
        # Successful validation depends on the presence and value of "exclusiveMinimum":
        if schema_content['exclusiveMinimum'] == true
          # if "exclusiveMinimum" is present and has boolean value true, the instance is valid if it is
          # strictly greater than the value of "minimum".
          validate(
            instance > value,
            'validation.keyword.minimum.with_exclusiveMinimum.less_or_equal',
            "instance is less than or equal to `minimum` value with `exclusiveMinimum` = true",
            keyword: 'minimum',
          )
        else
          # if "exclusiveMinimum" is not present, or has boolean value false, then the instance is
          # valid if it is greater than, or equal to, the value of "minimum"
          validate(
            instance >= value,
            'validation.keyword.minimum.less',
            "instance is less than `minimum` value",
            keyword: 'minimum',
          )
        end
      end
    end
  end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
MAX_ITEMS =
element_map do
  Schema::Element.new(keyword: 'maxItems') do |element|
    element.add_action(:validate) do
  if keyword?('maxItems')
    value = schema_content['maxItems']
    # The value of this keyword MUST be a non-negative integer.
    if internal_integer?(value) && value >= 0
      if instance.respond_to?(:to_ary)
        # An array instance is valid against "maxItems" if its size is less than, or equal to, the value of this keyword.
        size = instance.to_ary.size
        validate(
          size <= value,
          'validation.keyword.maxItems.size_greater',
          'instance array size is greater than `maxItems` value',
          keyword: 'maxItems',
          instance_size: size,
        )
      end
    end
  end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
MIN_ITEMS =
element_map do
  Schema::Element.new(keyword: 'minItems') do |element|
    element.add_action(:validate) do
  if keyword?('minItems')
    value = schema_content['minItems']
    # The value of this keyword MUST be a non-negative integer.
    if internal_integer?(value) && value >= 0
      if instance.respond_to?(:to_ary)
        # An array instance is valid against "minItems" if its size is greater than, or equal to, the value of this keyword.
        size = instance.to_ary.size
        validate(
          size >= value,
          'validation.keyword.minItems.size_less',
          'instance array size is less than `minItems` value',
          keyword: 'minItems',
          instance_size: size,
        )
      end
    end
  end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
UNIQUE_ITEMS =
element_map do
  Schema::Element.new(keyword: 'uniqueItems') do |element|
    element.add_action(:validate) do
  if keyword?('uniqueItems')
    value = schema_content['uniqueItems']
    # The value of this keyword MUST be a boolean.
    if value == true
      if instance.respond_to?(:to_ary)
        # If it has boolean value true, the instance validates successfully if all of its elements are unique.
        # TODO instance.tally.select { |_, count| count > 1 }.keys.freeze when all supported Hash.method_defined?(:tally)
        duplicate_items = instance.group_by(&:itself).select { |_, v| v.size > 1 }.keys.freeze
        validate(
          duplicate_items.empty?,
          'validation.keyword.uniqueItems.not_unique',
          "instance array items are not unique with `uniqueItems` = true",
          keyword: 'uniqueItems',
          duplicate_items: duplicate_items,
        )
      end
    end
  end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
CONTENT_ENCODING =
element_map do
  Schema::Element.new(keyword: 'contentEncoding') do |element|
  end # Schema::Element.new
end
DEPENDENT_SCHEMAS =
element_map do
  Schema::Element.new(keyword: 'dependentSchemas') do |element|
    element.add_action(:subschema) do
      #> This keyword's value MUST be an object.
      next if !keyword_value_hash?('dependentSchemas')

      #> Each value in the object MUST be a valid JSON Schema.
      schema_content['dependentSchemas'].each_key do |property_name|
        cxt_yield(['dependentSchemas', property_name])
      end
    end

    element.add_action(:described_object_property_names) do
      next if !keyword_value_hash?('dependentSchemas')
      schema_content['dependentSchemas'].each_key(&block)
    end

    element.add_action(:inplace_application_requires_instance) { cxt_yield(true) if keyword?('dependentSchemas') }

    element.add_action(:inplace_applicate) do
      #> This keyword's value MUST be an object.
      next if !keyword_value_hash?('dependentSchemas')
      next if !instance.respond_to?(:to_hash)

      #> This keyword specifies subschemas that are evaluated if the
      #> instance is an object and contains a certain property.
      #
      #> If the object key is a property in the instance, the entire instance must validate
      #> against the subschema. Its use is dependent on the presence of the property.
      schema_content['dependentSchemas'].each_key do |property_name|
        if instance.key?(property_name)
          inplace_subschema_applicate(['dependentSchemas', property_name])
        end
      end
    end

    element.add_action(:validate) do
      #> This keyword's value MUST be an object.
      next if !keyword_value_hash?('dependentSchemas')
      next if !instance.respond_to?(:to_hash)

      #> This keyword specifies subschemas that are evaluated if the
      #> instance is an object and contains a certain property.
      #
      #> If the object key is a property in the instance, the entire instance must validate
      #> against the subschema. Its use is dependent on the presence of the property.
      results = {}
      schema_content['dependentSchemas'].each_key do |property_name|
        if instance.key?(property_name)
          results[property_name] = inplace_subschema_validate(['dependentSchemas', property_name])
        end
      end
      inplace_results_validate(
        results.each_value.all?(&:valid?),
        'validation.keyword.dependentSchemas.invalid',
        "instance object is not valid against all schemas corresponding to matched property names specified by `dependentSchemas`",
        keyword: 'dependentSchemas',
        results: results.each_value,
        dependentSchemas_properties_valid: results.inject({}) { |h, (k, r)| h.update({k => r.valid?}) }.freeze,
      )
    end
  end
end
MAX_PROPERTIES =
element_map do
  Schema::Element.new(keyword: 'maxProperties') do |element|
    element.add_action(:validate) do
  if keyword?('maxProperties')
    value = schema_content['maxProperties']
    # The value of this keyword MUST be a non-negative integer.
    if internal_integer?(value) && value >= 0
      if instance.respond_to?(:to_hash)
        # An object instance is valid against "maxProperties" if its number of properties is less than, or equal to, the value of this keyword.
        size = instance.size
        validate(
          size <= value,
          'validation.keyword.maxProperties.properties_count_greater',
          "instance object properties count is greater than `maxProperties` value",
          keyword: 'maxProperties',
          instance_properties_count: size,
        )
      end
    end
  end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
MIN_PROPERTIES =
element_map do
  Schema::Element.new(keyword: 'minProperties') do |element|
    element.add_action(:validate) do
  if keyword?('minProperties')
    value = schema_content['minProperties']
    # The value of this keyword MUST be a non-negative integer.
    if internal_integer?(value) && value >= 0
      if instance.respond_to?(:to_hash)
        # An object instance is valid against "minProperties" if its number of properties is greater than, or equal to, the value of this keyword.
        size = instance.size
        validate(
          size >= value,
          'validation.keyword.minProperties.properties_count_less',
          "instance object properties count is less than `minProperties` value",
          keyword: 'minProperties',
          instance_properties_count: size,
        )
      end
    end
  end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
MAX_LENGTH =
element_map do
  Schema::Element.new(keyword: 'maxLength') do |element|
    element.add_action(:validate) do
  if keyword?('maxLength')
    value = schema_content['maxLength']
    # The value of this keyword MUST be a non-negative integer.
    if internal_integer?(value) && value >= 0
      if instance.respond_to?(:to_str)
        # A string instance is valid against this keyword if its length is less than, or equal to, the
        # value of this keyword.
        length = instance.to_str.length
        validate(
          length <= value,
          'validation.keyword.maxLength.length_greater',
          "instance string length is greater than `maxLength` value",
          keyword: 'maxLength',
          instance_length: length,
        )
      end
    end
  end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
MIN_LENGTH =
element_map do
  Schema::Element.new(keyword: 'minLength') do |element|
    element.add_action(:validate) do
  if keyword?('minLength')
    value = schema_content['minLength']
    # The value of this keyword MUST be a non-negative integer.
    if internal_integer?(value) && value >= 0
      if instance.respond_to?(:to_str)
        # A string instance is valid against this keyword if its length is greater than, or equal to, the
        # value of this keyword.
        length = instance.to_str.length
        validate(
          length >= value,
          'validation.keyword.minLength.length_less',
          "instance string length is less than `minLength` value",
          keyword: 'minLength',
          instance_length: length,
        )
      end
    end
  end
    end # element.add_action(:validate)
  end # Schema::Element.new
end
UNEVALUATED_ITEMS =
element_map do
  Schema::Element.new(keyword: 'unevaluatedItems') do |element|
    element.depends_on_elements do |other_element|
      other_element.invokes?(:inplace_applicate) ||
        (other_element.invokes?(:child_applicate) && !other_element.invokes?(:application_requires_evaluated))
    end

    element.add_action(:application_requires_evaluated) { cxt_yield(true) if keyword?('unevaluatedItems') }

    element.add_action(:subschema) do
      #> The value of "unevaluatedItems" MUST be a valid JSON Schema.
      if keyword?('unevaluatedItems')
        cxt_yield(['unevaluatedItems'])
      end
    end

    element.add_action(:child_applicate) do
      if instance.respond_to?(:to_ary)
        if keyword?('unevaluatedItems')
          if !evaluated
            child_subschema_applicate(['unevaluatedItems'])
          end
        end
      end
    end

    element.add_action(:validate) do
      next if !keyword?('unevaluatedItems')
      next if !instance.respond_to?(:to_ary)
      results = {}
      instance.each_index do |i|
        if !result.evaluated_tokens.include?(i)
          results[i] = child_subschema_validate(i, ['unevaluatedItems'])
        end
      end

      child_results_validate(
        results.each_value.all?(&:valid?),
        'validation.keyword.unevaluatedItems.invalid',
        "instance array unevaluated items are not all valid against `unevaluatedItems` schema",
        keyword: 'unevaluatedItems',
        child_results: results,
        instance_indexes_valid: results.inject({}) { |h, (i, r)| h.update({i => r.valid?}) }.freeze,
      )
    end
  end
end
CONTENT_MEDIA_TYPE =
element_map do
  Schema::Element.new(keyword: 'contentMediaType') do |element|
  end # Schema::Element.new
end
DEPENDENT_REQUIRED =
element_map do
  Schema::Element.new(keyword: 'dependentRequired') do |element|
    element.add_action(:described_object_property_names) do
      next if !keyword_value_hash?('dependentRequired')
      schema_content['dependentRequired'].each do |property_name, dependent_property_names|
        cxt_yield(property_name)
        next if !dependent_property_names.respond_to?(:to_ary)
        dependent_property_names.each(&block)
      end
    end

    element.add_action(:validate) do
      #> The value of this keyword MUST be an object.
      next if !keyword_value_hash?('dependentRequired')
      next if !instance.respond_to?(:to_hash)

      #> This keyword specifies properties that are required if a specific other property is
      #> present. Their requirement is dependent on the presence of the other property.
      #
      #> Validation succeeds if, for each name that appears in both the instance and as a name
      #> within this keyword's value, every item in the corresponding array is also the name of
      #> a property in the instance.
      missing_dependent_required = {}
      schema_content['dependentRequired'].each do |property_name, dependent_property_names|
        #> Properties in this object, if any, MUST be arrays.
        next if !dependent_property_names.respond_to?(:to_ary)
        if instance.key?(property_name)
          missing_required = dependent_property_names.reject { |name| instance.key?(name) }
          unless missing_required.empty?
            missing_dependent_required[property_name] = missing_required
          end
        end
      end
      validate(
        missing_dependent_required.empty?,
        'validation.keyword.dependentRequired.missing_property_names',
        "instance object does not contain all dependent required property names specified by `dependentRequired`",
        keyword: 'dependentRequired',
        missing_dependent_required: missing_dependent_required,
      )
    end
  end
end
UNEVALUATED_PROPERTIES =
element_map do
  Schema::Element.new(keyword: 'unevaluatedProperties') do |element|
    element.depends_on_elements do |other_element|
      other_element.invokes?(:inplace_applicate) ||
        (other_element.invokes?(:child_applicate) && !other_element.invokes?(:application_requires_evaluated))
    end

    element.add_action(:application_requires_evaluated) { cxt_yield(true) if keyword?('unevaluatedProperties') }

    element.add_action(:subschema) do
      #> The value of "unevaluatedProperties" MUST be a valid JSON Schema.
      if keyword?('unevaluatedProperties')
        cxt_yield(['unevaluatedProperties'])
      end
    end

    element.add_action(:child_applicate) do
      if instance.respond_to?(:to_hash)
        if keyword?('unevaluatedProperties')
          if !evaluated
            child_subschema_applicate(['unevaluatedProperties'])
          end
        end
      end
    end

    element.add_action(:validate) do
      next if !keyword?('unevaluatedProperties')
      next if !instance.respond_to?(:to_hash)
      results = {}
      instance.each_key do |property_name|
        if !result.evaluated_tokens.include?(property_name)
          results[property_name] = child_subschema_validate(property_name, ['unevaluatedProperties'])
        end
      end

      child_results_validate(
        results.each_value.all?(&:valid?),
        'validation.keyword.unevaluatedProperties.invalid',
        "instance object unevaluated properties are not all valid against `unevaluatedProperties` schema",
        keyword: 'unevaluatedProperties',
        child_results: results,
        instance_properties_valid: results.inject({}) { |h, (k, r)| h.update({k => r.valid?}) }.freeze,
      )
    end
  end
end

Class Method Summary collapse

Class Method Details

.element_map(&block) ⇒ Object



5
6
7
# File 'lib/jsi/schema/elements.rb', line 5

def self.element_map(&block)
  Util::MemoMap::Immutable.new(&block)
end