dry-schema 1.3.2 → 1.4.2

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.
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  [gem]: https://rubygems.org/gems/dry-schema
2
- [travis]: https://travis-ci.com/dry-rb/dry-schema
2
+ [actions]: https://github.com/dry-rb/dry-schema/actions
3
3
  [codeclimate]: https://codeclimate.com/github/dry-rb/dry-schema
4
4
  [chat]: https://dry-rb.zulipchat.com
5
5
  [inchpages]: http://inch-ci.org/github/dry-rb/dry-schema
@@ -7,7 +7,7 @@
7
7
  # dry-schema [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat]
8
8
 
9
9
  [![Gem Version](https://badge.fury.io/rb/dry-schema.svg)][gem]
10
- [![Build Status](https://travis-ci.com/dry-rb/dry-schema.svg?branch=master)][travis]
10
+ [![CI Status](https://github.com/dry-rb/dry-schema/workflows/ci/badge.svg)][actions]
11
11
  [![Code Climate](https://codeclimate.com/github/dry-rb/dry-schema/badges/gpa.svg)][codeclimate]
12
12
  [![Test Coverage](https://codeclimate.com/github/dry-rb/dry-schema/badges/coverage.svg)][codeclimate]
13
13
  [![Inline docs](http://inch-ci.org/github/dry-rb/dry-schema.svg?branch=master)][inchpages]
@@ -30,7 +30,7 @@ module Dry
30
30
  #
31
31
  # @api public
32
32
  def self.define(**options, &block)
33
- DSL.new(options, &block).call
33
+ DSL.new(**options, &block).call
34
34
  end
35
35
 
36
36
  # Define a schema suitable for HTTP params
@@ -26,10 +26,11 @@ module Dry
26
26
  # An error raised when a localized message cannot be found
27
27
  MissingMessageError = Class.new(StandardError) do
28
28
  # @api private
29
- def initialize(path)
29
+ def initialize(path, paths = [])
30
30
  *rest, rule = path
31
31
  super(<<~STR)
32
- Message template for #{rule.inspect} under #{rest.join(DOT).inspect} was not found
32
+ Message template for #{rule.inspect} under #{rest.join(DOT).inspect} was not found. Searched in:
33
+ #{paths.map { |string| "\"#{string}\"" }.join("\n")}
33
34
  STR
34
35
  end
35
36
  end
@@ -9,6 +9,7 @@ require 'dry/schema/types'
9
9
  require 'dry/schema/macros'
10
10
 
11
11
  require 'dry/schema/processor'
12
+ require 'dry/schema/processor_steps'
12
13
  require 'dry/schema/key_map'
13
14
  require 'dry/schema/key_coercer'
14
15
  require 'dry/schema/value_coercer'
@@ -50,8 +51,6 @@ module Dry
50
51
 
51
52
  extend Dry::Initializer
52
53
 
53
- include ::Dry::Equalizer(:options)
54
-
55
54
  # @return [Compiler] The rule compiler object
56
55
  option :compiler, default: -> { Compiler.new }
57
56
 
@@ -64,18 +63,21 @@ module Dry
64
63
  # @return [Compiler] A key=>type map defined within the DSL
65
64
  option :types, default: -> { EMPTY_HASH.dup }
66
65
 
67
- # @return [DSL] An optional parent DSL object that will be used to merge keys and rules
68
- option :parent, optional: true
66
+ # @return [Array] Optional parent DSL objects, that will be used to merge keys and rules
67
+ option :parent, Types::Coercible::Array, default: -> { EMPTY_ARRAY.dup }, as: :parents
69
68
 
70
69
  # @return [Config] Configuration object exposed via `#configure` method
71
70
  option :config, optional: true, default: proc { parent ? parent.config.dup : Config.new }
72
71
 
72
+ # @return [ProcessorSteps] Steps for the processor
73
+ option :steps, default: proc { parent ? parent.steps.dup : ProcessorSteps.new }
74
+
73
75
  # Build a new DSL object and evaluate provided block
74
76
  #
75
77
  # @param [Hash] options
76
78
  # @option options [Class] :processor The processor type (`Params`, `JSON` or a custom sub-class)
77
79
  # @option options [Compiler] :compiler An instance of a rule compiler (must be compatible with `Schema::Compiler`) (optional)
78
- # @option options [DSL] :parent An instance of the parent DSL (optional)
80
+ # @option options [Array[DSL]] :parent One or more instances of the parent DSL (optional)
79
81
  # @option options [Config] :config A configuration object (optional)
80
82
  #
81
83
  # @see Schema.define
@@ -189,9 +191,10 @@ module Dry
189
191
  #
190
192
  # @api private
191
193
  def call
192
- steps = [key_coercer]
193
- steps << filter_schema.rule_applier if filter_rules?
194
- steps << value_coercer << rule_applier
194
+ steps[:key_coercer] = key_coercer
195
+ steps[:value_coercer] = value_coercer
196
+ steps[:rule_applier] = rule_applier
197
+ steps[:filter_schema] = filter_schema.rule_applier if filter_rules?
195
198
 
196
199
  processor_type.new(schema_dsl: self, steps: steps)
197
200
  end
@@ -215,14 +218,54 @@ module Dry
215
218
  -> member_type { type_registry['array'].of(resolve_type(member_type)) }
216
219
  end
217
220
 
221
+ # Method allows steps injection to the processor
222
+ #
223
+ # @example
224
+ # before(:rule_applier) do |input|
225
+ # input.compact
226
+ # end
227
+ #
228
+ # @return [DSL]
229
+ #
230
+ # @api public
231
+ def before(key, &block)
232
+ steps.before(key, &block)
233
+ self
234
+ end
235
+
236
+ # Method allows steps injection to the processor
237
+ #
238
+ # @example
239
+ # after(:rule_applier) do |input|
240
+ # input.compact
241
+ # end
242
+ #
243
+ # @return [DSL]
244
+ #
245
+ # @api public
246
+ def after(key, &block)
247
+ steps.after(key, &block)
248
+ self
249
+ end
250
+
251
+ # The parent (last from parents) which is used for copying non mergeable configuration
252
+ #
253
+ # @return DSL
254
+ #
255
+ # @api public
256
+ def parent
257
+ @parent ||= parents.last
258
+ end
259
+
218
260
  # Return type schema used by the value coercer
219
261
  #
220
262
  # @return [Dry::Types::Safe]
221
263
  #
222
264
  # @api private
223
265
  def type_schema
224
- schema = type_registry['hash'].schema(types).lax
225
- parent ? parent.type_schema.schema(schema.to_a) : schema
266
+ our_schema = type_registry['hash'].schema(types).lax
267
+ schemas = [*parents.map(&:type_schema), our_schema]
268
+ schemas.inject { |result, schema| result.schema(schema.to_a) }
226
269
  end
227
270
 
228
271
  # Return a new DSL instance using the same processor type
@@ -230,8 +273,8 @@ module Dry
230
273
  # @return [Dry::Types::Safe]
231
274
  #
232
275
  # @api private
233
- def new(options = EMPTY_HASH, &block)
234
- self.class.new(options.merge(processor_type: processor_type, config: config), &block)
276
+ def new(**options, &block)
277
+ self.class.new(**options, processor_type: processor_type, config: config, &block)
235
278
  end
236
279
 
237
280
  # Set a type for the given key name
@@ -276,14 +319,18 @@ module Dry
276
319
  #
277
320
  # @api private
278
321
  def filter_schema_dsl
279
- @filter_schema_dsl ||= new(parent: parent_filter_schema)
322
+ @filter_schema_dsl ||= new(parent: parent_filter_schemas)
280
323
  end
281
324
 
282
325
  # Check if any filter rules were defined
283
326
  #
284
327
  # @api private
285
328
  def filter_rules?
286
- (instance_variable_defined?('@filter_schema_dsl') && !filter_schema_dsl.macros.empty?) || parent&.filter_rules?
329
+ if instance_variable_defined?('@filter_schema_dsl') && !filter_schema_dsl.macros.empty?
330
+ return true
331
+ end
332
+
333
+ parents.any?(&:filter_rules?)
287
334
  end
288
335
 
289
336
  protected
@@ -323,10 +370,8 @@ module Dry
323
370
  private
324
371
 
325
372
  # @api private
326
- def parent_filter_schema
327
- return unless parent
328
-
329
- parent.filter_schema if parent.filter_rules?
373
+ def parent_filter_schemas
374
+ parents.select(&:filter_rules?).map(&:filter_schema)
330
375
  end
331
376
 
332
377
  # Build a key coercer
@@ -380,12 +425,12 @@ module Dry
380
425
 
381
426
  # @api private
382
427
  def parent_rules
383
- parent&.rules || EMPTY_HASH
428
+ parents.reduce({}) { |rules, parent| rules.merge(parent.rules) }
384
429
  end
385
430
 
386
431
  # @api private
387
432
  def parent_key_map
388
- parent&.key_map || EMPTY_ARRAY
433
+ parents.reduce([]) { |key_map, parent| parent.key_map + key_map }
389
434
  end
390
435
  end
391
436
  end
@@ -19,7 +19,7 @@ module Dry
19
19
  attr_reader :hints
20
20
 
21
21
  # @api private
22
- def initialize(*args)
22
+ def initialize(*, **)
23
23
  super
24
24
  @hints = @options.fetch(:hints, true)
25
25
  end
@@ -27,8 +27,8 @@ module Dry
27
27
  end
28
28
 
29
29
  # @api private
30
- def self.new(*args)
31
- fetch_or_store(*args) { super }
30
+ def self.new(*args, **kwargs)
31
+ fetch_or_store(args, kwargs) { super }
32
32
  end
33
33
 
34
34
  # @api private
@@ -65,8 +65,8 @@ module Dry
65
65
  end
66
66
 
67
67
  # @api private
68
- def new(new_opts = EMPTY_HASH)
69
- self.class.new(id, { name: name, coercer: coercer }.merge(new_opts))
68
+ def new(**new_opts)
69
+ self.class.new(id, name: name, coercer: coercer, **new_opts)
70
70
  end
71
71
 
72
72
  # @api private
@@ -28,8 +28,8 @@ module Dry
28
28
  option :schema_dsl, optional: true
29
29
 
30
30
  # @api private
31
- def new(options = EMPTY_HASH)
32
- self.class.new({ name: name, compiler: compiler, schema_dsl: schema_dsl }.merge(options))
31
+ def new(**options)
32
+ self.class.new(name: name, compiler: compiler, schema_dsl: schema_dsl, **options)
33
33
  end
34
34
 
35
35
  # @api private
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'dry/logic/operators'
4
+ require 'dry/types/predicate_inferrer'
5
+ require 'dry/types/primitive_inferrer'
4
6
 
5
7
  require 'dry/schema/macros/core'
6
8
 
@@ -26,7 +28,13 @@ module Dry
26
28
  # PredicateInferrer is used to infer predicate type-check from a type spec
27
29
  # @return [PredicateInferrer]
28
30
  # @api private
29
- option :predicate_inferrer, default: proc { PredicateInferrer.new(compiler.predicates) }
31
+ option :predicate_inferrer, default: proc { ::Dry::Types::PredicateInferrer.new(compiler.predicates) }
32
+
33
+ # @!attribute [r] primitive_inferrer
34
+ # PrimitiveInferrer used to get a list of primitive classes from configured type
35
+ # @return [PrimitiveInferrer]
36
+ # @api private
37
+ option :primitive_inferrer, default: proc { ::Dry::Types::PrimitiveInferrer.new }
30
38
 
31
39
  # @overload value(*predicates, **predicate_opts)
32
40
  # Set predicates without and with arguments
@@ -49,11 +57,12 @@ module Dry
49
57
  # @return [Macros::Core]
50
58
  #
51
59
  # @api public
52
- def value(*predicates, **opts, &block)
60
+ def value(*predicates, &block)
53
61
  append_macro(Macros::Value) do |macro|
54
- macro.call(*predicates, **opts, &block)
62
+ macro.call(*predicates, &block)
55
63
  end
56
64
  end
65
+ ruby2_keywords :value if respond_to?(:ruby2_keywords, true)
57
66
 
58
67
  # Prepends `:filled?` predicate
59
68
  #
@@ -66,11 +75,12 @@ module Dry
66
75
  # @return [Macros::Core]
67
76
  #
68
77
  # @api public
69
- def filled(*args, **opts, &block)
78
+ def filled(*args, &block)
70
79
  append_macro(Macros::Filled) do |macro|
71
- macro.call(*args, **opts, &block)
80
+ macro.call(*args, &block)
72
81
  end
73
82
  end
83
+ ruby2_keywords :filled if respond_to?(:ruby2_keywords, true)
74
84
 
75
85
  # Specify a nested hash without enforced `hash?` type-check
76
86
  #
@@ -91,6 +101,7 @@ module Dry
91
101
  macro.call(*args, &block)
92
102
  end
93
103
  end
104
+ ruby2_keywords :schema if respond_to?(:ruby2_keywords, true)
94
105
 
95
106
  # Specify a nested hash with enforced `hash?` type-check
96
107
  #
@@ -105,6 +116,7 @@ module Dry
105
116
  macro.call(*args, &block)
106
117
  end
107
118
  end
119
+ ruby2_keywords :hash if respond_to?(:ruby2_keywords, true)
108
120
 
109
121
  # Specify predicates that should be applied to each element of an array
110
122
  #
@@ -128,6 +140,7 @@ module Dry
128
140
  macro.value(*args, &block)
129
141
  end
130
142
  end
143
+ ruby2_keywords :each if respond_to?(:ruby2_keywords, true)
131
144
 
132
145
  # Like `each` but sets `array?` type-check
133
146
  #
@@ -147,6 +160,7 @@ module Dry
147
160
  macro.value(*args, &block)
148
161
  end
149
162
  end
163
+ ruby2_keywords :array if respond_to?(:ruby2_keywords, true)
150
164
 
151
165
  # Set type spec
152
166
  #
@@ -192,9 +206,7 @@ module Dry
192
206
  predicates = Array(type_spec ? args[1..-1] : args)
193
207
 
194
208
  if type_spec
195
- resolved_type = schema_dsl.resolve_type(
196
- nullable && !type_spec.is_a?(::Array) ? [:nil, type_spec] : type_spec
197
- )
209
+ resolved_type = resolve_type(type_spec, nullable)
198
210
 
199
211
  type(resolved_type) if set_type
200
212
 
@@ -207,6 +219,17 @@ module Dry
207
219
 
208
220
  yield(*predicates, type_spec: type_spec)
209
221
  end
222
+
223
+ # @api private
224
+ def resolve_type(type_spec, nullable)
225
+ resolved = schema_dsl.resolve_type(type_spec)
226
+
227
+ if type_spec.is_a?(::Array) || !nullable || resolved.optional?
228
+ resolved
229
+ else
230
+ schema_dsl.resolve_type([:nil, resolved])
231
+ end
232
+ end
210
233
  end
211
234
  end
212
235
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/schema/primitive_inferrer'
4
3
  require 'dry/schema/macros/value'
5
4
 
6
5
  module Dry
@@ -10,12 +9,6 @@ module Dry
10
9
  #
11
10
  # @api private
12
11
  class Filled < Value
13
- # @!attribute [r] primitive_inferrer
14
- # PrimitiveInferrer used to get a list of primitive classes from configured type
15
- # @return [PrimitiveInferrer]
16
- # @api private
17
- option :primitive_inferrer, default: proc { PrimitiveInferrer.new }
18
-
19
12
  # @api private
20
13
  def call(*predicates, **opts, &block)
21
14
  ensure_valid_predicates(predicates)
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/schema/predicate_inferrer'
4
3
  require 'dry/schema/processor'
5
4
  require 'dry/schema/macros/dsl'
6
5
  require 'dry/schema/constants'
@@ -31,6 +30,7 @@ module Dry
31
30
  (filter_schema_dsl[name] || filter_schema_dsl.optional(name)).value(*args, &block)
32
31
  self
33
32
  end
33
+ ruby2_keywords(:filter) if respond_to?(:ruby2_keywords, true)
34
34
 
35
35
  # @overload value(type_spec, *predicates, **predicate_opts)
36
36
  # Set type specification and predicates
@@ -88,7 +88,7 @@ module Dry
88
88
  def maybe(*args, **opts, &block)
89
89
  extract_type_spec(*args, nullable: true) do |*predicates, type_spec:|
90
90
  append_macro(Macros::Maybe) do |macro|
91
- macro.call(*predicates, **opts, &block)
91
+ macro.call(*predicates, type_spec: type_spec, **opts, &block)
92
92
  end
93
93
  end
94
94
  end
@@ -28,8 +28,8 @@ module Dry
28
28
  definition = schema_dsl.new(&block)
29
29
  schema = definition.call
30
30
  type_schema =
31
- if array?
32
- parent_type.of(definition.type_schema)
31
+ if array_type?(parent_type)
32
+ build_array_type(parent_type, definition.type_schema)
33
33
  elsif redefined_schema?(args)
34
34
  parent_type.schema(definition.types)
35
35
  else
@@ -56,11 +56,6 @@ module Dry
56
56
  parent_type.optional?
57
57
  end
58
58
 
59
- # @api private
60
- def array?
61
- parent_type.respond_to?(:of)
62
- end
63
-
64
59
  # @api private
65
60
  def schema?
66
61
  parent_type.respond_to?(:schema)
@@ -17,8 +17,8 @@ module Dry
17
17
  current_type = schema_dsl.types[name]
18
18
 
19
19
  updated_type =
20
- if current_type.respond_to?(:of)
21
- current_type.of(schema.type_schema)
20
+ if array_type?(current_type)
21
+ build_array_type(current_type, schema.type_schema)
22
22
  else
23
23
  schema.type_schema
24
24
  end
@@ -39,6 +39,25 @@ module Dry
39
39
  self
40
40
  end
41
41
 
42
+ # @api private
43
+ def array_type?(type)
44
+ primitive_inferrer[type].eql?([::Array])
45
+ end
46
+
47
+ # @api private
48
+ def build_array_type(array_type, member)
49
+ if array_type.respond_to?(:of)
50
+ array_type.of(member)
51
+ else
52
+ raise ArgumentError, <<~ERROR.split("\n").join(' ')
53
+ Cannot define schema for a nominal array type.
54
+ Array types must be instances of Dry::Types::Array,
55
+ usually constructed with Types::Constructor(Array) { ... } or
56
+ Dry::Types['array'].constructor { ... }
57
+ ERROR
58
+ end
59
+ end
60
+
42
61
  # @api private
43
62
  def respond_to_missing?(meth, include_private = false)
44
63
  super || meth.to_s.end_with?(QUESTION_MARK)