dry-validation 0.13.3 → 1.7.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 (207) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +506 -95
  3. data/LICENSE +1 -1
  4. data/README.md +16 -12
  5. data/config/errors.yml +3 -88
  6. data/dry-validation.gemspec +37 -24
  7. data/lib/dry/validation/config.rb +24 -0
  8. data/lib/dry/validation/constants.rb +43 -0
  9. data/lib/dry/validation/contract/class_interface.rb +230 -0
  10. data/lib/dry/validation/contract.rb +171 -0
  11. data/lib/dry/validation/evaluator.rb +224 -0
  12. data/lib/dry/validation/extensions/hints.rb +67 -0
  13. data/lib/dry/validation/extensions/monads.rb +24 -8
  14. data/lib/dry/validation/extensions/predicates_as_macros.rb +75 -0
  15. data/lib/dry/validation/failures.rb +70 -0
  16. data/lib/dry/validation/function.rb +44 -0
  17. data/lib/dry/validation/macro.rb +38 -0
  18. data/lib/dry/validation/macros.rb +104 -0
  19. data/lib/dry/validation/message.rb +80 -80
  20. data/lib/dry/validation/message_set.rb +80 -105
  21. data/lib/dry/validation/messages/resolver.rb +131 -0
  22. data/lib/dry/validation/result.rb +183 -41
  23. data/lib/dry/validation/rule.rb +135 -0
  24. data/lib/dry/validation/schema_ext.rb +19 -0
  25. data/lib/dry/validation/values.rb +104 -0
  26. data/lib/dry/validation/version.rb +3 -1
  27. data/lib/dry/validation.rb +45 -28
  28. data/lib/dry-validation.rb +3 -1
  29. metadata +46 -344
  30. data/.codeclimate.yml +0 -17
  31. data/.gitignore +0 -9
  32. data/.rspec +0 -3
  33. data/.travis.yml +0 -29
  34. data/CONTRIBUTING.md +0 -31
  35. data/Gemfile +0 -25
  36. data/Rakefile +0 -22
  37. data/benchmarks/benchmark_form_invalid.rb +0 -64
  38. data/benchmarks/benchmark_form_valid.rb +0 -64
  39. data/benchmarks/benchmark_schema_invalid_huge.rb +0 -52
  40. data/benchmarks/profile_schema_call_invalid.rb +0 -20
  41. data/benchmarks/profile_schema_call_valid.rb +0 -20
  42. data/benchmarks/profile_schema_definition.rb +0 -14
  43. data/benchmarks/profile_schema_huge_invalid.rb +0 -30
  44. data/benchmarks/profile_schema_messages_invalid.rb +0 -20
  45. data/benchmarks/suite.rb +0 -5
  46. data/examples/basic.rb +0 -15
  47. data/examples/each.rb +0 -14
  48. data/examples/json.rb +0 -12
  49. data/examples/multiple.rb +0 -27
  50. data/examples/nested.rb +0 -22
  51. data/examples/params.rb +0 -11
  52. data/lib/dry/validation/compat/form.rb +0 -67
  53. data/lib/dry/validation/deprecations.rb +0 -24
  54. data/lib/dry/validation/executor.rb +0 -91
  55. data/lib/dry/validation/extensions/struct.rb +0 -32
  56. data/lib/dry/validation/extensions.rb +0 -7
  57. data/lib/dry/validation/input_processor_compiler/json.rb +0 -45
  58. data/lib/dry/validation/input_processor_compiler/params.rb +0 -49
  59. data/lib/dry/validation/input_processor_compiler/sanitizer.rb +0 -47
  60. data/lib/dry/validation/input_processor_compiler.rb +0 -137
  61. data/lib/dry/validation/message_compiler/visitor_opts.rb +0 -37
  62. data/lib/dry/validation/message_compiler.rb +0 -188
  63. data/lib/dry/validation/messages/abstract.rb +0 -119
  64. data/lib/dry/validation/messages/i18n.rb +0 -47
  65. data/lib/dry/validation/messages/namespaced.rb +0 -39
  66. data/lib/dry/validation/messages/yaml.rb +0 -61
  67. data/lib/dry/validation/messages.rb +0 -14
  68. data/lib/dry/validation/predicate_registry.rb +0 -115
  69. data/lib/dry/validation/predicates.rb +0 -19
  70. data/lib/dry/validation/schema/check.rb +0 -37
  71. data/lib/dry/validation/schema/class_interface.rb +0 -190
  72. data/lib/dry/validation/schema/deprecated.rb +0 -30
  73. data/lib/dry/validation/schema/dsl.rb +0 -118
  74. data/lib/dry/validation/schema/form.rb +0 -9
  75. data/lib/dry/validation/schema/json.rb +0 -21
  76. data/lib/dry/validation/schema/key.rb +0 -71
  77. data/lib/dry/validation/schema/params.rb +0 -22
  78. data/lib/dry/validation/schema/rule.rb +0 -202
  79. data/lib/dry/validation/schema/value.rb +0 -211
  80. data/lib/dry/validation/schema.rb +0 -126
  81. data/lib/dry/validation/schema_compiler.rb +0 -81
  82. data/lib/dry/validation/template.rb +0 -66
  83. data/lib/dry/validation/type_specs.rb +0 -70
  84. data/log/.gitkeep +0 -0
  85. data/spec/extensions/monads/result_spec.rb +0 -40
  86. data/spec/extensions/struct/schema_spec.rb +0 -32
  87. data/spec/fixtures/locales/en.yml +0 -8
  88. data/spec/fixtures/locales/pl.yml +0 -22
  89. data/spec/integration/custom_error_messages_spec.rb +0 -54
  90. data/spec/integration/custom_predicates_spec.rb +0 -228
  91. data/spec/integration/hints_spec.rb +0 -170
  92. data/spec/integration/injecting_rules_spec.rb +0 -30
  93. data/spec/integration/json/defining_base_schema_spec.rb +0 -41
  94. data/spec/integration/localized_error_messages_spec.rb +0 -72
  95. data/spec/integration/message_compiler_spec.rb +0 -405
  96. data/spec/integration/messages/i18n_spec.rb +0 -104
  97. data/spec/integration/optional_keys_spec.rb +0 -28
  98. data/spec/integration/params/predicates/array_spec.rb +0 -287
  99. data/spec/integration/params/predicates/empty_spec.rb +0 -263
  100. data/spec/integration/params/predicates/eql_spec.rb +0 -327
  101. data/spec/integration/params/predicates/even_spec.rb +0 -455
  102. data/spec/integration/params/predicates/excluded_from_spec.rb +0 -455
  103. data/spec/integration/params/predicates/excludes_spec.rb +0 -391
  104. data/spec/integration/params/predicates/false_spec.rb +0 -455
  105. data/spec/integration/params/predicates/filled_spec.rb +0 -467
  106. data/spec/integration/params/predicates/format_spec.rb +0 -454
  107. data/spec/integration/params/predicates/gt_spec.rb +0 -519
  108. data/spec/integration/params/predicates/gteq_spec.rb +0 -519
  109. data/spec/integration/params/predicates/included_in_spec.rb +0 -455
  110. data/spec/integration/params/predicates/includes_spec.rb +0 -391
  111. data/spec/integration/params/predicates/key_spec.rb +0 -67
  112. data/spec/integration/params/predicates/lt_spec.rb +0 -519
  113. data/spec/integration/params/predicates/lteq_spec.rb +0 -519
  114. data/spec/integration/params/predicates/max_size_spec.rb +0 -391
  115. data/spec/integration/params/predicates/min_size_spec.rb +0 -391
  116. data/spec/integration/params/predicates/none_spec.rb +0 -265
  117. data/spec/integration/params/predicates/not_eql_spec.rb +0 -327
  118. data/spec/integration/params/predicates/odd_spec.rb +0 -455
  119. data/spec/integration/params/predicates/size/fixed_spec.rb +0 -393
  120. data/spec/integration/params/predicates/size/range_spec.rb +0 -396
  121. data/spec/integration/params/predicates/true_spec.rb +0 -455
  122. data/spec/integration/params/predicates/type_spec.rb +0 -391
  123. data/spec/integration/result_spec.rb +0 -81
  124. data/spec/integration/schema/array_schema_spec.rb +0 -59
  125. data/spec/integration/schema/check_rules_spec.rb +0 -119
  126. data/spec/integration/schema/check_with_nested_el_spec.rb +0 -37
  127. data/spec/integration/schema/check_with_nth_el_spec.rb +0 -25
  128. data/spec/integration/schema/default_settings_spec.rb +0 -11
  129. data/spec/integration/schema/defining_base_schema_spec.rb +0 -41
  130. data/spec/integration/schema/dynamic_predicate_args_spec.rb +0 -43
  131. data/spec/integration/schema/each_with_set_spec.rb +0 -70
  132. data/spec/integration/schema/extending_dsl_spec.rb +0 -27
  133. data/spec/integration/schema/form_spec.rb +0 -236
  134. data/spec/integration/schema/hash_schema_spec.rb +0 -47
  135. data/spec/integration/schema/inheriting_schema_spec.rb +0 -31
  136. data/spec/integration/schema/input_processor_spec.rb +0 -46
  137. data/spec/integration/schema/json/explicit_types_spec.rb +0 -157
  138. data/spec/integration/schema/json_spec.rb +0 -163
  139. data/spec/integration/schema/macros/confirmation_spec.rb +0 -35
  140. data/spec/integration/schema/macros/each_spec.rb +0 -268
  141. data/spec/integration/schema/macros/filled_spec.rb +0 -87
  142. data/spec/integration/schema/macros/input_spec.rb +0 -139
  143. data/spec/integration/schema/macros/maybe_spec.rb +0 -99
  144. data/spec/integration/schema/macros/rule_spec.rb +0 -75
  145. data/spec/integration/schema/macros/value_spec.rb +0 -119
  146. data/spec/integration/schema/macros/when_spec.rb +0 -62
  147. data/spec/integration/schema/nested_schemas_spec.rb +0 -236
  148. data/spec/integration/schema/nested_values_spec.rb +0 -46
  149. data/spec/integration/schema/not_spec.rb +0 -34
  150. data/spec/integration/schema/numbers_spec.rb +0 -19
  151. data/spec/integration/schema/option_with_default_spec.rb +0 -64
  152. data/spec/integration/schema/or_spec.rb +0 -87
  153. data/spec/integration/schema/params/defining_base_schema_spec.rb +0 -41
  154. data/spec/integration/schema/params/explicit_types_spec.rb +0 -195
  155. data/spec/integration/schema/params_spec.rb +0 -234
  156. data/spec/integration/schema/predicate_verification_spec.rb +0 -9
  157. data/spec/integration/schema/predicates/array_spec.rb +0 -295
  158. data/spec/integration/schema/predicates/custom_spec.rb +0 -103
  159. data/spec/integration/schema/predicates/empty_spec.rb +0 -263
  160. data/spec/integration/schema/predicates/eql_spec.rb +0 -327
  161. data/spec/integration/schema/predicates/even_spec.rb +0 -455
  162. data/spec/integration/schema/predicates/excluded_from/array_spec.rb +0 -459
  163. data/spec/integration/schema/predicates/excluded_from/range_spec.rb +0 -459
  164. data/spec/integration/schema/predicates/excludes_spec.rb +0 -391
  165. data/spec/integration/schema/predicates/filled_spec.rb +0 -467
  166. data/spec/integration/schema/predicates/format_spec.rb +0 -455
  167. data/spec/integration/schema/predicates/gt_spec.rb +0 -519
  168. data/spec/integration/schema/predicates/gteq_spec.rb +0 -519
  169. data/spec/integration/schema/predicates/hash_spec.rb +0 -69
  170. data/spec/integration/schema/predicates/included_in/array_spec.rb +0 -459
  171. data/spec/integration/schema/predicates/included_in/range_spec.rb +0 -459
  172. data/spec/integration/schema/predicates/includes_spec.rb +0 -391
  173. data/spec/integration/schema/predicates/key_spec.rb +0 -88
  174. data/spec/integration/schema/predicates/lt_spec.rb +0 -520
  175. data/spec/integration/schema/predicates/lteq_spec.rb +0 -519
  176. data/spec/integration/schema/predicates/max_size_spec.rb +0 -391
  177. data/spec/integration/schema/predicates/min_size_spec.rb +0 -391
  178. data/spec/integration/schema/predicates/none_spec.rb +0 -265
  179. data/spec/integration/schema/predicates/not_eql_spec.rb +0 -391
  180. data/spec/integration/schema/predicates/odd_spec.rb +0 -455
  181. data/spec/integration/schema/predicates/size/fixed_spec.rb +0 -398
  182. data/spec/integration/schema/predicates/size/range_spec.rb +0 -395
  183. data/spec/integration/schema/predicates/type_spec.rb +0 -413
  184. data/spec/integration/schema/reusing_schema_spec.rb +0 -33
  185. data/spec/integration/schema/using_types_spec.rb +0 -135
  186. data/spec/integration/schema/validate_spec.rb +0 -120
  187. data/spec/integration/schema/xor_spec.rb +0 -35
  188. data/spec/integration/schema_builders_spec.rb +0 -17
  189. data/spec/integration/schema_spec.rb +0 -173
  190. data/spec/shared/message_compiler.rb +0 -11
  191. data/spec/shared/predicate_helper.rb +0 -15
  192. data/spec/shared/rule_compiler.rb +0 -8
  193. data/spec/spec_helper.rb +0 -62
  194. data/spec/support/define_struct.rb +0 -25
  195. data/spec/support/matchers.rb +0 -38
  196. data/spec/support/mutant.rb +0 -9
  197. data/spec/support/predicates_integration.rb +0 -7
  198. data/spec/unit/input_processor_compiler/json_spec.rb +0 -283
  199. data/spec/unit/input_processor_compiler/params_spec.rb +0 -328
  200. data/spec/unit/message_compiler/visit_failure_spec.rb +0 -38
  201. data/spec/unit/message_compiler/visit_spec.rb +0 -16
  202. data/spec/unit/message_compiler_spec.rb +0 -7
  203. data/spec/unit/predicate_registry_spec.rb +0 -34
  204. data/spec/unit/schema/key_spec.rb +0 -38
  205. data/spec/unit/schema/rule_spec.rb +0 -42
  206. data/spec/unit/schema/value_spec.rb +0 -131
  207. data/spec/unit/schema_spec.rb +0 -35
