class Frise::Validator
Checks if a pre-loaded config object conforms to a schema file.
The validate and validate_at
static methods read schema files and validates config objects against the parsed schema. They can optionally be initialized with a set of user-defined validators that can be used in the schema files for custom validations.
Attributes
errors[R]
Public Class Methods
new(root, validators = nil)
click to toggle source
# File lib/frise/validator.rb, line 16 def initialize(root, validators = nil) @root = root @validators = validators @errors = [] end
parse_symbols(obj)
click to toggle source
# File lib/frise/validator.rb, line 156 def self.parse_symbols(obj) case obj when Array then obj.map { |e| parse_symbols(e) } when Hash then Hash[obj.map { |k, v| [parse_symbols(k), parse_symbols(v)] }] when String then obj.start_with?('$') ? obj[1..-1].to_sym : obj else obj end end
validate(config, schema_file, options = {})
click to toggle source
# File lib/frise/validator.rb, line 190 def self.validate(config, schema_file, options = {}) validate_obj_at(config, [], Parser.parse(schema_file) || { allow_unknown_keys: true }, **options) end
validate_at(config, at_path, schema_file, options = {})
click to toggle source
# File lib/frise/validator.rb, line 194 def self.validate_at(config, at_path, schema_file, options = {}) validate_obj_at(config, at_path, Parser.parse(schema_file) || { allow_unknown_keys: true }, **options) end
validate_obj(config, schema, options = {})
click to toggle source
# File lib/frise/validator.rb, line 165 def self.validate_obj(config, schema, options = {}) validate_obj_at(config, [], schema, **options) end
validate_obj_at(config, at_path, schema, path_prefix: nil, validators: nil, print: nil, fatal: nil, raise_error: nil)
click to toggle source
# File lib/frise/validator.rb, line 169 def self.validate_obj_at(config, at_path, schema, path_prefix: nil, validators: nil, print: nil, fatal: nil, raise_error: nil) schema = parse_symbols(schema) at_path.reverse.each { |key| schema = { key => schema, :allow_unknown_keys => true } } validator = Validator.new(config, validators) validator.validate_object((path_prefix || []).join('.'), config, schema) if validator.errors.any? if print puts "#{validator.errors.length} config error(s) found:" validator.errors.each do |error| puts " - #{error}" end end exit 1 if fatal raise ValidationError.new(validator.errors), 'Invalid configuration' if raise_error end validator.errors end
Public Instance Methods
add_validation_error(path, msg)
click to toggle source
# File lib/frise/validator.rb, line 29 def add_validation_error(path, msg) logged_path = path.empty? ? '<root>' : path @errors << "At #{logged_path}: #{msg}" end
get_expected_types(full_schema)
click to toggle source
# File lib/frise/validator.rb, line 64 def get_expected_types(full_schema) type_key = full_schema.fetch(:type, 'Hash') allowed_types = %w[Hash Array String Integer Float Object] return [Object.const_get(type_key)] if allowed_types.include?(type_key) return [TrueClass, FalseClass] if type_key == 'Boolean' raise "Invalid expected type in schema: #{type_key}" end
get_full_schema(schema)
click to toggle source
# File lib/frise/validator.rb, line 34 def get_full_schema(schema) case schema when Hash then default_type = schema[:enum] || schema[:one_of] ? 'Object' : 'Hash' { type: default_type }.merge(schema) when Symbol then { type: 'Object', validate: schema } when Array if schema.size == 1 { type: 'Array', all: schema[0] } else (raise "Invalid schema: #{schema.inspect}") end when String if schema.end_with?('?') { type: schema[0..-2], optional: true } else { type: schema } end else raise "Invalid schema: #{schema.inspect}" end end
validate_custom(full_schema, obj, path)
click to toggle source
# File lib/frise/validator.rb, line 82 def validate_custom(full_schema, obj, path) if full_schema[:validate] begin @validators.method(full_schema[:validate]).call(@root, obj) rescue StandardError => e add_validation_error(path, e.message) end end true end
validate_enum(full_schema, obj, path)
click to toggle source
# File lib/frise/validator.rb, line 93 def validate_enum(full_schema, obj, path) if full_schema[:enum] && !full_schema[:enum].include?(obj) add_validation_error(path, "invalid value #{obj.inspect}. " \ "Accepted values are #{full_schema[:enum].map(&:inspect).join(', ')}") return false end true end
validate_object(path, obj, schema)
click to toggle source
# File lib/frise/validator.rb, line 142 def validate_object(path, obj, schema) full_schema = get_full_schema(schema) return unless validate_optional(full_schema, obj, path) return unless validate_type(full_schema, obj, path) return unless validate_custom(full_schema, obj, path) return unless validate_enum(full_schema, obj, path) return unless validate_one_of(full_schema, obj, path) processed_keys = Set.new return unless validate_spec_keys(full_schema, obj, path, processed_keys) validate_remaining_keys(full_schema, obj, path, processed_keys) end
validate_one_of(full_schema, obj, path)
click to toggle source
# File lib/frise/validator.rb, line 102 def validate_one_of(full_schema, obj, path) if full_schema[:one_of] full_schema[:one_of].each do |schema_opt| opt_validator = Validator.new(@root, @validators) opt_validator.validate_object(path, obj, schema_opt) return true if opt_validator.errors.empty? end add_validation_error(path, "#{obj.inspect} does not match any of the possible schemas") return false end true end
validate_optional(full_schema, obj, path)
click to toggle source
# File lib/frise/validator.rb, line 56 def validate_optional(full_schema, obj, path) if obj.nil? add_validation_error(path, 'missing required value') unless full_schema[:optional] return false end true end
validate_remaining_keys(full_schema, obj, path, processed_keys)
click to toggle source
# File lib/frise/validator.rb, line 124 def validate_remaining_keys(full_schema, obj, path, processed_keys) expected_types = get_expected_types(full_schema) if expected_types.size == 1 && expected_types[0].ancestors.member?(Enumerable) hash = obj.is_a?(Hash) ? obj : Hash[obj.map.with_index { |x, i| [i, x] }] hash.each do |key, value| validate_object(path, key, full_schema[:all_keys]) if full_schema[:all_keys] && !key.is_a?(Symbol) next if processed_keys.member? key if full_schema[:all] validate_object(path.empty? ? key : "#{path}.#{key}", value, full_schema[:all]) elsif !full_schema[:allow_unknown_keys] add_validation_error(path, "unknown key: #{key}") end end end true end
validate_spec_keys(full_schema, obj, path, processed_keys)
click to toggle source
# File lib/frise/validator.rb, line 115 def validate_spec_keys(full_schema, obj, path, processed_keys) full_schema.each do |spec_key, spec_value| next if spec_key.is_a?(Symbol) validate_object(path.empty? ? spec_key : "#{path}.#{spec_key}", obj[spec_key], spec_value) processed_keys << spec_key end true end
validate_type(full_schema, obj, path)
click to toggle source
# File lib/frise/validator.rb, line 72 def validate_type(full_schema, obj, path) expected_types = get_expected_types(full_schema) unless expected_types.any? { |typ| obj.is_a?(typ) } type_key = full_schema.fetch(:type, 'Hash') add_validation_error(path, "expected #{type_key}, found #{widened_class(obj)}") return false end true end
widened_class(obj)
click to toggle source
# File lib/frise/validator.rb, line 22 def widened_class(obj) class_name = obj.class.to_s return 'Boolean' if %w[TrueClass FalseClass].include? class_name return 'Integer' if %w[Fixnum Bignum].include? class_name class_name end