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.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -8
  3. data/CHANGELOG.md +34 -1
  4. data/Gemfile +2 -0
  5. data/Rakefile +12 -3
  6. data/config/errors.yml +1 -0
  7. data/dry-validation.gemspec +3 -2
  8. data/lib/dry/validation/executor.rb +3 -27
  9. data/lib/dry/validation/extensions/monads.rb +17 -0
  10. data/lib/dry/validation/extensions/struct.rb +32 -0
  11. data/lib/dry/validation/extensions.rb +7 -0
  12. data/lib/dry/validation/input_processor_compiler.rb +19 -3
  13. data/lib/dry/validation/message.rb +43 -30
  14. data/lib/dry/validation/message_compiler/visitor_opts.rb +39 -0
  15. data/lib/dry/validation/message_compiler.rb +98 -52
  16. data/lib/dry/validation/message_set.rb +59 -30
  17. data/lib/dry/validation/predicate_registry.rb +17 -6
  18. data/lib/dry/validation/result.rb +39 -17
  19. data/lib/dry/validation/schema/check.rb +5 -4
  20. data/lib/dry/validation/schema/class_interface.rb +6 -13
  21. data/lib/dry/validation/schema/dsl.rb +9 -3
  22. data/lib/dry/validation/schema/form.rb +12 -3
  23. data/lib/dry/validation/schema/json.rb +12 -3
  24. data/lib/dry/validation/schema/key.rb +6 -6
  25. data/lib/dry/validation/schema/rule.rb +14 -8
  26. data/lib/dry/validation/schema/value.rb +23 -21
  27. data/lib/dry/validation/schema.rb +9 -12
  28. data/lib/dry/validation/schema_compiler.rb +16 -2
  29. data/lib/dry/validation/version.rb +1 -1
  30. data/lib/dry/validation.rb +11 -23
  31. data/spec/extensions/monads/result_spec.rb +38 -0
  32. data/spec/extensions/struct/schema_spec.rb +32 -0
  33. data/spec/integration/custom_predicates_spec.rb +7 -6
  34. data/spec/integration/form/predicates/size/fixed_spec.rb +0 -2
  35. data/spec/integration/form/predicates/size/range_spec.rb +0 -2
  36. data/spec/integration/hints_spec.rb +2 -6
  37. data/spec/integration/json/defining_base_schema_spec.rb +41 -0
  38. data/spec/integration/{error_compiler_spec.rb → message_compiler_spec.rb} +79 -131
  39. data/spec/integration/result_spec.rb +26 -4
  40. data/spec/integration/schema/check_with_nested_el_spec.rb +1 -1
  41. data/spec/integration/schema/check_with_nth_el_spec.rb +1 -1
  42. data/spec/integration/schema/defining_base_schema_spec.rb +3 -0
  43. data/spec/integration/schema/dynamic_predicate_args_spec.rb +34 -9
  44. data/spec/integration/schema/form/defining_base_schema_spec.rb +41 -0
  45. data/spec/integration/schema/json_spec.rb +1 -0
  46. data/spec/integration/schema/macros/input_spec.rb +26 -0
  47. data/spec/integration/schema/macros/rule_spec.rb +2 -1
  48. data/spec/integration/schema/macros/value_spec.rb +1 -1
  49. data/spec/integration/schema/macros/when_spec.rb +1 -24
  50. data/spec/integration/schema/or_spec.rb +87 -0
  51. data/spec/integration/schema/predicates/custom_spec.rb +4 -4
  52. data/spec/integration/schema/predicates/even_spec.rb +10 -10
  53. data/spec/integration/schema/predicates/odd_spec.rb +10 -10
  54. data/spec/integration/schema/predicates/size/fixed_spec.rb +0 -3
  55. data/spec/integration/schema/predicates/size/range_spec.rb +0 -2
  56. data/spec/integration/schema/predicates/type_spec.rb +22 -0
  57. data/spec/integration/schema/using_types_spec.rb +14 -41
  58. data/spec/integration/schema/validate_spec.rb +83 -0
  59. data/spec/integration/schema/xor_spec.rb +5 -5
  60. data/spec/integration/schema_builders_spec.rb +4 -2
  61. data/spec/integration/schema_spec.rb +8 -0
  62. data/spec/shared/message_compiler.rb +11 -0
  63. data/spec/shared/predicate_helper.rb +5 -3
  64. data/spec/spec_helper.rb +15 -0
  65. data/spec/unit/input_processor_compiler/form_spec.rb +3 -3
  66. data/spec/unit/message_compiler/visit_failure_spec.rb +38 -0
  67. data/spec/unit/message_compiler/visit_spec.rb +16 -0
  68. data/spec/unit/message_compiler_spec.rb +7 -0
  69. data/spec/unit/predicate_registry_spec.rb +2 -2
  70. data/spec/unit/schema/key_spec.rb +19 -12
  71. data/spec/unit/schema/rule_spec.rb +14 -6
  72. data/spec/unit/schema/value_spec.rb +49 -52
  73. metadata +50 -20
  74. data/lib/dry/validation/constants.rb +0 -6
  75. data/lib/dry/validation/error.rb +0 -26
  76. data/lib/dry/validation/error_compiler.rb +0 -81
  77. data/lib/dry/validation/hint_compiler.rb +0 -104
  78. data/spec/unit/error_compiler_spec.rb +0 -7
  79. 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 = registry[:hash?]