@@ -1,82 +1,224 @@
1
- require 'dry/equalizer'
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent/map"
4
+ require "dry/core/equalizer"
5
+
6
+ require "dry/validation/constants"
7
+ require "dry/validation/message_set"
8
+ require "dry/validation/values"
2
9
 
3
10
  module Dry
4
11
  module Validation
12
+ # Result objects are returned by contracts
13
+ #
14
+ # @api public
5
15
  class Result
6
- include Dry::Equalizer(:output, :errors)
7
- include Enumerable
8
-
9
- attr_reader :output
10
- attr_reader :results
11
- attr_reader :message_compiler
12
- attr_reader :path
13
-
14
- alias_method :to_hash, :output
15
- alias_method :to_h, :output # for MRI 2.0, remove it when drop support
16
+ include Dry::Equalizer(:schema_result, :context, :errors, inspect: false)
17
+
18
+ # Build a new result
19
+ #
20
+ # @param [Dry::Schema::Result] schema_result
21
+ #
22
+ # @api private
23
+ def self.new(schema_result, context = ::Concurrent::Map.new, options = EMPTY_HASH)
24
+ result = super
25
+ yield(result) if block_given?
26
+ result.freeze
27
+ end
16
28
 
17
- def initialize(output, results, message_compiler, path)
18
- @output = output
19
- @results = results
20
- @message_compiler = message_compiler
21
- @path = path
29
+ # Context that's shared between rules
30
+ #
31
+ # @return [Concurrent::Map]
32
+ #
33
+ # @api public
34
+ attr_reader :context
35
+
36
+ # Result from contract's schema
37
+ #
38
+ # @return [Dry::Schema::Result]
39
+ #
40
+ # @api private
41
+ attr_reader :schema_result
42
+
43
+ # Result options
44
+ #
45
+ # @return [Hash]
46
+ #
47
+ # @api private
48
+ attr_reader :options
49
+
50
+ # Initialize a new result
51
+ #
52
+ # @api private
53
+ def initialize(schema_result, context, options)
54
+ @schema_result = schema_result
55
+ @context = context
56
+ @options = options
57
+ @errors = initialize_errors
22
58
  end
