class AutoParse::Instance

Public Instance Methods

[](key, raw=false) click to toggle source
# File lib/autoparse/instance.rb, line 371
def [](key, raw=false)
  if raw == true
    return @data[key]
  else
    return self.__get__(key)
  end
end
[]=(key, raw=false, value=:undefined) click to toggle source
# File lib/autoparse/instance.rb, line 379
def []=(key, raw=false, value=:undefined)
  if value == :undefined
    # Due to the way Ruby handles default values in assignment methods,
    # we have to swap some values around here.
    raw, value = false, raw
  end
  if raw == true
    return @data[key] = value
  else
    return self.__set__(key, value)
  end
end
inspect() click to toggle source

Returns a String representation of the schema instance.

@return [String] The instance’s state, as a String.

# File lib/autoparse/instance.rb, line 469
def inspect
  sprintf(
    "#<%s:%#0x DATA:%s>",
    self.class.to_s, self.object_id, self.to_hash.inspect
  )
end
method_missing(method, *params, &block) click to toggle source
# File lib/autoparse/instance.rb, line 311
def method_missing(method, *params, &block)
  schema_data = self.class.data
  unless schema_data['additionalProperties']
    # Do nothing special if additionalProperties is not set.
    super
  else
    # We can't modify the method in-place because this affects the call
    # to super.
    property_name = method.to_s
    assignment = false
    # Property names simply identify the property and thus don't
    # include the assignment operator.
    if property_name[-1..-1] == '='
      assignment = true
      property_name[-1..-1] = ''
    end
    property_key = self.class.keys[property_name]
    property_schema = self.class.properties[property_key]
    # TODO: Properly support additionalProperties.
    if property_key == nil || property_schema == nil
      # Method not found.
      return super
    end
    # If additionalProperties is simply set to true, no parsing takes
    # place and all values are treated as 'any'.
    if assignment
      new_value = params[0]
      __set__(property_name, new_value)
    else
      __get__(property_name)
    end
  end
end
to_hash() click to toggle source
# File lib/autoparse/instance.rb, line 449
def to_hash
  return @data
end
to_json(*args) click to toggle source

Converts the instance value to JSON.

@return [String] The instance value converted to JSON.

@note

Ignores extra arguments to avoid throwing errors w/ certain JSON
libraries.
# File lib/autoparse/instance.rb, line 461
def to_json(*args)
  return MultiJson.dump(self.to_hash)
end
valid?() click to toggle source

Validates the parsed data against the schema.

# File lib/autoparse/instance.rb, line 394
def valid?
  unvalidated_fields = @data.keys.dup
  for property_key, schema_class in self.class.properties
    property_value = @data[property_key]
    if !self.class.validate_property_value(
        property_value, schema_class.data)
      return false
    end
    if property_value == nil && schema_class.data['required'] != true
      # Value was omitted, but not required. Still valid. Skip dependency
      # checks.
      next
    end

    # Verify property dependencies
    property_dependencies = self.class.property_dependencies[property_key]
    case property_dependencies
    when String, Array
      property_dependencies = [property_dependencies].flatten
      for dependency_key in property_dependencies
        dependency_value = @data[dependency_key]
        return false if dependency_value == nil
      end
    when Class
      if property_dependencies.ancestors.include?(Instance)
        dependency_instance = property_dependencies.new(property_value)
        return false unless dependency_instance.valid?
      else
        raise TypeError,
          "Expected schema Class, got #{property_dependencies.class}."
      end
    end
  end
  if self.class.additional_properties_schema == nil
    # No additional properties allowed
    return false unless unvalidated_fields.empty?
  elsif self.class.additional_properties_schema != EMPTY_SCHEMA
    # Validate all remaining fields against this schema
    for property_key in unvalidated_fields
      property_value = @data[property_key]
      if !self.class.additional_properties_schema.validate_property_value(
          property_value, self.class.additional_properties_schema.data)
        return false
      end
    end
  end
  if self.class.superclass && self.class.superclass != Instance &&
      self.class.ancestors.first != Instance
    # The spec actually only defined the 'extends' semantics as children
    # must also validate aainst the parent.
    return false unless self.class.superclass.new(@data).valid?
  end
  return true
end

Protected Instance Methods

__get__(property_name) click to toggle source
# File lib/autoparse/instance.rb, line 345
def __get__(property_name)
  property_key = self.class.keys[property_name] || property_name
  schema_class = self.class.property(property_name)
  if !schema_class
    @data[property_key]
  else
    if @data.has_key?(property_key)
      value = @data[property_key]
    else
      value = schema_class.data['default']
    end
    AutoParse.import(value, schema_class)
  end    end
