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)