23
59
 
24
- def each(&block)
25
- output.each(&block)
60
+ # Return values wrapper with the input processed by schema
61
+ #
62
+ # @return [Values]
63
+ #
64
+ # @api public
65
+ def values
66
+ @values ||= Values.new(schema_result.to_h)
26
67
  end
27
68
 
28
- def [](name)
29
- output.fetch(name)
69
+ # Get error set
70
+ #
71
+ # @!macro errors-options
72
+ # @param [Hash] new_options
73
+ # @option new_options [Symbol] :locale Set locale for messages
74
+ # @option new_options [Boolean] :hints Enable/disable hints
75
+ # @option new_options [Boolean] :full Get messages that include key names
76
+ #
77
+ # @return [MessageSet]
78
+ #
79
+ # @api public
80
+ def errors(new_options = EMPTY_HASH)
81
+ new_options.empty? ? @errors : @errors.with(schema_errors(new_options), new_options)
30
82
  end
31
83
 
84
+ # Check if result is successful
85
+ #
86
+ # @return [Bool]
87
+ #
88
+ # @api public
32
89
  def success?
33
- results.empty?
90
+ @errors.empty?
34
91
  end
35
92
 
93
+ # Check if result is not successful
94
+ #
95
+ # @return [Bool]
96
+ #
97
+ # @api public
36
98
  def failure?
