diff --git a/lib/errapi.rb b/lib/errapi.rb index 67a3d51..c281bbf 100644 --- a/lib/errapi.rb +++ b/lib/errapi.rb @@ -14,10 +14,12 @@ def self.default_config Configuration.new.tap do |config| config.plugins << Errapi::Plugins::ErrorCodes.new config.plugins << Errapi::Plugins::Messages.new - config.register_validator :length, Errapi::Validators::Length - config.register_validator :presence, Errapi::Validators::Presence + config.register_validation :length, Errapi::Validations::Length + config.register_validation :presence, Errapi::Validations::Presence config.register_condition Errapi::Condition::SimpleCheck config.register_condition Errapi::Condition::ErrorCheck + config.register_location :dotted, Errapi::Locations::Dotted + config.register_location :json, Errapi::Locations::Json end end end diff --git a/lib/errapi/configuration.rb b/lib/errapi/configuration.rb index a15a392..3b9f965 100644 --- a/lib/errapi/configuration.rb +++ b/lib/errapi/configuration.rb @@ -3,36 +3,62 @@ class Errapi::Configuration def initialize @plugins = [] - @validators = {} - @conditions = {} + @validation_factories = {} + @condition_factories = {} + @location_factories = {} + end + + def new_error options = {} + Errapi::ValidationError.new options + end + + def build_error error, context + apply_plugins :build_error, error, context end def new_context - Errapi::ValidationContext.new plugins: @plugins + Errapi::ValidationContext.new config: self + end + + def register_validation name, factory + @validation_factories[name] = factory end - def register_validator name, factory - @validators[name] = factory + def validation name, options = {} + raise ArgumentError, "No validation factory registered for name #{name.inspect}" unless @validation_factories.key? name + @validation_factories[name].new options end - def validator name, options = {} - raise ArgumentError, "No validator factory registered for name #{name.inspect}" unless @validators.key? name - @validators[name].new options + def register_location name, factory + @location_factories[name] = factory + end + + def location name, initial_location = nil + raise ArgumentError, "No location factory registered for name #{name.inspect}" unless @location_factories.key? name + @location_factories[name].new initial_location end def register_condition factory factory.conditionals.each do |conditional| raise ArgumentError, "Conditional #{conditional} should start with 'if' or 'unless'." unless conditional.to_s.match /^(if|unless)/ - @conditions[conditional] = factory + @condition_factories[conditional] = factory end end def extract_conditions! source, options = {} [].tap do |conditions| - @conditions.each_pair do |conditional,factory| + @condition_factories.each_pair do |conditional,factory| next unless source.key? conditional conditions << factory.new(conditional, source.delete(conditional), options) end end end + + private + + def apply_plugins operation, *args + @plugins.each do |plugin| + plugin.send operation, *args if plugin.respond_to? operation + end + end end diff --git a/lib/errapi/errors.rb b/lib/errapi/errors.rb index 2a83182..8006666 100644 --- a/lib/errapi/errors.rb +++ b/lib/errapi/errors.rb @@ -1,12 +1,13 @@ module Errapi class Error < StandardError; end class ValidationErrorInvalid < Error; end + class ValidationDefinitionInvalid < Error; end class ValidationFailed < Error attr_reader :context def initialize context - super "A validation error occurred." + super "#{context.errors.length} errors were found during validation." @context = context end end diff --git a/lib/errapi/validators.rb b/lib/errapi/locations.rb similarity index 80% rename from lib/errapi/validators.rb rename to lib/errapi/locations.rb index 3d7046c..cd342ab 100644 --- a/lib/errapi/validators.rb +++ b/lib/errapi/locations.rb @@ -1,4 +1,4 @@ -module Errapi::Validators +module Errapi::Locations end Dir[File.join File.dirname(__FILE__), File.basename(__FILE__, '.*'), '*.rb'].each{ |lib| require lib } diff --git a/lib/errapi/locations/dotted.rb b/lib/errapi/locations/dotted.rb new file mode 100644 index 0000000..bc57338 --- /dev/null +++ b/lib/errapi/locations/dotted.rb @@ -0,0 +1,29 @@ +module Errapi + + class Locations::Dotted + + def initialize location = nil + @location = location.to_s.sub /^\./, '' unless location.nil? + end + + def relative parts + if @location.nil? + self.class.new parts + else + self.class.new "#{@location}.#{parts.to_s.sub(/^\./, '')}" + end + end + + def error_options + @location.nil? ? {} : { location: @location, location_type: :dotted } + end + + def serialize + @location.nil? ? nil : @location + end + + def to_s + @location.to_s + end + end +end diff --git a/lib/errapi/locations/json.rb b/lib/errapi/locations/json.rb new file mode 100644 index 0000000..f1d28ff --- /dev/null +++ b/lib/errapi/locations/json.rb @@ -0,0 +1,29 @@ +module Errapi + + class Locations::Json + + def initialize location = nil + @location = location.nil? ? '' : location.to_s.sub(/^\//, '').sub(/\/$/, '') + end + + def relative parts + if @location.nil? + self.class.new parts + else + self.class.new "#{@location}/#{parts.to_s.sub(/^\./, '').sub(/\/$/, '')}" + end + end + + def error_options + { location: @location, location_type: :json } + end + + def serialize + @location + end + + def to_s + @location + end + end +end diff --git a/lib/errapi/locations/none.rb b/lib/errapi/locations/none.rb new file mode 100644 index 0000000..5a8ea21 --- /dev/null +++ b/lib/errapi/locations/none.rb @@ -0,0 +1,28 @@ +require 'singleton' + +module Errapi + + class Locations::None + include Singleton + + def relative parts + self + end + + def error_options + {} + end + + def serialize + nil + end + + def to_s + LOCATION_STRING + end + + private + + LOCATION_STRING = '' + end +end diff --git a/lib/errapi/model.rb b/lib/errapi/model.rb index 6e0c878..01263c3 100644 --- a/lib/errapi/model.rb +++ b/lib/errapi/model.rb @@ -10,8 +10,8 @@ def validate *args context = args.shift end - validations = self.class.errapi name - validations.validate self, context, options + validator = self.class.errapi name + validator.validate self, context, options end def self.included mod @@ -20,9 +20,10 @@ def self.included mod module ClassMethods - def errapi name = nil, &block - @errapi_validations ||= {} - @errapi_validations[name || :default] ||= Errapi::ObjectValidations.new(&block) + def errapi name = :default, &block + @errapi_validators ||= {} + @errapi_validators[name] = Errapi::ObjectValidator.new(&block) if block + @errapi_validators[name] end end end diff --git a/lib/errapi/object_validations.rb b/lib/errapi/object_validations.rb deleted file mode 100644 index 7995b07..0000000 --- a/lib/errapi/object_validations.rb +++ /dev/null @@ -1,231 +0,0 @@ -class Errapi::ObjectValidations - - def initialize options = {}, &block - - @validations = [] - @current_options = {} - @config = options[:config] || Errapi.config - - instance_eval &block if block - end - - def validates *args, &block - register_validations *args, &block - end - - def validates_each *args, &block - - options = args.last.kind_of?(Hash) ? args.pop : {} - options[:each] = args.shift - options[:each_options] = options.delete :each_options if options.key? :each_options - args << options - - validates *args, &block - end - - def validate value, context, options = {} - - @context = context - - with_options options do - - @validations.each do |validation| - - validation_options = validation[:validation_options] - each = validation[:each] - - values = if each - extract(value, each, options)[:value] || [] - else - [ value ] - end - - values_set = if each - values.collect{ |v| true } - else - [ !!options[:value_set] ] - end - - each_options = validation[:each_options] || {} - each_options[:location] = actual_location at: each if each - - with_options each_options do - - values.each.with_index do |value,i| - - next if validation[:conditions].any?{ |condition| !condition.fulfilled?(value, self) } - - iteration_options = {} - iteration_options = { location: actual_location(at: i), value_set: values_set[i] } if each - - with_options iteration_options do - - target = validation[:target] - - value_options = validation_options.dup - value_options[:location] = actual_location(extract_location(value_options) || { at: target }) - - value_data = extract value, target, value_set: values_set[i] - current_value = value_data[:value] - - value_options[:value] = current_value - value_options[:value_set] = value_data[:value_set] - - validator = validation[:validator] - value_options[:validator_name] = validation[:validator_name] if validation[:validator_name] - value_options[:validator_options] = validation[:validator_options] - - with_options value_options do - - validator_options = validation[:validator_options] - validator_options[:location] = @current_options[:location] if @current_options[:location] - validator_options[:value_set] = @current_options[:value_set] if @current_options.key? :value_set - - validator.validate current_value, validator.kind_of?(self.class) ? context : self, validator_options - end - end - end - end - end - end - - @context = nil - - # TODO: add config option to raise error by default - raise Errapi::ValidationFailed.new(context) if options[:raise_error] && context.errors? - end - - def add_error options = {}, &block - @context.add_error options do |error| - - error.location = @current_options[:location].to_s - %i(value_set validator_name validator_options).each do |attr| - error[attr] = @current_options[attr] if @current_options.key? attr - end - - block.call error if block - end - end - - def errors? criteria = {}, &block - - if criteria[:at_absolute_location] - criteria[:location] = criteria[:at_absolute_location] - elsif %i(at at_location at_relative_location).any?{ |k| criteria.key? k } - criteria[:location] = actual_location criteria - end - - %i(at at_location at_relative_location at_absolute_location).each{ |key| criteria.delete key } - - @context.errors? criteria, &block - end - - private - - def with_options options = {} - original_options = @current_options - @current_options = @current_options.merge options - yield - @current_options = original_options - end - - def extract value, target, options = {} - - value_set = !!options[:value_set] - - if target.nil? - { value: value, value_set: value_set } - elsif target.respond_to? :call - { value: target.call(value), value_set: value_set } - elsif value.kind_of? Hash - { value: value[target], value_set: value.key?(target) } - elsif value.respond_to?(target) - { value: value.send(target), value_set: value_set } - else - { value_set: false } - end - end - - def extract_location options = {} - if options[:at_absolute_location] - { at_absolute_location: options[:at_absolute_location] } - elsif key = %i(at at_location at_relative_location).find{ |key| options.key? key } - { key => options[key] } - end - end - - def actual_location options = {} - if options[:at_absolute_location] - options[:at_absolute_location] - elsif key = %i(at at_location at_relative_location).find{ |key| options.key? key } - @current_options[:location] ? "#{@current_options[:location]}.#{options[key]}" : options[key] - else - @current_options[:location] - end - end - - def extract_validation_options! options = {} - {}.tap do |h| - %i(at at_location at_relative_location at_absolute_location).each do |key| - h[key] = options.delete key if options.key? key - end - end - end - - def register_validations *args, &block - - options = args.last.kind_of?(Hash) ? args.pop : {} - validation_options = extract_validation_options! options - - custom_validators = [] - custom_validators << options.delete(:with) if options[:with] # TODO: allow array - custom_validators << Errapi::ObjectValidations.new(&block) if block - - conditions = @config.extract_conditions! options - - each_options = {} - each_options[:each] = options.delete :each if options[:each] - each_options[:each_options] = options.delete :each_options if options[:each_options] - - # TODO: fail if there are no validations declared - args = [ nil ] if args.empty? - - args.each do |target| - - target = nil if target == self - target_options = { target: target } - - unless custom_validators.empty? - custom_validators.each do |custom_validator| - @validations << { - validator: custom_validator, - validator_options: {}, - validation_options: validation_options, - conditions: conditions - }.merge(target_options).merge(each_options) - end - end - - options.each_pair do |validator_name,validator_options| - next unless validator_options - - validation = { - validator_name: validator_name - } - - validator_options = validator_options.kind_of?(Hash) ? validator_options : {} - - validation.merge!({ - validator_options: validator_options, - # FIXME: do not allow conflicting location options to be merged - validation_options: validation_options.merge(extract_validation_options!(validator_options)), - conditions: conditions + @config.extract_conditions!(validator_options) - }) - - validation[:validator] = @config.validator validator_name, validator_options - - @validations << validation.merge(target_options).merge(each_options) - end - end - end -end diff --git a/lib/errapi/object_validator.rb b/lib/errapi/object_validator.rb new file mode 100644 index 0000000..1a83318 --- /dev/null +++ b/lib/errapi/object_validator.rb @@ -0,0 +1,184 @@ +class Errapi::ObjectValidator + + def initialize options = {}, &block + + @validations = [] + @config = options[:config] || Errapi.config + + instance_eval &block if block + end + + def validates *args, &block + register_validations *args, &block + end + + def validates_each *args, &block + + options = args.last.kind_of?(Hash) ? args.pop : {} + options[:each] = args.shift + options[:each_options] = options.delete(:each_options) || {} + args << options + + validates *args, &block + end + + def validate value, context, options = {} + + return context.valid? unless @validations + + context_proxy = ContextProxy.new context, self + + location = if options[:location] + options[:location] + elsif options[:location_type] + @config.location options[:location_type] + else + Errapi::Locations::None.instance + end + + @validations.each do |validation_definition| + + each = validation_definition[:each] + + each_values = nil + each_values_set = nil + + if each + each_values = extract(value, each[:target], options)[:value] + each_values_set = each_values.collect{ |v| true } if each_values.kind_of? Array + each_values = [] unless each_values.kind_of? Array + else + each_values = [ value ] + each_values_set = [ !!options[:values_set] ] + end + + each_location = each ? location.relative(each[:options][:as] || each[:target]) : location + + each_values.each.with_index do |each_value,i| + + next if validation_definition[:conditions].any?{ |condition| !condition.fulfilled?(each_value, context_proxy) } + + each_index_location = each ? each_location.relative(i) : location + + validation_definition[:validations].each do |validation| + + next if validation[:conditions] && validation[:conditions].any?{ |condition| !condition.fulfilled?(each_value, context_proxy) } + + validation_definition[:targets].each do |target| + + target_value_info = extract each_value, target, value_set: each_values_set[i] + + validation_location = target ? each_index_location.relative(validation[:target_alias] || target) : each_index_location + + error_options = { + value_set: target_value_info[:value_set], + validation_options: validation[:validation_options] + }.merge(validation_location.error_options) + + error_options[:validation_name] = validation[:validation_name] if validation[:validation_name] + + context_proxy.with_error_options error_options do + validation_options = { location: validation_location, value_set: target_value_info[:value_set] } + validation[:validation].validate target_value_info[:value], context_proxy, validation_options + end + end + end + end + end + + # TODO: add config option to raise error by default + raise Errapi::ValidationFailed.new(context) if options[:raise_error] && context.errors? + + context_proxy.valid? + end + + private + + def extract value, target, options = {} + + value_set = !!options[:value_set] + + if target.nil? + { value: value, value_set: value_set } + elsif target.respond_to? :call + { value: target.call(value), value_set: value_set } + elsif value.kind_of? Hash + { value: value[target], value_set: value.key?(target) } + elsif value.respond_to?(target) + { value: value.send(target), value_set: value_set } + else + { value_set: false } + end + end + + def register_validations *args, &block + + options = args.last.kind_of?(Hash) ? args.pop : {} + target_alias = options.delete :as + + validations_definition = { + validations: [] + } + + # FIXME: register all validations (from :with, from block and from hash) in the order they are given + if options[:with] + validations_definition[:validations] += [*options.delete(:with)].collect{ |with| { validation: with, validation_options: {}, target_alias: target_alias } } + end + + if block + validations_definition[:validations] << { validation: self.class.new(config: @config, &block), validation_options: {}, target_alias: target_alias } + end + + if options[:each] + validations_definition[:each] = { + target: options.delete(:each), + options: options.delete(:each_options) + } + end + + validations_definition[:conditions] = @config.extract_conditions! options + + validations = options + raise Errapi::ValidationDefinitionInvalid, "No validation was defined. Use registered validations (e.g. `presence: true`), the :with option, or a block to define validations." if validations_definition[:validations].empty? && validations.empty? + + validations.each do |validation_name,options| + next unless options + validation_options = options.kind_of?(Hash) ? options : {} + validation_target_alias = validation_options.delete(:as) || target_alias + conditions = @config.extract_conditions! validation_options + validation = @config.validation validation_name, validation_options + validations_definition[:validations] << { validation: validation, validation_name: validation_name, validation_options: validation_options, target_alias: validation_target_alias, conditions: conditions } + end + + validations_definition[:targets] = args.empty? ? [ nil ] : args + + @validations << validations_definition + end + + class ContextProxy + instance_methods.each{ |m| undef_method m unless m =~ /(^__|^send$|^object_id$)/ } + + def initialize context, validator + @context = context + @validator = validator + @error_options = {} + end + + def add_error options = {}, &block + @context.add_error @error_options.merge(options), &block + end + + def with_error_options error_options = {}, &block + previous_error_options = @error_options + @error_options = error_options + block.call + @error_options = previous_error_options + end + + protected + + def method_missing name, *args, &block + @context.send name, *args, &block + end + end +end diff --git a/lib/errapi/plugins/error_codes.rb b/lib/errapi/plugins/error_codes.rb index fe191a1..c285966 100644 --- a/lib/errapi/plugins/error_codes.rb +++ b/lib/errapi/plugins/error_codes.rb @@ -13,7 +13,7 @@ class Errapi::Plugins::ErrorCodes } def build_error error, context - if !error.code && CODES.key?(error.validator_name) && code = CODES[error.validator_name][error.cause] + if !error.code && CODES.key?(error.validation_name) && code = CODES[error.validation_name][error.cause] error.code = code end end diff --git a/lib/errapi/plugins/messages.rb b/lib/errapi/plugins/messages.rb index 6e37669..b8e6a1a 100644 --- a/lib/errapi/plugins/messages.rb +++ b/lib/errapi/plugins/messages.rb @@ -6,16 +6,16 @@ class Errapi::Plugins::Messages blank: 'This value cannot be blank.' }, string_length: { - wrong_length: 'This string must be exactly %{constraint_length} characters long but has %{constrained_value}.', - too_short: 'This string must be at least %{constraint_length} characters long but has only %{constrained_value}.', - too_long: 'This string must be at most %{constraint_length} characters long but has %{constrained_value}.' + wrong_length: 'This string must be exactly %{check_value} characters long but has %{checked_value}.', + too_short: 'This string must be at least %{check_value} characters long but has only %{checked_value}.', + too_long: 'This string must be at most %{check_value} characters long but has %{checked_value}.' } } def build_error error, context - if !error.message && MESSAGES.key?(error.validator_name) && message = MESSAGES[error.validator_name][error.cause] + if !error.message && MESSAGES.key?(error.validation_name) && message = MESSAGES[error.validation_name][error.cause] - %w(constraint_length constrained_value).each do |interpolated| + %w(check_value checked_value).each do |interpolated| if error.respond_to? interpolated message = message.gsub /\%\{#{interpolated}\}/, interpolated end diff --git a/lib/errapi/validation_context.rb b/lib/errapi/validation_context.rb index ab09a5c..17a24c3 100644 --- a/lib/errapi/validation_context.rb +++ b/lib/errapi/validation_context.rb @@ -2,32 +2,28 @@ class Errapi::ValidationContext attr_reader :data - attr_reader :plugins attr_reader :errors + attr_reader :config def initialize options = {} @errors = [] - @plugins = options[:plugins] || [] - @data = OpenStruct.new + @data = OpenStruct.new options[:data] || {} + @config = options[:config] end def add_error options = {}, &block - options = plug :build_error_options, options.dup - error = Errapi::ValidationError.new options - + error = options.kind_of?(Errapi::ValidationError) ? options : @config.new_error(options) yield error if block_given? - - error = plug :build_error, error + @config.build_error error, self @errors << error - error + self end def errors? criteria = {}, &block - plug :build_error_criteria, criteria return !@errors.empty? if criteria.empty? && !block - @errors.any?{ |err| err.matches?(criteria) && (!block || block.call(err)) } + block ? @errors.any?{ |err| err.matches?(criteria) && block.call(err) } : @errors.any?{ |err| err.matches?(criteria) } end def valid? @@ -38,15 +34,4 @@ def clear @errors.clear @data = OpenStruct.new end - - private - - def plug operation, value - - @plugins.each do |plugin| - plugin.send operation, value, self if plugin.respond_to? operation - end - - value - end end diff --git a/lib/errapi/validation_error.rb b/lib/errapi/validation_error.rb index c7e038a..69537a8 100644 --- a/lib/errapi/validation_error.rb +++ b/lib/errapi/validation_error.rb @@ -1,23 +1,49 @@ require 'ostruct' -class Errapi::ValidationError < OpenStruct +class Errapi::ValidationError + attr_accessor :cause + attr_accessor :check_value + attr_accessor :checked_value + attr_accessor :validation_name + attr_accessor :validation_options + attr_accessor :message + attr_accessor :code + attr_accessor :location + attr_accessor :location_type def initialize options = {} - super options + ATTRIBUTES.each do |attr| + instance_variable_set "@#{attr}", options[attr] if options.key? attr + end end def matches? criteria = {} - criteria.all?{ |key,value| value_matches? key, value } + unknown_criteria = criteria.keys - ATTRIBUTES + raise "Unknown error attributes: #{unknown_criteria.join(', ')}." if unknown_criteria.any? + ATTRIBUTES.all?{ |attr| criterion_matches? criteria, attr } end def serializable_hash options = {} - # TODO: handle :only and :except options - to_h.select{ |k,v| !options.key?(:only) || options[:only].include?(k) }.reject{ |k,v| options[:except] && options[:except].include?(k) } + + attrs = SERIALIZABLE_ATTRIBUTES + attrs = attrs.select{ |attr| options[:only].include? attr } if options.key? :only + attrs = attrs.reject{ |attr| options[:except].include? attr } if options.key? :except + + attrs.inject({}) do |memo,attr| + value = send attr + memo[attr] = value unless value.nil? + memo + end end private - def value_matches? attr, value + SERIALIZABLE_ATTRIBUTES = %i(message code location location_type) + ATTRIBUTES = %i(cause check_value checked_value validation_name) + SERIALIZABLE_ATTRIBUTES + + def criterion_matches? criteria, attr + return true unless criteria.key? attr + value = criteria[attr] value.kind_of?(Regexp) ? !!value.match(send(attr).to_s) : value == send(attr) end end diff --git a/lib/errapi/validations.rb b/lib/errapi/validations.rb new file mode 100644 index 0000000..b2fb2e2 --- /dev/null +++ b/lib/errapi/validations.rb @@ -0,0 +1,4 @@ +module Errapi::Validations +end + +Dir[File.join File.dirname(__FILE__), File.basename(__FILE__, '.*'), '*.rb'].each{ |lib| require lib } diff --git a/lib/errapi/validators/presence.rb b/lib/errapi/validations/presence.rb similarity index 93% rename from lib/errapi/validators/presence.rb rename to lib/errapi/validations/presence.rb index 9c59607..00b60c6 100644 --- a/lib/errapi/validators/presence.rb +++ b/lib/errapi/validations/presence.rb @@ -1,4 +1,4 @@ -class Errapi::Validators::Presence +class Errapi::Validations::Presence def initialize options = {} end diff --git a/lib/errapi/validators/string_length.rb b/lib/errapi/validations/string_length.rb similarity index 96% rename from lib/errapi/validators/string_length.rb rename to lib/errapi/validations/string_length.rb index f57e83e..092442e 100644 --- a/lib/errapi/validators/string_length.rb +++ b/lib/errapi/validations/string_length.rb @@ -1,4 +1,4 @@ -class Errapi::Validators::Length +class Errapi::Validations::Length CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze CAUSES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze diff --git a/spec/example_spec.rb b/spec/example_spec.rb index 8fb439f..a49ba89 100644 --- a/spec/example_spec.rb +++ b/spec/example_spec.rb @@ -46,24 +46,24 @@ errapi :with_age do validates :name, presence: true - validates Proc.new(&:age), presence: true, at: 'age' + validates Proc.new(&:age), presence: true, as: 'age' end end o = klass.new - o.validate context + o.validate context, location_type: :dotted expect(context.errors?).to be(true) expect(context.errors?(location: 'name')).to be(true) expect(context.errors).to have(1).item context.clear o.name = 'foo' - o.validate context + o.validate context, location_type: :dotted expect(context.errors?).to be(false) context.clear o.name = nil - o.validate :with_age, context + o.validate :with_age, context, location_type: :dotted expect(context.errors?).to be(true) expect(context.errors?(location: 'name')).to be(true) expect(context.errors?(location: 'age')).to be(true) @@ -84,11 +84,11 @@ ] } - bar_validations = Errapi::ObjectValidations.new do + bar_validations = Errapi::ObjectValidator.new do validates :foo, presence: true end - validations = Errapi::ObjectValidations.new do + validations = Errapi::ObjectValidator.new do validates :foo, presence: true validates :bar, with: bar_validations @@ -99,12 +99,12 @@ validates :baz, presence: true end - validates :bar, at_absolute_location: 'corge' do - validates :qux, presence: true, at_relative_location: 'grault' + validates :bar, as: 'corge' do + validates :qux, presence: true, as: 'grault' end end - validations.validate h, context + validations.validate h, context, location_type: :dotted expect(context.errors?).to be(true) expect(context.errors).to have(6).items @@ -123,7 +123,7 @@ bar: {} } - validations = Errapi::ObjectValidations.new do + validations = Errapi::ObjectValidator.new do validates :baz, presence: { if: :baz } validates :qux, presence: { if: Proc.new{ |h| h[:foo] == 'baz' } } validates :corge, presence: { unless: :bar } @@ -134,7 +134,7 @@ end end - validations.validate h, context + validations.validate h, context, location_type: :dotted expect(context.errors?).to be(false) expect(context.errors).to be_empty @@ -144,7 +144,7 @@ baz: [] } - validations.validate h, context + validations.validate h, context, location_type: :dotted expect(context.errors?).to be(true) expect(context.errors).to have(6).items @@ -164,17 +164,17 @@ qux: {} } - validations = Errapi::ObjectValidations.new do + validations = Errapi::ObjectValidator.new do validates :foo, presence: true validates :bar, presence: true, if_error: { location: 'foo' } - validates :baz, presence: { unless_error: { at_location: 'foo' } } + validates :baz, presence: { unless_error: { location: 'foo' } } validates :qux do - validates :corge, presence: { unless_error: { at_absolute_location: 'foo' } } - validates :grault, presence: { if_error: { at_relative_location: 'corge' } } + validates :corge, presence: { unless_error: { location: 'foo' } } + validates :grault, presence: { if_error: { location: 'qux.corge' } } end end - validations.validate h, context + validations.validate h, context, location_type: :dotted expect(context.errors?).to be(true) expect(context.errors).to have(3).item @@ -188,7 +188,7 @@ } context.clear - validations.validate h, context + validations.validate h, context, location_type: :dotted expect(context.errors?).to be(true) expect(context.errors).to have(2).items