Class/Module Index [+]

Quicksearch

AutoParse

Copyright 2010 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Constants

EMPTY_SCHEMA

The empty schema accepts all JSON.

INFLECTOR

Public Class Methods

additional_properties_schema() click to toggle source
# File lib/autoparse.rb, line 48
def self.additional_properties_schema
  # Override the superclass implementation so we're not always returning
  # the empty schema.
  if @additional_properties_schema.data['$ref']
    # Dereference the schema if necessary.
    @additional_properties_schema =
      @additional_properties_schema.dereference
  end
  return @additional_properties_schema
end
export(value, schema_class, type=nil) click to toggle source
# File lib/autoparse.rb, line 171
def self.export(value, schema_class, type=nil)
  type = schema_class.data['type'] if type == nil
  case type
  when 'string'
    AutoParse.export_string(value, schema_class)
  when 'boolean'
    AutoParse.export_boolean(value, schema_class)
  when 'integer'
    AutoParse.export_integer(value, schema_class)
  when 'number'
    AutoParse.export_number(value, schema_class)
  when 'array'
    AutoParse.export_array(value, schema_class)
  when 'object'
    AutoParse.export_object(value, schema_class)
  when 'null'
    nil
  when Array
    AutoParse.export_union(value, schema_class)
  else
    AutoParse.export_any(value, schema_class)
  end
end
export_any(value, schema_class) click to toggle source
# File lib/autoparse.rb, line 361
def self.export_any(value, schema_class)
  value
end
export_array(value, schema_class) click to toggle source
# File lib/autoparse.rb, line 311
def self.export_array(value, schema_class)
  if value == nil
    value
  elsif value.respond_to?(:to_ary)
    value = value.to_ary.dup
    items_schema = schema_class.property('items')
    value.map! do |item|
      AutoParse.export(item, items_schema)
    end
    value
  else
    raise TypeError, "Expected Array, got #{value.class}."
  end
end
export_boolean(value, schema_class) click to toggle source
# File lib/autoparse.rb, line 253
def self.export_boolean(value, schema_class)
  case value.to_s.downcase
  when 'true', 'yes', 'y', 'on', '1'
    true
  when 'false', 'no', 'n', 'off', '0'
    false
  when 'nil', 'null', 'undefined', ''
    nil
  else
    raise TypeError, "Expected boolean, got #{value.class}."
  end
end
export_integer(value, schema_class) click to toggle source
# File lib/autoparse.rb, line 290
def self.export_integer(value, schema_class)
  if value == nil
    value
  else
    Integer(value)
  end
end
export_number(value, schema_class) click to toggle source
# File lib/autoparse.rb, line 274
def self.export_number(value, schema_class)
  if value == nil
    value
  else
    Float(value)
  end
end
export_object(value, schema_class) click to toggle source
# File lib/autoparse.rb, line 330
def self.export_object(value, schema_class)
  # FIXME: Every field must be exported as well.
  if value.nil?
    nil
  elsif value.respond_to?(:to_hash)
    value.to_hash
  elsif value.respond_to?(:to_json)
    MultiJson.load(value.to_json)
  else
    raise TypeError, "Expected Hash, got #{value.class}."
  end
end
export_string(value, schema_class) click to toggle source
# File lib/autoparse.rb, line 214
def self.export_string(value, schema_class)
  format = schema_class.data['format']
  if format == 'byte'
    Base64.encode64(value)
  elsif format == 'date-time'
    if value.respond_to?(:to_str)
      value = Time.parse(value.to_str)
    elsif !value.respond_to?(:xmlschema)
      raise TypeError,
        "Could not obtain RFC 3339 timestamp from #{value.class}."
    end
    value.xmlschema
  elsif format == 'url'
    # This effectively does limited URI validation.
    Addressable::URI.parse(value).to_str
  elsif format =~ /^u?int(32|64)$/
    value.to_s
  elsif value.respond_to?(:to_str)
    value.to_str
  elsif value.kind_of?(Symbol)
    value.to_s
  else
    raise TypeError, "Expected String or Symbol, got #{value.class}."
  end
end
export_union(value, schema_class) click to toggle source
# File lib/autoparse.rb, line 350
def self.export_union(value, schema_class)
  export_type = match_type(
    value, schema_class.data['type'], schema_class
  )
  AutoParse.export(value, schema_class, export_type)
