class JsonSchema::Faker::Strategy::Simple

Public Class Methods

formats() click to toggle source
# File lib/json_schema/faker/strategy/simple.rb, line 9
def formats
  @formats ||= {
    "date-time" => ::JsonSchema::Faker::Formats.method(:date_time),
    "email"     => ::JsonSchema::Faker::Formats.method(:email),
    "hostname"  => ::JsonSchema::Faker::Formats.method(:hostname),
    "ipv4"      => ::JsonSchema::Faker::Formats.method(:ipv4),
    "ipv6"      => ::JsonSchema::Faker::Formats.method(:ipv6),
    "uri"       => ::JsonSchema::Faker::Formats.method(:uri),
  }
end

Public Instance Methods

call(schema, hint: nil, position:) click to toggle source
# File lib/json_schema/faker/strategy/simple.rb, line 34
def call(schema, hint: nil, position:)
  if ::JsonSchema::Faker::Configuration.logger
    ::JsonSchema::Faker::Configuration.logger.debug "current position: #{position}"
    ::JsonSchema::Faker::Configuration.logger.debug "current hint:     #{hint.inspect}"
  end

  raise "here comes nil for schema at #{position}" unless schema

  # merge one_of/any_of/all_of
  ::JsonSchema::Faker::Configuration.logger.debug schema.inspect_schema if ::JsonSchema::Faker::Configuration.logger
  schema = compact_schema(schema, position: position)

  return schema.default if schema.default && schema.validate(schema.default).first
  return self.class.formats[schema.format].call(schema, hint: hint, position: position) if self.class.formats.has_key?(schema.format)

  if schema.not
    hint ||= {}
    # too difficult
    # TODO: support one_of/any_of/all_of
    hint[:not_have_keys] = schema.not.required if schema.not.required
    hint[:not_be_values] = schema.not.enum     if schema.not.enum
  end

  # http://json-schema.org/latest/json-schema-validation.html#anchor75
  if schema.enum
    generate_by_enum(schema, hint: hint, position: position)
  else
    generate_by_type(schema, hint: hint, position: position)
  end
end
Also aliased as: generate
compact_schema(schema, position:) click to toggle source

TODO: compacting all_of and one_of/any_of at once will not work

# File lib/json_schema/faker/strategy/simple.rb, line 255
def compact_schema(schema, position:)
  return schema if schema.one_of.empty? && schema.any_of.empty? && schema.all_of.empty?

  if ::JsonSchema::Faker::Configuration.logger
    ::JsonSchema::Faker::Configuration.logger.info "start to compact at #{position}"
    ::JsonSchema::Faker::Configuration.logger.debug schema.inspect_schema
  end

  merged_schema = ::JsonSchema::Schema.new
  merged_schema.copy_from(schema)
  merged_schema.one_of = []
  merged_schema.any_of = []
  merged_schema.all_of = []

  unless schema.all_of.empty?
    ::JsonSchema::Faker::Configuration.logger.info "compact all_of" if ::JsonSchema::Faker::Configuration.logger

    all_of = schema.all_of.each.with_index.inject(nil) do |(a, _), (b, i)|
      if a
        take_logical_and_of_schema(a, b, a_position: "#{position}/all_of", b_position: "#{position}/all_of[#{i+1}]")
      else
        b
      end
    end

    unless schema.one_of.empty? && schema.any_of.empty?
      ::JsonSchema::Faker::Configuration.logger.info "find from one_of and any_of which satiffy all_of" if ::JsonSchema::Faker::Configuration.logger
      one_of_candidate = schema.one_of.find do |s|
        s2 = take_logical_and_of_schema(s, all_of)
        compare_schema(s, s2) # FIXME: check s2 > s
      end

      any_of_candidate = schema.any_of.find do |s|
        s2 = take_logical_and_of_schema(s, all_of)
        compare_schema(s, s2) # FIXME: check s2 > s
      end

      unless one_of_candidate || any_of_candidate
        ::JsonSchema::Faker::Configuration.logger.error "failed to find condition which satfisfy all_of in one_of and any_of" if ::JsonSchema::Faker::Configuration.logger
        merged_schema = take_logical_and_of_schema(merged_schema, all_of, a_position: position, b_position: "#{position}/all_of")
      else
        merged_schema = take_logical_and_of_schema(merged_schema, one_of_candidate, a_position: position, b_position: "#{position}/one_of") if one_of_candidate
        merged_schema = take_logical_and_of_schema(merged_schema, any_of_candidate, a_position: position, b_position: "#{position}/any_of") if any_of_candidate
      end
    else
      merged_schema = take_logical_and_of_schema(merged_schema, all_of, a_position: position, b_position: "#{position}/all_of")
    end
  else
    unless schema.one_of.empty?
      ::JsonSchema::Faker::Configuration.logger.info "compact one_of" if ::JsonSchema::Faker::Configuration.logger
      merged_schema = take_logical_and_of_schema(merged_schema, schema.one_of.first, a_position: position, b_position: "#{position}/one_of[0]")
    end

    unless schema.any_of.empty?
      ::JsonSchema::Faker::Configuration.logger.info "compact any_of" if ::JsonSchema::Faker::Configuration.logger
      merged_schema = take_logical_and_of_schema(merged_schema, schema.any_of.first, a_position: position, b_position: "#{position}/any_of[0]")
    end
  end

  # compact recursively
  merged_schema = compact_schema(merged_schema, position: position)

  ::JsonSchema::Faker::Configuration.logger.debug "compacted: #{merged_schema.inspect_schema}" if ::JsonSchema::Faker::Configuration.logger

  merged_schema
