dry-schema 1.4.2 → 1.5.3

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +217 -78
  3. data/LICENSE +1 -1
  4. data/README.md +4 -4
  5. data/config/errors.yml +4 -0
  6. data/dry-schema.gemspec +46 -0
  7. data/lib/dry-schema.rb +1 -1
  8. data/lib/dry/schema.rb +19 -6
  9. data/lib/dry/schema/compiler.rb +5 -5
  10. data/lib/dry/schema/config.rb +15 -6
  11. data/lib/dry/schema/constants.rb +16 -7
  12. data/lib/dry/schema/dsl.rb +87 -27
  13. data/lib/dry/schema/extensions.rb +10 -2
  14. data/lib/dry/schema/extensions/hints.rb +15 -8
  15. data/lib/dry/schema/extensions/hints/message_compiler_methods.rb +1 -1
  16. data/lib/dry/schema/extensions/hints/message_set_methods.rb +0 -47
  17. data/lib/dry/schema/extensions/info.rb +27 -0
  18. data/lib/dry/schema/extensions/info/schema_compiler.rb +105 -0
  19. data/lib/dry/schema/extensions/monads.rb +1 -1
  20. data/lib/dry/schema/extensions/struct.rb +32 -0
  21. data/lib/dry/schema/json.rb +1 -1
  22. data/lib/dry/schema/key.rb +16 -1
  23. data/lib/dry/schema/key_coercer.rb +4 -4
  24. data/lib/dry/schema/key_map.rb +9 -4
  25. data/lib/dry/schema/key_validator.rb +67 -0
  26. data/lib/dry/schema/macros.rb +8 -8
  27. data/lib/dry/schema/macros/array.rb +17 -4
  28. data/lib/dry/schema/macros/core.rb +9 -4
  29. data/lib/dry/schema/macros/dsl.rb +34 -19
  30. data/lib/dry/schema/macros/each.rb +4 -4
  31. data/lib/dry/schema/macros/filled.rb +5 -5
  32. data/lib/dry/schema/macros/hash.rb +21 -3
  33. data/lib/dry/schema/macros/key.rb +9 -9
  34. data/lib/dry/schema/macros/maybe.rb +4 -5
  35. data/lib/dry/schema/macros/optional.rb +1 -1
  36. data/lib/dry/schema/macros/required.rb +1 -1
  37. data/lib/dry/schema/macros/schema.rb +23 -2
  38. data/lib/dry/schema/macros/value.rb +34 -7
  39. data/lib/dry/schema/message.rb +35 -9
  40. data/lib/dry/schema/message/or.rb +18 -39
  41. data/lib/dry/schema/message/or/abstract.rb +28 -0
  42. data/lib/dry/schema/message/or/multi_path.rb +37 -0
  43. data/lib/dry/schema/message/or/single_path.rb +64 -0
  44. data/lib/dry/schema/message_compiler.rb +55 -19
  45. data/lib/dry/schema/message_compiler/visitor_opts.rb +2 -2
  46. data/lib/dry/schema/message_set.rb +26 -37
  47. data/lib/dry/schema/messages.rb +6 -6
  48. data/lib/dry/schema/messages/abstract.rb +54 -56
  49. data/lib/dry/schema/messages/i18n.rb +29 -27
  50. data/lib/dry/schema/messages/namespaced.rb +12 -2
  51. data/lib/dry/schema/messages/template.rb +19 -44
  52. data/lib/dry/schema/messages/yaml.rb +61 -14
  53. data/lib/dry/schema/params.rb +1 -1
  54. data/lib/dry/schema/path.rb +44 -5
  55. data/lib/dry/schema/predicate.rb +4 -2
  56. data/lib/dry/schema/predicate_inferrer.rb +4 -184
  57. data/lib/dry/schema/predicate_registry.rb +2 -2
  58. data/lib/dry/schema/primitive_inferrer.rb +16 -0
  59. data/lib/dry/schema/processor.rb +49 -28
  60. data/lib/dry/schema/processor_steps.rb +50 -27
  61. data/lib/dry/schema/result.rb +52 -5
  62. data/lib/dry/schema/rule_applier.rb +7 -7
  63. data/lib/dry/schema/step.rb +79 -0
  64. data/lib/dry/schema/trace.rb +5 -4
  65. data/lib/dry/schema/type_container.rb +3 -3
  66. data/lib/dry/schema/type_registry.rb +2 -2
  67. data/lib/dry/schema/types.rb +1 -1
  68. data/lib/dry/schema/value_coercer.rb +2 -2
  69. data/lib/dry/schema/version.rb +1 -1
  70. metadata +21 -7
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/schema/macros/array'
4
- require 'dry/schema/macros/each'
5
- require 'dry/schema/macros/filled'
6
- require 'dry/schema/macros/schema'
7
- require 'dry/schema/macros/hash'
8
- require 'dry/schema/macros/maybe'
9
- require 'dry/schema/macros/optional'
10
- require 'dry/schema/macros/required'
3
+ require "dry/schema/macros/array"
4
+ require "dry/schema/macros/each"
5
+ require "dry/schema/macros/filled"
6
+ require "dry/schema/macros/schema"
7
+ require "dry/schema/macros/hash"
8
+ require "dry/schema/macros/maybe"
9
+ require "dry/schema/macros/optional"
10
+ require "dry/schema/macros/required"
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/schema/macros/dsl'
3
+ require "dry/schema/macros/dsl"
4
4
 
