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