dry-schema 1.4.2 → 1.5.3

Sign up to get free protection for your applications and to get access to all the features.
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)