5
5
  module Dry
6
6
  module Schema
@@ -13,16 +13,29 @@ module Dry
13
13
  def value(*args, **opts, &block)
14
14
  type(:array)
15
15
 
16
- extract_type_spec(*args, set_type: false) do |*predicates, type_spec:|
16
+ extract_type_spec(*args, set_type: false) do |*predicates, type_spec:, type_rule:|
17
17
  type(schema_dsl.array[type_spec]) if type_spec
18
18
 
19
19
  is_hash_block = type_spec.equal?(:hash)
20
20
 
21
21
  if predicates.any? || opts.any? || !is_hash_block
22
- super(*predicates, type_spec: type_spec, **opts, &(is_hash_block ? nil : block))
22
+ super(
23
+ *predicates, type_spec: type_spec, type_rule: type_rule, **opts,
24
+ &(is_hash_block ? nil : block)
25
+ )
23
26
  end
24
27
 
25
- hash(&block) if is_hash_block
28
+ is_op = args.size.equal?(2) && args[1].is_a?(Logic::Operations::Abstract)
29
+
30
+ if is_hash_block && !is_op
31
+ hash(&block)
32
+ elsif is_op
33
+ hash = Value.new(schema_dsl: schema_dsl.new, name: name).hash(args[1])
34
+
35
+ trace.captures.concat(hash.trace.captures)
36
+
37
+ type(schema_dsl.types[name].of(hash.schema_dsl.types[name]))
38
+ end
26
39
  end
27
40
 
28
41
  self
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/initializer'
3
+ require "dry/initializer"
4
4
 
5
- require 'dry/schema/constants'
6
- require 'dry/schema/compiler'
7
- require 'dry/schema/trace'
5
+ require "dry/schema/constants"
6
+ require "dry/schema/compiler"
7
+ require "dry/schema/trace"
8
8
 
9
9
  module Dry
10
10
  module Schema
@@ -32,6 +32,11 @@ module Dry
32
32
  self.class.new(name: name, compiler: compiler, schema_dsl: schema_dsl, **options)
33
33
  end
34
34
 
35
+ # @api private
36
+ def path
37
+ schema_dsl.path
38
+ end
39
+
35
40
  # @api private
36
41
  def to_rule
37
42
  compiler.visit(to_ast)
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/logic/operators'
4
- require 'dry/types/predicate_inferrer'
5
- require 'dry/types/primitive_inferrer'
3
+ require "dry/logic/operators"
6
4
 
