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
@@ -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
|