37
99
  !success?
38
100
  end
39
101
 
40
- def messages(options = EMPTY_HASH)
41
- message_set(options).dump
102
+ # Check if values include an error for the provided key
103
+ #
104
+ # @api public
105
+ def error?(key)
106
+ errors.any? { |msg| Schema::Path[msg.path].include?(Schema::Path[key]) }
107
+ end
108
+
109
+ # Check if the base schema (without rules) includes an error for the provided key
110
+ #
111
+ # @api private
112
+ def schema_error?(key)
113
+ schema_result.error?(key)
114
+ end
115
+
116
+ # Check if the rules includes an error for the provided key
117
+ #
118
+ # @api private
119
+ def rule_error?(key)
120
+ !schema_error?(key) && error?(key)
42
121
  end
43
122
 
44
- def errors(options = EMPTY_HASH)
45
- message_set(options.merge(hints: false)).dump
123
+ # Check if there's any error for the provided key
124
+ #
125
+ # This does not consider errors from the nested values
126
+ #
127
+ # @api private
128
+ def base_error?(key)
129
+ schema_result.errors.any? { |error|
130
+ key_path = Schema::Path[key]
131
+ err_path = Schema::Path[error.path]
132
+
133
+ next unless key_path.same_root?(err_path)
134
+
135
+ key_path == err_path
136
+ }
46
137
  end