7
- require 'dry/schema/macros/core'
5
+ require "dry/schema/macros/core"
6
+ require "dry/schema/predicate_inferrer"
7
+ require "dry/schema/primitive_inferrer"
8
8
 
9
9
  module Dry
10
10
  module Schema
@@ -28,13 +28,13 @@ module Dry
28
28
  # PredicateInferrer is used to infer predicate type-check from a type spec
29
29
  # @return [PredicateInferrer]
30
30
  # @api private
31
- option :predicate_inferrer, default: proc { ::Dry::Types::PredicateInferrer.new(compiler.predicates) }
31
+ option :predicate_inferrer, default: proc { PredicateInferrer.new(compiler.predicates) }
32
32
 
33
33
  # @!attribute [r] primitive_inferrer
34
34
  # PrimitiveInferrer used to get a list of primitive classes from configured type
35
35
  # @return [PrimitiveInferrer]
36
36
  # @api private
37
- option :primitive_inferrer, default: proc { ::Dry::Types::PrimitiveInferrer.new }
37
+ option :primitive_inferrer, default: proc { PrimitiveInferrer.new }
38
38
 
39
39
  # @overload value(*predicates, **predicate_opts)
40
40
  # Set predicates without and with arguments
@@ -177,6 +177,11 @@ module Dry
177
177
  self
178
178
  end
179
179
 
180
+ # @api private
181
+ def custom_type?
182
+ schema_dsl.custom_type?(name)
183
+ end
184
+
180
185
  private
181
186
 
182
187
  # @api private
@@ -195,29 +200,32 @@ module Dry
195
200
 
196
201
  # @api private
197
202
  def extract_type_spec(*args, nullable: false, set_type: true)
198
- type_spec = args[0]
199
-
200
- is_type_spec = type_spec.is_a?(Dry::Schema::Processor) ||
201
- type_spec.is_a?(Symbol) &&
202
- type_spec.to_s.end_with?(QUESTION_MARK)
203
-
204
- type_spec = nil if is_type_spec
203
+ type_spec = args[0] unless schema_or_predicate?(args[0])
205
204
 
206
205
  predicates = Array(type_spec ? args[1..-1] : args)
206
+ type_rule = nil
207
207
 
208
208
  if type_spec
209
209
  resolved_type = resolve_type(type_spec, nullable)
210
210
 
211
- type(resolved_type) if set_type
212
-
213
- type_predicates = predicate_inferrer[resolved_type]
211
+ if type_spec.is_a?(::Array)
212
+ type_rule = type_spec.map { |ts| new(chain: false).value(ts) }.reduce(:|)
213
+ else
214
+ type_predicates = predicate_inferrer[resolved_type]
214
215
 
215
- predicates.replace(type_predicates + predicates) unless type_predicates.empty?
216
+ predicates.replace(type_predicates + predicates) unless type_predicates.empty?
216
217
 
217
- return self if predicates.empty?
218
+ return self if predicates.empty?
219
+ end
218
220
  end
219
221
 
220
- yield(*predicates, type_spec: type_spec)
222
+ type(resolved_type) if set_type && resolved_type
223
+
224
+ if type_rule
225
+ yield(*predicates, type_spec: nil, type_rule: type_rule)
226
+ else
227
+ yield(*predicates, type_spec: type_spec, type_rule: nil)
228
+ end
221
229
  end
222
230
 
223
231
  # @api private
@@ -230,6 +238,13 @@ module Dry
230
238
  schema_dsl.resolve_type([:nil, resolved])
231
239
  end
232
240
  end
241
+
242
+ # @api private
243
+ def schema_or_predicate?(arg)
244
+ arg.is_a?(Dry::Schema::Processor) ||
245
+ arg.is_a?(Symbol) &&
246
+ arg.to_s.end_with?(QUESTION_MARK)
247
+ end
233
248
  end
234
249
  end