34
+ predicate = predicate(:hash?)
35
35
 
36
36
  if block
37
37
  val = value.instance_eval(&block)
38
38
 
39
- rule = create_rule([:val, predicate.to_ast])
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([:val, predicate.to_ast]))
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 = registry[meth].curry(*args)
57
+ predicate = predicate(meth, args)
58
58
 
59
59
  if block
60
60
  val = value.instance_eval(&block)
61
- add_rule(create_rule([:and, [[:val, predicate.to_ast], val.to_ast]]))
61
+ add_rule(create_rule([:and, [predicate, val.to_ast]]))
62
62
  else
63
- rule = create_rule([type, [name, predicate.to_ast]])
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.or(right)
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
- node
128
+ rule_node
127
129
  else
128
- [:guard, [deps, node]]
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, [node, other.to_ast]])
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, [node, other.to_ast]])
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, [node, other.to_ast]])
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, [node, other.to_ast]])
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 = Value.new(type: type, registry: registry)
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 { |name| val.value(name) }, &block)
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]) { |left, right| left.eql?(right) }
105
+ rule(conf => [conf, name]) do |left, right|
106
+ left.eql?(right)
107
+ end
107
108
  end
108
109
 
109
- def value(name)
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([:val, registry[:key?].curry(name).to_ast])
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
- [type, [name, predicate(input, *args).to_ast]]
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([:val, predicate(meth, *args).to_ast])
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/error_compiler'
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 :error_compiler
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
- @error_compiler = options.fetch(:error_compiler)
53
- @hint_compiler = options.fetch(:hint_compiler)
48
+ @message_compiler = options.fetch(:message_compiler)
54
49
  @input_processor = options[:input_processor]
55
- @input_rule = rule_compiler.visit(config.input_rule.to_ast) if config.input_rule
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(config.path) do |steps|
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, error_compiler, hint_compiler)
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 to_ast
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.respond_to?(:success)
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.evaluate_args!(schema)
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)
@@ -1,5 +1,5 @@
1
1
  module Dry
2
2
  module Validation
3
- VERSION = '0.9.5'.freeze
3
+ VERSION = '0.10.0'.freeze
4
4
  end
5
5
  end
@@ -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(options = {}, &block)
49
- Validation.Schema(Schema::Form, options, &block)
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(options = {}, &block)
53
- Validation.Schema(Schema::JSON, options, &block)
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