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
@@ -1,20 +1,27 @@
1
- require 'dry/validation/constants'
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
- attr_reader :messages, :hints, :paths, :placeholders
9
+ HINT_EXCLUSION = %i(key? filled? none? bool? str? int? float? decimal? date? date_time? time? hash? array?).freeze
9
10
 
10
- def self.[](messages)
11
- new(messages.flatten)
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
- @paths = map(&:path).uniq
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? && messages.all?(&:root?)
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
- group_by(&:path).reduce(placeholders) do |hash, (path, msgs)|
48
- node = path.reduce(hash) { |a, e| a[e] }
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
- msgs.each do |msg|
51
- node << msg
52
- msg_hints = hints[msg.index_path]
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
- if msg_hints
55
- node.concat(msg_hints)
56
- node.uniq!(&:signature)
57
- end
58
- end
68
+ msgs.each do |msg|
69
+ node << msg
59
70
 
60
- node.map!(&:to_s)
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
- hash
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 to_a
69
- to_h.values.flatten
95
+ def hint_groups
96
+ @hint_groups ||= hints.group_by(&:path)
70
97
  end
71
98
 
72
- private
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
- unbound_predicates = other.each_with_object({}) { |(n, p), res|
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.key?(name)
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.key?(name)
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/validation/constants'
1
+ require 'dry/core/constants'
2
2
 
3
3
  module Dry
4
4
  module Validation
5
5
  class Result
6
- include Dry::Equalizer(:output, :messages)
6
+ include Core::Constants
7
+ include Dry::Equalizer(:output, :errors)
7
8
  include Enumerable
8
9
 
9
10
  attr_reader :output
10
- attr_reader :errors
11
- attr_reader :error_compiler
12
- attr_reader :hint_compiler
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, errors, error_compiler, hint_compiler)
18
+ def initialize(output, results, message_compiler, path)
18
19
  @output = output
19
- @errors = errors
20
- @error_compiler = error_compiler
21
- @hint_compiler = hint_compiler
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
- errors.empty?
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
- error_compiler
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
- [:set, error_ast]
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 error_ast
58
- @error_ast ||= errors.map { |error| error.to_ast }
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, [name, schema.to_ast], [path]]))
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 = [name, *vals.map(&:name)]
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 = registry[meth].curry(*args)
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 = dsl.infer_predicates(Array(target.config.input))
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.error_compiler
155
- @error_compiler ||= ErrorCompiler.new(messages)
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
- error_compiler: error_compiler,
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, name].flatten.compact.uniq
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 do |config|
7
- config.input_processor = :form
8
- config.hash_type = :symbolized
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 do |config|
7
- config.input_processor = :json
8
- config.hash_type = :symbolized
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