__set__(property_name, value) click to toggle source
# File lib/autoparse/instance.rb, line 360
def __set__(property_name, value)
  property_key = self.class.keys[property_name] || property_name
  schema_class = self.class.property(property_name)
  if !schema_class
    @data[property_key] = value
  else
    @data[property_key] = AutoParse.export(value, schema_class)
  end
end

Public Class Methods

additional_properties_schema() click to toggle source
# File lib/autoparse/instance.rb, line 98
def self.additional_properties_schema
  return EMPTY_SCHEMA
end
data() click to toggle source
# File lib/autoparse/instance.rb, line 106
def self.data
  return @schema_data ||= {}
end
dereference() click to toggle source
# File lib/autoparse/instance.rb, line 30
def self.dereference
  if @schema_data['$ref']
    # Dereference the schema if necessary.
    ref_uri = Addressable::URI.parse(@schema_data['$ref'])
    if self.uri
      schema_uri =
        self.uri + Addressable::URI.parse(@schema_data['$ref'])
    else
      if ref_uri.relative?
        warn("Schema URI is relative, could not resolve against parent.")
      else
        warn("Could not resolve URI against parent.")
      end
      schema_uri = ref_uri
    end
    schema_class = AutoParse.schemas[schema_uri]
    warn("Schema URI mismatch.") if schema_class.uri != schema_uri
    if schema_class == nil
      raise ArgumentError,
        "Could not find schema: #{@schema_data['$ref']}. " +
        "Referenced schema must be parsed first."
    else
      return schema_class
    end
  else
    return self
  end
end
description() click to toggle source
# File lib/autoparse/instance.rb, line 110
def self.description
  return self.data['description']
end
keys() click to toggle source
# File lib/autoparse/instance.rb, line 88
def self.keys
  return @keys ||= (
    if self.superclass.ancestors.include?(::AutoParse::Instance)
      self.superclass.keys.dup
    else
      {}
    end
  )
end
new(data={}) click to toggle source
# File lib/autoparse/instance.rb, line 284
def initialize(data={})
  if (self.class.data || {})['type'] == nil
    # Type is omitted, default value is any.
  else
    type_set = [(self.class.data || {})['type']].flatten.compact
    if !type_set.include?('object')
      raise TypeError,
        "Only schemas of type 'object' are instantiable:\n" +
        "#{self.class.data.inspect}"
    end
  end
  if data.respond_to?(:to_hash)
    data = data.to_hash
  elsif data.respond_to?(:to_json)
    data = JSON.parse(data.to_json)
  else
    raise TypeError,
      'Unable to parse. ' +
      'Expected data to respond to either :to_hash or :to_json.'
  end
  if data['$ref']
    raise TypeError,
      "Cannot instantiate a reference schema. Must be dereferenced first."
  end
  @data = data
end
properties() click to toggle source
# File lib/autoparse/instance.rb, line 59
def self.properties
  return @properties ||= (
    if self.superclass.ancestors.include?(::AutoParse::Instance)
      self.superclass.properties.dup
    else
      {}
    end
  )
end
property(property_name) click to toggle source
# File lib/autoparse/instance.rb, line 69
def self.property(property_name)
  property_key = self.keys[property_name] || property_name
  schema_class = self.properties[property_key]
  if !schema_class
    if self.data[property_name]
      schema_class = AutoParse.generate(self.data[property_name], :parent => self)
    else
      schema_class = self.additional_properties_schema
    end
  end
  if schema_class.data['$ref']
    # Dereference the schema if necessary.
    schema_class = schema_class.dereference
    # Avoid this dereference in the future.
    self.properties[property_key] = schema_class
  end
  return schema_class
end
property_dependencies() click to toggle source
# File lib/autoparse/instance.rb, line 102
def self.property_dependencies
  return @property_dependencies ||= {}
end
uri() click to toggle source
# File lib/autoparse/instance.rb, line 24
def self.uri
  return (@uri ||=
    (@schema_data ? Addressable::URI.parse(@schema_data['id']) : nil)
  )
end
validate_array_property(property_value, schema_data) click to toggle source
# File lib/autoparse/instance.rb, line 151
def self.validate_array_property(property_value, schema_data)
  if property_value.respond_to?(:to_ary)
    property_value = property_value.to_ary
  else
    return false
  end
  property_value.each do |item_value|
    unless self.validate_property_value(item_value, schema_data['items'])
      return false
    end
  end
  return true
