lib/domainic/attributer/attribute/validator.rb in domainic-attributer-0.1.0 vs lib/domainic/attributer/attribute/validator.rb in domainic-attributer-0.2.0

- old
+ new

@@ -1,18 +1,23 @@ # frozen_string_literal: true require 'domainic/attributer/attribute/mixin/belongs_to_attribute' +require 'domainic/attributer/errors/error' +require 'domainic/attributer/errors/validation_execution_error' +require 'domainic/attributer/undefined' module Domainic module Attributer class Attribute - # A class responsible for validating attribute values. + # A class responsible for validating attribute values # # This class manages the validation of values assigned to an attribute. Validation - # can be performed either by a Proc that accepts a single value argument and returns - # a boolean, or by any object that responds to the `===` operator. + # can be performed either by a {Proc} that accepts a single value argument and returns + # a boolean, or by any object that responds to the `===` operator # + # @api private + # @!visibility private # @author {https://aaronmallen.me Aaron Allen} # @since 0.1.0 class Validator # @rbs! # type handler = proc | Proc | _ValidHandler @@ -35,67 +40,90 @@ include BelongsToAttribute # @rbs @handlers: Array[handler] - # Initialize a new Validator instance. + # Initialize a new Validator instance # - # @param attribute [Attribute] the attribute this Validator belongs to + # @param attribute [Attribute] the {Attribute} this instance belongs to # @param handlers [Array<Class, Module, Object, Proc>] the handlers to use for processing # - # @return [Validator] the new instance of Validator + # @return [Validator] the new Validator instance # @rbs (Attribute attribute, Array[handler] | handler handlers) -> void def initialize(attribute, handlers = []) super @handlers = [*handlers].map do |handler| validate_handler!(handler) handler end.uniq end - # Validate a value using all configured validators. + # Validate a value using all configured validators # # @param instance [Object] the instance on which to perform validation # @param value [Object] the value to validate # # @raise [ArgumentError] if the value fails validation + # @raise [ValidationExecutionError] if errors occur during validation execution # @return [void] # @rbs (untyped instance, untyped value) -> void def call(instance, value) return if value == Undefined && handle_undefined! return if value.nil? && handle_nil! - return if @handlers.all? { |handler| validate_value!(handler, instance, value) } - raise ArgumentError, "`#{attribute_method_name}`: has invalid value: #{value.inspect}" + run_validations!(instance, value) end private - # Handle a `nil` value. + # Handle a nil value # # @raise [ArgumentError] if the attribute is not nilable - # @return [true] if the attribute is nilable + # @return [Boolean] true if the attribute is nilable # @rbs () -> bool def handle_nil! return true if @attribute.signature.nilable? raise ArgumentError, "`#{attribute_method_name}`: cannot be nil" end - # Handle an {Undefined} value. + # Handle an {Undefined} value # # @raise [ArgumentError] if the attribute is required - # @return [true] if the attribute is optional + # @return [Boolean] true if the attribute is optional # @rbs () -> bool def handle_undefined! return true if @attribute.signature.optional? raise ArgumentError, "`#{attribute_method_name}`: is required" end - # Validate that a validation handler is valid. + # Run all configured validations # + # @param instance [Object] the instance on which to perform validation + # @param value [Object] the value to validate + # + # @raise [ArgumentError] if the value fails validation + # @raise [ValidationExecutionError] if errors occur during validation execution + # @return [void] + # @rbs (untyped instance, untyped value) -> void + def run_validations!(instance, value) + errors = [] + is_valid = true + + @handlers.each do |handler| + is_valid = validate_value!(handler, instance, value) + rescue StandardError => e + errors << e + end + + raise ValidationExecutionError, errors unless errors.empty? + raise ArgumentError, "`#{attribute_method_name}`: has invalid value: #{value.inspect}" unless is_valid + end + + # Validate that a validation handler is valid + # # @param handler [Object] the handler to validate # # @raise [TypeError] if the handler is not valid # @return [void] # @rbs (handler handler) -> void @@ -104,22 +132,24 @@ raise TypeError, "`#{attribute_method_name}`: invalid validator: #{handler.inspect}. Must be a Proc " \ 'or an object responding to `#===`.' end - # Validate a value using a single handler. + # Validate a value using a single handler # # @param handler [Object] the handler to use for validation # @param instance [Object] the instance on which to perform validation # @param value [Object] the value to validate + # + # @return [Boolean] true if validation succeeds # @rbs (handler handler, untyped instance, untyped value) -> bool def validate_value!(handler, instance, value) if handler.is_a?(Proc) instance.instance_exec(value, &handler) elsif handler.respond_to?(:===) handler === value # rubocop:disable Style/CaseEquality else - # We should never get here because we validate the handlers in the initializer. + # We should never get here because we validate the handlers in the initializer raise TypeError, "`#{attribute_method_name}`: invalid validator: #{handler.inspect}" end end end end