dry-validation 0.9.5 → 0.10.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/.travis.yml +1 -8
- data/CHANGELOG.md +34 -1
- data/Gemfile +2 -0
- data/Rakefile +12 -3
- data/config/errors.yml +1 -0
- data/dry-validation.gemspec +3 -2
- data/lib/dry/validation/executor.rb +3 -27
- data/lib/dry/validation/extensions/monads.rb +17 -0
- data/lib/dry/validation/extensions/struct.rb +32 -0
- data/lib/dry/validation/extensions.rb +7 -0
- data/lib/dry/validation/input_processor_compiler.rb +19 -3
- data/lib/dry/validation/message.rb +43 -30
- data/lib/dry/validation/message_compiler/visitor_opts.rb +39 -0
- data/lib/dry/validation/message_compiler.rb +98 -52
- data/lib/dry/validation/message_set.rb +59 -30
- data/lib/dry/validation/predicate_registry.rb +17 -6
- data/lib/dry/validation/result.rb +39 -17
- data/lib/dry/validation/schema/check.rb +5 -4
- data/lib/dry/validation/schema/class_interface.rb +6 -13
- data/lib/dry/validation/schema/dsl.rb +9 -3
- data/lib/dry/validation/schema/form.rb +12 -3
- data/lib/dry/validation/schema/json.rb +12 -3
- data/lib/dry/validation/schema/key.rb +6 -6
- data/lib/dry/validation/schema/rule.rb +14 -8
- data/lib/dry/validation/schema/value.rb +23 -21
- data/lib/dry/validation/schema.rb +9 -12
- data/lib/dry/validation/schema_compiler.rb +16 -2
- data/lib/dry/validation/version.rb +1 -1
- data/lib/dry/validation.rb +11 -23
- data/spec/extensions/monads/result_spec.rb +38 -0
- data/spec/extensions/struct/schema_spec.rb +32 -0
- data/spec/integration/custom_predicates_spec.rb +7 -6
- data/spec/integration/form/predicates/size/fixed_spec.rb +0 -2
- data/spec/integration/form/predicates/size/range_spec.rb +0 -2
- data/spec/integration/hints_spec.rb +2 -6
- data/spec/integration/json/defining_base_schema_spec.rb +41 -0
- data/spec/integration/{error_compiler_spec.rb → message_compiler_spec.rb} +79 -131
- data/spec/integration/result_spec.rb +26 -4
- data/spec/integration/schema/check_with_nested_el_spec.rb +1 -1
- data/spec/integration/schema/check_with_nth_el_spec.rb +1 -1
- data/spec/integration/schema/defining_base_schema_spec.rb +3 -0
- data/spec/integration/schema/dynamic_predicate_args_spec.rb +34 -9
- data/spec/integration/schema/form/defining_base_schema_spec.rb +41 -0
- data/spec/integration/schema/json_spec.rb +1 -0
- data/spec/integration/schema/macros/input_spec.rb +26 -0
- data/spec/integration/schema/macros/rule_spec.rb +2 -1
- data/spec/integration/schema/macros/value_spec.rb +1 -1
- data/spec/integration/schema/macros/when_spec.rb +1 -24
- data/spec/integration/schema/or_spec.rb +87 -0
- data/spec/integration/schema/predicates/custom_spec.rb +4 -4
- data/spec/integration/schema/predicates/even_spec.rb +10 -10
- data/spec/integration/schema/predicates/odd_spec.rb +10 -10
- data/spec/integration/schema/predicates/size/fixed_spec.rb +0 -3
- data/spec/integration/schema/predicates/size/range_spec.rb +0 -2
- data/spec/integration/schema/predicates/type_spec.rb +22 -0
- data/spec/integration/schema/using_types_spec.rb +14 -41
- data/spec/integration/schema/validate_spec.rb +83 -0
- data/spec/integration/schema/xor_spec.rb +5 -5
- data/spec/integration/schema_builders_spec.rb +4 -2
- data/spec/integration/schema_spec.rb +8 -0
- data/spec/shared/message_compiler.rb +11 -0
- data/spec/shared/predicate_helper.rb +5 -3
- data/spec/spec_helper.rb +15 -0
- data/spec/unit/input_processor_compiler/form_spec.rb +3 -3
- data/spec/unit/message_compiler/visit_failure_spec.rb +38 -0
- data/spec/unit/message_compiler/visit_spec.rb +16 -0
- data/spec/unit/message_compiler_spec.rb +7 -0
- data/spec/unit/predicate_registry_spec.rb +2 -2
- data/spec/unit/schema/key_spec.rb +19 -12
- data/spec/unit/schema/rule_spec.rb +14 -6
- data/spec/unit/schema/value_spec.rb +49 -52
- metadata +50 -20
- data/lib/dry/validation/constants.rb +0 -6
- data/lib/dry/validation/error.rb +0 -26
- data/lib/dry/validation/error_compiler.rb +0 -81
- data/lib/dry/validation/hint_compiler.rb +0 -104
- data/spec/unit/error_compiler_spec.rb +0 -7
- data/spec/unit/hint_compiler_spec.rb +0 -51
@@ -1,20 +1,27 @@
|
|
1
|
-
require 'dry/
|
1
|
+
require 'dry/core/constants'
|
2
2
|
|
3
3
|
module Dry
|
4
4
|
module Validation
|
5
5
|
class MessageSet
|
6
|
+
include Core::Constants
|
6
7
|
include Enumerable
|
7
8
|
|
8
|
-
|
9
|
+
HINT_EXCLUSION = %i(key? filled? none? bool? str? int? float? decimal? date? date_time? time? hash? array?).freeze
|
9
10
|
|
10
|
-
|
11
|
-
|
11
|
+
attr_reader :messages, :failures, :hints, :paths, :placeholders, :options
|
12
|
+
|
13
|
+
def self.[](messages, options = EMPTY_HASH)
|
14
|
+
new(messages.flatten, options)
|
12
15
|
end
|
13
16
|
|
14
|
-
def initialize(messages)
|
17
|
+
def initialize(messages, options = EMPTY_HASH)
|
15
18
|
@messages = messages
|
16
|
-
@hints =
|
17
|
-
@
|
19
|
+
@hints = messages.select(&:hint?)
|
20
|
+
@failures = messages - hints
|
21
|
+
@paths = failures.map(&:path).uniq
|
22
|
+
@options = options
|
23
|
+
|
24
|
+
initialize_hints!
|
18
25
|
initialize_placeholders!
|
19
26
|
end
|
20
27
|
|
@@ -22,12 +29,16 @@ module Dry
|
|
22
29
|
root? ? to_a : to_h
|
23
30
|
end
|
24
31
|
|
32
|
+
def failures?
|
33
|
+
options[:failures].equal?(true)
|
34
|
+
end
|
35
|
+
|
25
36
|
def empty?
|
26
37
|
messages.empty?
|
27
38
|
end
|
28
39
|
|
29
40
|
def root?
|
30
|
-
!empty? &&
|
41
|
+
!empty? && failures.all?(&:root?)
|
31
42
|
end
|
32
43
|
|
33
44
|
def each(&block)
|
@@ -35,41 +46,59 @@ module Dry
|
|
35
46
|
messages.each(&block)
|
36
47
|
end
|
37
48
|
|
38
|
-
def with_hints!(hints)
|
39
|
-
@hints.update(hints.group_by(&:index_path))
|
40
|
-
self
|
41
|
-
end
|
42
|
-
|
43
49
|
def to_h
|
44
50
|
if root?
|
45
|
-
{ nil => map(&:to_s) }
|
51
|
+
{ nil => failures.map(&:to_s) }
|
46
52
|
else
|
47
|
-
|
48
|
-
|
53
|
+
failures? ? messages_map : hints_map
|
54
|
+
end
|
55
|
+
end
|
56
|
+
alias_method :to_hash, :to_h
|
57
|
+
|
58
|
+
def to_a
|
59
|
+
to_h.values.flatten
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
49
63
|
|
50
|
-
|
51
|
-
|
52
|
-
|
64
|
+
def messages_map
|
65
|
+
failures.group_by(&:path).reduce(placeholders) do |hash, (path, msgs)|
|
66
|
+
node = path.reduce(hash) { |a, e| a[e] }
|
53
67
|
|
54
|
-
|
55
|
-
|
56
|
-
node.uniq!(&:signature)
|
57
|
-
end
|
58
|
-
end
|
68
|
+
msgs.each do |msg|
|
69
|
+
node << msg
|
59
70
|
|
60
|
-
|
71
|
+
msg_hints = hint_groups[msg.path]
|
72
|
+
node.concat(msg_hints) if msg_hints
|
73
|
+
end
|
74
|
+
|
75
|
+
node.map!(&:to_s)
|
76
|
+
|
77
|
+
hash
|
78
|
+
end
|
79
|
+
end
|
61
80
|
|
62
|
-
|
81
|
+
def hints_map
|
82
|
+
hints.group_by(&:path).reduce(placeholders) do |hash, (path, msgs)|
|
83
|
+
node = path.reduce(hash) { |a, e| a[e] }
|
84
|
+
|
85
|
+
msgs.each do |msg|
|
86
|
+
node << msg
|
63
87
|
end
|
88
|
+
|
89
|
+
node.map!(&:to_s)
|
90
|
+
|
91
|
+
hash
|
64
92
|
end
|
65
93
|
end
|
66
|
-
alias_method :to_hash, :to_h
|
67
94
|
|
68
|
-
def
|
69
|
-
|
95
|
+
def hint_groups
|
96
|
+
@hint_groups ||= hints.group_by(&:path)
|
70
97
|
end
|
71
98
|
|
72
|
-
|
99
|
+
def initialize_hints!
|
100
|
+
hints.reject! { |hint| HINT_EXCLUSION.include?(hint.predicate) }
|
101
|
+
end
|
73
102
|
|
74
103
|
def initialize_placeholders!
|
75
104
|
@placeholders = paths.reduce({}) do |hash, path|
|
@@ -1,6 +1,11 @@
|
|
1
|
+
require 'dry/core/constants'
|
2
|
+
require 'dry/logic/rule/predicate'
|
3
|
+
|
1
4
|
module Dry
|
2
5
|
module Validation
|
3
6
|
class PredicateRegistry
|
7
|
+
include Core::Constants
|
8
|
+
|
4
9
|
attr_reader :predicates
|
5
10
|
attr_reader :external
|
6
11
|
|
@@ -23,10 +28,7 @@ module Dry
|
|
23
28
|
end
|
24
29
|
|
25
30
|
def update(other)
|
26
|
-
|
27
|
-
res[n] = Logic::Predicate.new(n, fn: p)
|
28
|
-
}
|
29
|
-
predicates.update(unbound_predicates)
|
31
|
+
predicates.update(other)
|
30
32
|
self
|
31
33
|
end
|
32
34
|
end
|
@@ -59,7 +61,7 @@ module Dry
|
|
59
61
|
|
60
62
|
def [](name)
|
61
63
|
predicates.fetch(name) do
|
62
|
-
if external.
|
64
|
+
if external.public_methods.include?(name)
|
63
65
|
external[name]
|
64
66
|
else
|
65
67
|
raise_unknown_predicate_error(name)
|
@@ -68,7 +70,16 @@ module Dry
|
|
68
70
|
end
|
69
71
|
|
70
72
|
def key?(name)
|
71
|
-
predicates.key?(name) || external.
|
73
|
+
predicates.key?(name) || external.public_methods.include?(name)
|
74
|
+
end
|
75
|
+
|
76
|
+
def arg_list(name, *values)
|
77
|
+
predicate = self[name]
|
78
|
+
|
79
|
+
predicate
|
80
|
+
.parameters
|
81
|
+
.map(&:last)
|
82
|
+
.zip(values + Array.new(predicate.arity - values.size, Undefined))
|
72
83
|
end
|
73
84
|
|
74
85
|
def ensure_valid_predicate(name, args_or_arity, schema = nil)
|
@@ -1,25 +1,25 @@
|
|
1
|
-
require 'dry/
|
1
|
+
require 'dry/core/constants'
|
2
2
|
|
3
3
|
module Dry
|
4
4
|
module Validation
|
5
5
|
class Result
|
6
|
-
include
|
6
|
+
include Core::Constants
|
7
|
+
include Dry::Equalizer(:output, :errors)
|
7
8
|
include Enumerable
|
8
9
|
|
9
10
|
attr_reader :output
|
10
|
-
attr_reader :
|
11
|
-
attr_reader :
|
12
|
-
attr_reader :
|
11
|
+
attr_reader :results
|
12
|
+
attr_reader :message_compiler
|
13
|
+
attr_reader :path
|
13
14
|
|
14
15
|
alias_method :to_hash, :output
|
15
16
|
alias_method :to_h, :output # for MRI 2.0, remove it when drop support
|
16
17
|
|
17
|
-
def initialize(output,
|
18
|
+
def initialize(output, results, message_compiler, path)
|
18
19
|
@output = output
|
19
|
-
@
|
20
|
-
@
|
21
|
-
@
|
22
|
-
@messages = EMPTY_HASH if success?
|
20
|
+
@results = results
|
21
|
+
@message_compiler = message_compiler
|
22
|
+
@path = path
|
23
23
|
end
|
24
24
|
|
25
25
|
def each(&block)
|
@@ -31,7 +31,7 @@ module Dry
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def success?
|
34
|
-
|
34
|
+
results.empty?
|
35
35
|
end
|
36
36
|
|
37
37
|
def failure?
|
@@ -42,20 +42,42 @@ module Dry
|
|
42
42
|
message_set(options).dump
|
43
43
|
end
|
44
44
|
|
45
|
+
def errors(options = EMPTY_HASH)
|
46
|
+
message_set(options.merge(hints: false)).dump
|
47
|
+
end
|
48
|
+
|
49
|
+
def hints(options = EMPTY_HASH)
|
50
|
+
message_set(options.merge(failures: false)).dump
|
51
|
+
end
|
52
|
+
|
45
53
|
def message_set(options = EMPTY_HASH)
|
46
|
-
|
47
|
-
.with(options).(error_ast)
|
48
|
-
.with_hints!(hint_compiler.with(options).())
|
54
|
+
message_compiler.with(options).(result_ast)
|
49
55
|
end
|
50
56
|
|
51
57
|
def to_ast
|
52
|
-
|
58
|
+
if name
|
59
|
+
[type, [name, [:set, result_ast]]]
|
60
|
+
else
|
61
|
+
ast
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def ast(*)
|
66
|
+
[:set, result_ast]
|
67
|
+
end
|
68
|
+
|
69
|
+
def name
|
70
|
+
Array(path).last
|
53
71
|
end
|
54
72
|
|
55
73
|
private
|
56
74
|
|
57
|
-
def
|
58
|
-
|
75
|
+
def type
|
76
|
+
success? ? :success : :failure
|
77
|
+
end
|
78
|
+
|
79
|
+
def result_ast
|
80
|
+
@result_ast ||= results.map(&:to_ast)
|
59
81
|
end
|
60
82
|
end
|
61
83
|
end
|
@@ -13,7 +13,7 @@ module Dry
|
|
13
13
|
schema.config.input_processor = other.class.config.input_processor
|
14
14
|
end
|
15
15
|
|
16
|
-
hash?.and(create_rule([:check, [
|
16
|
+
hash?.and(create_rule([:check, [[path], schema.to_ast]]))
|
17
17
|
end
|
18
18
|
|
19
19
|
private
|
@@ -21,12 +21,13 @@ module Dry
|
|
21
21
|
def method_missing(meth, *meth_args)
|
22
22
|
vals, args = meth_args.partition { |arg| arg.class < DSL }
|
23
23
|
|
24
|
-
keys = [
|
24
|
+
keys = [path, vals.map(&:path)].reject(&:empty?)
|
25
25
|
|
26
26
|
registry.ensure_valid_predicate(meth, args.size + keys.size, schema_class)
|
27
|
-
predicate =
|
27
|
+
predicate = predicate(meth, args)
|
28
|
+
|
29
|
+
rule = create_rule([:check, [keys.reverse, predicate]], name)
|
28
30
|
|
29
|
-
rule = create_rule([:check, [name, predicate.to_ast, keys]])
|
30
31
|
add_rule(rule)
|
31
32
|
rule
|
32
33
|
end
|
@@ -48,7 +48,9 @@ module Dry
|
|
48
48
|
target = dsl.schema_class
|
49
49
|
|
50
50
|
if config.input
|
51
|
-
config.input_rule =
|
51
|
+
config.input_rule = -> predicates {
|
52
|
+
Schema::Value.new(registry: predicates).infer_predicates(Array(target.config.input)).to_ast
|
53
|
+
}
|
52
54
|
end
|
53
55
|
|
54
56
|
rules = target.config.rules + (options.fetch(:rules, []) + dsl.rules)
|
@@ -85,10 +87,6 @@ module Dry
|
|
85
87
|
klass =
|
86
88
|
if other.is_a?(self)
|
87
89
|
Class.new(other.class)
|
88
|
-
elsif other.is_a?(Class) && other < Types::Struct
|
89
|
-
Validation.Schema(parent: target, build: false) do
|
90
|
-
other.schema.each { |attr, type| required(attr).filled(type) }
|
91
|
-
end
|
92
90
|
elsif other.respond_to?(:schema) && other.schema.is_a?(self)
|
93
91
|
Class.new(other.schema.class)
|
94
92
|
else
|
@@ -151,12 +149,8 @@ module Dry
|
|
151
149
|
end
|
152
150
|
end
|
153
151
|
|
154
|
-
def self.
|
155
|
-
@
|
156
|
-
end
|
157
|
-
|
158
|
-
def self.hint_compiler
|
159
|
-
@hint_compiler ||= HintCompiler.new(messages, rules: rule_ast)
|
152
|
+
def self.message_compiler
|
153
|
+
@message_compiler ||= MessageCompiler.new(messages)
|
160
154
|
end
|
161
155
|
|
162
156
|
def self.rule_ast
|
@@ -165,8 +159,7 @@ module Dry
|
|
165
159
|
|
166
160
|
def self.default_options
|
167
161
|
@default_options ||= { predicate_registry: registry,
|
168
|
-
|
169
|
-
hint_compiler: hint_compiler,
|
162
|
+
message_compiler: message_compiler,
|
170
163
|
input_processor: input_processor,
|
171
164
|
checks: config.checks }
|
172
165
|
end
|
@@ -6,6 +6,7 @@ module Dry
|
|
6
6
|
class Schema
|
7
7
|
class DSL < BasicObject
|
8
8
|
include ::Dry::Validation::Deprecations
|
9
|
+
include ::Dry::Core::Constants
|
9
10
|
|
10
11
|
attr_reader :name, :registry, :rules, :checks, :parent, :options
|
11
12
|
|
@@ -15,6 +16,7 @@ module Dry
|
|
15
16
|
|
16
17
|
def initialize(options = {})
|
17
18
|
@name = options[:name]
|
19
|
+
@path = options.fetch(:path, name)
|
18
20
|
@parent = options[:parent]
|
19
21
|
@registry = options.fetch(:registry)
|
20
22
|
@rules = options.fetch(:rules, [])
|
@@ -49,7 +51,7 @@ module Dry
|
|
49
51
|
end
|
50
52
|
|
51
53
|
def add_check(check)
|
52
|
-
checks << check
|
54
|
+
checks << check.to_rule
|
53
55
|
self
|
54
56
|
end
|
55
57
|
|
@@ -67,7 +69,7 @@ module Dry
|
|
67
69
|
end
|
68
70
|
|
69
71
|
def path
|
70
|
-
items = [parent && parent.path,
|
72
|
+
items = [parent && parent.path, *@path].flatten.compact.uniq
|
71
73
|
items.size == 1 ? items[0] : items
|
72
74
|
end
|
73
75
|
|
@@ -75,6 +77,10 @@ module Dry
|
|
75
77
|
self.class.new(options.merge(new_options))
|
76
78
|
end
|
77
79
|
|
80
|
+
def predicate(name, args = EMPTY_ARRAY)
|
81
|
+
[:predicate, [name, registry.arg_list(name, *args)]]
|
82
|
+
end
|
83
|
+
|
78
84
|
def predicate?(meth)
|
79
85
|
registry.key?(meth)
|
80
86
|
end
|
@@ -104,7 +110,7 @@ module Dry
|
|
104
110
|
end
|
105
111
|
end
|
106
112
|
|
107
|
-
def create_rule(node)
|
113
|
+
def create_rule(node, name = self.name)
|
108
114
|
Schema::Rule.new(node, name: name, target: self)
|
109
115
|
end
|
110
116
|
end
|
@@ -3,10 +3,19 @@ require 'dry/validation/schema'
|
|
3
3
|
module Dry
|
4
4
|
module Validation
|
5
5
|
class Schema::Form < Schema
|
6
|
-
configure
|
7
|
-
|
8
|
-
|
6
|
+
def self.configure(klass = nil, &block)
|
7
|
+
if klass
|
8
|
+
klass.configure do |config|
|
9
|
+
config.input_processor = :form
|
10
|
+
config.hash_type = :symbolized
|
11
|
+
end
|
12
|
+
klass
|
13
|
+
else
|
14
|
+
super(&block)
|
15
|
+
end
|
9
16
|
end
|
17
|
+
|
18
|
+
configure(self)
|
10
19
|
end
|
11
20
|
end
|
12
21
|
end
|
@@ -3,10 +3,19 @@ require 'dry/validation/schema'
|
|
3
3
|
module Dry
|
4
4
|
module Validation
|
5
5
|
class Schema::JSON < Schema
|
6
|
-
configure
|
7
|
-
|
8
|
-
|
6
|
+
def self.configure(klass = nil, &block)
|
7
|
+
if klass
|
8
|
+
klass.configure do |config|
|
9
|
+
config.input_processor = :json
|
10
|
+
config.hash_type = :symbolized
|
11
|
+
end
|
12
|
+
klass
|
13
|
+
else
|
14
|
+
super(&block)
|
15
|
+
end
|
9
16
|
end
|
17
|
+
|
18
|
+
configure(self)
|
10
19
|
end
|
11
20
|
end
|
12
21
|
end
|