235
250
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/types/type'
4
- require 'dry/schema/macros/dsl'
3
+ require "dry/types/type"
4
+ require "dry/schema/macros/dsl"
5
5
 
6
6
  module Dry
7
7
  module Schema
@@ -12,12 +12,12 @@ module Dry
12
12
  class Each < DSL
13
13
  # @api private
14
14
  def value(*args, **opts)
15
- extract_type_spec(*args, set_type: false) do |*predicates, type_spec:|
15
+ extract_type_spec(*args, set_type: false) do |*predicates, type_spec:, type_rule:|
16
16
  if type_spec && !type_spec.is_a?(Dry::Types::Type)
17
17
  type(schema_dsl.array[type_spec])
18
18
  end
19
19
 
20
- super(*predicates, type_spec: type_spec, **opts)
20
+ super(*predicates, type_spec: type_spec, type_rule: type_rule, **opts)
21
21
  end
22
22
  end
23
23
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/schema/macros/value'
3
+ require "dry/schema/macros/value"
4
4
 
5
5
  module Dry
6
6
  module Schema
@@ -15,23 +15,23 @@ module Dry
15
15
 
16
16
  if opts[:type_spec] && !filter_empty_string?
17
17
  value(predicates[0], :filled?, *predicates[1..predicates.size - 1], **opts, &block)
18
+ elsif opts[:type_rule]
19
+ value(:filled?).value(*predicates, **opts, &block)
18
20
  else
19
21
  value(:filled?, *predicates, **opts, &block)
20
22
  end
21
23
  end
22
24
 
23
25
  # @api private
24
- # rubocop:disable Style/GuardClause
25
26
  def ensure_valid_predicates(predicates)
26
27
  if predicates.include?(:empty?)
27
- raise ::Dry::Schema::InvalidSchemaError, 'Using filled with empty? predicate is invalid'
28
+ raise ::Dry::Schema::InvalidSchemaError, "Using filled with empty? predicate is invalid"
28
29
  end
29
30
 
30
31
  if predicates.include?(:filled?)
31
- raise ::Dry::Schema::InvalidSchemaError, 'Using filled with filled? is redundant'
32
+ raise ::Dry::Schema::InvalidSchemaError, "Using filled with filled? is redundant"
32
33
  end
33
34
  end
34
- # rubocop:enable Style/GuardClause
35
35
 
36
36
  # @api private
37
37
  def filter_empty_string?
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/schema/macros/schema'
3
+ require "dry/schema/macros/schema"
4
4
 
5
5
  module Dry
6
6
  module Schema
@@ -11,8 +11,26 @@ module Dry
11
11
  class Hash < Schema
12
12
  # @api private
13
13
  def call(*args, &block)
14
- trace << hash?
15
- super(*args, &block)
14
+ if args.size >= 1 && args[0].respond_to?(:keys)
15
+ hash_type = args[0]
16
+ type_predicates = predicate_inferrer[hash_type]
17
+ all_predicats = type_predicates + args.drop(1)
18
+
19
+ super(*all_predicats) do
20
+ hash_type.each do |key|
21
+ if key.required?
22
+ required(key.name).value(key.type)
23
+ else
24
+ optional(key.name).value(key.type)
25
+ end
26
+ instance_exec(&block) if block
27
+ end
28
+ end
29
+ else
30
+ trace << hash?
31
+
32
+ super(*args, &block)
33
+ end
16
34
  end
17
35
  end
18
36
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/schema/processor'
4
- require 'dry/schema/macros/dsl'
5
- require 'dry/schema/constants'
3
+ require "dry/schema/processor"
4
+ require "dry/schema/macros/dsl"
5
+ require "dry/schema/constants"
6
6
 
7
7
  module Dry
8
8
  module Schema
@@ -54,8 +54,8 @@ module Dry
54
54
  #
55
55
  # @api public
56
56
  def value(*args, **opts, &block)
