lib/render/schema.rb in render-0.0.8 vs lib/render/schema.rb in render-0.0.9
- old
+ new
@@ -6,91 +6,152 @@
require "render/extensions/dottable_hash"
module Render
class Schema
DEFAULT_TITLE = "untitled".freeze
+ CONTAINER_KEYWORDS = %w(items properties).freeze
+ ROOT_POINTER = "#".freeze
+ POINTER_SEPARATOR = %r{\/}.freeze
attr_accessor :title,
:type,
:definition,
:array_attribute,
:hash_attributes,
- :universal_title,
- :raw_data,
- :serialized_data,
- :rendered_data
+ :id
def initialize(definition_or_title)
Render.logger.debug("Loading #{definition_or_title}")
- self.definition = determine_definition(definition_or_title)
- title_or_default = definition.fetch(:title, DEFAULT_TITLE)
- self.title = title_or_default.to_sym
+ process_definition!(definition_or_title)
+ interpolate_refs!(definition)
+
+ self.title = definition.fetch(:title, DEFAULT_TITLE)
+ self.id = Definition.parse_id(definition)
self.type = Type.parse(definition[:type]) || Object
- self.universal_title = definition.fetch(:universal_title, nil)
- if definition.keys.include?(:items)
+ if array_schema?
self.array_attribute = ArrayAttribute.new(definition)
else
self.hash_attributes = definition.fetch(:properties).collect do |name, attribute_definition|
HashAttribute.new({ name => attribute_definition })
end
+ require_attributes!
end
end
def serialize!(explicit_data = nil)
if (type == Array)
- self.serialized_data = array_attribute.serialize(explicit_data)
+ array_attribute.serialize(explicit_data)
else
- self.serialized_data = hash_attributes.inject({}) do |processed_explicit_data, attribute|
- explicit_data ||= {}
+ explicit_data ||= {}
+ hash_attributes.inject({}) do |processed_explicit_data, attribute|
value = explicit_data.fetch(attribute.name, nil)
maintain_nil = explicit_data.has_key?(attribute.name)
-
serialized_attribute = attribute.serialize(value, maintain_nil)
processed_explicit_data.merge!(serialized_attribute)
end
end
end
def render!(explicit_data = nil, endpoint = nil)
- self.raw_data = Render.live ? request(endpoint) : explicit_data
- serialize!(raw_data)
- yield serialized_data if block_given?
- self.rendered_data = Extensions::DottableHash.new(hash_with_title_prefixes(serialized_data))
+ raw_data = Render.live ? request(endpoint) : explicit_data
+ data = serialize!(raw_data)
+ data.is_a?(Array) ? data : Extensions::DottableHash.new(data)
end
+ def attributes
+ array_schema? ? array_attribute : hash_attributes
+ end
+
private
- def determine_definition(definition_or_title)
- if (definition_or_title.is_a?(Hash) && !definition_or_title.empty?)
- definition_or_title
+ def require_attributes!
+ definition.fetch(:required, []).each do |required_attribute|
+ attribute = attributes.detect { |attribute| attribute.name == required_attribute.to_sym }
+ attribute.required = true
+ end
+ rescue
+ raise Errors::Schema::InvalidRequire.new(definition)
+ end
+
+ def process_definition!(title_or_definition)
+ raw_definition = determine_definition(title_or_definition)
+
+ if container?(raw_definition)
+ self.definition = raw_definition
else
- Definition.find(definition_or_title)
+ partitions = raw_definition.partition { |(key, value)| container?(value) }
+ subschemas, container = partitions.map { |partition| Hash[partition] }
+ container[:type] = Object
+ container[:properties] = subschemas
+
+ self.definition = container
end
end
- def hash_with_title_prefixes(data)
- if universal_title
- { universal_title => { title => data } }
+ def interpolate_refs!(working_definition, current_scope = [])
+ return unless working_definition.is_a?(Hash)
+
+ working_definition.each do |(instance_name, instance_value)|
+ next unless instance_value.is_a?(Hash)
+
+ if instance_value.has_key?(:$ref)
+ ref = instance_value.fetch(:$ref)
+ ref_definition = Definition.find(ref, false) || find_local_schema(ref, current_scope)
+ instance_value.replace(ref_definition)
+ end
+
+ interpolate_refs!(instance_value, current_scope.dup << instance_name)
+ end
+ end
+
+ def find_local_schema(ref, scopes)
+ paths = ref.split(POINTER_SEPARATOR)
+ if (paths.first == ROOT_POINTER)
+ paths.shift
+ find_at_path(paths) || {}
else
- { title => data }
+ find_at_closest_scope(paths, scopes) || {}
end
end
+ def find_at_closest_scope(path, scopes)
+ return if scopes.empty?
+ find_at_path(scopes + path) || find_at_closest_scope(path, scopes[0...-1])
+ end
+
+ def find_at_path(paths)
+ paths.reduce(definition) do |reduction, path|
+ reduction[path.to_sym] || return
+ end
+ end
+
+ def container?(definition)
+ return false unless definition.is_a?(Hash)
+ definition.any? { |(key, value)| CONTAINER_KEYWORDS.include?(key.to_s) }
+ end
+
+ def array_schema?
+ definition.keys.include?(:items)
+ end
+
+ def determine_definition(definition_or_title)
+ if (definition_or_title.is_a?(Hash) && !definition_or_title.empty?)
+ definition_or_title
+ else
+ Definition.find(definition_or_title)
+ end
+ end
+
def request(endpoint)
default_request(endpoint)
end
def default_request(endpoint)
response = Net::HTTP.get_response(URI(endpoint))
if response.kind_of?(Net::HTTPSuccess)
- response = JSON.parse(response.body.to_s)
- if response.is_a?(Array)
- Extensions::SymbolizableArray.new(response).recursively_symbolize_keys!
- else
- Extensions::DottableHash.new(response).recursively_symbolize_keys!
- end
+ JSON.parse(response.body.to_s, { symbolize_names: true })
else
raise Errors::Schema::RequestError.new(endpoint, response)
end
rescue JSON::ParserError => error
raise Errors::Schema::InvalidResponse.new(endpoint, response.body)