class Dry::Schema::JSONSchema::SchemaCompiler
@api private
def call(ast)
- Api: - private
def call(ast) visit(ast) end
def fetch_filled_options(type, _target)
- Api: - private
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
def fetch_type_opts_for_predicate(name, rest, target)
- Api: - private
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
def initialize(root: false, loose: false)
- Api: - private
def initialize(root: false, loose: false) @keys = EMPTY_HASH.dup @required = Set.new @root = root @loose = loose end
def loose?
- Api: - private
def loose? @loose end
def merge_opts!(orig_opts, new_opts)
- Api: - private
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
def raise_unknown_conversion_error!(type, name)
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
def root?
- Api: - private
def root? @root end
def to_hash
- Api: - private
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
def visit(node, opts = EMPTY_HASH)
- Api: - private
def visit(node, opts = EMPTY_HASH) meth, rest = node public_send(:"visit_#{meth}", rest, opts) end
def visit_and(node, opts = EMPTY_HASH)
- Api: - private
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
def visit_each(node, opts = EMPTY_HASH)
- Api: - private
def visit_each(node, opts = EMPTY_HASH) visit(node, opts.merge(member: true)) end
def visit_implication(node, opts = EMPTY_HASH)
- Api: - private
def visit_implication(node, opts = EMPTY_HASH) node.each do |el| visit(el, **opts, required: false) end end
def visit_key(node, opts = EMPTY_HASH)
- Api: - private
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
def visit_not(node, opts = EMPTY_HASH)
- Api: - private
def visit_not(node, opts = EMPTY_HASH) _name, rest = node visit_predicate(rest, opts) end
def visit_or(node, opts = EMPTY_HASH)
- Api: - private
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
def visit_predicate(node, opts = EMPTY_HASH)
- Api: - private
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
def visit_set(node, opts = EMPTY_HASH)
- Api: - private
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