dry-schema 1.9.3 → 1.10.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c2dead9a036c0d9322c68b50938417ebe12a4b62ed2f99a8ae6d4f49f894dabd
4
- data.tar.gz: ac3b386e3266e8498aa62549cddfbaece9414281beee5710c1930b062eaacde8
3
+ metadata.gz: 8aa8d227152a712ff8580d62686ad5a4b83b4439dc8d58f42651b869e203697c
4
+ data.tar.gz: e22028010d0ac11567a8a615725496f77db8a2acd107350363b20174da01a20c
5
5
  SHA512:
6
- metadata.gz: '0831055ac93da2898c55b6194703e87bda1c27040ac23965c5f261c2e59387888f679b9dcd967a4a0665cef220b7c97db823670067267b34b40ed09a3c315b81'
7
- data.tar.gz: d82ee10469742e83ee412ebe6c06593a86c44c1e0f01fd29067700d9a37ecc461dc3e4ad6e2a09bfe4694d1e95b6c28132810bf243d27338257f66e0c50cc98e
6
+ metadata.gz: ad3350414c764971f4c0e69ca07044901d67488625c691f2a7396c6d17b33d31a2714e8959d5f07598c03894883b7854090c6d77b182f7569acc7a32b43be3d3
7
+ data.tar.gz: 93320abcdadd4f3c48666507f829d4aaba2c9fc5ae181d2cd68f44e28eb850a8ead7adec29c7be935e17fe4112df09d17ac735dac9f50c49d45e5031f15d1ce6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  <!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
2
2
 
