dry-types 1.5.1 → 1.7.2

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