47
138
 
48
- def hints(options = EMPTY_HASH)
49
- message_set(options.merge(failures: false)).dump
139
+ # Add a new error for the provided key
140
+ #
141
+ # @api private
142
+ def add_error(error)
143
+ @errors.add(error)
144
+ self
50
145
  end
51
146
 
52
- def message_set(options = EMPTY_HASH)
53
- message_compiler.with(options).(result_ast)
147
+ # Read a value under provided key
148
+ #
149
+ # @param [Symbol] key
150
+ #
151
+ # @return [Object]
152
+ #
153
+ # @api public
154
+ def [](key)
155
+ values[key]
54
156
  end
55
157
 
56
- def to_ast
57
- if name
58
- [type, [name, [:set, result_ast]]]
158
+ # Check if a key was set
159
+ #
160
+ # @param [Symbol] key
161
+ #
162
+ # @return [Bool]
163
+ #
164
+ # @api public
165
+ def key?(key)
166
+ values.key?(key)
167
+ end
168
+
169
+ # Coerce to a hash
170
+ #
171
+ # @api public
172
+ def to_h
173
+ values.to_h
174
+ end
175
+
176
+ # Return a string representation
177
+ #
178
+ # @api public
179
+ def inspect
180
+ if context.empty?
181
+ "#<#{self.class}#{to_h} errors=#{errors.to_h}>"
59
182
  else