end
generate(schema, hint: nil, position:)
Alias for: call
generate_by_enum(schema, hint: nil, position:) click to toggle source
# File lib/json_schema/faker/strategy/simple.rb, line 148
def generate_by_enum(schema, hint: nil, position:)
  black_list = (hint ? hint[:not_be_values] : nil)
  list       = black_list ? schema.enum - black_list : schema.enum

  if ::JsonSchema::Faker::Configuration.logger
    ::JsonSchema::Faker::Configuration.logger.info "generate by enum at #{position}"
    ::JsonSchema::Faker::Configuration.logger.debug schema.inspect_schema
    ::JsonSchema::Faker::Configuration.logger.debug "black list: #{black_list}" if black_list
    ::JsonSchema::Faker::Configuration.logger.debug "list: #{list}"
  end

  return hint[:example] if hint && hint[:example] && hint[:example] && list.include?(hint[:example])

  list.first
end
generate_by_type(schema, hint: nil, position:) click to toggle source
# File lib/json_schema/faker/strategy/simple.rb, line 164
def generate_by_type(schema, hint: nil, position:)
  if ::JsonSchema::Faker::Configuration.logger
    ::JsonSchema::Faker::Configuration.logger.info "generate by type at #{position}"
    ::JsonSchema::Faker::Configuration.logger.debug schema.inspect_schema
  end

  # http://json-schema.org/latest/json-schema-core.html#anchor8
  # TODO: use include? than first
  case schema.type.first
  when "array"
    generate_for_array(schema, hint: hint, position: position)
  when "boolean"
    return hint[:example] if hint && hint[:example] && (hint[:example] === true || hint[:example] === false)

    true
  when "integer", "number"
    generate_for_number(schema, hint: hint, position: position)
  when "null"
    nil
  when "object", nil
    # here comes object without properties
    generate_for_object(schema, hint: hint, position: position)
  when "string"
    generate_for_string(schema, hint: hint, position: position)
  else
    raise "unknown type for #{schema.inspect_schema}"
  end
end
generate_for_array(schema, hint: nil, position:) click to toggle source
# File lib/json_schema/faker/strategy/simple.rb, line 193
def generate_for_array(schema, hint: nil, position:)
  # completing each items is difficult
  return hint[:example] if hint && hint[:example] && hint[:example].is_a?(Array)

  # http://json-schema.org/latest/json-schema-validation.html#anchor36
  # additionalItems items maxItems minItems uniqueItems
  length = schema.min_items || 0

  if schema.items.nil?
    length.times.map.with_index {|i| i }
  else
    if schema.items.is_a?(Array)
      items = schema.items.map.with_index {|e, i| generate(e, hint: hint, position: "#{position}/items[#{i}]") }

      items + (length - items.size).times.map.with_index {|i| schema.additional_items === false ? i : generate(schema.additional_items, hint: hint, position: "#{position}/additional_items[#{i}]") }
    else
      length.times.map.with_index {|i| generate(schema.items, hint: hint, position: "#{position}/items[#{i}]") }
    end
  end
end
generate_for_number(schema, hint: nil, position:) click to toggle source
# File lib/json_schema/faker/strategy/simple.rb, line 214
def generate_for_number(schema, hint: nil, position:)
  return hint[:example] if hint && hint[:example] && hint[:example].is_a?(Numeric)

  # http://json-schema.org/latest/json-schema-validation.html#anchor13
  # TODO: use hint[:not_be_values]
  min = schema.min
  max = schema.max

  if schema.multiple_of
    min = (min + schema.multiple_of - min % schema.multiple_of) if min
    max = (max - max % schema.multiple_of) if max
  end

  delta = schema.multiple_of ? schema.multiple_of : 1

  # TODO: more sophisticated caluculation
  min, max = [ (min || (max ? max - delta * 2 : 0)), (max || (min ? min + delta * 2 : 0)) ]

  # to get average of min and max can avoid exclusive*
  if schema.type.first == "integer"
    (min / delta + max / delta) / 2 * delta
  else
    (min + max) / 2.0
  end
