# frozen_string_literal: true require 'dry/equalizer' require 'dry/validation/constants' require 'dry/validation/function' module Dry module Validation # Rules capture configuration and evaluator blocks # # When a rule is applied, it creates an `Evaluator` using schema result and its # block will be evaluated in the context of the evaluator. # # @see Contract#rule # # @api public class Rule < Function include Dry::Equalizer(:keys, :block, inspect: false) # @!attribute [r] keys # @return [Array] # @api private option :keys # @!attribute [r] macros # @return [Array] # @api private option :macros, default: proc { EMPTY_ARRAY.dup } # Evaluate the rule within the provided context # # @param [Contract] contract # @param [Result] result # # @api private def call(contract, result) Evaluator.new( contract, keys: keys, macros: macros, block_options: block_options, result: result, values: result.values, _context: result.context, &block ) end # Define which macros should be executed # # @see Contract#rule # @return [Rule] # # @api public def validate(*macros, &block) @macros = parse_macros(*macros) @block = block if block self end # Define a validation function for each element of an array # # The function will be applied only if schema checks passed # for a given array item. # # @example # rule(:nums).each do # key.failure("must be greater than 0") if value < 0 # end # rule(:nums).each(min: 3) # rule(address: :city) do # key.failure("oops") if value != 'Munich' # end # # @return [Rule] # # @api public def each(*macros, &block) root = keys[0] macros = parse_macros(*macros) @keys = [] @block = proc do (values[root] || []).each_with_index do |_, idx| path = [*Schema::Path[root].to_a, idx] next if result.error?(path) evaluator = with(macros: macros, keys: [path], &block) failures.concat(evaluator.failures) end end self end # Return a nice string representation # # @return [String] # # @api public def inspect %(#<#{self.class} keys=#{keys.inspect}>) end # Parse function arguments into macros structure # # @return [Array] # # @api private def parse_macros(*args) args.each_with_object([]) do |spec, macros| case spec when Hash spec.each { |k, v| macros << [k, Array(v)] } else macros << Array(spec) end end end end end end