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