module JSONSchemaUtils
Constants
- SCHEMA_PARSE_RULES
Public Class Methods
apply_schema_defaults(hash, schema)
click to toggle source
# File lib/aspace_client/json_schema_utils.rb, line 356 def self.apply_schema_defaults(hash, schema) fn = proc do |hash, schema| result = hash.clone schema["properties"].each do |property, definition| if definition.has_key?("default") && !hash.has_key?(property.to_s) && !hash.has_key?(property.intern) result[property] = definition["default"] elsif definition['type'] == 'array' && !hash.has_key?(property.to_s) && !hash.has_key?(property.intern) # Array values that weren't provided default to empty result[property] = [] end end result end map_hash_with_schema(hash, schema, [fn]) end
drop_empty_elements(obj)
click to toggle source
# File lib/aspace_client/json_schema_utils.rb, line 318 def self.drop_empty_elements(obj) if obj.is_a?(Hash) Hash[obj.map do |k, v| v = drop_empty_elements(v) [k, v] if !is_blank?(v) end] elsif obj.is_a?(Array) obj.map {|elt| drop_empty_elements(elt)}.reject {|elt| is_blank?(elt)} else obj end end
drop_unknown_properties(hash, schema, drop_readonly = false)
click to toggle source
Drop any keys from ‘hash’ that aren’t defined in the JSON
schema.
If drop_readonly is true, also drop any values where the schema has ‘readonly’ set to true. These values are produced by the system for the client, but are not part of the data model.
# File lib/aspace_client/json_schema_utils.rb, line 338 def self.drop_unknown_properties(hash, schema, drop_readonly = false) fn = proc do |hash, schema| result = {} hash.each do |k, v| if schema["properties"].has_key?(k.to_s) && (!drop_readonly || !schema["properties"][k.to_s]["readonly"]) result[k] = v end end result end hash = drop_empty_elements(hash) map_hash_with_schema(hash, schema, [fn]) end
extract_suberrors(errors)
click to toggle source
For a given error, find its list of sub errors.
# File lib/aspace_client/json_schema_utils.rb, line 175 def self.extract_suberrors(errors) errors = Array[errors].flatten result = errors.map do |error| if !error[:errors] error else self.extract_suberrors(error[:errors]) end end result.flatten end
fragment_join(fragment, property = nil)
click to toggle source
# File lib/aspace_client/json_schema_utils.rb, line 3 def self.fragment_join(fragment, property = nil) fragment = fragment.gsub(/^#\//, "") property = property.gsub(/^#\//, "") if property if property && fragment != "" && fragment !~ /\/$/ fragment = "#{fragment}/" end "#{fragment}#{property}" end
is_blank?(obj)
click to toggle source
# File lib/aspace_client/json_schema_utils.rb, line 313 def self.is_blank?(obj) obj.nil? || obj == "" || obj == {} end
map_hash_with_schema(record, schema, transformations = [])
click to toggle source
Given a hash representing a record tree, map across the hash and this model’s schema in lockstep.
Each proc in the ‘transformations’ array is called with the current node in the record tree as its first argument, and the part of the schema that corresponds to it. Whatever the proc returns is used to replace the node in the record tree.
# File lib/aspace_client/json_schema_utils.rb, line 237 def self.map_hash_with_schema(record, schema, transformations = []) return record if not record.is_a?(Hash) if schema.is_a?(String) schema = resolve_schema_reference(schema) end # Sometimes a schema won't specify anything other than the required type # (like {'type' => 'object'}). If there's nothing more to check, we're # done. return record if !schema.has_key?("properties") # Apply transformations to the current level of the tree transformations.each do |transform| record = transform.call(record, schema) end # Now figure out how to traverse the remainder of the tree... result = {} record.each do |k, v| k = k.to_s properties = schema['properties'] if properties.has_key?(k) && (properties[k]["type"] == "object") result[k] = self.map_hash_with_schema(v, properties[k], transformations) elsif v.is_a?(Array) && properties.has_key?(k) && (properties[k]["type"] == "array") # Arrays are tricky because they can either consist of a single type, or # a number of different types. if properties[k]["items"]["type"].is_a?(Array) result[k] = v.map {|elt| if elt.is_a?(Hash) next_schema = determine_schema_for(elt, properties[k]["items"]["type"]) self.map_hash_with_schema(elt, next_schema, transformations) elsif elt.is_a?(Array) raise "Nested arrays aren't supported here (yet)" else elt end } # The array contains a single type of object elsif properties[k]["items"]["type"] === "object" result[k] = v.map {|elt| self.map_hash_with_schema(elt, properties[k]["items"], transformations)} else # Just one valid type result[k] = v.map {|elt| self.map_hash_with_schema(elt, properties[k]["items"]["type"], transformations)} end elsif (v.is_a?(Hash) || v.is_a?(Array)) && (properties.has_key?(k) && properties[k]["type"].is_a?(Array)) # Multiple possible types for this single value results = (v.is_a?(Array) ? v : [v]).map {|elt| next_schema = determine_schema_for(elt, properties[k]["type"]) self.map_hash_with_schema(elt, next_schema, transformations) } result[k] = v.is_a?(Array) ? results : results[0] elsif properties.has_key?(k) && JSONModel.parse_jsonmodel_ref(properties[k]["type"]) result[k] = self.map_hash_with_schema(v, properties[k]["type"], transformations) else result[k] = v end end result end
parse_schema_messages(messages, validator)
click to toggle source
Given a list of error messages produced by JSON
schema validation, parse them into a structured format like:
{
:errors => {:attr1 => "(What was wrong with attr1)"}, :warnings => {:attr2 => "(attr2 not quite right either)"}
}
# File lib/aspace_client/json_schema_utils.rb, line 198 def self.parse_schema_messages(messages, validator) messages = self.extract_suberrors(messages) msgs = { :errors => {}, :warnings => {}, # to lookup e.g., msgs[:attribute_types]['extents/0/extent_type'] => 'ArchivesSpaceDynamicEnum' :attribute_types => {}, :state => {} # give the parse rules somewhere to store useful state for a run } messages.each do |message| SCHEMA_PARSE_RULES.each do |rule| if (rule[:failed_attribute].nil? || rule[:failed_attribute].include?(message[:failed_attribute])) and message[:message] =~ rule[:pattern] rule[:do].call(msgs, message, message[:fragment], *message[:message].scan(rule[:pattern]).flatten) break end end end msgs.delete(:state) msgs end
schema_path_lookup(schema, path)
click to toggle source
# File lib/aspace_client/json_schema_utils.rb, line 15 def self.schema_path_lookup(schema, path) if path.is_a? String return self.schema_path_lookup(schema, path.split("/")) end if schema.has_key?('properties') schema = schema['properties'] end if path.length == 1 schema[path.first] else if schema[path.first] self.schema_path_lookup(schema[path.first], path.drop(1)) else nil end end end
Private Class Methods
determine_schema_for(elt, possible_schemas)
click to toggle source
# File lib/aspace_client/json_schema_utils.rb, line 390 def self.determine_schema_for(elt, possible_schemas) # A number of different types. Match them up based on the value of the 'jsonmodel_type' property schema_types = possible_schemas.map {|schema| schema.is_a?(Hash) ? schema["type"] : schema} jsonmodel_type = elt["jsonmodel_type"] if !jsonmodel_type raise JSONModel::ValidationException.new(:errors => {"record" => ["Can't unambiguously match #{elt.inspect} against schema types: #{schema_types.inspect}. " + "Resolve this by adding a 'jsonmodel_type' property to #{elt.inspect}"]}) end next_schema = schema_types.find {|type| (type.is_a?(String) && type.include?("JSONModel(:#{jsonmodel_type})")) || (type.is_a?(Hash) && type["jsonmodel_type"] === jsonmodel_type) } if next_schema.nil? raise "Couldn't determine type of '#{elt.inspect}'. Must be one of: #{schema_types.inspect}" end next_schema end
resolve_schema_reference(schema_reference)
click to toggle source
# File lib/aspace_client/json_schema_utils.rb, line 380 def self.resolve_schema_reference(schema_reference) # This should be a reference to a different JSONModel type. Resolve it # and return its schema. ref = JSONModel.parse_jsonmodel_ref(schema_reference) raise "Invalid schema given: #{schema_reference}" if !ref JSONModel.JSONModel(ref[0]).schema end