dry-validation 0.9.5 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -31,18 +31,18 @@ module Dry
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def hash?(&block)
|
34
|
-
predicate =
|
34
|
+
predicate = predicate(:hash?)
|
35
35
|
|
36
36
|
if block
|
37
37
|
val = value.instance_eval(&block)
|
38
38
|
|
39
|
-
rule = create_rule(
|
39
|
+
rule = create_rule(predicate)
|
40
40
|
.and(create_rule([type, [name, val.to_ast]]))
|
41
41
|
|
42
42
|
add_rule(rule)
|
43
43
|
rule
|
44
44
|
else
|
45
|
-
add_rule(create_rule(
|
45
|
+
add_rule(create_rule(predicate))
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
@@ -54,13 +54,13 @@ module Dry
|
|
54
54
|
|
55
55
|
def method_missing(meth, *args, &block)
|
56
56
|
registry.ensure_valid_predicate(meth, args)
|
57
|
-
predicate =
|
57
|
+
predicate = predicate(meth, args)
|
58
58
|
|
59
59
|
if block
|
60
60
|
val = value.instance_eval(&block)
|
61
|
-
add_rule(create_rule([:and, [
|
61
|
+
add_rule(create_rule([:and, [predicate, val.to_ast]]))
|
62
62
|
else
|
63
|
-
rule = create_rule([type, [name, predicate
|
63
|
+
rule = create_rule([type, [name, predicate]])
|
64
64
|
add_rule(rule)
|
65
65
|
rule
|
66
66
|
end
|
@@ -88,14 +88,14 @@ module Dry
|
|
88
88
|
end
|
89
89
|
|
90
90
|
def maybe(*predicates, &block)
|
91
|
-
left = key(:none?)
|
91
|
+
left = key(:none?).not
|
92
92
|
|
93
93
|
from_predicates = infer_predicates(predicates, :maybe).reduce(:and)
|
94
94
|
from_block = block ? Key[name, registry: registry].instance_eval(&block) : nil
|
95
95
|
|
96
96
|
right = [from_predicates, from_block].compact.reduce(:and) || key(:filled?)
|
97
97
|
|
98
|
-
rule = left.
|
98
|
+
rule = left.then(right)
|
99
99
|
|
100
100
|
add_rule(__send__(type, rule))
|
101
101
|
end
|
@@ -122,10 +122,12 @@ module Dry
|
|
122
122
|
end
|
123
123
|
|
124
124
|
def to_ast
|
125
|
+
rule_node = name ? [:rule, [name, node]] : node
|
126
|
+
|
125
127
|
if deps.empty?
|
126
|
-
|
128
|
+
rule_node
|
127
129
|
else
|
128
|
-
[:guard, [deps,
|
130
|
+
[:guard, [deps, rule_node]]
|
129
131
|
end
|
130
132
|
end
|
131
133
|
|
@@ -138,22 +140,22 @@ module Dry
|
|
138
140
|
end
|
139
141
|
|
140
142
|
def and(other)
|
141
|
-
new([:and, [
|
143
|
+
new([:and, [to_ast, other.to_ast]])
|
142
144
|
end
|
143
145
|
alias_method :&, :and
|
144
146
|
|
145
147
|
def or(other)
|
146
|
-
new([:or, [
|
148
|
+
new([:or, [to_ast, other.to_ast]])
|
147
149
|
end
|
148
150
|
alias_method :|, :or
|
149
151
|
|
150
152
|
def xor(other)
|
151
|
-
new([:xor, [
|
153
|
+
new([:xor, [to_ast, other.to_ast]])
|
152
154
|
end
|
153
155
|
alias_method :^, :xor
|
154
156
|
|
155
157
|
def then(other)
|
156
|
-
new([:implication, [
|
158
|
+
new([:implication, [to_ast, other.to_ast]])
|
157
159
|
end
|
158
160
|
alias_method :>, :then
|
159
161
|
|
@@ -173,6 +175,10 @@ module Dry
|
|
173
175
|
self.class.new(node, options.merge(new_options))
|
174
176
|
end
|
175
177
|
|
178
|
+
def to_rule
|
179
|
+
self
|
180
|
+
end
|
181
|
+
|
176
182
|
private
|
177
183
|
|
178
184
|
def method_missing(meth, *args, &block)
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'dry/struct'
|
1
2
|
require 'dry/validation/schema/dsl'
|
2
3
|
|
3
4
|
module Dry
|
@@ -76,26 +77,24 @@ module Dry
|
|
76
77
|
|
77
78
|
def when(*predicates, &block)
|
78
79
|
left = infer_predicates(predicates, Check[path, type: type, registry: registry])
|
80
|
+
right = Value.new(type: type, registry: registry).instance_eval(&block)
|
79
81
|
|
80
|
-
right
|
81
|
-
right.instance_eval(&block)
|
82
|
-
|
83
|
-
add_check(left.then(create_rule(right.to_ast)))
|
82
|
+
add_check(left.then(right.to_rule))
|
84
83
|
|
85
84
|
self
|
86
85
|
end
|
87
86
|
|
88
87
|
def rule(id = nil, **options, &block)
|
89
88
|
if id
|
90
|
-
val = Value[id, registry: registry]
|
89
|
+
val = Value[id, registry: registry, schema_class: schema_class]
|
91
90
|
res = val.instance_exec(&block)
|
92
91
|
else
|
93
92
|
id, deps = options.to_a.first
|
94
|
-
val = Value[id, registry: registry]
|
95
|
-
res = val.instance_exec(*deps.map { |
|
93
|
+
val = Value[id, registry: registry, schema_class: schema_class]
|
94
|
+
res = val.instance_exec(*deps.map { |path| val.value(id, path: path) }, &block)
|
96
95
|
end
|
97
96
|
|
98
|
-
add_check(val.with(rules: [res.with(deps: deps || [])]))
|
97
|
+
add_check(val.with(rules: [res.with(name: id, deps: deps || [])]))
|
99
98
|
end
|
100
99
|
|
101
100
|
def confirmation
|
@@ -103,17 +102,26 @@ module Dry
|
|
103
102
|
|
104
103
|
parent.optional(conf).maybe
|
105
104
|
|
106
|
-
rule(conf => [conf, name])
|
105
|
+
rule(conf => [conf, name]) do |left, right|
|
106
|
+
left.eql?(right)
|
107
|
+
end
|
107
108
|
end
|
108
109
|
|
109
|
-
def value(
|
110
|
-
check(name, registry: registry, rules: rules)
|
110
|
+
def value(path, opts = {})
|
111
|
+
check(name || path, { registry: registry, rules: rules, path: path }.merge(opts))
|
111
112
|
end
|
112
113
|
|
113
114
|
def check(name, options = {})
|
114
115
|
Check[name, options.merge(type: type)]
|
115
116
|
end
|
116
117
|
|
118
|
+
def validate(**opts, &block)
|
119
|
+
id, *deps = opts.to_a.flatten
|
120
|
+
name = deps.size > 1 ? id : deps.first
|
121
|
+
rule = create_rule([:check, [deps, [:custom, [id, block]]]], name).with(deps: deps)
|
122
|
+
add_check(rule)
|
123
|
+
end
|
124
|
+
|
117
125
|
def configure(&block)
|
118
126
|
schema_class.class_eval(&block)
|
119
127
|
@registry = schema_class.registry
|
@@ -141,21 +149,15 @@ module Dry
|
|
141
149
|
end
|
142
150
|
|
143
151
|
def key?(name)
|
144
|
-
create_rule(
|
145
|
-
end
|
146
|
-
|
147
|
-
def predicate(name, *args)
|
148
|
-
registry.ensure_valid_predicate(name, args, schema_class)
|
149
|
-
registry[name].curry(*args)
|
152
|
+
create_rule(predicate(:key?, name))
|
150
153
|
end
|
151
154
|
|
152
155
|
def node(input, *args)
|
153
156
|
if input.is_a?(::Symbol)
|
154
|
-
|
157
|
+
registry.ensure_valid_predicate(input, args, schema_class)
|
158
|
+
[type, [name, predicate(input, args)]]
|
155
159
|
elsif input.respond_to?(:rule)
|
156
160
|
[type, [name, [:type, input]]]
|
157
|
-
elsif input.is_a?(::Class) && input < ::Dry::Types::Struct
|
158
|
-
[type, [name, [:schema, Schema.create_class(self, input)]]]
|
159
161
|
elsif input.is_a?(Schema)
|
160
162
|
[type, [name, schema(input).to_ast]]
|
161
163
|
else
|
@@ -190,7 +192,7 @@ module Dry
|
|
190
192
|
def method_missing(meth, *args, &block)
|
191
193
|
return schema_class.instance_method(meth) if dyn_arg?(meth)
|
192
194
|
|
193
|
-
val_rule = create_rule(
|
195
|
+
val_rule = create_rule(predicate(meth, args))
|
194
196
|
|
195
197
|
if block
|
196
198
|
val = new.instance_eval(&block)
|
@@ -7,11 +7,9 @@ require 'dry/validation/schema/key'
|
|
7
7
|
require 'dry/validation/schema/value'
|
8
8
|
require 'dry/validation/schema/check'
|
9
9
|
|
10
|
-
require 'dry/validation/error'
|
11
10
|
require 'dry/validation/result'
|
12
11
|
require 'dry/validation/messages'
|
13
|
-
require 'dry/validation/
|
14
|
-
require 'dry/validation/hint_compiler'
|
12
|
+
require 'dry/validation/message_compiler'
|
15
13
|
|
16
14
|
require 'dry/validation/schema/deprecated'
|
17
15
|
require 'dry/validation/schema/class_interface'
|
@@ -34,9 +32,7 @@ module Dry
|
|
34
32
|
|
35
33
|
attr_reader :rule_compiler
|
36
34
|
|
37
|
-
attr_reader :
|
38
|
-
|
39
|
-
attr_reader :hint_compiler
|
35
|
+
attr_reader :message_compiler
|
40
36
|
|
41
37
|
attr_reader :options
|
42
38
|
|
@@ -49,16 +45,16 @@ module Dry
|
|
49
45
|
@config = self.class.config
|
50
46
|
@predicates = options.fetch(:predicate_registry).bind(self)
|
51
47
|
@rule_compiler = SchemaCompiler.new(predicates, options)
|
52
|
-
@
|
53
|
-
@hint_compiler = options.fetch(:hint_compiler)
|
48
|
+
@message_compiler = options.fetch(:message_compiler)
|
54
49
|
@input_processor = options[:input_processor]
|
55
|
-
|
50
|
+
|
51
|
+
@input_rule = rule_compiler.visit(config.input_rule.(predicates)) if config.input_rule
|
56
52
|
|
57
53
|
initialize_options(options)
|
58
54
|
initialize_rules(rules)
|
59
55
|
initialize_checks(options.fetch(:checks, []))
|
60
56
|
|
61
|
-
@executor = Executor.new
|
57
|
+
@executor = Executor.new do |steps|
|
62
58
|
steps << ProcessInput.new(input_processor) if input_processor
|
63
59
|
steps << ApplyInputRule.new(input_rule) if input_rule
|
64
60
|
steps << ApplyRules.new(@rules)
|
@@ -74,7 +70,7 @@ module Dry
|
|
74
70
|
|
75
71
|
def call(input)
|
76
72
|
output, result = executor.(input)
|
77
|
-
Result.new(output, result,
|
73
|
+
Result.new(output, result, message_compiler, config.path)
|
78
74
|
end
|
79
75
|
|
80
76
|
def curry(*curry_args)
|
@@ -89,9 +85,10 @@ module Dry
|
|
89
85
|
1
|
90
86
|
end
|
91
87
|
|
92
|
-
def
|
88
|
+
def ast(*)
|
93
89
|
self.class.to_ast
|
94
90
|
end
|
91
|
+
alias_method :to_ast, :ast
|
95
92
|
|
96
93
|
private
|
97
94
|
|
@@ -14,6 +14,10 @@ module Dry
|
|
14
14
|
rule.(input) if deps_valid?(results)
|
15
15
|
end
|
16
16
|
|
17
|
+
def with(options)
|
18
|
+
self.class.new(rule.with(options), deps)
|
19
|
+
end
|
20
|
+
|
17
21
|
private
|
18
22
|
|
19
23
|
def deps_valid?(results)
|
@@ -21,7 +25,7 @@ module Dry
|
|
21
25
|
result = nil
|
22
26
|
Array(path).each do |name|
|
23
27
|
curr = results[name]
|
24
|
-
result = curr.success? if curr
|
28
|
+
result = curr.success? if curr
|
25
29
|
end
|
26
30
|
result
|
27
31
|
end
|
@@ -37,8 +41,18 @@ module Dry
|
|
37
41
|
@schema = predicates.schema
|
38
42
|
end
|
39
43
|
|
44
|
+
def visit_rule(node)
|
45
|
+
id, other = node
|
46
|
+
visit(other).with(id: id)
|
47
|
+
end
|
48
|
+
|
40
49
|
def visit_predicate(node)
|
41
|
-
super.
|
50
|
+
super.eval_args(schema)
|
51
|
+
end
|
52
|
+
|
53
|
+
def visit_custom(node)
|
54
|
+
id, predicate = node
|
55
|
+
Logic::Rule.new(predicate).with(id: id).bind(schema)
|
42
56
|
end
|
43
57
|
|
44
58
|
def visit_schema(klass)
|
data/lib/dry/validation.rb
CHANGED
@@ -1,32 +1,16 @@
|
|
1
1
|
require 'dry-equalizer'
|
2
2
|
require 'dry-configurable'
|
3
3
|
require 'dry-container'
|
4
|
+
require 'dry/core/extensions'
|
4
5
|
|
5
6
|
require 'dry/validation/schema'
|
6
7
|
require 'dry/validation/schema/form'
|
7
8
|
require 'dry/validation/schema/json'
|
8
9
|
|
9
10
|
module Dry
|
10
|
-
# FIXME: move this to dry-logic if it works lol
|
11
|
-
require 'dry/logic/predicate'
|
12
|
-
module Logic
|
13
|
-
class Predicate
|
14
|
-
class Curried < Predicate
|
15
|
-
def evaluate_args!(schema)
|
16
|
-
@args = args.map { |arg|
|
17
|
-
arg.is_a?(UnboundMethod) ? arg.bind(schema).() : arg
|
18
|
-
}
|
19
|
-
self
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def evaluate_args!(*)
|
24
|
-
self
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
11
|
module Validation
|
12
|
+
extend Dry::Core::Extensions
|
13
|
+
|
30
14
|
MissingMessageError = Class.new(StandardError)
|
31
15
|
InvalidSchemaError = Class.new(StandardError)
|
32
16
|
|
@@ -45,12 +29,16 @@ module Dry
|
|
45
29
|
end
|
46
30
|
end
|
47
31
|
|
48
|
-
def self.Form(
|
49
|
-
|
32
|
+
def self.Form(base = nil, **options, &block)
|
33
|
+
klass = base ? Schema::Form.configure(Class.new(base)) : Schema::Form
|
34
|
+
Validation.Schema(klass, options, &block)
|
50
35
|
end
|
51
36
|
|
52
|
-
def self.JSON(
|
53
|
-
|
37
|
+
def self.JSON(base = Schema::JSON, **options, &block)
|
38
|
+
klass = base ? Schema::JSON.configure(Class.new(base)) : Schema::JSON
|
39
|
+
Validation.Schema(klass, options, &block)
|
54
40
|
end
|
55
41
|
end
|
56
42
|
end
|
43
|
+
|
44
|
+
require 'dry/validation/extensions'
|
@@ -0,0 +1,38 @@
|
|
1
|
+
RSpec.describe Dry::Validation::Result do
|
2
|
+
before { Dry::Validation.load_extensions(:monads) }
|
3
|
+
|
4
|
+
let(:schema) { Dry::Validation.Schema { required(:name).filled(:str?, size?: 2..4) } }
|
5
|
+
|
6
|
+
context 'with valid input' do
|
7
|
+
let(:input) { { name: 'Jane' } }
|
8
|
+
|
9
|
+
describe '#to_either' do
|
10
|
+
it 'returns a Right instance' do
|
11
|
+
either = result.to_either
|
12
|
+
|
13
|
+
expect(either).to be_right
|
14
|
+
expect(either.value).to eql(name: 'Jane')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'with invalid input' do
|
20
|
+
let(:input) { { name: '' } }
|
21
|
+
|
22
|
+
describe '#to_either' do
|
23
|
+
it 'returns a Left instance' do
|
24
|
+
either = result.to_either
|
25
|
+
|
26
|
+
expect(either).to be_left
|
27
|
+
expect(either.value).to eql(name: ['must be filled', 'length must be within 2 - 4'])
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'returns full messages' do
|
31
|
+
either = result.to_either(full: true)
|
32
|
+
|
33
|
+
expect(either).to be_left
|
34
|
+
expect(either.value).to eql(name: ['name must be filled', 'name length must be within 2 - 4'])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
RSpec.describe Dry::Validation::Schema, 'defining schema using dry struct' do
|
2
|
+
before do
|
3
|
+
Dry::Validation.load_extensions(:struct)
|
4
|
+
end
|
5
|
+
|
6
|
+
subject(:schema) do
|
7
|
+
Dry::Validation.Schema do
|
8
|
+
required(:person).filled(Test::Person)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
before do
|
13
|
+
class Test::Name < Dry::Struct::Value
|
14
|
+
attribute :given_name, Dry::Types['strict.string']
|
15
|
+
attribute :family_name, Dry::Types['strict.string']
|
16
|
+
end
|
17
|
+
|
18
|
+
class Test::Person < Dry::Struct::Value
|
19
|
+
attribute :name, Test::Name
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'handles nested structs' do
|
24
|
+
expect(schema.call(person: { name: { given_name: 'Tim', family_name: 'Cooper' } })).to be_success
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'fails when input is not valid' do
|
28
|
+
expect(schema.call(person: { name: { given_name: 'Tim' } }).messages).to eq(
|
29
|
+
person: { name: { family_name: ['is missing', 'must be String'] } }
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|