dry-types 1.5.1 → 1.7.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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/LICENSE +1 -1
  4. data/README.md +7 -13
  5. data/dry-types.gemspec +19 -18
  6. data/lib/dry/types/array/constructor.rb +0 -2
  7. data/lib/dry/types/array/member.rb +1 -3
  8. data/lib/dry/types/array.rb +0 -3
  9. data/lib/dry/types/builder.rb +43 -14
  10. data/lib/dry/types/builder_methods.rb +1 -1
  11. data/lib/dry/types/coercions/params.rb +4 -3
  12. data/lib/dry/types/compat.rb +1 -0
  13. data/lib/dry/types/compiler.rb +1 -3
  14. data/lib/dry/types/composition.rb +152 -0
  15. data/lib/dry/types/constrained.rb +0 -5
  16. data/lib/dry/types/constraints.rb +3 -7
  17. data/lib/dry/types/constructor/function.rb +8 -9
  18. data/lib/dry/types/constructor.rb +4 -10
  19. data/lib/dry/types/container.rb +1 -3
  20. data/lib/dry/types/core.rb +2 -3
  21. data/lib/dry/types/decorator.rb +0 -2
  22. data/lib/dry/types/default.rb +3 -6
  23. data/lib/dry/types/enum.rb +0 -3
  24. data/lib/dry/types/errors.rb +13 -1
  25. data/lib/dry/types/extensions/maybe.rb +8 -5
  26. data/lib/dry/types/extensions/monads.rb +7 -2
  27. data/lib/dry/types/fn_container.rb +0 -2
  28. data/lib/dry/types/hash/constructor.rb +2 -4
  29. data/lib/dry/types/hash.rb +1 -6
  30. data/lib/dry/types/implication.rb +66 -0
  31. data/lib/dry/types/intersection.rb +108 -0
  32. data/lib/dry/types/lax.rb +1 -4
  33. data/lib/dry/types/map.rb +9 -3
  34. data/lib/dry/types/module.rb +18 -9
  35. data/lib/dry/types/nominal.rb +2 -13
  36. data/lib/dry/types/predicate_inferrer.rb +8 -9
  37. data/lib/dry/types/predicate_registry.rb +7 -7
  38. data/lib/dry/types/primitive_inferrer.rb +0 -2
  39. data/lib/dry/types/printer/composition.rb +44 -0
  40. data/lib/dry/types/printer.rb +116 -131
  41. data/lib/dry/types/result.rb +0 -2
  42. data/lib/dry/types/schema/key.rb +1 -4
  43. data/lib/dry/types/schema.rb +6 -4
  44. data/lib/dry/types/sum.rb +3 -95
  45. data/lib/dry/types/type.rb +1 -3
  46. data/lib/dry/types/version.rb +1 -1
  47. data/lib/dry/types.rb +49 -22
  48. metadata +32 -46
@@ -1,15 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "dry/types/printer/composition"
4
+
3
5
  module Dry
6
+ # rubocop:disable Metrics/AbcSize
7
+ # rubocop:disable Metrics/PerceivedComplexity
4
8
  module Types
5
9
  # @api private
6
10
  class Printer
7
11
  MAPPING = {
8
12
  Nominal => :visit_nominal,
9
13
  Constructor => :visit_constructor,
10
- Constrained => :visit_constrained,
11
- Constrained::Coercible => :visit_constrained,
12
- Hash => :visit_hash,
14
+ [
15
+ Constrained,
16
+ Constrained::Coercible
17
+ ] => :visit_constrained,
18
+ Types::Hash => :visit_hash,
13
19
  Schema => :visit_schema,
14
20
  Schema::Key => :visit_key,
15
21
  Map => :visit_map,
@@ -17,12 +23,22 @@ module Dry
17
23
  Array::Member => :visit_array_member,
18
24
  Lax => :visit_lax,
19
25
  Enum => :visit_enum,
20
- Default => :visit_default,
21
- Default::Callable => :visit_default,
22
- Sum => :visit_sum,
23
- Sum::Constrained => :visit_sum,
26
+ [Default, Default::Callable] => :visit_default,
27
+ [
28
+ Sum,
29
+ Sum::Constrained,
30
+ Intersection,
31
+ Intersection::Constrained,
32
+ Implication,
33
+ Implication::Constrained
34
+ ] => :visit_composition,
24
35
  Any.class => :visit_any
25
- }
36
+ }.flat_map { |k, v| Array(k).map { |kk| [kk, v] } }.to_h
37
+
38
+ def initialize
39
+ @composition_printers = {}
40
+ freeze
41
+ end
26
42
 