57
- extract_type_spec(*args) do |*predicates, type_spec:|
58
- super(*predicates, type_spec: type_spec, **opts, &block)
57
+ extract_type_spec(*args) do |*predicates, type_spec:, type_rule:|
58
+ super(*predicates, type_spec: type_spec, type_rule: type_rule, **opts, &block)
59
59
  end
60
60
  end
61
61
 
@@ -70,8 +70,8 @@ module Dry
70
70
  #
71
71
  # @api public
72
72
  def filled(*args, **opts, &block)
73
- extract_type_spec(*args) do |*predicates, type_spec:|
74
- super(*predicates, type_spec: type_spec, **opts, &block)
73
+ extract_type_spec(*args) do |*predicates, type_spec:, type_rule:|
74
+ super(*predicates, type_spec: type_spec, type_rule: type_rule, **opts, &block)
75
75
  end
76
76
  end
77
77
 
@@ -86,9 +86,9 @@ module Dry
86
86
  #
87
87
  # @api public
88
88
  def maybe(*args, **opts, &block)
89
- extract_type_spec(*args, nullable: true) do |*predicates, type_spec:|
89
+ extract_type_spec(*args, nullable: true) do |*predicates, type_spec:, type_rule:|
90
90
  append_macro(Macros::Maybe) do |macro|
91
- macro.call(*predicates, type_spec: type_spec, **opts, &block)
91
+ macro.call(*predicates, type_spec: type_spec, type_rule: type_rule, **opts, &block)
92
92
  end
93
93
  end
94
94
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/schema/macros/dsl'
3
+ require "dry/schema/macros/dsl"
4
4
 
5
5
  module Dry
6
6
  module Schema
@@ -12,11 +12,11 @@ module Dry
12
12
  # @api private
13
13
  def call(*args, **opts, &block)
14
14
  if args.include?(:empty?)
15
- raise ::Dry::Schema::InvalidSchemaError, 'Using maybe with empty? predicate is invalid'
15
+ raise ::Dry::Schema::InvalidSchemaError, "Using maybe with empty? predicate is invalid"
16
16
  end
17
17
 
18
18
  if args.include?(:nil?)
19
- raise ::Dry::Schema::InvalidSchemaError, 'Using maybe with nil? predicate is redundant'
19
+ raise ::Dry::Schema::InvalidSchemaError, "Using maybe with nil? predicate is redundant"
20
20
  end
21
21
 
22
22
  value(*args, **opts, &block)
@@ -30,8 +30,7 @@ module Dry
30
30
  [
31
31
  [:not, [:predicate, [:nil?, [[:input, Undefined]]]]],
32
32
  trace.to_rule.to_ast
33
- ]
34
- ]
33
+ ]]
35
34
  end
36
35
  end
37
36
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/schema/macros/key'
3
+ require "dry/schema/macros/key"
4
4
 
5
5
  module Dry
6
6
  module Schema
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/schema/macros/key'
3
+ require "dry/schema/macros/key"
4
4
 
5
5
  module Dry
6
6
  module Schema
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/schema/macros/value'
3
+ require "dry/schema/macros/value"
4
4
 
5
5
  module Dry
6
6
  module Schema
@@ -13,8 +13,13 @@ module Dry
13
13
  def call(*args, &block)
14
14
  super(*args, &nil) unless args.empty?
15
15
 
16
+ if args.size.equal?(1) && (op = args.first).is_a?(Dry::Logic::Operations::Abstract)
17
+ process_operation(op)
18
+ end
19
+
16
20
  if block
17
21
  schema = define(*args, &block)
22
+ import_steps(schema)
18
23
  trace << schema.to_rule
19
24
  end
20
25
 
@@ -23,9 +28,25 @@ module Dry
23
28
 
24
29
  private
25
30
 
