/
rule.rb
135 lines (119 loc) · 3.32 KB
/
rule.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# frozen_string_literal: true
require "dry/validation/constants"
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 |index:|
# key([:number, index]).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
#
# rubocop:disable Metrics/AbcSize
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].nil?
values[root].each_with_index do |_, idx|
path = [*Schema::Path[root].to_a, idx]
next if result.schema_error?(path)
evaluator = with(macros: macros, keys: [path], index: idx, &block)
failures.concat(evaluator.failures)
end
end
end
@block_options = map_keywords(block) if block
self
end
# rubocop:enable Metrics/AbcSize
# 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