dry-validation 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/README.md +35 -499
- data/lib/dry/validation/error_compiler.rb +9 -4
- data/lib/dry/validation/hint_compiler.rb +69 -0
- data/lib/dry/validation/predicate.rb +0 -4
- data/lib/dry/validation/result.rb +8 -0
- data/lib/dry/validation/rule.rb +44 -0
- data/lib/dry/validation/rule/check.rb +15 -0
- data/lib/dry/validation/rule/composite.rb +20 -7
- data/lib/dry/validation/rule/result.rb +46 -0
- data/lib/dry/validation/rule_compiler.rb +14 -0
- data/lib/dry/validation/schema.rb +33 -3
- data/lib/dry/validation/schema/definition.rb +25 -4
- data/lib/dry/validation/schema/key.rb +8 -8
- data/lib/dry/validation/schema/result.rb +15 -2
- data/lib/dry/validation/schema/rule.rb +32 -5
- data/lib/dry/validation/schema/value.rb +15 -6
- data/lib/dry/validation/version.rb +1 -1
- data/spec/integration/custom_error_messages_spec.rb +1 -1
- data/spec/integration/error_compiler_spec.rb +30 -56
- data/spec/integration/hints_spec.rb +39 -0
- data/spec/integration/localized_error_messages_spec.rb +2 -2
- data/spec/integration/schema/check_rules_spec.rb +28 -0
- data/spec/integration/schema/each_with_set_spec.rb +71 -0
- data/spec/integration/schema/nested_spec.rb +31 -0
- data/spec/integration/schema/not_spec.rb +34 -0
- data/spec/integration/schema/xor_spec.rb +32 -0
- data/spec/integration/schema_form_spec.rb +2 -2
- data/spec/integration/schema_spec.rb +1 -1
- data/spec/shared/predicates.rb +2 -0
- data/spec/spec_helper.rb +2 -2
- data/spec/unit/hint_compiler_spec.rb +32 -0
- data/spec/unit/predicate_spec.rb +0 -10
- data/spec/unit/rule/check_spec.rb +29 -0
- data/spec/unit/rule_compiler_spec.rb +44 -7
- data/spec/unit/schema/rule_spec.rb +31 -0
- data/spec/unit/schema/value_spec.rb +84 -0
- metadata +24 -2
@@ -14,8 +14,8 @@ module Dry
|
|
14
14
|
ast.map { |node| visit(node) }.reduce(:merge) || DEFAULT_RESULT
|
15
15
|
end
|
16
16
|
|
17
|
-
def with(
|
18
|
-
self.class.new(messages, options)
|
17
|
+
def with(new_options)
|
18
|
+
self.class.new(messages, options.merge(new_options))
|
19
19
|
end
|
20
20
|
|
21
21
|
def visit(node, *args)
|
@@ -28,7 +28,12 @@ module Dry
|
|
28
28
|
|
29
29
|
def visit_input(input, *args)
|
30
30
|
name, value, rules = input
|
31
|
-
{ name => rules.map { |rule| visit(rule, name, value) } }
|
31
|
+
{ name => [rules.map { |rule| visit(rule, name, value) }, value] }
|
32
|
+
end
|
33
|
+
|
34
|
+
def visit_check(node, *args)
|
35
|
+
name, _ = node
|
36
|
+
messages[name, rule: name]
|
32
37
|
end
|
33
38
|
|
34
39
|
def visit_key(rule, name, value)
|
@@ -51,7 +56,7 @@ module Dry
|
|
51
56
|
template = messages[predicate_name, lookup_options]
|
52
57
|
tokens = visit(predicate, value).merge(name: name)
|
53
58
|
|
54
|
-
|
59
|
+
template % tokens
|
55
60
|
end
|
56
61
|
|
57
62
|
def visit_key?(*args, value)
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'dry/validation/error_compiler'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Validation
|
5
|
+
class HintCompiler < ErrorCompiler
|
6
|
+
attr_reader :messages, :rules, :options
|
7
|
+
|
8
|
+
def initialize(messages, options = {})
|
9
|
+
@messages = messages
|
10
|
+
@options = Hash[options]
|
11
|
+
@rules = @options.delete(:rules)
|
12
|
+
end
|
13
|
+
|
14
|
+
def with(new_options)
|
15
|
+
super(new_options.merge(rules: rules))
|
16
|
+
end
|
17
|
+
|
18
|
+
def call
|
19
|
+
messages = Hash.new { |h, k| h[k] = [] }
|
20
|
+
|
21
|
+
rules.map { |node| visit(node) }.compact.each do |hints|
|
22
|
+
name, msgs = hints
|
23
|
+
messages[name].concat(msgs)
|
24
|
+
end
|
25
|
+
|
26
|
+
messages
|
27
|
+
end
|
28
|
+
|
29
|
+
def visit_or(node)
|
30
|
+
left, right = node
|
31
|
+
[visit(left), Array(visit(right)).flatten.compact].compact
|
32
|
+
end
|
33
|
+
|
34
|
+
def visit_and(node)
|
35
|
+
left, right = node
|
36
|
+
[visit(left), Array(visit(right)).flatten.compact].compact
|
37
|
+
end
|
38
|
+
|
39
|
+
def visit_val(node)
|
40
|
+
name, predicate = node
|
41
|
+
visit(predicate, name)
|
42
|
+
end
|
43
|
+
|
44
|
+
def visit_predicate(node, name)
|
45
|
+
predicate_name, args = node
|
46
|
+
|
47
|
+
lookup_options = options.merge(rule: name, arg_type: args[0].class)
|
48
|
+
|
49
|
+
template = messages[predicate_name, lookup_options]
|
50
|
+
predicate_opts = visit(node, args)
|
51
|
+
|
52
|
+
return unless predicate_opts
|
53
|
+
|
54
|
+
tokens = predicate_opts.merge(name: name)
|
55
|
+
|
56
|
+
template % tokens
|
57
|
+
end
|
58
|
+
|
59
|
+
def visit_key(node)
|
60
|
+
name, _ = node
|
61
|
+
name
|
62
|
+
end
|
63
|
+
|
64
|
+
def method_missing(name, *args)
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -13,6 +13,14 @@ module Dry
|
|
13
13
|
rule_results.each(&block)
|
14
14
|
end
|
15
15
|
|
16
|
+
def to_h
|
17
|
+
each_with_object({}) { |result, hash| hash[result.name] = result }
|
18
|
+
end
|
19
|
+
|
20
|
+
def merge!(other)
|
21
|
+
rule_results.concat(other.rule_results)
|
22
|
+
end
|
23
|
+
|
16
24
|
def to_ary
|
17
25
|
failures.map(&:to_ary)
|
18
26
|
end
|
data/lib/dry/validation/rule.rb
CHANGED
@@ -5,11 +5,41 @@ module Dry
|
|
5
5
|
|
6
6
|
attr_reader :name, :predicate
|
7
7
|
|
8
|
+
class Negation < Rule
|
9
|
+
include Dry::Equalizer(:rule)
|
10
|
+
|
11
|
+
attr_reader :rule
|
12
|
+
|
13
|
+
def initialize(rule)
|
14
|
+
@rule = rule
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(*args)
|
18
|
+
rule.(*args).negated
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_ary
|
22
|
+
[:not, rule.to_ary]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
8
26
|
def initialize(name, predicate)
|
9
27
|
@name = name
|
10
28
|
@predicate = predicate
|
11
29
|
end
|
12
30
|
|
31
|
+
def predicate_id
|
32
|
+
predicate.id
|
33
|
+
end
|
34
|
+
|
35
|
+
def type
|
36
|
+
:rule
|
37
|
+
end
|
38
|
+
|
39
|
+
def call(*args)
|
40
|
+
Validation.Result(args, predicate.call, self)
|
41
|
+
end
|
42
|
+
|
13
43
|
def to_ary
|
14
44
|
[type, [name, predicate.to_ary]]
|
15
45
|
end
|
@@ -25,11 +55,24 @@ module Dry
|
|
25
55
|
end
|
26
56
|
alias_method :|, :or
|
27
57
|
|
58
|
+
def xor(other)
|
59
|
+
ExclusiveDisjunction.new(self, other)
|
60
|
+
end
|
61
|
+
alias_method :^, :xor
|
62
|
+
|
28
63
|
def then(other)
|
29
64
|
Implication.new(self, other)
|
30
65
|
end
|
31
66
|
alias_method :>, :then
|
32
67
|
|
68
|
+
def negation
|
69
|
+
Negation.new(self)
|
70
|
+
end
|
71
|
+
|
72
|
+
def new(predicate)
|
73
|
+
self.class.new(name, predicate)
|
74
|
+
end
|
75
|
+
|
33
76
|
def curry(*args)
|
34
77
|
self.class.new(name, predicate.curry(*args))
|
35
78
|
end
|
@@ -42,5 +85,6 @@ require 'dry/validation/rule/value'
|
|
42
85
|
require 'dry/validation/rule/each'
|
43
86
|
require 'dry/validation/rule/set'
|
44
87
|
require 'dry/validation/rule/composite'
|
88
|
+
require 'dry/validation/rule/check'
|
45
89
|
require 'dry/validation/rule/group'
|
46
90
|
require 'dry/validation/rule/result'
|
@@ -6,11 +6,14 @@ module Dry
|
|
6
6
|
attr_reader :name, :left, :right
|
7
7
|
|
8
8
|
def initialize(left, right)
|
9
|
-
@name = left.name
|
10
9
|
@left = left
|
11
10
|
@right = right
|
12
11
|
end
|
13
12
|
|
13
|
+
def name
|
14
|
+
:"#{left.name}_#{type}_#{right.name}"
|
15
|
+
end
|
16
|
+
|
14
17
|
def to_ary
|
15
18
|
[type, [left.to_ary, right.to_ary]]
|
16
19
|
end
|
@@ -18,8 +21,8 @@ module Dry
|
|
18
21
|
end
|
19
22
|
|
20
23
|
class Rule::Implication < Rule::Composite
|
21
|
-
def call(
|
22
|
-
left.(
|
24
|
+
def call(*args)
|
25
|
+
left.(*args) > right
|
23
26
|
end
|
24
27
|
|
25
28
|
def type
|
@@ -28,8 +31,8 @@ module Dry
|
|
28
31
|
end
|
29
32
|
|
30
33
|
class Rule::Conjunction < Rule::Composite
|
31
|
-
def call(
|
32
|
-
left.(
|
34
|
+
def call(*args)
|
35
|
+
left.(*args).and(right)
|
33
36
|
end
|
34
37
|
|
35
38
|
def type
|
@@ -38,13 +41,23 @@ module Dry
|
|
38
41
|
end
|
39
42
|
|
40
43
|
class Rule::Disjunction < Rule::Composite
|
41
|
-
def call(
|
42
|
-
left.(
|
44
|
+
def call(*args)
|
45
|
+
left.(*args).or(right)
|
43
46
|
end
|
44
47
|
|
45
48
|
def type
|
46
49
|
:or
|
47
50
|
end
|
48
51
|
end
|
52
|
+
|
53
|
+
class Rule::ExclusiveDisjunction < Rule::Composite
|
54
|
+
def call(*args)
|
55
|
+
left.(*args).xor(right)
|
56
|
+
end
|
57
|
+
|
58
|
+
def type
|
59
|
+
:xor
|
60
|
+
end
|
61
|
+
end
|
49
62
|
end
|
50
63
|
end
|
@@ -2,6 +2,7 @@ module Dry
|
|
2
2
|
module Validation
|
3
3
|
def self.Result(input, value, rule)
|
4
4
|
case value
|
5
|
+
when Rule::Result then value.class.new(value.input, value.success?, rule)
|
5
6
|
when Array then Rule::Result::Set.new(input, value, rule)
|
6
7
|
else Rule::Result::Value.new(input, value, rule)
|
7
8
|
end
|
@@ -30,6 +31,31 @@ module Dry
|
|
30
31
|
alias_method :to_a, :to_ary
|
31
32
|
end
|
32
33
|
|
34
|
+
class Rule::Result::Verified < Rule::Result
|
35
|
+
attr_reader :predicate_id
|
36
|
+
|
37
|
+
def initialize(result, predicate_id)
|
38
|
+
@input = result.input
|
39
|
+
@value = result.value
|
40
|
+
@rule = result.rule
|
41
|
+
@name = result.name
|
42
|
+
@predicate_id = predicate_id
|
43
|
+
end
|
44
|
+
|
45
|
+
def call
|
46
|
+
Validation.Result(input, success?, rule)
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_ary
|
50
|
+
[:input, [name, input, [rule.to_ary]]]
|
51
|
+
end
|
52
|
+
alias_method :to_a, :to_ary
|
53
|
+
|
54
|
+
def success?
|
55
|
+
rule.predicate_id == predicate_id
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
33
59
|
def initialize(input, value, rule)
|
34
60
|
@input = input
|
35
61
|
@value = value
|
@@ -37,6 +63,22 @@ module Dry
|
|
37
63
|
@name = rule.name
|
38
64
|
end
|
39
65
|
|
66
|
+
def call
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
def curry(predicate_id = nil)
|
71
|
+
if predicate_id
|
72
|
+
Rule::Result::Verified.new(self, predicate_id)
|
73
|
+
else
|
74
|
+
self
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def negated
|
79
|
+
self.class.new(input, !value, rule)
|
80
|
+
end
|
81
|
+
|
40
82
|
def >(other)
|
41
83
|
if success?
|
42
84
|
other.(input)
|
@@ -61,6 +103,10 @@ module Dry
|
|
61
103
|
end
|
62
104
|
end
|
63
105
|
|
106
|
+
def xor(other)
|
107
|
+
Validation.Result(input, success? ^ other.(input).success?, rule)
|
108
|
+
end
|
109
|
+
|
64
110
|
def success?
|
65
111
|
@value
|
66
112
|
end
|
@@ -18,6 +18,15 @@ module Dry
|
|
18
18
|
send(:"visit_#{name}", nodes)
|
19
19
|
end
|
20
20
|
|
21
|
+
def visit_check(node)
|
22
|
+
name, predicate = node
|
23
|
+
Rule::Check.new(name, visit(predicate))
|
24
|
+
end
|
25
|
+
|
26
|
+
def visit_not(node)
|
27
|
+
visit(node).negation
|
28
|
+
end
|
29
|
+
|
21
30
|
def visit_key(node)
|
22
31
|
name, predicate = node
|
23
32
|
Rule::Key.new(name, visit(predicate))
|
@@ -53,6 +62,11 @@ module Dry
|
|
53
62
|
visit(left) | visit(right)
|
54
63
|
end
|
55
64
|
|
65
|
+
def visit_xor(node)
|
66
|
+
left, right = node
|
67
|
+
visit(left) ^ visit(right)
|
68
|
+
end
|
69
|
+
|
56
70
|
def visit_implication(node)
|
57
71
|
left, right = node
|
58
72
|
visit(left) > visit(right)
|
@@ -4,6 +4,7 @@ require 'dry/validation/error'
|
|
4
4
|
require 'dry/validation/rule_compiler'
|
5
5
|
require 'dry/validation/messages'
|
6
6
|
require 'dry/validation/error_compiler'
|
7
|
+
require 'dry/validation/hint_compiler'
|
7
8
|
require 'dry/validation/result'
|
8
9
|
require 'dry/validation/schema/result'
|
9
10
|
|
@@ -26,6 +27,10 @@ module Dry
|
|
26
27
|
ErrorCompiler.new(messages)
|
27
28
|
end
|
28
29
|
|
30
|
+
def self.hint_compiler
|
31
|
+
HintCompiler.new(messages, rules: rules.map(&:to_ary))
|
32
|
+
end
|
33
|
+
|
29
34
|
def self.messages
|
30
35
|
default =
|
31
36
|
case config.messages
|
@@ -50,24 +55,49 @@ module Dry
|
|
50
55
|
@__rules__ ||= []
|
51
56
|
end
|
52
57
|
|
58
|
+
def self.schemas
|
59
|
+
@__schemas__ ||= []
|
60
|
+
end
|
61
|
+
|
53
62
|
def self.groups
|
54
63
|
@__groups__ ||= []
|
55
64
|
end
|
56
65
|
|
57
|
-
|
66
|
+
def self.checks
|
67
|
+
@__checks__ ||= []
|
68
|
+
end
|
69
|
+
|
70
|
+
attr_reader :rules, :schemas, :groups, :checks
|
58
71
|
|
59
72
|
attr_reader :error_compiler
|
60
73
|
|
61
|
-
|
74
|
+
attr_reader :hint_compiler
|
75
|
+
|
76
|
+
def initialize(error_compiler = self.class.error_compiler, hint_compiler = self.class.hint_compiler)
|
62
77
|
compiler = RuleCompiler.new(self)
|
63
78
|
@rules = compiler.(self.class.rules.map(&:to_ary))
|
79
|
+
@checks = self.class.checks
|
64
80
|
@groups = compiler.(self.class.groups.map(&:to_ary))
|
81
|
+
@schemas = self.class.schemas.map(&:new)
|
65
82
|
@error_compiler = error_compiler
|
83
|
+
@hint_compiler = hint_compiler
|
66
84
|
end
|
67
85
|
|
68
86
|
def call(input)
|
69
87
|
result = Validation::Result.new(rules.map { |rule| rule.(input) })
|
70
88
|
|
89
|
+
schemas.each do |schema|
|
90
|
+
result.merge!(schema.(input).result)
|
91
|
+
end
|
92
|
+
|
93
|
+
if checks.size > 0
|
94
|
+
compiled_checks = RuleCompiler.new(result.to_h).(checks)
|
95
|
+
|
96
|
+
compiled_checks.each do |rule|
|
97
|
+
result << rule.()
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
71
101
|
groups.each do |group|
|
72
102
|
result.with_values(group.rules) do |values|
|
73
103
|
result << group.(*values)
|
@@ -76,7 +106,7 @@ module Dry
|
|
76
106
|
|
77
107
|
errors = Error::Set.new(result.failures.map { |failure| Error.new(failure) })
|
78
108
|
|
79
|
-
Schema::Result.new(input, result, errors, error_compiler)
|
109
|
+
Schema::Result.new(input, result, errors, error_compiler, hint_compiler)
|
80
110
|
end
|
81
111
|
|
82
112
|
def [](name)
|