end
generate(schema_data, options={}) click to toggle source
# File lib/autoparse.rb, line 26
def self.generate(schema_data, options={})
  uri = options[:uri]
  parent = options[:parent]
  if schema_data["extends"]
    super_uri = uri + Addressable::URI.parse(schema_data["extends"])
    super_schema = self.schemas[super_uri]
    if super_schema == nil
      raise ArgumentError,
        "Could not find schema to extend: #{schema_data["extends"]} " +
        "Parent schema must be parsed before child schema."
    end
  else
    super_schema = Instance
  end
  schema = Class.new(super_schema) do
    @uri = Addressable::URI.parse(uri)
    @uri.normalize! if @uri != nil
    @schema_data = schema_data
    if !self.uri && parent
      @uri = parent.uri
    end

    def self.additional_properties_schema
      # Override the superclass implementation so we're not always returning
      # the empty schema.
      if @additional_properties_schema.data['$ref']
        # Dereference the schema if necessary.
        @additional_properties_schema =
          @additional_properties_schema.dereference
      end
      return @additional_properties_schema
    end

    (@schema_data['properties'] || []).each do |(k, v)|
      property_key, property_schema = k, v
      property_name = INFLECTOR.underscore(property_key).gsub("-", "_")
      property_super_schema = super_schema.properties[property_key]
      if property_super_schema
        # TODO: Not sure if this should be a recursive merge or not...

        # TODO: Might need to raise an error if a schema is extended in
        # a way that violates the requirement that all child instances also
        # validate against the parent schema.
        property_schema = property_super_schema.data.merge(property_schema)
      end

      # If the schema has no ID, it inherits the ID from the parent schema.
      property_schema_class =
        AutoParse.generate(property_schema, :parent => self)

      self.properties[property_key] = property_schema_class
      self.keys[property_name] = property_key

      define_method(property_name) do
        __get__(property_name)
      end
      define_method(property_name + '=') do |value|
        __set__(property_name, value)
      end
    end

    if schema_data['additionalProperties'] == true ||
        schema_data['additionalProperties'] == nil
      # Schema-less unknown properties are allowed.
      @additional_properties_schema = EMPTY_SCHEMA
    elsif schema_data['additionalProperties']
      # Unknown properties follow the supplied schema.
      ap_schema = AutoParse.generate(
        schema_data['additionalProperties'], :parent => self
      )
      @additional_properties_schema = ap_schema
    else
      @additional_properties_schema = nil
    end

    define_method('method_missing') do |method, *params, &block|
      # We need to convert from Ruby calling style to JavaScript calling
      # style. If this fails, attempt to use JavaScript calling style
      # directly.

      # We can't modify the method in-place because this affects the call
      # to super.
      stripped_method = method.to_s
      assignment = false
      if stripped_method[-1..-1] == '='
        assignment = true
        stripped_method[-1..-1] = ''
      end
      key = INFLECTOR.camelize(stripped_method)
      key[0..0] = key[0..0].downcase
      if @data[key] != nil
        # Data found
      elsif @data[stripped_method] != nil
        # Data found
        key = stripped_method
      else
        # Method not found.
        super(method, *params, &block)
      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]
        self.__set__(key, new_value)
      else
        self.__get__(key)
      end
    end

    if schema_data['dependencies']
      for dependency_key, dependency_data in schema_data['dependencies']
        self.property_dependencies[dependency_key] = dependency_data
      end
    end
  end

  # Register the new schema.
  self.schemas[schema.uri] = schema unless parent && parent.uri == schema.uri
  return schema
end
import(value, schema_class, type=nil) click to toggle source
# File lib/autoparse.rb, line 147
def self.import(value, schema_class, type=nil)
  type = schema_class.data['type'] if type == nil
  case type
  when 'string'
    return AutoParse.import_string(value, schema_class)
  when 'boolean'
    return AutoParse.import_boolean(value, schema_class)
  when 'integer'
    return AutoParse.import_integer(value, schema_class)
  when 'number'
    return AutoParse.import_number(value, schema_class)
  when 'array'
    return AutoParse.import_array(value, schema_class)
  when 'object'
    return AutoParse.import_object(value, schema_class)
  when 'null'
    return nil
  when Array
    return AutoParse.import_union(value, schema_class)
  else
    return AutoParse.import_any(value, schema_class)
  end
