class Blumquist
JSON Pointer This is a VERY BASIC implementation to support pointers in the form of:
1. #/key1/key2/.../keyN 2. path_to_file.json 3. path_to_file.json#/key1/key2/.../keyN More about JSON Pointer & Reference Specification https://cswr.github.io/JsonSchema/spec/definitions_references/#json-pointers
Constants
- PRIMITIVE_TYPES
- VERSION
Attributes
_type[R]
Public Class Methods
new(options)
click to toggle source
# File lib/blumquist.rb, line 13 def initialize(options) # Poor man's deep clone: json 🆗 🆒 @data = JSON.parse(options.fetch(:data).to_json) @schema = options.fetch(:schema).with_indifferent_access @original_properties = options.fetch(:schema).with_indifferent_access[:properties] @validate = options.fetch(:validate, true) validate_schema validate_data resolve_json_pointers define_getters end
Public Instance Methods
==(other)
click to toggle source
# File lib/blumquist.rb, line 39 def ==(other) self.class == other.class && other.marshal_dump == marshal_dump end
Also aliased as: eql?
marshal_dump()
click to toggle source
# File lib/blumquist.rb, line 31 def marshal_dump [@schema, @data, @validate] end
marshal_load(array)
click to toggle source
# File lib/blumquist.rb, line 35 def marshal_load(array) @schema, @data, @validate = array end
to_s()
click to toggle source
# File lib/blumquist.rb, line 27 def to_s inspect end
Protected Instance Methods
_type=(_type)
click to toggle source
# File lib/blumquist.rb, line 303 def _type=(_type) @_type = _type end
Private Instance Methods
all_primitive_types(types)
click to toggle source
# File lib/blumquist.rb, line 296 def all_primitive_types(types) return false unless types.is_a?(Array) types.all? { |t| primitive_type?(t) } end
blumquistify_array(property)
click to toggle source
# File lib/blumquist.rb, line 235 def blumquistify_array(property) # We only support arrays with one type defined, either through # # "type": "array", # "items": { "$ref": "#/definitions/mytype" } # # or through # # "type": "array", # "items": [{ "$ref": "#/definitions/mytype" }] # # or through # # "type": "array", # "items": [{ "type": "number" }] # type_def = [@schema[:properties][property][:items]].flatten.first # The items of this array are defined by a pointer if type_def[:$ref] reference_type = type_from_type_def(type_def) item_schema = resolve_json_pointer!(type_def) sub_schema = item_schema.merge( definitions: @schema[:definitions] ) @data[property] ||= [] @data[property] = @data[property].map do |item| sub_blumquist = Blumquist.new(schema: sub_schema, data: item, validate: false) sub_blumquist._type = reference_type sub_blumquist end # The items are objects, defined directly or through oneOf elsif type_def[:type] == 'object' || type_def[:oneOf] sub_schema = type_def.merge( definitions: @schema[:definitions] ) @data[property] ||= [] @data[property] = @data[property].map do |item| blumquistify_object(schema: sub_schema, data: item) end # The items are all of the same primitive type elsif primitive_type?(type_def[:type]) # The items might all be primitives, that would be OK elsif all_primitive_types(type_def[:type]) # We don't know what to do, so let's panic else raise(Errors::UnsupportedType, type_def[:type]) end end
blumquistify_object(options)
click to toggle source
# File lib/blumquist.rb, line 138 def blumquistify_object(options) sub_schema = options[:schema] data = options[:data] # If properties are defined directly, like this: # # { "type": "object", "properties": { ... } } # if sub_schema[:properties] if sub_schema[:type].is_a?(String) sub_blumquist = Blumquist.new(schema: sub_schema, data: data, validate: false) return sub_blumquist end # If the type is an array, we can't make much of it # because we wouldn't know which type to model as a # blumquist object. Unless, of course, it's one object # and one or more primitives. if sub_schema[:type].is_a?(Array) # It's an array but only contains one allowed type, # this is easy. if sub_schema[:type].length == 1 sub_schema[:type] = sub_schema[:type].first sub_blumquist = Blumquist.new(schema: sub_schema, data: data, validate: false) return sub_blumquist end # We can implement the other cases at a leter point. end # We shouldn't arrive here raise(Errors::UnsupportedType, sub_schema) end # Properties not defined directly, object must be 'oneOf', # like this: # # { "type": "object", "oneOf": [{...}] } # # The json schema v4 draft specifies, that: # # "the oneOf keyword is new in draft v4; its value is an array of # schemas, and an instance is valid if and only if it is valid # against exactly one of these schemas" # # *See: http://json-schema.org/example2.html # # That means we can just go through the oneOfs and return # the first that matches: if sub_schema[:oneOf] primitive_allowed = false sub_schema[:oneOf].each do |one| begin if primitive_type?(one[:type]) primitive_allowed = true else if one[:type] schema = one.merge(definitions: @schema[:definitions]) else schema = resolve_json_pointer(one).merge( definitions: @schema[:definitions] ) end sub_blumquist = Blumquist.new(data: data, schema: schema, validate: true) sub_blumquist._type = type_from_type_def(one) return sub_blumquist end rescue # On to the next oneOf end end # We found no matching object definition. # If a primitve is part of the `oneOfs, # that's no problem though. # # TODOs this is only ok if data is actually of that primitive type # # Also check https://gist.github.com/jayniz/e8849ea528af6d205698 and # https://github.com/ruby-json-schema/json-schema/issues/319 return data if primitive_allowed # We didn't find a schema in oneOf that matches our data raise(Errors::NoCompatibleOneOf, one_ofs: sub_schema[:oneOf], data: data) end # If there's neither `properties` nor `oneOf`, we don't # know what to do and shall panic: raise(Errors::MissingProperties, sub_schema) end
blumquistify_property(property)
click to toggle source
# File lib/blumquist.rb, line 127 def blumquistify_property(property) sub_schema = @schema[:properties][property].merge( definitions: @schema[:definitions] ) data = @data[property] sub_blumquist = blumquistify_object(schema: sub_schema, data: data) # In case of oneOf the definition was already set sub_blumquist._type = type_name_for(property) if sub_blumquist && sub_blumquist._type.nil? sub_blumquist end
define_getter(property)
click to toggle source
# File lib/blumquist.rb, line 114 def define_getter(property) # # Inheritance: # Define methods under the Blumquist namespace # to allow subclasses to overwrite methods. # Blumquist.class_eval do define_method(property) do @data[property] end end end
define_getters()
click to toggle source
# File lib/blumquist.rb, line 83 def define_getters @schema[:properties].each do |property, type_def| # The type_def can contain one or more types. # We only support multiple primitive types, or one # normal type and the null type. types = [type_def[:type]].flatten - ["null"] type = types.first # Wrap objects recursively if type == 'object' || type_def[:oneOf] @data[property] = blumquistify_property(property) # Turn array elements into Blumquists elsif type == 'array' blumquistify_array(property) # Nothing to do for primitive values elsif primitive_type?(type) elsif all_primitive_types(types) # We don't know what to do, so let's panic else raise(Errors::UnsupportedType, type) end # And define the getter define_getter(property) end end
primitive_type?(type)
click to toggle source
# File lib/blumquist.rb, line 79 def primitive_type?(type) PRIMITIVE_TYPES.include?(type.to_s) end
resolve_json_pointer(type_def)
click to toggle source
# File lib/blumquist.rb, line 66 def resolve_json_pointer(type_def) pointer = JSONPointer.new(type_def[:$ref], document: @schema) type_def.merge(pointer.value) end
resolve_json_pointer!(type_def)
click to toggle source
# File lib/blumquist.rb, line 72 def resolve_json_pointer!(type_def) pointer_path = type_def.delete(:$ref) pointer = JSONPointer.new(pointer_path, document: @schema) type_def.merge!(pointer.value) end
resolve_json_pointers()
click to toggle source
# File lib/blumquist.rb, line 59 def resolve_json_pointers @schema[:properties].each do |property, type_def| next unless type_def[:$ref] resolve_json_pointer!(type_def) end end
type_from_type_def(type_def)
click to toggle source
# File lib/blumquist.rb, line 230 def type_from_type_def(type_def) return 'object' unless type_def.is_a?(Hash) && type_def.has_key?(:$ref) type_def[:$ref].split("/").last end
type_name_for(property)
click to toggle source
# File lib/blumquist.rb, line 292 def type_name_for(property) type_from_type_def(@original_properties[property]) end
validate_data()
click to toggle source
# File lib/blumquist.rb, line 47 def validate_data return unless @validate errors = JSON::Validator.fully_validate(@schema, @data) return true if errors.length == 0 raise(Errors::ValidationError, [errors.map { |e| e.split("\n") }, @data]) end
validate_schema()
click to toggle source
# File lib/blumquist.rb, line 54 def validate_schema return if @schema[:type] == 'object' raise(Errors::UnsupportedSchema, type: @schema[:type]) end