136 lines
3.2 KiB
Ruby
136 lines
3.2 KiB
Ruby
# 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<Symbol, String, Hash>]
|
|
# @api private
|
|
option :keys
|
|
|
|
# @!attribute [r] macros
|
|
# @return [Array<Symbol>]
|
|
# @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
|
|
unless result.base_error?(root) || !values.key?(root)
|
|
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
|
|
end
|
|
|
|
@block_options = map_keywords(block) if block
|
|
|
|
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
|
|
add_macro_from_hash(macros, spec)
|
|
else
|
|
macros << Array(spec)
|
|
end
|
|
end
|
|
end
|
|
|
|
def add_macro_from_hash(macros, spec)
|
|
spec.each do |k, v|
|
|
macros << [k, v.is_a?(Array) ? v : [v]]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|