end
validate_boolean_property(property_value, schema_data) click to toggle source
# File lib/autoparse/instance.rb, line 124
def self.validate_boolean_property(property_value, schema_data)
  return false if property_value != true && property_value != false
  # TODO: implement more than type-checking
  return true
end
validate_integer_property(property_value, schema_data) click to toggle source
# File lib/autoparse/instance.rb, line 130
def self.validate_integer_property(property_value, schema_data)
  return false if !property_value.kind_of?(Integer)
  if schema_data['minimum'] && schema_data['exclusiveMinimum']
    return false if property_value <= schema_data['minimum']
  elsif schema_data['minimum']
    return false if property_value < schema_data['minimum']
  end
  if schema_data['maximum'] && schema_data['exclusiveMaximum']
    return false if property_value >= schema_data['maximum']
  elsif schema_data['maximum']
    return false if property_value > schema_data['maximum']
  end
  return true
end
validate_number_property(property_value, schema_data) click to toggle source
# File lib/autoparse/instance.rb, line 145
def self.validate_number_property(property_value, schema_data)
  return false if !property_value.kind_of?(Numeric)
  # TODO: implement more than type-checking
  return true
end
validate_object_property(property_value, schema_data) click to toggle source
# File lib/autoparse/instance.rb, line 165
def self.validate_object_property(property_value, schema_data)
  if property_value.kind_of?(Instance)
    return property_value.valid?
  else
    # This is highly ineffecient, but currently hard to avoid given the
    # schema is anonymous, making lookups very difficult.
    schema = AutoParse.generate(schema_data, :parent => self)
    begin
      return schema.new(property_value).valid?
    rescue TypeError, ArgumentError, ::JSON::ParserError
      return false
    end
  end
end
validate_property_value(property_value, schema_data) click to toggle source

@api private

# File lib/autoparse/instance.rb, line 222
def self.validate_property_value(property_value, schema_data)
  if property_value == nil && schema_data['required'] == true
    return false
  elsif property_value == nil
    # Value was omitted, but not required. Still valid.
    return true
  end

  # Verify property values
  if schema_data['$ref']
    if self.uri
      schema_uri = self.uri + Addressable::URI.parse(schema_data['$ref'])
    else
      schema_uri = Addressable::URI.parse(schema_data['$ref'])
    end
    schema = AutoParse.schemas[schema_uri]
    if schema == nil
      raise ArgumentError,
        "Could not find schema: #{schema_data['$ref']}. " +
        "Referenced schema must be parsed first."
    end
    schema_data = schema.data
  end
  case schema_data['type']
  when 'string'
    return false unless self.validate_string_property(
      property_value, schema_data
    )
  when 'boolean'
    return false unless self.validate_boolean_property(
      property_value, schema_data
    )
  when 'integer'
    return false unless self.validate_integer_property(
      property_value, schema_data
    )
  when 'number'
    return false unless self.validate_number_property(
      property_value, schema_data
    )
  when 'array'
    return false unless self.validate_array_property(
      property_value, schema_data
    )
  when 'object'
    return false unless self.validate_object_property(
      property_value, schema_data
    )
  when 'null'
    return false unless property_value.nil?
  when Array
    return false unless self.validate_union_property(
      property_value, schema_data
    )
  else
    # Either type 'any' or we don't know what this is,
    # default to anything goes. Validation of an 'any' property always
    # succeeds.
  end
  return true
end
validate_string_property(property_value, schema_data) click to toggle source
# File lib/autoparse/instance.rb, line 114
def self.validate_string_property(property_value, schema_data)
  property_value = property_value.to_str rescue property_value
  if !property_value.kind_of?(String)
    return false
  else
    # TODO: implement more than type-checking
    return true
  end
end
validate_union_property(property_value, schema_data) click to toggle source
# File lib/autoparse/instance.rb, line 180
def self.validate_union_property(property_value, schema_data)
  union = schema_data['type']
  possible_types = [union].flatten.compact
  for type in possible_types
    case type
    when 'string'
      return true if self.validate_string_property(
        property_value, schema_data
      )
    when 'boolean'
      return true if self.validate_boolean_property(
        property_value, schema_data
      )
    when 'integer'
      return true if self.validate_integer_property(
        property_value, schema_data
      )
    when 'number'
      return true if self.validate_number_property(
        property_value, schema_data
      )
    when 'array'
      return true if self.validate_array_property(
        property_value, schema_data
      )
    when 'object'
      return true if self.validate_object_property(
        property_value, schema_data
      )
    when 'null'
      return true if property_value.nil?
    when 'any'
      return true
    end
  end
  # None of the union types validated.
  # An empty union will fail to validate anything.
  return false
end