60
- ast
183
+ "#<#{self.class}#{to_h} errors=#{errors.to_h} context=#{context.each.to_h}>"
61
184
  end
62
185
  end
63
186
 
64
- def ast(*)
65
- [:set, result_ast]
187
+ # Freeze result and its error set
188
+ #
189
+ # @api private
190
+ def freeze
191
+ values.freeze
192
+ errors.freeze
193
+ super
66
194
  end
67
195
 
68
- def name
69
- Array(path).last
196
+ if RUBY_VERSION >= "2.7"
197
+ # Pattern matching
198
+ #
199
+ # @api private
200
+ def deconstruct_keys(keys)
201
+ values.deconstruct_keys(keys)
202
+ end
203
+
204
+ # Pattern matching
205
+ #
206
+ # @api private
207
+ def deconstruct
208
+ [values, context.each.to_h]
209
+ end
70
210
  end
71
211
 
72
212
  private
73
213
 
74
- def type
75
- success? ? :success : :failure
214
+ # @api private
215
+ def initialize_errors(options = self.options)
216
+ MessageSet.new(schema_errors(options), options)
76
217
  end
77
218
 
78
- def result_ast
79
- @result_ast ||= results.map(&:to_ast)
219
+ # @api private
220
+ def schema_errors(options)
221
+ schema_result.message_set(options).to_a
80
222
  end
81
223
  end
82
224
  end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/equalizer"
