class Dry::Schema::JSONSchema::SchemaCompiler

@api private

Constants

IDENTITY
PREDICATE_TO_TYPE
TO_INTEGER
UnknownConversionError

An error raised when a predicate cannot be converted

Attributes

keys[R]

@api private

required[R]

@api private

Public Class Methods

new(root: false, loose: false) click to toggle source

@api private

# File lib/dry/schema/extensions/json_schema/schema_compiler.rb, line 61
def initialize(root: false, loose: false)
  @keys = EMPTY_HASH.dup
  @required = Set.new
  @root = root
  @loose = loose
end

Public Instance Methods

call(ast) click to toggle source

@api private

# File lib/dry/schema/extensions/json_schema/schema_compiler.rb, line 79
def call(ast)
  visit(ast)
end
fetch_filled_options(type, _target) click to toggle source

@api private

# File lib/dry/schema/extensions/json_schema/schema_compiler.rb, line 194
def fetch_filled_options(type, _target)
  case type
  when "string"
    {minLength: 1}
  when "array"
    raise_unknown_conversion_error!(:type, :array) unless loose?

    {not: {type: "null"}}
  else
    {not: {type: "null"}}
  end
end
fetch_type_opts_for_predicate(name, rest, target) click to toggle source

@api private

# File lib/dry/schema/extensions/json_schema/schema_compiler.rb, line 182
def fetch_type_opts_for_predicate(name, rest, target)
  type_opts = PREDICATE_TO_TYPE.fetch(name) do
    raise_unknown_conversion_error!(:predicate, name) unless loose?

    EMPTY_HASH
  end.dup
  type_opts.transform_values! { |v| v.respond_to?(:call) ? v.call(rest[0][1], target) : v }
  type_opts.merge!(fetch_filled_options(target[:type], target)) if name == :filled?
  type_opts
end
loose?() click to toggle source

@api private

# File lib/dry/schema/extensions/json_schema/schema_compiler.rb, line 225
def loose?
  @loose
end
merge_opts!(orig_opts, new_opts) click to toggle source

@api private

# File lib/dry/schema/extensions/json_schema/schema_compiler.rb, line 208
def merge_opts!(orig_opts, new_opts)
  new_type = new_opts[:type]
  orig_type = orig_opts[:type]

  if orig_type && new_type && orig_type != new_type
    new_opts[:type] = [orig_type, new_type]
  end

  orig_opts.merge!(new_opts)
end
raise_unknown_conversion_error!(type, name) click to toggle source
# File lib/dry/schema/extensions/json_schema/schema_compiler.rb, line 229
        def raise_unknown_conversion_error!(type, name)
          message = <<~MSG
            Could not find an equivalent conversion for #{type} #{name.inspect}.

            This means that your generated JSON schema may be missing this validation.

            You can ignore this by generating the schema in "loose" mode, i.e.:
                my_schema.json_schema(loose: true)
          MSG

          raise UnknownConversionError, message.chomp
        end
root?() click to toggle source

@api private

# File lib/dry/schema/extensions/json_schema/schema_compiler.rb, line 220
def root?
  @root
end
to_h()
Alias for: to_hash
to_hash() click to toggle source

@api private

# File lib/dry/schema/extensions/json_schema/schema_compiler.rb, line 69
def to_hash
  result = {}
  result[:$schema] = "http://json-schema.org/draft-06/schema#" if root?
  result.merge!(type: "object", properties: keys, required: required.to_a)
  result
end
Also aliased as: to_h
visit(node, opts = EMPTY_HASH) click to toggle source

@api private

# File lib/dry/schema/extensions/json_schema/schema_compiler.rb, line 84
def visit(node, opts = EMPTY_HASH)
  meth, rest = node
  public_send(:"visit_#{meth}", rest, opts)
end
visit_and(node, opts = EMPTY_HASH) click to toggle source

@api private

# File lib/dry/schema/extensions/json_schema/schema_compiler.rb, line 104
def visit_and(node, opts = EMPTY_HASH)
  left, right = node

  # We need to know the type first to apply filled macro
  if left[1][0] == :filled?
    visit(right, opts)
    visit(left, opts)
  else
    visit(left, opts)
    visit(right, opts)
  end
end
visit_each(node, opts = EMPTY_HASH) click to toggle source

@api private

# File lib/dry/schema/extensions/json_schema/schema_compiler.rb, line 137
def visit_each(node, opts = EMPTY_HASH)
  visit(node, opts.merge(member: true))
end
visit_implication(node, opts = EMPTY_HASH) click to toggle source

@api private

# File lib/dry/schema/extensions/json_schema/schema_compiler.rb, line 130
def visit_implication(node, opts = EMPTY_HASH)
  node.each do |el|
    visit(el, **opts, required: false)
  end
end
visit_key(node, opts = EMPTY_HASH) click to toggle source

@api private

# File lib/dry/schema/extensions/json_schema/schema_compiler.rb, line 142
def visit_key(node, opts = EMPTY_HASH)
  name, rest = node

  if opts.fetch(:required, :true)
    required << name.to_s
  else
    opts.delete(:required)
  end

  visit(rest, opts.merge(key: name))
end
visit_not(node, opts = EMPTY_HASH) click to toggle source

@api private

# File lib/dry/schema/extensions/json_schema/schema_compiler.rb, line 155
def visit_not(node, opts = EMPTY_HASH)
  _name, rest = node

  visit_predicate(rest, opts)
end
visit_or(node, opts = EMPTY_HASH) click to toggle source

@api private

# File lib/dry/schema/extensions/json_schema/schema_compiler.rb, line 118
def visit_or(node, opts = EMPTY_HASH)
  node.each do |child|
    c = self.class.new(loose: loose?)
    c.keys.update(subschema: {})
    c.visit(child, opts.merge(key: :subschema))

    any_of = (keys[opts[:key]][:anyOf] ||= [])
    any_of << c.keys[:subschema]
  end
end
visit_predicate(node, opts = EMPTY_HASH) click to toggle source

@api private

# File lib/dry/schema/extensions/json_schema/schema_compiler.rb, line 162
def visit_predicate(node, opts = EMPTY_HASH)
  name, rest = node

  if name.equal?(:key?)
    prop_name = rest[0][1]
    keys[prop_name] = {}
  else
    target = keys[opts[:key]]
    type_opts = fetch_type_opts_for_predicate(name, rest, target)

    if target[:type]&.include?("array")
      target[:items] ||= {}
      merge_opts!(target[:items], type_opts)
    else
      merge_opts!(target, type_opts)
    end
  end
end
visit_set(node, opts = EMPTY_HASH) click to toggle source

@api private

# File lib/dry/schema/extensions/json_schema/schema_compiler.rb, line 90
def visit_set(node, opts = EMPTY_HASH)
  target = (key = opts[:key]) ? self.class.new(loose: loose?) : self

  node.map { |child| target.visit(child, opts) }

  return unless key

  target_info = opts[:member] ? {items: target.to_h} : target.to_h
  type = opts[:member] ? "array" : "object"

  keys.update(key => {**keys[key], type: type, **target_info})
end