27
43
  def call(type)
28
44
  output = "".dup
@@ -85,106 +101,10 @@ module Dry
85
101
  end
86
102
  end
87
103
 
88
- def visit_schema(schema)
89
- options = schema.options.dup
90
- size = schema.count
91
- key_fn_str = ""
92
- type_fn_str = ""
93
- strict_str = ""
94
-
95
- strict_str = "strict " if options.delete(:strict)
96
-
97
- if key_fn = options.delete(:key_transform_fn)
98
- visit_callable(key_fn) do |fn|
99
- key_fn_str = "key_fn=#{fn} "
100
- end
101
- end
102
-
103
- if type_fn = options.delete(:type_transform_fn)
104
- visit_callable(type_fn) do |fn|
105
- type_fn_str = "type_fn=#{fn} "
106
- end
107
- end
108
-
109
- keys = options.delete(:keys)
110
-
111
- visit_options(options, schema.meta) do |opts|
112
- opts = "#{opts[1..-1]} " unless opts.empty?
113
- schema_parameters = "#{key_fn_str}#{type_fn_str}#{strict_str}#{opts}"
114
-
115
- header = "Schema<#{schema_parameters}keys={"
116
-
117
- if size.zero?
118
- yield "#{header}}>"
119
- else
120
- yield header.dup << keys.map { |key|
121
- visit(key) { |type| type }
122
- }.join(" ") << "}>"
123
- end
124
- end
125
- end
126
-
127
- def visit_map(map)
128
- visit(map.key_type) do |key|
129
- visit(map.value_type) do |value|
130
- options = map.options.dup
131
- options.delete(:key_type)
132
- options.delete(:value_type)
133
-
134
- visit_options(options) do |_opts|
135
- yield "Map<#{key} => #{value}>"
136
- end
137
- end
138
- end
139
- end
140
-
141
- def visit_key(key)
142
- visit(key.type) do |type|
143
- if key.required?
144
- yield "#{key.name}: #{type}"
145
- else
146
- yield "#{key.name}?: #{type}"
147
- end
148
- end
149
- end
150
-
151
- def visit_sum(sum)
152
- visit_sum_constructors(sum) do |constructors|
153
- visit_options(sum.options, sum.meta) do |opts|
154
- yield "Sum<#{constructors}#{opts}>"
155
- end
156
- end
157
- end
158
-
159
- def visit_sum_constructors(sum)
160
- case sum.left
161
- when Sum
162
- visit_sum_constructors(sum.left) do |left|
163
- case sum.right
164
- when Sum
165
- visit_sum_constructors(sum.right) do |right|
166
- yield "#{left} | #{right}"
167
- end
168
- else
169
- visit(sum.right) do |right|
170
- yield "#{left} | #{right}"
171
- end
172
- end
173
- end
174
- else
175
- visit(sum.left) do |left|
176
- case sum.right
177
- when Sum
178
- visit_sum_constructors(sum.right) do |right|
179
- yield "#{left} | #{right}"
180
- end
181
- else
182
- visit(sum.right) do |right|
183
- yield "#{left} | #{right}"
184
- end
185
- end
186
- end
187
- end
104
+ def visit_composition(composition, &block)
105
+ klass = composition.class
106
+ @composition_printers[klass] = Composition.new(self, klass)
107
+ @composition_printers[klass].visit(composition, &block)
188
108
  end
189
109
 
190
110
  def visit_enum(enum)
@@ -232,25 +152,6 @@ module Dry
232
152
  end
233
153
  end