31
+ # @api private
32
+ def process_operation(op)
33
+ schemas = op.rules.select { |rule| rule.is_a?(Processor) }
34
+
35
+ hash_schema = hash_type.schema(
36
+ schemas.map(&:schema_dsl).map(&:types).reduce(:merge)
37
+ )
38
+
39
+ type(hash_schema)
40
+ end
41
+
42
+ # @api private
43
+ def hash_type
44
+ schema_dsl.resolve_type(:hash)
45
+ end
46
+
26
47
  # @api private
27
48
  def define(*args, &block)
28
- definition = schema_dsl.new(&block)
49
+ definition = schema_dsl.new(path: schema_dsl.path, &block)
29
50
  schema = definition.call
30
51
  type_schema =
31
52
  if array_type?(parent_type)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/schema/macros/dsl'
3
+ require "dry/schema/path"
4
+ require "dry/schema/macros/dsl"
4
5
 
5
6
  module Dry
6
7
  module Schema
@@ -13,6 +14,8 @@ module Dry
13
14
  def call(*predicates, **opts, &block)
14
15
  schema = predicates.detect { |predicate| predicate.is_a?(Processor) }
15
16
 
17
+ type_spec = opts[:type_spec]
18
+
16
19
  if schema
17
20
  current_type = schema_dsl.types[name]
18
21
 
@@ -23,17 +26,32 @@ module Dry
23
26
  schema.type_schema
24
27
  end
25
28
 
26
- schema_dsl.set_type(name, updated_type)
29
+ import_steps(schema)
30
+
31
+ type(updated_type) unless custom_type? && !current_type.respond_to?(:of)
27
32
  end
28
33
 
29
- trace.evaluate(*predicates, **opts)
30
- trace.append(new(chain: false).instance_exec(&block)) if block
34
+ trace_opts = opts.reject { |key, _| key == :type_spec || key == :type_rule }
35
+
36
+ if (type_rule = opts[:type_rule])
37
+ trace.append(type_rule).evaluate(*predicates, **trace_opts)
38
+ trace.append(new(chain: false).instance_exec(&block)) if block
39
+ else
40
+ trace.evaluate(*predicates, **trace_opts)
41
+
42
+ if block && type_spec.equal?(:hash)
43
+ hash(&block)
44
+ elsif type_spec.is_a?(::Dry::Types::Type) && hash_type?(type_spec)
45
+ hash(type_spec)
46
+ elsif block
47
+ trace.append(new(chain: false).instance_exec(&block))
48
+ end
49
+ end
31
50
 
32
51
  if trace.captures.empty?
33
- raise ArgumentError, 'wrong number of arguments (given 0, expected at least 1)'
52
+ raise ArgumentError, "wrong number of arguments (given 0, expected at least 1)"
34
53
  end
35
54
 
36
- type_spec = opts[:type_spec]
37
55
  each(type_spec.type.member) if type_spec.respond_to?(:member)
38
56
 
39
57
  self
@@ -44,12 +62,16 @@ module Dry
44
62
  primitive_inferrer[type].eql?([::Array])
45
63
  end
46
64
 
65
+ def hash_type?(type)
66
+ primitive_inferrer[type].eql?([::Hash])
67
+ end
68
+
47
69
  # @api private
48
70
  def build_array_type(array_type, member)
49
71
  if array_type.respond_to?(:of)
50
72
  array_type.of(member)
51
73
  else
52
- raise ArgumentError, <<~ERROR.split("\n").join(' ')
74
+ raise ArgumentError, <<~ERROR.split("\n").join(" ")
53
75
  Cannot define schema for a nominal array type.
54
76
  Array types must be instances of Dry::Types::Array,
55
77
  usually constructed with Types::Constructor(Array) { ... } or
@@ -58,6 +80,11 @@ module Dry
58
80
  end
59
81
  end
60
82
 
83
+ # @api private
84
+ def import_steps(schema)
85
+ schema_dsl.steps.import_callbacks(Path[[*path, name]], schema.steps)
86
+ end
87
+
61
88
  # @api private
62
89
  def respond_to_missing?(meth, include_private = false)
63
90
  super || meth.to_s.end_with?(QUESTION_MARK)