3
+ ## 1.10.1 2022-08-22
4
+
5
+
6
+ ### Changed
7
+
8
+ - Reverted zeitwerk-related changes that were included in 1.10.0 by an accident (@solnic)
9
+
10
+ [Compare v1.10.0...v1.10.1](https://github.com/dry-rb/dry-schema/compare/v1.10.0...v1.10.1)
11
+
12
+ ## 1.10.0 2022-08-16
13
+
14
+
15
+ ### Added
16
+
17
+ - Allow nested `filled` and `value` macro usage (via #412) (@robhanlon22)
18
+ - Support for more complex scenarios when composing schemas (via #420) (@robhanlon22)
19
+
20
+ ### Fixed
21
+
22
+ - Fix `or` messages for complex schemas (via #413) (@robhanlon22)
23
+ - Using `filled` with a constrained constructor type works as expected (via #416) (@robhanlon22)
24
+ - Fix types and key maps for composed schemas (via #415) (@robhanlon22)
25
+
26
+ ### Changed
27
+
28
+ - Freeze message hash (fixes #417 via #418) (@solnic)
29
+
30
+ [Compare v1.9.3...v1.10.0](https://github.com/dry-rb/dry-schema/compare/v1.9.3...v1.10.0)
31
+
3
32
  ## 1.9.3 2022-06-23
4
33
 
5
34
 
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015-2021 dry-rb team
3
+ Copyright (c) 2015-2022 dry-rb team
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "dry/initializer"
4
+ require "dry/logic"
4
5
 
5
6
  require "dry/schema/constants"
6
7
  require "dry/schema/path"
@@ -16,6 +17,7 @@ require "dry/schema/key_coercer"
16
17
  require "dry/schema/key_validator"
17
18
  require "dry/schema/value_coercer"
18
19
  require "dry/schema/rule_applier"
20
+ require "dry/schema/types_merger"
19
21
 
20
22
  module Dry
21
23
  module Schema
@@ -316,7 +318,7 @@ module Dry
316
318
  type = resolve_type(spec)
317
319
  meta = {required: false, maybe: type.optional?}
318
320
 
319
- types[name] = type.meta(meta)
321
+ @types[name] = type.meta(meta)
320
322
  end
321
323
 
322
324
  # Check if a custom type was set under provided key name
@@ -369,6 +371,18 @@ module Dry
369
371
  parents.any?(&:filter_rules?)
370
372
  end
371
373
 
374
+ # This DSL's type map merged with any parent type maps
375
+ #
376
+ # @api private
377
+ def types
378
+ [*parents.map(&:types), @types].reduce(:merge)
379
+ end
380
+
381
+ # @api private
382
+ def merge_types(op_class, lhs, rhs)
383
+ types_merger.(op_class, lhs, rhs)
384
+ end
385
+
372
386
  protected
373
387
 
374
388
  # Build a rule applier
@@ -495,6 +509,10 @@ module Dry
495
509
 
496
510
  (parent || Schema).config.dup
497
511
  end
512
+
513
+ def types_merger
514
+ @types_merger ||= TypesMerger.new(type_registry)
515
+ end
498
516
  end
499
517
  end
500
518
  end
@@ -15,7 +15,7 @@ module Dry
15
15
  def value(*args, **opts, &block)
16
16
  type(:array)
17
17
 
18
- extract_type_spec(*args, set_type: false) do |*predicates, type_spec:, type_rule:|
18
+ extract_type_spec(args, set_type: false) do |*predicates, type_spec:, type_rule:|
19
19
  type(schema_dsl.array[type_spec]) if type_spec
20
20
 
21
21
  is_hash_block = type_spec.equal?(:hash)
@@ -58,12 +58,13 @@ module Dry
58
58
  # @return [Macros::Core]
59
59
  #
60
60
  # @api public
61
- def value(...)
62
- append_macro(Macros::Value) do |macro|
63
- macro.call(...)
61
+ def value(*args, **opts, &block)
62
+ extract_type_spec(args) do |*predicates, type_spec:, type_rule:|
63
+ append_macro(Macros::Value) do |macro|
64
+ macro.call(*predicates, type_spec: type_spec, type_rule: type_rule, **opts, &block)
65
+ end
64
66
  end
65
67
  end
66
- ruby2_keywords :value if respond_to?(:ruby2_keywords, true)
67
68
 
68
69
  # Prepends `:filled?` predicate
69
70
  #
@@ -76,12 +77,13 @@ module Dry
76
77
  # @return [Macros::Core]
77
78
  #
78
79
  # @api public
79
- def filled(...)
80
- append_macro(Macros::Filled) do |macro|
81
- macro.call(...)
80
+ def filled(*args, **opts, &block)
81
+ extract_type_spec(args) do |*predicates, type_spec:, type_rule:|
82
+ append_macro(Macros::Filled) do |macro|
83
+ macro.call(*predicates, type_spec: type_spec, type_rule: type_rule, **opts, &block)
84
+ end
82
85
  end
83
86
  end
84
- ruby2_keywords :filled if respond_to?(:ruby2_keywords, true)
85
87
 
86
88
  # Specify a nested hash without enforced `hash?` type-check
87
89
  #
@@ -201,7 +203,7 @@ module Dry
201
203
 
202
204
  # @api private
203
205
  # rubocop: disable Metrics/PerceivedComplexity
204
- def extract_type_spec(*args, nullable: false, set_type: true)
206
+ def extract_type_spec(args, nullable: false, set_type: true)
205
207
  type_spec = args[0] unless schema_or_predicate?(args[0])
206
208
 
207
209
  predicates = Array(type_spec ? args[1..] : args)
@@ -11,13 +11,15 @@ module Dry
11
11
  # @api private
12
12
  class Each < DSL
13
13
  # @api private
14
- def value(*args, **opts)
15
- extract_type_spec(*args, set_type: false) do |*predicates, type_spec:, type_rule:|
14
+ def value(*args, **opts, &block)
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, type_rule: type_rule, **opts)
20
+ append_macro(Macros::Value) do |macro|
21
+ macro.call(*predicates, type_spec: type_spec, type_rule: type_rule, **opts, &block)
22
+ end
21
23
  end
22
24
  end
23
25
 
@@ -32,49 +32,6 @@ module Dry
32
32
  end
33
33
  ruby2_keywords(:filter) if respond_to?(:ruby2_keywords, true)
34
34
 
35
- # @overload value(type_spec, *predicates, **predicate_opts)
36
- # Set type specification and predicates
37
- #
38
- # @param [Symbol,Types::Type,Array] type_spec
39
- # @param [Array<Symbol>] predicates
40
- # @param [Hash] predicate_opts
41
- #
42
- # @example with a predicate
43
- # required(:name).value(:string, :filled?)
44
- #
45
- # @example with a predicate with arguments
46
- # required(:name).value(:string, min_size?: 2)
47
- #
48
- # @example with a block
49
- # required(:name).value(:string) { filled? & min_size?(2) }
50
- #
51
- # @return [Macros::Key]
52
- #
53
- # @see Macros::DSL#value
54
- #
55
- # @api public
56
- def value(*args, **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
- end
60
- end
61
-
62
- # Set type specification and predicates for a filled value
63
- #
64
- # @example
65
- # required(:name).filled(:string)
66
- #
67
- # @see Macros::Key#value
68
- #
69
- # @return [Macros::Key]
70
- #
71
- # @api public
72
- def filled(*args, **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
- end
76
- end
77
-
78
35
  # Set type specification and predicates for a maybe value
79
36
  #
80
37
  # @example
@@ -86,7 +43,7 @@ module Dry
86
43
  #
87
44
  # @api public
88
45
  def maybe(*args, **opts, &block)
89
- extract_type_spec(*args, nullable: true) do |*predicates, type_spec:, type_rule:|
46
+ extract_type_spec(args, nullable: true) do |*predicates, type_spec:, type_rule:|
90
47
  append_macro(Macros::Maybe) do |macro|
91
48
  macro.call(*predicates, type_spec: type_spec, type_rule: type_rule, **opts, &block)
92
49
  end
@@ -30,13 +30,7 @@ module Dry
30
30
 
31
31
  # @api private
32
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)
33
+ type(hash_type.schema(merge_operation_types(op)))
40
34
  end
41
35
 
42
36
  # @api private
@@ -44,6 +38,23 @@ module Dry
44
38
  schema_dsl.resolve_type(:hash)
45
39
  end
46
40
 
41
+ # @api private
42
+ def merge_operation_types(op)
43
+ op.rules.reduce({}) do |acc, rule|
44
+ types =
45
+ case rule
46
+ when Dry::Logic::Operations::Abstract
47
+ merge_operation_types(rule)
48
+ when Processor
49
+ rule.types
50
+ else
51
+ EMPTY_HASH.dup
52
+ end
53
+
54
+ schema_dsl.merge_types(op.class, acc, types)
55
+ end
56
+ end
57
+
47
58
  # @api private
48
59
  # rubocop: disable Metrics/AbcSize
49
60
  def define(*args, &block)
@@ -34,17 +34,16 @@ module Dry
34
34
  end
35
35
  end
36
36
 
37
- # @api private
38
- def self.handler(message)
39
- handlers.find { |k,| message.is_a?(k) }&.last
40
- end
37
+ MESSAGE_ARRAY_HANDLER = -> { MessageArray.new(_1) }
41
38
 
42
39
  # @api private
43
- private_class_method def self.handlers
44
- @handlers ||= {
45
- self => -> { _1 },
46
- Array => -> { MessageArray.new(_1) }
47
- }.freeze
40
+ def self.handler(message)
41
+ case message
42
+ when self
43
+ IDENTITY
44
+ when Array
45
+ MESSAGE_ARRAY_HANDLER
46
+ end
48
47
  end
49
48
 
50
49
  # @api public
@@ -59,7 +58,7 @@ module Dry
59
58
 
60
59
  # @api private
61
60
  def root
62
- @root ||= _messages.flat_map(&:_paths).reduce(:&)
61
+ @root ||= _paths.reduce(:&)
63
62
  end
64
63
 
65
64
  # @api private
@@ -67,9 +66,14 @@ module Dry
67
66
  root
68
67
  end
69
68
 
69
+ # @api private
70
+ def _path
71
+ @_path ||= Path[root]
72
+ end
73
+
70
74
  # @api private
71
75
  def _paths
72
- @paths ||= [Path[root]]
76
+ @paths ||= _messages.flat_map(&:_paths)
73
77
  end
74
78
 
75
79
  # @api private
@@ -21,10 +21,11 @@ module Dry
21
21
 
22
22
  # @api private
23
23
  def initialize(*args, messages)
24
- super(*args)
24
+ super(*args.map { [_1].flatten })
25
25
  @messages = messages
26
- @path = left.path
27
- @_path = left._path
26
+ message = left.first
27
+ @path = message.path
28
+ @_path = message._path
28
29
  end
29
30
 
30
31
  # Dump a message into a string
@@ -38,7 +39,7 @@ module Dry
38
39
  #
39
40
  # @api public
40
41
  def dump
41
- @dump ||= "#{left.dump} #{messages[:or]} #{right.dump}"
42
+ @dump ||= [*left, *right].map(&:dump).join(" #{messages[:or]} ")
42
43
  end
43
44
  alias_method :to_s, :dump
44
45
 
@@ -55,7 +56,16 @@ module Dry
55
56
 
56
57
  # @api private
57
58
  def to_a
58
- @to_a ||= [left, right]
59
+ @to_a ||= [*left, *right]
60
+ end
61
+
62
+ # @api private
63
+ def to_or(root)
64
+ to_ored = [left, right].map do |msgs|
65
+ msgs.map { _1.to_or(root) }
66
+ end
67
+
68
+ self.class.new(*to_ored, messages)
59
69
  end
60
70
  end
61
71
  end
@@ -105,7 +105,7 @@ module Dry
105
105
 
106
106
  # @api private
107
107
  def messages_map(messages = self.messages)
108
- combine_message_hashes(messages.map(&:to_h))
108
+ combine_message_hashes(messages.map(&:to_h)).freeze
109
109
  end
110
110
 
111
111
  # @api private
@@ -188,6 +188,13 @@ module Dry
188
188
  rule_applier.rules
189
189
  end
190
190
 
191
+ # Return the types from the schema DSL
192
+ #
193
+ # @api private
194
+ def types
195
+ schema_dsl.types
196
+ end
197
+
191
198
  # Check if there are filter rules
192
199
  #
193
200
  # @api private
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module Schema
5
+ # Combines multiple logical operations into a single type, taking into
6
+ # account the type of logical operation (or, and, implication) and the
7
+ # underlying types (schemas, nominals, etc.)
8
+ #
9
+ # @api private
10
+ class TypesMerger
11
+ attr_reader :type_registry
12
+
13
+ # @api private
14
+ class ValueMerger
15
+ attr_reader :types_merger
16
+ attr_reader :op_class
17
+ attr_reader :old
18
+ attr_reader :new
19
+
20
+ # @api private
21
+ def initialize(types_merger, op_class, old, new)
22
+ @types_merger = types_merger
23
+ @op_class = op_class
24
+ @old = old
25
+ @new = new
26
+ end
27
+
28
+ # @api private
29
+ def call
30
+ handlers.fetch(op_class).call
31
+ end
32
+
33
+ private
34
+
35
+ # @api private
36
+ def handlers
37
+ @handlers ||=
38
+ {
39
+ Dry::Logic::Operations::Or => method(:handle_or),
40
+ Dry::Logic::Operations::And => method(:handle_and),
41
+ Dry::Logic::Operations::Implication => method(:handle_implication)
42
+ }
43
+ end
44
+
45
+ # @api private
46
+ def handle_or
47
+ old | new
48
+ end
49
+
50
+ # @api private
51
+ def handle_ordered
52
+ return old if old == new
53
+
54
+ unwrapped_old, old_rule = unwrap_type(old)
55
+ unwrapped_new, new_rule = unwrap_type(new)
56
+
57
+ type = merge_underlying_types(unwrapped_old, unwrapped_new)
58
+
59
+ rule = [old_rule, new_rule].compact.reduce { op_class.new(_1, _2) }
60
+
61
+ type = Dry::Types::Constrained.new(type, rule: rule) if rule
62
+
63
+ type
64
+ end
65
+
66
+ alias_method :handle_and, :handle_ordered
67
+ alias_method :handle_implication, :handle_ordered
68
+
69
+ # @api private
70
+ def merge_underlying_types(unwrapped_old, unwrapped_new)
71
+ case [unwrapped_old, unwrapped_new]
72
+ in Dry::Types::Schema, Dry::Types::Schema
73
+ types_merger.type_registry["hash"].schema(
74
+ types_merger.call(
75
+ op_class,
76
+ unwrapped_old.name_key_map,
77
+ unwrapped_new.name_key_map
78
+ )
79
+ )
80
+ in [Dry::Types::AnyClass, _] | [Dry::Types::Hash, Dry::Types::Schema]
81
+ unwrapped_new
82
+ in [Dry::Types::Hash, Dry::Types::Schema] | [_, Dry::Types::AnyClass]
83
+ unwrapped_old
84
+ else
85
+ if unwrapped_old.primitive != unwrapped_new.primitive
86
+ raise ArgumentError, <<~MESSAGE
87
+ Can't merge types, unwrapped_old=#{unwrapped_old.inspect}, unwrapped_new=#{unwrapped_new.inspect}
88
+ MESSAGE
89
+ end
90
+
91
+ unwrapped_old
92
+ end
93
+ end
94
+
95
+ # @api private
96
+ def unwrap_type(type)
97
+ rules = []
98
+
99
+ while type.is_a?(Dry::Types::Decorator)
100
+ rules << type.rule if type.is_a?(Dry::Types::Constrained)
101
+
102
+ if type.meta[:maybe] & type.respond_to?(:right)
103
+ type = type.right
104
+ else
105
+ type = type.type
106
+ end
107
+ end
108
+
109
+ [type, rules.reduce(:&)]
110
+ end
111
+ end
112
+
113
+ def initialize(type_registry = TypeRegistry.new)
114
+ @type_registry = type_registry
115
+ end
116
+
117
+ # @api private
118
+ def call(op_class, lhs, rhs)
119
+ lhs.merge(rhs) do |_k, old, new|
120
+ ValueMerger.new(self, op_class, old, new).call
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Dry
4
4
  module Schema
5
- VERSION = "1.9.3"
5
+ VERSION = "1.10.1"
6
6
  end
7
7
  end
data/lib/dry/schema.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "dry/core/extensions"
4
+ require "dry/configurable"
5
+ require "dry/logic"
4
6
 
5
7
  require "dry/schema/config"
6
8
  require "dry/schema/constants"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-schema
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.3
4
+ version: 1.10.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-23 00:00:00.000000000 Z
11
+ date: 2022-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -230,6 +230,7 @@ files:
230
230
  - lib/dry/schema/type_container.rb
231
231
  - lib/dry/schema/type_registry.rb
232
232
  - lib/dry/schema/types.rb
233
+ - lib/dry/schema/types_merger.rb
233
234
  - lib/dry/schema/value_coercer.rb
234
235
  - lib/dry/schema/version.rb
235
236
  homepage: https://dry-rb.org/gems/dry-schema