234
154
 
235
- def visit_hash(hash)
236
- options = hash.options.dup
237
- type_fn_str = ""
238
-
239
- if type_fn = options.delete(:type_transform_fn)
240
- visit_callable(type_fn) do |fn|
241
- type_fn_str = "type_fn=#{fn}"
242
- end
243
- end
244
-
245
- visit_options(options, hash.meta) do |opts|
246
- if opts.empty? && type_fn_str.empty?
247
- yield "Hash"
248
- else
249
- yield "Hash<#{type_fn_str}#{opts}>"
250
- end
251
- end
252
- end
253
-
254
155
  def visit_callable(callable)
255
156
  fn = callable.is_a?(String) ? FnContainer[callable] : callable
256
157
 
@@ -263,9 +164,9 @@ module Dry
263
164
  if line&.zero?
264
165
  yield ".#{path}"
265
166
  elsif path
266
- yield "#{path.sub(Dir.pwd + "/", EMPTY_STRING)}:#{line}"
167
+ yield "#{path.sub("#{Dir.pwd}/", EMPTY_STRING)}:#{line}"
267
168
  else
268
- match = fn.to_s.match(/\A#<Proc:0x\h+\(&:(?<name>\w+)\)(:? \(lambda\))?>\z/)
169
+ match = fn.to_s.match(/\A#<Proc:0x\h+\(&:(?<name>\w+)\)(:? \(lambda\))?>\z/) # rubocop:disable Lint/MixedRegexpCaptureTypes
269
170
 
270
171
  if match
271
172
  yield ".#{match[:name]}"
@@ -286,7 +187,89 @@ module Dry
286
187
  end
287
188
  end
288
189
 
289
- def visit_options(options, meta = EMPTY_HASH)
190
+ def visit_schema(schema)
191
+ options = schema.options.dup
192
+ size = schema.count
193
+ key_fn_str = ""
194
+ type_fn_str = ""
195
+ strict_str = ""
196
+
197
+ strict_str = "strict " if options.delete(:strict)
198
+
199
+ if (key_fn = options.delete(:key_transform_fn))
200
+ visit_callable(key_fn) do |fn|
201
+ key_fn_str = "key_fn=#{fn} "
202
+ end
203
+ end
204
+
205
+ if (type_fn = options.delete(:type_transform_fn))
206
+ visit_callable(type_fn) do |fn|
207
+ type_fn_str = "type_fn=#{fn} "
208
+ end
209
+ end
210
+
211
+ keys = options.delete(:keys)
212
+
213
+ visit_options(options, schema.meta) do |opts|
214
+ opts = "#{opts[1..]} " unless opts.empty?
215
+ schema_parameters = "#{key_fn_str}#{type_fn_str}#{strict_str}#{opts}"
216
+
217
+ header = "Schema<#{schema_parameters}keys={"
218
+
219
+ if size.zero?
220
+ yield "#{header}}>"
221
+ else
222
+ yield header.dup << keys.map { |key|
223
+ visit(key) { |type| type }
224
+ }.join(" ") << "}>"
225
+ end
226
+ end
227
+ end
228
+
229
+ def visit_map(map)
230
+ visit(map.key_type) do |key|
231
+ visit(map.value_type) do |value|
232
+ options = map.options.dup
233
+ options.delete(:key_type)
234
+ options.delete(:value_type)
235
+
236
+ visit_options(options) do |_opts|
237
+ yield "Map<#{key} => #{value}>"
238
+ end
239
+ end
240
+ end
241
+ end
242
+
243
+ def visit_key(key)
244
+ visit(key.type) do |type|
245
+ if key.required?
246
+ yield "#{key.name}: #{type}"
247
+ else
248
+ yield "#{key.name}?: #{type}"
249
+ end
250
+ end
251
+ end
252
+
253
+ def visit_hash(hash)
254
+ options = hash.options.dup
255
+ type_fn_str = ""
256
+
257
+ if (type_fn = options.delete(:type_transform_fn))
258
+ visit_callable(type_fn) do |fn|
259
+ type_fn_str = "type_fn=#{fn}"
260
+ end
261
+ end
262
+
263
+ visit_options(options, hash.meta) do |opts|
264
+ if opts.empty? && type_fn_str.empty?
265
+ yield "Hash"
266
+ else
267
+ yield "Hash<#{type_fn_str}#{opts}>"
268
+ end
269
+ end
270
+ end
271
+
272
+ def visit_options(options, meta = EMPTY_HASH) # rubocop:disable Metrics/PerceivedComplexity
290
273
  if options.empty? && meta.empty?
291
274
  yield ""
292
275
  else
@@ -310,6 +293,8 @@ module Dry
310
293
  end
311
294
  end
312
295
 
313
- PRINTER = Printer.new.freeze
296
+ PRINTER = Printer.new
314
297
  end
298
+ # rubocop:enable Metrics/AbcSize
299
+ # rubocop:enable Metrics/PerceivedComplexity
315
300
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/core/equalizer"
4
-
5
3
  module Dry
6
4
  module Types
7
5
  # Result class used by {Type#try}
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/core/equalizer"
4
- require "dry/core/deprecations"
5
-
6
3
  module Dry
7
4
  module Types
8
5
  # Schema is a hash with explicit member types defined
@@ -15,7 +12,7 @@ module Dry
15
12
  #
16
13
  # @see Dry::Types::Schema
17
14
  class Key
18
- extend ::Dry::Core::Deprecations[:'dry-types']
15
+ extend ::Dry::Core::Deprecations[:"dry-types"]
19
16
  include Type
20
17
  include Dry::Equalizer(:name, :type, :options, inspect: false, immutable: true)
21
18
  include Decorator
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/types/fn_container"
4
-
5
3
  module Dry
6
4
  module Types
7
5
  # The built-in Hash type can be defined in terms of keys and associated types
@@ -92,6 +90,8 @@ module Dry
92
90
  # @return [Object] if coercion fails and a block is given
93
91
  #
94
92
  # @api public
93
+ # rubocop:disable Metrics/AbcSize
94
+ # rubocop:disable Metrics/PerceivedComplexity
95
95
  def try(input)
96
96
  if primitive?(input)
97
97
  success = true
@@ -138,6 +138,8 @@ module Dry
138
138
  failure
139
139
  end
140
140
  end
141
+ # rubocop:enable Metrics/AbcSize
142
+ # rubocop:enable Metrics/PerceivedComplexity
141
143
 
142
144
  # @param meta [Boolean] Whether to dump the meta to the AST
143
145
  #
@@ -167,7 +169,7 @@ module Dry
167
169
  # @return [Schema]
168
170
  #
169
171
  # @api public
170
- def strict(strict = true)
172
+ def strict(strict = true) # rubocop:disable Style/OptionalBooleanParameter
171
173
  with(strict: strict)
172
174
  end
173
175
 
@@ -368,7 +370,7 @@ module Dry
368
370
  # Try to add missing keys to the hash
369
371
  #
370
372
  # @api private
371
- def resolve_missing_keys(hash, options)
373
+ def resolve_missing_keys(hash, options) # rubocop:disable Metrics/PerceivedComplexity
372
374
  skip_missing = options.fetch(:skip_missing, false)
373
375
  resolve_defaults = options.fetch(:resolve_defaults, true)
374
376
 
data/lib/dry/types/sum.rb CHANGED
@@ -1,71 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/core/equalizer"
4
- require "dry/types/options"
5
- require "dry/types/meta"
6
-
7
3
  module Dry
8
4
  module Types
9
5
  # Sum type
10
6
  #
11
7
  # @api public
12
8
  class Sum
13
- include Type
14
- include Builder
15
- include Options
16
- include Meta
17
- include Printable
18
- include Dry::Equalizer(:left, :right, :options, :meta, inspect: false, immutable: true)
19
-
20
- # @return [Type]
21
- attr_reader :left
22
-
23
- # @return [Type]
24
- attr_reader :right
25
-
26
- # @api private
27
- class Constrained < Sum
28
- # @return [Dry::Logic::Operations::Or]
29
- def rule
30
- left.rule | right.rule
31
- end
32
-
33
- # @return [true]
34
- def constrained?
35
- true
36
- end
37
- end
38
-
39
- # @param [Type] left
40
- # @param [Type] right
41
- # @param [Hash] options
42
- #
43
- # @api private
44
- def initialize(left, right, **options)
45
- super
46
- @left, @right = left, right
47
- freeze
48
- end
9
+ include Composition
49
10
 
50
- # @return [String]
51
- #
52
- # @api public
53
- def name
54
- [left, right].map(&:name).join(" | ")
55
- end
56
-
57
- # @return [false]
58
- #
59
- # @api public
60
- def default?
61
- false
62
- end
63
-
64
- # @return [false]
65
- #
66
- # @api public
67
- def constrained?
68
- false
11
+ def self.operator
12
+ :|
69
13
  end
70
14
 
71
15
  # @return [Boolean]
@@ -108,26 +52,6 @@ module Dry
108
52
  end
109
53
  end
110
54
 
111
- # @api private
112
- def success(input)
113
- if left.valid?(input)
114
- left.success(input)
115
- elsif right.valid?(input)
116
- right.success(input)
117
- else
118
- raise ArgumentError, "Invalid success value '#{input}' for #{inspect}"
119
- end
120
- end
121
-
122
- # @api private
123
- def failure(input, _error = nil)
124
- if !left.valid?(input)
125
- left.failure(input, left.try(input).error)
126
- else
127
- right.failure(input, right.try(input).error)
128
- end
129
- end
130
-
131
55
  # @param [Object] value
132
56
  #
133
57
  # @return [Boolean]
@@ -153,13 +77,6 @@ module Dry
153
77
  end
154
78
  end
155
79
 
156
- # @see Nominal#to_ast
157
- #
158
- # @api public
159
- def to_ast(meta: true)
160
- [:sum, [left.to_ast(meta: meta), right.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]]
161
- end
162
-
163
80
  # @param [Hash] options
164
81
  #
165
82
  # @return [Constrained,Sum]
@@ -174,15 +91,6 @@ module Dry
174
91
  super
175
92
  end
176
93
  end
177
-
178
- # Wrap the type with a proc
179
- #
180
- # @return [Proc]
181
- #
182
- # @api public
183
- def to_proc
184
- proc { |value| self.(value) }
185
- end
186
94
  end
187
95
  end
188
96
  end
@@ -1,14 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/core/deprecations"
4
-
5
3
  module Dry
6
4
  module Types
7
5
  # Common Type module denoting an object is a Type
8
6
  #
9
7
  # @api public
10
8
  module Type
11
- extend ::Dry::Core::Deprecations[:'dry-types']
9
+ extend ::Dry::Core::Deprecations[:"dry-types"]
12
10
 
13
11
  deprecate(:safe, :lax)
14
12
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Dry
4
4
  module Types
5
- VERSION = "1.5.1"
5
+ VERSION = "1.7.2"
6
6
  end
7
7
  end
data/lib/dry/types.rb CHANGED
@@ -3,36 +3,63 @@
3
3
  require "bigdecimal"
4
4
  require "date"
5
5
  require "set"
6
+ require "zeitwerk"
6
7
 
7
8
  require "concurrent/map"
8
9
 
9
- require "dry/container"
10
- require "dry/core/extensions"
11
- require "dry/core/constants"
12
- require "dry/core/class_attributes"
10
+ require "dry/core"
11
+ require "dry/logic"
13
12
 
13
+ require "dry/types/constraints"
14
+ require "dry/types/errors"
14
15
  require "dry/types/version"
15
- require "dry/types/container"
16
+
17
+ # This must be required explicitly as it may conflict with dry-inflector
16
18
  require "dry/types/inflector"
17
- require "dry/types/type"
18
- require "dry/types/printable"
19
- require "dry/types/nominal"
20
- require "dry/types/constructor"
21
19
  require "dry/types/module"
22
20
 
23
- require "dry/types/errors"
24
-
25
21
  module Dry
26
22
  # Main library namespace
27
23
  #
28
24
  # @api public
29
25
  module Types
30
- extend Dry::Core::Extensions
31
- extend Dry::Core::ClassAttributes
32
- extend Dry::Core::Deprecations[:'dry-types']
33
- include Dry::Core::Constants
26
+ extend ::Dry::Core::Extensions
27
+ extend ::Dry::Core::ClassAttributes
28
+ extend ::Dry::Core::Deprecations[:"dry-types"]
29
+ include ::Dry::Core::Constants
30
+
31
+ TYPE_SPEC_REGEX = /(.+)<(.+)>/
32
+
33
+ def self.loader
34
+ @loader ||= ::Zeitwerk::Loader.new.tap do |loader|
35
+ root = ::File.expand_path("..", __dir__)
36
+ loader.tag = "dry-types"
37
+ loader.inflector = ::Zeitwerk::GemInflector.new("#{root}/dry-types.rb")
38
+ loader.inflector.inflect("json" => "JSON")
39
+ loader.push_dir(root)
40
+ loader.ignore(
41
+ "#{root}/dry-types.rb",
42
+ "#{root}/dry/types/extensions",
43
+ "#{root}/dry/types/printer",
44
+ "#{root}/dry/types/spec/types.rb",
45
+ "#{root}/dry/types/{#{%w[
46
+ compat
47
+ constraints
48
+ core
49
+ errors
50
+ extensions
51
+ inflector
52
+ module
53
+ json
54
+ params
55
+ printer
56
+ version
57
+ ].join(",")}}.rb"
58
+ )
59
+ end
60
+ end
34
61
 