end
import_any(value, schema_class) click to toggle source
# File lib/autoparse.rb, line 357
def self.import_any(value, schema_class)
  value
end
import_array(value, schema_class) click to toggle source
# File lib/autoparse.rb, line 298
def self.import_array(value, schema_class)
  value = (if value != nil && !value.respond_to?(:to_ary)
    raise TypeError,
      "Expected Array, got #{value.class}."
  else
    (value || []).to_ary.dup
  end)
  items_schema = schema_class.property('items')
  value.map! do |item|
    AutoParse.import(item, items_schema)
  end
  value  end
import_boolean(value, schema_class) click to toggle source
# File lib/autoparse.rb, line 240
def self.import_boolean(value, schema_class)
  case value.to_s.downcase
  when 'true', 'yes', 'y', 'on', '1'
    true
  when 'false', 'no', 'n', 'off', '0'
    false
  when 'nil', 'null', 'undefined', ''
    nil
  else
    raise TypeError, "Expected boolean, got #{value.class}."
  end
end
import_integer(value, schema_class) click to toggle source
# File lib/autoparse.rb, line 282
def self.import_integer(value, schema_class)
  if value == nil
    value
  else
    Integer(value)
  end
end
import_number(value, schema_class) click to toggle source
# File lib/autoparse.rb, line 266
def self.import_number(value, schema_class)
  if value == nil
    value
  else
    Float(value)
  end
end
import_object(value, schema_class) click to toggle source
# File lib/autoparse.rb, line 326
def self.import_object(value, schema_class)
  value ? schema_class.new(value) : nil
end
import_string(value, schema_class) click to toggle source
# File lib/autoparse.rb, line 195
def self.import_string(value, schema_class)
  if value != nil
    format = schema_class.data['format']
    if format == 'byte'
      Base64.decode64(value)
    elsif format == 'date-time'
      Time.parse(value)
    elsif format == 'url'
      Addressable::URI.parse(value)
    elsif format =~ /^u?int(32|64)$/
      value.to_i
    else
      value
    end
  else
    nil
  end
end
import_union(value, schema_class) click to toggle source
# File lib/autoparse.rb, line 343
def self.import_union(value, schema_class)
  import_type = match_type(
    value, schema_class.data['type'], schema_class
  )
  AutoParse.import(value, schema_class, import_type)
end
match_type(value, union, parent=nil) click to toggle source

Given a value and a union of types, selects the type which is the best match for the given value. More than one type may match the value, in which case, the first type in the union will be returned.

# File lib/autoparse.rb, line 369
def self.match_type(value, union, parent=nil)
  possible_types = [union].flatten.compact
  # Strict pass
  for type in possible_types
    # We import as the first type in the list that validates.
    case type
    when 'string'
      return 'string' if value.kind_of?(String)
    when 'boolean'
      return 'boolean' if value == true or value == false
    when 'integer'
      return 'integer' if value.kind_of?(Integer)
    when 'number'
      return 'number' if value.kind_of?(Numeric)
    when 'array'
      return 'array' if value.kind_of?(Array)
    when 'object'
      return 'object' if value.kind_of?(Hash) || value.kind_of?(Instance)
    when 'null'
      return 'null' if value.nil?
    when Hash
      # Schema embedded directly.
      schema_class = AutoParse.generate(type, :parent => parent)
      if type['$ref']
        schema_class = schema_class.dereference
      end
      return schema_class if schema_class.new(value).valid?
    end
  end
  # Lenient pass
  for type in possible_types
    # We import as the first type in the list that validates.
    case type
    when 'string'
      return 'string' if value.respond_to?(:to_str) || value.kind_of?(Symbol)
    when 'boolean'
      if ['true', 'yes', 'y', 'on', '1',
          'false', 'no', 'n', 'off', '0'].include?(value.to_s.downcase)
        return 'boolean'
      end
    when 'integer'
      return 'integer' if value.to_i != 0 || value == "0"
    when 'number'
      return 'number' if value.to_f != 0.0 || value == "0" || value == "0.0"
    when 'array'
      return 'array' if value.respond_to?(:to_ary)
    when 'object'
      if value.respond_to?(:to_hash) || value.respond_to?(:to_json)
        return 'object'
      end
    when 'any'
      return 'any'
    end
  end
  return nil
end
schemas() click to toggle source
# File lib/autoparse.rb, line 22
def self.schemas
  @schemas ||= {}
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.