4
+
5
+ require "dry/validation/constants"
6
+ require "dry/validation/function"
7
+
8
+ module Dry
9
+ module Validation
10
+ # Rules capture configuration and evaluator blocks
11
+ #
12
+ # When a rule is applied, it creates an `Evaluator` using schema result and its
13
+ # block will be evaluated in the context of the evaluator.
14
+ #
15
+ # @see Contract#rule
16
+ #
17
+ # @api public
18
+ class Rule < Function
19
+ include Dry::Equalizer(:keys, :block, inspect: false)
20
+
21
+ # @!attribute [r] keys
22
+ # @return [Array<Symbol, String, Hash>]
23
+ # @api private
24
+ option :keys
25
+
26
+ # @!attribute [r] macros
27
+ # @return [Array<Symbol>]
28
+ # @api private
29
+ option :macros, default: proc { EMPTY_ARRAY.dup }
30
+
31
+ # Evaluate the rule within the provided context
32
+ #
33
+ # @param [Contract] contract
34
+ # @param [Result] result
35
+ #
36
+ # @api private
37
+ def call(contract, result)
38
+ Evaluator.new(
39
+ contract,
40
+ keys: keys,
41
+ macros: macros,
42
+ block_options: block_options,
43
+ result: result,
44
+ values: result.values,
45
+ _context: result.context,
46
+ &block
47
+ )
48
+ end
49
+
50
+ # Define which macros should be executed
51
+ #
52
+ # @see Contract#rule
53
+ # @return [Rule]
54
+ #
55
+ # @api public
56
+ def validate(*macros, &block)
57
+ @macros = parse_macros(*macros)
58
+ @block = block if block
59
+ self
60
+ end
61
+
62
+ # Define a validation function for each element of an array
63
+ #
64
+ # The function will be applied only if schema checks passed
65
+ # for a given array item.
66
+ #
67
+ # @example
68
+ # rule(:nums).each do |index:|
69
+ # key([:number, index]).failure("must be greater than 0") if value < 0
70
+ # end
71
+ # rule(:nums).each(min: 3)
72
+ # rule(address: :city) do
73
+ # key.failure("oops") if value != 'Munich'
74
+ # end
75
+ #
76
+ # @return [Rule]
77
+ #
78
+ # @api public
79
+ def each(*macros, &block)
80
+ root = keys[0]
81
+ macros = parse_macros(*macros)
82
+ @keys = []
83
+
84
+ @block = proc do
85
+ unless result.base_error?(root) || !values.key?(root)
86
+ values[root].each_with_index do |_, idx|
87
+ path = [*Schema::Path[root].to_a, idx]
88
+
89
+ next if result.schema_error?(path)
90
+
91
+ evaluator = with(macros: macros, keys: [path], index: idx, &block)
92
+
93
+ failures.concat(evaluator.failures)
94
+ end
95
+ end
96
+ end
97
+
98
+ @block_options = map_keywords(block) if block
99
+
100
+ self
101
+ end
102
+
103
+ # Return a nice string representation
104
+ #
105
+ # @return [String]
106
+ #
107
+ # @api public
108
+ def inspect
109
+ %(#<#{self.class} keys=#{keys.inspect}>)
110
+ end
111
+
112
+ # Parse function arguments into macros structure
113
+ #
114
+ # @return [Array]
115
+ #
116
+ # @api private
117
+ def parse_macros(*args)
118
+ args.each_with_object([]) do |spec, macros|
119
+ case spec
120
+ when Hash
121
+ add_macro_from_hash(macros, spec)
122
+ else
123
+ macros << Array(spec)
124
+ end
125
+ end
126
+ end
127
+
128
+ def add_macro_from_hash(macros, spec)
129
+ spec.each do |k, v|
130
+ macros << [k, v.is_a?(Array) ? v : [v]]
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema/path"
4
+
5
+ module Dry
6
+ module Schema
7
+ class Path
8
+ # @api private
9
+ def multi_value?
10
+ last.is_a?(Array)
11
+ end
12
+
13
+ # @api private
14
+ def expand
15
+ to_a[0..-2].product(last).map { |spec| self.class[spec] }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/equalizer"
4
+ require "dry/schema/path"
5
+ require "dry/validation/constants"
6
+
7
+ module Dry
8
+ module Validation
9
+ # A convenient wrapper for data processed by schemas
10
+ #
11
+ # Values are available within the rule blocks. They act as hash-like
12
+ # objects and expose a convenient API for accessing data.
13
+ #
14
+ # @api public
15
+ class Values
16
+ include Enumerable
17
+ include Dry::Equalizer(:data)
18
+
19
+ # Schema's result output
20
+ #
21
+ # @return [Hash]
22
+ #
23
+ # @api private
24
+ attr_reader :data
25
+
26
+ # @api private
27
+ def initialize(data)
28
+ @data = data
29
+ end
30
+
31
+ # Read from the provided key
32
+ #
33
+ # @example
34
+ # rule(:age) do
35
+ # key.failure('must be > 18') if values[:age] <= 18
36
+ # end
37
+ #
38
+ # @param args [Symbol, String, Hash, Array<Symbol>] If given as a single
39
+ # Symbol, String, Array or Hash, build a key array using
40
+ # {Dry::Schema::Path} digging for data. If given as positional
41
+ # arguments, use these with Hash#dig on the data directly.
42
+ #
43
+ # @return [Object]
44
+ #
45
+ # @api public
46
+ def [](*args)
47
+ return data.dig(*args) if args.size > 1
48
+
49
+ case (key = args[0])
50
+ when Symbol, String, Array, Hash
51
+ keys = Schema::Path[key].to_a
52
+
53
+ return data.dig(*keys) unless keys.last.is_a?(Array)
54
+
55
+ last = keys.pop
56
+ vals = self.class.new(data.dig(*keys))
57
+ vals.fetch_values(*last) { nil }
58
+ else
59
+ raise ArgumentError, "+key+ must be a valid path specification"
60
+ end
61
+ end
62
+
63
+ # @api public
64
+ def key?(key, hash = data)
65
+ return hash.key?(key) if key.is_a?(Symbol)
66
+
67
+ Schema::Path[key].reduce(hash) do |a, e|
68
+ if e.is_a?(Array)
69
+ result = e.all? { |k| key?(k, a) }
70
+ return result
71
+ elsif e.is_a?(Symbol) && a.is_a?(Array)
72
+ return false
73
+ elsif a.nil?
74
+ return false
75
+ elsif a.is_a?(String)
76
+ return false
77
+ else
78
+ return false unless a.is_a?(Array) ? (e >= 0 && e < a.size) : a.key?(e)
79
+ end
80
+ a[e]
81
+ end
82
+
83
+ true
84
+ end
85
+
86
+ # @api private
87
+ def respond_to_missing?(meth, include_private = false)
88
+ super || data.respond_to?(meth, include_private)
89
+ end
90
+
91
+ private
92
+
93
+ # @api private
94
+ def method_missing(meth, *args, &block)
95
+ if data.respond_to?(meth)
96
+ data.public_send(meth, *args, &block)
97
+ else
98
+ super
99
+ end
100
+ end
101
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
102
+ end
103
+ end
104
+ end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dry
2
4
  module Validation
3
- VERSION = '0.13.3'.freeze
5
+ VERSION = "1.7.0"
4
6
  end
5
7
  end
@@ -1,43 +1,60 @@
1
- require 'dry/core/extensions'
2
- require 'dry/core/constants'
1
+ # frozen_string_literal: true
3
2
 
3
+ require "dry/validation/constants"
4
+ require "dry/validation/contract"
5
+ require "dry/validation/macros"
6
+
7
+ # Main namespace
8
+ #
9
+ # @api public
4
10
  module Dry
11
+ # Main library namespace
12
+ #
13
+ # @api public
5
14
  module Validation
6
15
  extend Dry::Core::Extensions
7
- include Dry::Core::Constants
8
-
9
- MissingMessageError = Class.new(StandardError)
10
- InvalidSchemaError = Class.new(StandardError)
16
+ extend Macros::Registrar
11
17
 
12
- def self.messages_paths
13
- Messages::Abstract.config.paths
18
+ register_extension(:monads) do
19
+ require "dry/validation/extensions/monads"
14
20
  end
15
21
 
16
- def self.Schema(base = Schema, **options, &block)
17
- schema_class = Class.new(base.is_a?(Schema) ? base.class : base)
18
- klass = schema_class.define(options.merge(schema_class: schema_class), &block)
22
+ register_extension(:hints) do
23
+ require "dry/validation/extensions/hints"
24
+ end
19
25
 
20
- if options[:build] == false
21
- klass
22
- else
23
- klass.new
24
- end
26
+ register_extension(:predicates_as_macros) do
27
+ require "dry/validation/extensions/predicates_as_macros"
25
28
  end
26
29
 
27
- def self.Params(base = nil, **options, &block)
28
- klass = base ? Schema::Params.configure(Class.new(base)) : Schema::Params
29
- Validation.Schema(klass, options, &block)
30
+ # Define a contract and build its instance
31
+ #
32
+ # @example
33
+ # my_contract = Dry::Validation.Contract do
34
+ # params do
35
+ # required(:name).filled(:string)
36
+ # end
37
+ # end
38
+ #
39
+ # my_contract.call(name: "Jane")
40
+ #
41
+ # @param [Hash] options Contract options
42
+ #
43
+ # @see Contract
44
+ #
45
+ # @return [Contract]
46
+ #
47
+ # @api public
48
+ #
49
+ def self.Contract(options = EMPTY_HASH, &block)
50
+ Contract.build(options, &block)
30
51
  end
31
52
 
32
- def self.JSON(base = Schema::JSON, **options, &block)
33
- klass = base ? Schema::JSON.configure(Class.new(base)) : Schema::JSON
34
- Validation.Schema(klass, options, &block)
53
+ # This is needed by Macros::Registrar
54
+ #
55
+ # @api private
56
+ def self.macros
57
+ Macros
35
58
  end
36
59
  end
37
60
  end
38
-
39
- require 'dry/validation/schema'
40
- require 'dry/validation/schema/params'
41
- require 'dry/validation/schema/json'
42
- require 'dry/validation/extensions'
43
- require 'dry/validation/version'
@@ -1 +1,3 @@
1
- require 'dry/validation'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/validation"