35
- TYPE_SPEC_REGEX = /(.+)<(.+)>/.freeze
62
+ loader.setup
36
63
 
37
64
  # @see Dry.Types
38
65
  def self.module(*namespaces, default: :nominal, **aliases)
@@ -120,7 +147,7 @@ module Dry
120
147
  #
121
148
  # @return [String]
122
149
  def self.identifier(klass)
123
- Inflector.underscore(klass).tr("/", ".")
150
+ Types::Inflector.underscore(klass).tr("/", ".")
124
151
  end
125
152
 
126
153
  # Cached type map
@@ -134,7 +161,7 @@ module Dry
134
161
 
135
162
  # @api private
136
163
  def self.const_missing(const)
137
- underscored = Inflector.underscore(const)
164
+ underscored = Types::Inflector.underscore(const)
138
165
 
139
166
  if container.keys.any? { |key| key.split(".")[0] == underscored }
140
167
  raise ::NameError,
@@ -180,7 +207,7 @@ module Dry
180
207
  #
181
208
  # module Types
182
209
  # # imports all types as constants, uses modules for namespaces
183
- # include Dry::Types()
210
+ # include Dry.Types()
184
211
  # end
185
212
  # # strict types are exported by default
186
213
  # Types::Integer
@@ -191,7 +218,7 @@ module Dry
191
218
  # @example changing default types
192
219
  #
193
220
  # module Types
194
- # include Dry::Types(default: :nominal)
221
+ # include Dry.Types(default: :nominal)
195
222
  # end
196
223
  # Types::Integer
197
224
  # # => #<Dry::Types[Nominal<Integer>]>
@@ -199,7 +226,7 @@ module Dry
199
226
  # @example cherry-picking namespaces
200
227
  #
201
228
  # module Types
202
- # include Dry::Types(:strict, :coercible)
229
+ # include Dry.Types(:strict, :coercible)
203
230
  # end
204
231
  # # cherry-picking discards default types,
205
232
  # # provide the :default option along with the list of
@@ -208,7 +235,7 @@ module Dry
208
235
  #
209
236
  # @example custom names
210
237
  # module Types
211
- # include Dry::Types(coercible: :Kernel)
238
+ # include Dry.Types(coercible: :Kernel)
212
239
  # end
213
240
  # Types::Kernel::Integer
214
241
  # # => #<Dry::Types[Constructor<Nominal<Integer> fn=Kernel.Integer>]>