end
generate_for_object(schema, hint: nil, position:) click to toggle source
# File lib/json_schema/faker/strategy/simple.rb, line 66
def generate_for_object(schema, hint: nil, position:)
  # complete hint
  object = if hint && hint[:example] && hint[:example].is_a?(Hash)
    hint[:example].each.with_object({}) do |(key, value), hash|
      if value.is_a?(Hash)
        if schema.properties.has_key?(key)
          hash[key] = generate(schema.properties[key], hint: { example: hint[:example][key] }, position: "#{position}/#{key}")
        else
          # TODO: support pattern properties
          hash[key] = value
        end
      else
        hash[key] = value
      end
    end
  else
    {}
  end

  # http://json-schema.org/latest/json-schema-validation.html#anchor53
  if schema.required
    keys   = schema.required
    required_length = schema.min_properties || keys.length

    (keys - object.keys).each.with_object(object) do |key, hash|
      hash[key] = generate(schema.properties[key], hint: hint_descend(hint, key), position: "#{position}/#{key}")
    end
  else
    required_length = schema.min_properties || schema.max_properties || 0

    keys = (schema.properties || {}).keys - object.keys
    keys -= hint[:not_have_keys] if hint && hint[:not_have_keys]

    keys.first(required_length).each.with_object(object) do |key, hash|
      hash[key] = generate(schema.properties[key], hint: hint_descend(hint, key), position: "#{position}/#{key}")
    end
  end

  # if length is not enough
  if schema.additional_properties === false
    (required_length - object.keys.length).times.each.with_object(object) do |i, hash|
      if schema.pattern_properties.empty?
        key = (schema.properties.keys - object.keys).first
        hash[key] = generate(schema.properties[key], hint: hint_descend(hint, key), position: "#{position}/#{key}")
      else
        name = ::Pxeger.new(schema.pattern_properties.keys.first).generate
        hash[name] = generate(schema.pattern_properties.values.first, hint: hint_descend(hint, name), position: "#{position}/#{name}")
      end
    end
  else
    # FIXME: key confilct with properties
    (required_length - object.keys.length).times.each.with_object(object) do |i, hash|
      hash[i.to_s] = i
    end
  end

  # consider dependency
  depended_keys = object.keys & schema.dependencies.keys
  if depended_keys.all? {|key| schema.dependencies[key].is_a?(Array) }
    # FIXME: circular dependency is not supported
    depended_keys.each.with_object(object) do |key, hash|
      schema.dependencies[key].each do |additional_key|
        hash[additional_key] = generate(schema.properties[additional_key], hint: hint_descend(hint, additional_key), position: "#{position}/dependencies/#{key}/#{additional_key}") unless object.has_key?(additional_key)
      end
    end
  else
    ::JsonSchema::Faker::Configuration.logger.info "generate again because of dependended keys exists: #{depended_keys}" if ::JsonSchema::Faker::Configuration.logger

    merged_schema = ::JsonSchema::Schema.new.tap {|s| s.copy_from(schema) }
    depended_keys.each do |key|
      dependency = schema.dependencies[key]
      if dependency.is_a?(::JsonSchema::Schema)
        merged_schema = compact_schema(take_logical_and_of_schema(merged_schema, dependency), position: position)
      else
        merged_schema.required = (merged_schema.required + dependency).uniq
      end
    end
    merged_schema.dependencies = nil # XXX: recursive dependency will fail
    generate_for_object(merged_schema, hint: hint, position: position)
  end
end
generate_for_string(schema, hint: nil, position:) click to toggle source
# File lib/json_schema/faker/strategy/simple.rb, line 240
def generate_for_string(schema, hint: nil, position:)
  return hint[:example] if hint && hint[:example] && hint[:example].is_a?(String)

  # http://json-schema.org/latest/json-schema-validation.html#anchor25
  # TODO: use hint[:not_be_values]
  # TODO: support format
  if schema.pattern
    ::Pxeger.new(schema.pattern).generate
  else
    length = schema.min_length || 0
    "a" * length
  end
end
hint_descend(hint, key) click to toggle source
# File lib/json_schema/faker/strategy/simple.rb, line 21
def hint_descend(hint, key)
  if hint && hint[:example]
    hint_without_example = hint.reject{|k,v|k == :example}
    if hint[:example][key]
      hint_without_example.merge(example: hint[:example][key])
    else
      hint_without_example
    end
  else
    hint
  end
end