class JsonSchema::Validator
Constants
- DATE_PATTERN
- DATE_TIME_PATTERN
- DEFAULT_FORMAT_VALIDATORS
- EMAIL_PATTERN
- HOSTNAME_PATTERN
- IPV4_PATTERN
- IPV6_PATTERN
- UUID_PATTERN
Attributes
errors[RW]
Public Class Methods
new(schema)
click to toggle source
# File lib/json_schema/validator.rb, line 7 def initialize(schema) @schema = schema end
Public Instance Methods
validate(data, fail_fast: false)
click to toggle source
# File lib/json_schema/validator.rb, line 11 def validate(data, fail_fast: false) @errors = [] @visits = {} @fail_fast = fail_fast # This dynamically creates the "strict_or_fast_and" method which is used # throughout the validator to combine the previous validation result with # another validation check. # Logic wise, we could simply define this method without meta programming # and decide every time to either call fast_and or strict_end. # Unfortunately this has a small overhead, that adds up over the runtime # of the validator – about 5% if we check @fail_fast everytime. # For more details, please see https://github.com/brandur/json_schema/pull/96 and_operation = method(@fail_fast ? :fast_and : :strict_and) define_singleton_method(:strict_or_fast_and, and_operation) catch(:fail_fast) do validate_data(@schema, data, @errors, ['#']) end @errors.size == 0 end
validate!(data, fail_fast: false)
click to toggle source
# File lib/json_schema/validator.rb, line 33 def validate!(data, fail_fast: false) if !validate(data, fail_fast: fail_fast) raise AggregateError.new(@errors) end end
Private Instance Methods
fast_and(valid_old, valid_new)
click to toggle source
# File lib/json_schema/validator.rb, line 77 def fast_and(valid_old, valid_new) throw :fail_fast, false if !valid_new valid_old && valid_new end
find_parent(schema)
click to toggle source
# File lib/json_schema/validator.rb, line 550 def find_parent(schema) fragment = schema.fragment key = if fragment =~ /patternProperties/ split_pointer = schema.pointer.split("/") idx = split_pointer.index("patternProperties") # this join mimics the fragment format below in that it's # parent + key if idx - 2 >= 0 parts = split_pointer[(idx - 2)..(idx - 1)] end # protect against a `nil` that could occur if # `patternProperties` has no parent parts ? parts.compact.join("/") : nil end key || fragment end
first_visit(schema, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 41 def first_visit(schema, errors, path) true # removed until more comprehensive testing can be performed .. this is # currently causing validation loop detections to go off on all non-trivial # schemas =begin key = "#{schema.object_id}-#{schema.pointer}-#{path.join("/")}" if !@visits.key?(key) @visits[key] = true true else message = %{Validation loop detected.} errors << ValidationError.new(schema, path, message, :loop_detected) false end =end end
get_extra_keys(schema, data)
click to toggle source
for use with additionalProperties and strictProperties
# File lib/json_schema/validator.rb, line 60 def get_extra_keys(schema, data) extra = data.keys - schema.properties.keys if schema.pattern_properties schema.pattern_properties.keys.each do |pattern| extra -= extra.select { |k| k =~ pattern } end end extra end
strict_and(valid_old, valid_new)
click to toggle source
works around &&'s “lazy” behavior
# File lib/json_schema/validator.rb, line 73 def strict_and(valid_old, valid_new) valid_old && valid_new end
validate_additional_properties(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 135 def validate_additional_properties(schema, data, errors, path) return true if schema.additional_properties == true # schema indicates that all properties not in `properties` should be # validated according to subschema if schema.additional_properties.is_a?(Schema) extra = get_extra_keys(schema, data) validations = extra.map do |key| validate_data(schema.additional_properties, data[key], errors, path + [key]) end # true only if all keys validate validations.all? # boolean indicates whether additional properties are allowed else validate_extra(schema, data, errors, path) end end
validate_all_of(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 155 def validate_all_of(schema, data, errors, path) return true if schema.all_of.empty? # We've kept this feature behind a configuration flag for now because # there is some performance implication to producing each sub error. # Normally we can short circuit the validation after encountering only # one problem, but here we have to evaluate all subschemas every time. if JsonSchema.configuration.all_of_sub_errors && !@fail_fast sub_errors = [] valid = schema.all_of.map do |subschema| current_sub_errors = [] sub_errors << current_sub_errors validate_data(subschema, data, current_sub_errors, path) end.all? else sub_errors = nil valid = schema.all_of.all? do |subschema| validate_data(subschema, data, errors, path) end end message = %{Not all subschemas of "allOf" matched.} errors << ValidationError.new(schema, path, message, :all_of_failed, sub_errors: sub_errors, data: data) if !valid valid end
validate_any_of(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 182 def validate_any_of(schema, data, errors, path) return true if schema.any_of.empty? sub_errors = schema.any_of.map do |subschema| current_sub_errors = [] valid = catch(:fail_fast) do validate_data(subschema, data, current_sub_errors, path) end return true if valid current_sub_errors end message = %{No subschema in "anyOf" matched.} errors << ValidationError.new(schema, path, message, :any_of_failed, sub_errors: sub_errors, data: data) false end
validate_data(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 82 def validate_data(schema, data, errors, path) valid = true # detect a validation loop if !first_visit(schema, errors, path) return false end # validation: any valid = strict_or_fast_and valid, validate_all_of(schema, data, errors, path) valid = strict_or_fast_and valid, validate_any_of(schema, data, errors, path) valid = strict_or_fast_and valid, validate_enum(schema, data, errors, path) valid = strict_or_fast_and valid, validate_one_of(schema, data, errors, path) valid = strict_or_fast_and valid, validate_not(schema, data, errors, path) valid = strict_or_fast_and valid, validate_type(schema, data, errors, path) # validation: array if data.is_a?(Array) valid = strict_or_fast_and valid, validate_items(schema, data, errors, path) valid = strict_or_fast_and valid, validate_max_items(schema, data, errors, path) valid = strict_or_fast_and valid, validate_min_items(schema, data, errors, path) valid = strict_or_fast_and valid, validate_unique_items(schema, data, errors, path) end # validation: integer/number if data.is_a?(Float) || data.is_a?(Integer) valid = strict_or_fast_and valid, validate_max(schema, data, errors, path) valid = strict_or_fast_and valid, validate_min(schema, data, errors, path) valid = strict_or_fast_and valid, validate_multiple_of(schema, data, errors, path) end # validation: object if data.is_a?(Hash) valid = strict_or_fast_and valid, validate_additional_properties(schema, data, errors, path) valid = strict_or_fast_and valid, validate_dependencies(schema, data, errors, path) valid = strict_or_fast_and valid, validate_max_properties(schema, data, errors, path) valid = strict_or_fast_and valid, validate_min_properties(schema, data, errors, path) valid = strict_or_fast_and valid, validate_pattern_properties(schema, data, errors, path) valid = strict_or_fast_and valid, validate_properties(schema, data, errors, path) valid = strict_or_fast_and valid, validate_required(schema, data, errors, path, schema.required) valid = strict_or_fast_and valid, validate_strict_properties(schema, data, errors, path) end # validation: string if data.is_a?(String) valid = strict_or_fast_and valid, validate_format(schema, data, errors, path) valid = strict_or_fast_and valid, validate_max_length(schema, data, errors, path) valid = strict_or_fast_and valid, validate_min_length(schema, data, errors, path) valid = strict_or_fast_and valid, validate_pattern(schema, data, errors, path) end valid end
validate_dependencies(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 201 def validate_dependencies(schema, data, errors, path) return true if schema.dependencies.empty? result = schema.dependencies.map do |key, obj| # if the key is not present, the dependency is fulfilled by definition next true unless data[key] if obj.is_a?(Schema) validate_data(obj, data, errors, path) else # if not a schema, value is an array of required fields validate_required(schema, data, errors, path, obj) end end result.all? end
validate_enum(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 231 def validate_enum(schema, data, errors, path) return true unless schema.enum if schema.enum.include?(data) true else message = %{#{data} is not a member of #{schema.enum}.} errors << ValidationError.new(schema, path, message, :invalid_type, data: data) false end end
validate_extra(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 242 def validate_extra(schema, data, errors, path) extra = get_extra_keys(schema, data) if extra.empty? true else message = %{"#{extra.sort.join('", "')}" } + (extra.length == 1 ? "is not a" : "are not") + %{ permitted key} + (extra.length == 1 ? "." : "s.") errors << ValidationError.new(schema, path, message, :invalid_keys) false end end
validate_format(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 216 def validate_format(schema, data, errors, path) return true unless schema.format validator = ( JsonSchema.configuration.custom_formats[schema.format] || DEFAULT_FORMAT_VALIDATORS[schema.format] ) if validator[data] true else message = %{#{data} is not a valid #{schema.format}.} errors << ValidationError.new(schema, path, message, :invalid_format, data: data) false end end
validate_items(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 257 def validate_items(schema, data, errors, path) return true unless schema.items if schema.items.is_a?(Array) if data.size < schema.items.count message = %{#{schema.items.count} item} + (schema.items.count == 1 ? "" : "s") + %{ required; only #{data.size} } + (data.size == 1 ? "was" : "were") + %{ supplied.} errors << ValidationError.new(schema, path, message, :min_items_failed, data: data) false elsif data.size > schema.items.count && !schema.additional_items? message = %{No more than #{schema.items.count} item} + (schema.items.count == 1 ? " is" : "s are") + %{ allowed; #{data.size} } + (data.size > 1 ? "were" : "was") + %{ supplied.} errors << ValidationError.new(schema, path, message, :max_items_failed, data: data) false else valid = true if data.size > schema.items.count && schema.additional_items.is_a?(Schema) (schema.items.count..data.count - 1).each do |i| valid = strict_or_fast_and valid, validate_data(schema.additional_items, data[i], errors, path + [i]) end end schema.items.each_with_index do |subschema, i| valid = strict_or_fast_and valid, validate_data(subschema, data[i], errors, path + [i]) end valid end else valid = true data.each_with_index do |value, i| valid = strict_or_fast_and valid, validate_data(schema.items, value, errors, path + [i]) end valid end end
validate_max(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 300 def validate_max(schema, data, errors, path) return true unless schema.max if schema.max_exclusive? && data < schema.max true elsif !schema.max_exclusive? && data <= schema.max true else message = %{#{data} must be less than} + (schema.max_exclusive? ? "" : " or equal to") + %{ #{schema.max}.} errors << ValidationError.new(schema, path, message, :max_failed, data: data) false end end
validate_max_items(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 315 def validate_max_items(schema, data, errors, path) return true unless schema.max_items if data.size <= schema.max_items true else message = %{No more than #{schema.max_items} item} + (schema.max_items == 1 ? " is" : "s are") + %{ allowed; #{data.size} } + (data.size == 1 ? "was" : "were")+ %{ supplied.} errors << ValidationError.new(schema, path, message, :max_items_failed, data: data) false end end
validate_max_length(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 330 def validate_max_length(schema, data, errors, path) return true unless schema.max_length if data.length <= schema.max_length true else message = %{Only #{schema.max_length} character} + (schema.max_length == 1 ? " is" : "s are") + %{ allowed; #{data.length} } + (data.length == 1 ? "was" : "were") + %{ supplied.} errors << ValidationError.new(schema, path, message, :max_length_failed, data: data) false end end
validate_max_properties(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 345 def validate_max_properties(schema, data, errors, path) return true unless schema.max_properties if data.keys.size <= schema.max_properties true else message = %{No more than #{schema.max_properties} propert} + (schema.max_properties == 1 ? "y is" : "ies are") + %{ allowed; #{data.keys.size} } + (data.keys.size == 1 ? "was" : "were") + %{ supplied.} errors << ValidationError.new(schema, path, message, :max_properties_failed, data: data) false end end
validate_min(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 360 def validate_min(schema, data, errors, path) return true unless schema.min if schema.min_exclusive? && data > schema.min true elsif !schema.min_exclusive? && data >= schema.min true else message = %{#{data} must be greater than} + (schema.min_exclusive? ? "" : " or equal to") + %{ #{schema.min}.} errors << ValidationError.new(schema, path, message, :min_failed, data: data) false end end
validate_min_items(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 375 def validate_min_items(schema, data, errors, path) return true unless schema.min_items if data.size >= schema.min_items true else message = %{#{schema.min_items} item} + (schema.min_items == 1 ? "" : "s") + %{ required; only #{data.size} } + (data.size == 1 ? "was" : "were") + %{ supplied.} errors << ValidationError.new(schema, path, message, :min_items_failed, data: data) false end end
validate_min_length(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 390 def validate_min_length(schema, data, errors, path) return true unless schema.min_length if data.length >= schema.min_length true else message = %{At least #{schema.min_length} character} + (schema.min_length == 1 ? " is" : "s are") + %{ required; only #{data.length} } + (data.length == 1 ? "was" : "were") + %{ supplied.} errors << ValidationError.new(schema, path, message, :min_length_failed, data: data) false end end
validate_min_properties(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 405 def validate_min_properties(schema, data, errors, path) return true unless schema.min_properties if data.keys.size >= schema.min_properties true else message = %{At least #{schema.min_properties} propert}+ (schema.min_properties == 1 ? "y is" : "ies are") + %{ required; #{data.keys.size} }+ (data.keys.size == 1 ? "was" : "were") + %{ supplied.} errors << ValidationError.new(schema, path, message, :min_properties_failed, data: data) false end end
validate_multiple_of(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 420 def validate_multiple_of(schema, data, errors, path) return true unless schema.multiple_of if data % schema.multiple_of == 0 true else message = %{#{data} is not a multiple of #{schema.multiple_of}.} errors << ValidationError.new(schema, path, message, :multiple_of_failed, data: data) false end end
validate_not(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 458 def validate_not(schema, data, errors, path) return true unless schema.not # don't bother accumulating these errors, they'll all be worded # incorrectly for the inverse condition valid = !validate_data(schema.not, data, [], path) if !valid message = %{Matched "not" subschema.} errors << ValidationError.new(schema, path, message, :not_failed, data: data) end valid end
validate_one_of(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 431 def validate_one_of(schema, data, errors, path) return true if schema.one_of.empty? sub_errors = [] num_valid = schema.one_of.count do |subschema| current_sub_errors = [] valid = catch(:fail_fast) do validate_data(subschema, data, current_sub_errors, path) end sub_errors << current_sub_errors valid end return true if num_valid == 1 message = if num_valid == 0 %{No subschema in "oneOf" matched.} else %{More than one subschema in "oneOf" matched.} end errors << ValidationError.new(schema, path, message, :one_of_failed, sub_errors: sub_errors, data: data) false end
validate_pattern(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 470 def validate_pattern(schema, data, errors, path) return true unless schema.pattern if data =~ schema.pattern true else message = %{#{data} does not match #{schema.pattern.inspect}.} errors << ValidationError.new(schema, path, message, :pattern_failed, data: data) false end end
validate_pattern_properties(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 482 def validate_pattern_properties(schema, data, errors, path) return true if schema.pattern_properties.empty? valid = true schema.pattern_properties.each do |pattern, subschema| data.each do |key, value| if key =~ pattern valid = strict_or_fast_and valid, validate_data(subschema, value, errors, path + [key]) end end end valid end
validate_properties(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 496 def validate_properties(schema, data, errors, path) return true if schema.properties.empty? valid = true schema.properties.each do |key, subschema| next unless data.key?(key) valid = strict_or_fast_and valid, validate_data(subschema, data[key], errors, path + [key]) end valid end
validate_required(schema, data, errors, path, required)
click to toggle source
# File lib/json_schema/validator.rb, line 507 def validate_required(schema, data, errors, path, required) return true if !required || required.empty? if (missing = required - data.keys).empty? true else message = %{"#{missing.sort.join('", "')}" } + (missing.length == 1 ? "wasn't" : "weren't") + %{ supplied.} errors << ValidationError.new(schema, path, message, :required_failed, data: missing) false end end
validate_strict_properties(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 520 def validate_strict_properties(schema, data, errors, path) return true if !schema.strict_properties strict_or_fast_and validate_extra(schema, data, errors, path), validate_required(schema, data, errors, path, schema.properties.keys) end
validate_type(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 527 def validate_type(schema, data, errors, path) return true if !schema.type || schema.type.empty? if schema.type_parsed.any? { |t| data.is_a?(t) } true else key = find_parent(schema) message = %{For '#{key}', #{data.inspect} is not #{ErrorFormatter.to_list(schema.type)}.} errors << ValidationError.new(schema, path, message, :invalid_type, data: data) false end end
validate_unique_items(schema, data, errors, path)
click to toggle source
# File lib/json_schema/validator.rb, line 539 def validate_unique_items(schema, data, errors, path) return true unless schema.unique_items? if data.size == data.uniq.size true else message = %{Duplicate items are not allowed.} errors << ValidationError.new(schema, path, message, :unique_items_failed, data: data) false end end