dry-types 1.4.0 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +78 -0
  3. data/LICENSE +1 -1
  4. data/README.md +1 -1
  5. data/dry-types.gemspec +2 -3
  6. data/lib/dry-types.rb +1 -1
  7. data/lib/dry/types.rb +55 -31
  8. data/lib/dry/types/any.rb +2 -2
  9. data/lib/dry/types/array.rb +2 -2
  10. data/lib/dry/types/array/constructor.rb +1 -1
  11. data/lib/dry/types/array/member.rb +1 -1
  12. data/lib/dry/types/builder.rb +66 -18
  13. data/lib/dry/types/builder_methods.rb +1 -2
  14. data/lib/dry/types/coercions/json.rb +5 -5
  15. data/lib/dry/types/coercions/params.rb +3 -3
  16. data/lib/dry/types/compiler.rb +10 -10
  17. data/lib/dry/types/constrained.rb +6 -9
  18. data/lib/dry/types/constraints.rb +3 -3
  19. data/lib/dry/types/constructor.rb +40 -6
  20. data/lib/dry/types/constructor/function.rb +32 -2
  21. data/lib/dry/types/constructor/wrapper.rb +94 -0
  22. data/lib/dry/types/container.rb +1 -1
  23. data/lib/dry/types/core.rb +12 -12
  24. data/lib/dry/types/decorator.rb +2 -2
  25. data/lib/dry/types/default.rb +14 -1
  26. data/lib/dry/types/enum.rb +4 -3
  27. data/lib/dry/types/errors.rb +1 -1
  28. data/lib/dry/types/extensions.rb +2 -2
  29. data/lib/dry/types/extensions/maybe.rb +5 -4
  30. data/lib/dry/types/extensions/monads.rb +1 -1
  31. data/lib/dry/types/fn_container.rb +1 -1
  32. data/lib/dry/types/hash.rb +9 -15
  33. data/lib/dry/types/hash/constructor.rb +1 -1
  34. data/lib/dry/types/inflector.rb +1 -1
  35. data/lib/dry/types/json.rb +15 -15
  36. data/lib/dry/types/lax.rb +2 -2
  37. data/lib/dry/types/map.rb +2 -2
  38. data/lib/dry/types/meta.rb +1 -1
  39. data/lib/dry/types/module.rb +6 -6
  40. data/lib/dry/types/nominal.rb +11 -11
  41. data/lib/dry/types/params.rb +30 -28
  42. data/lib/dry/types/predicate_inferrer.rb +51 -11
  43. data/lib/dry/types/predicate_registry.rb +1 -1
  44. data/lib/dry/types/primitive_inferrer.rb +1 -1
  45. data/lib/dry/types/printer.rb +25 -25
  46. data/lib/dry/types/result.rb +1 -1
  47. data/lib/dry/types/schema.rb +5 -13
  48. data/lib/dry/types/schema/key.rb +4 -4
  49. data/lib/dry/types/spec/types.rb +57 -45
  50. data/lib/dry/types/sum.rb +4 -3
  51. data/lib/dry/types/type.rb +1 -1
  52. data/lib/dry/types/version.rb +1 -1
  53. metadata +9 -22
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/core/cache'
4
- require 'dry/types/predicate_registry'
3
+ require "dry/core/cache"
4
+ require "dry/core/class_attributes"
5
+ require "dry/types/predicate_registry"
5
6
 
6
7
  module Dry
7
8
  module Types
@@ -12,13 +13,17 @@ module Dry
12
13
  extend Core::Cache
13
14
 
14
15
  TYPE_TO_PREDICATE = {
15
- DateTime => :date_time?,
16
- FalseClass => :false?,
17
- Integer => :int?,
18
- NilClass => :nil?,
19
- String => :str?,
20
- TrueClass => :true?,
21
- BigDecimal => :decimal?
16
+ ::DateTime => :date_time?,
17
+ ::Date => :date?,
18
+ ::Time => :time?,
19
+ ::FalseClass => :false?,
20
+ ::Integer => :int?,
21
+ ::Float => :float?,
22
+ ::NilClass => :nil?,
23
+ ::String => :str?,
24
+ ::TrueClass => :true?,
25
+ ::BigDecimal => :decimal?,
26
+ ::Array => :array?
22
27
  }.freeze
23
28
 
24
29
  REDUCED_TYPES = {
@@ -35,6 +40,11 @@ module Dry
35
40
  #
36
41
  # @api private
37
42
  class Compiler
43
+ extend Core::ClassAttributes
44
+
45
+ defines :infer_predicate_by_class_name
46
+ infer_predicate_by_class_name nil
47
+
38
48
  # @return [PredicateRegistry]
39
49
  # @api private
40
50
  attr_reader :registry
@@ -46,7 +56,37 @@ module Dry
46
56
 
47
57
  # @api private
48
58
  def infer_predicate(type)
49
- [TYPE_TO_PREDICATE.fetch(type) { :"#{type.name.split('::').last.downcase}?" }]
59
+ pred = TYPE_TO_PREDICATE.fetch(type) do
60
+ if type.name.nil? || self.class.infer_predicate_by_class_name.equal?(false)
61
+ nil
62
+ else
63
+ candidate = :"#{type.name.split("::").last.downcase}?"
64
+
65
+ if registry.key?(candidate)
66
+ if self.class.infer_predicate_by_class_name
67
+ candidate
68
+ else
69
+ raise ::KeyError, <<~MESSAGE
70
+ Automatic predicate inferring from class names is deprecated
71
+ and will be removed in dry-types 2.0.
72
+ Use `Dry::Types::PredicateInferrer::Compiler.infer_predicate_by_class_name true`
73
+ to restore the previous behavior
74
+ or `Dry::Types::PredicateInferrer::Compiler.infer_predicate_by_class_name false`
75
+ to explicitly opt-out (i.e. no exception + no inferring).
76
+ Note: for dry-schema and dry-validation use Dry::Schema::PredicateInferrer::Compiler.
77
+ MESSAGE
78
+ end
79
+ else
80
+ nil
81
+ end
82
+ end
83
+ end
84
+
85
+ if pred.nil?
86
+ EMPTY_ARRAY
87
+ else
88
+ [pred]
89
+ end
50
90
  end
51
91
 
52
92
  # @api private
@@ -60,7 +100,7 @@ module Dry
60
100
  type = node[0]
61
101
  predicate = infer_predicate(type)
62
102
 
63
- if registry.key?(predicate[0])
103
+ if !predicate.empty? && registry.key?(predicate[0])
64
104
  predicate
65
105
  else
66
106
  [type?: type]
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/logic/predicates'
3
+ require "dry/logic/predicates"
4
4
 
5
5
  module Dry
6
6
  module Types
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/core/cache'
3
+ require "dry/core/cache"
4
4
 
5
5
  module Dry
6
6
  module Types
@@ -7,8 +7,6 @@ module Dry
7
7
  MAPPING = {
8
8
  Nominal => :visit_nominal,
9
9
  Constructor => :visit_constructor,
10
- Hash::Constructor => :visit_constructor,
11
- Array::Constructor => :visit_constructor,
12
10
  Constrained => :visit_constrained,
13
11
  Constrained::Coercible => :visit_constrained,
14
12
  Hash => :visit_hash,
@@ -27,14 +25,16 @@ module Dry
27
25
  }
28
26
 
29
27
  def call(type)
30
- output = ''.dup
28
+ output = "".dup
31
29
  visit(type) { |str| output << str }
32
30
  "#<Dry::Types[#{output}]>"
33
31
  end
34
32
 
35
33
  def visit(type, &block)
36
34
  print_with = MAPPING.fetch(type.class) do
37
- if type.is_a?(Type)
35
+ if type.class < Constructor
36
+ :visit_constructor
37
+ elsif type.is_a?(Type)
38
38
  return yield type.inspect
39
39
  else
40
40
  raise ArgumentError, "Do not know how to print #{type.class}"
@@ -44,7 +44,7 @@ module Dry
44
44
  end
45
45
 
46
46
  def visit_any(_)
47
- yield 'Any'
47
+ yield "Any"
48
48
  end
49
49
 
50
50
  def visit_array(type)
@@ -88,11 +88,11 @@ module Dry
88
88
  def visit_schema(schema)
89
89
  options = schema.options.dup
90
90
  size = schema.count
91
- key_fn_str = ''
92
- type_fn_str = ''
93
- strict_str = ''
91
+ key_fn_str = ""
92
+ type_fn_str = ""
93
+ strict_str = ""
94
94
 
95
- strict_str = 'strict ' if options.delete(:strict)
95
+ strict_str = "strict " if options.delete(:strict)
96
96
 
97
97
  if key_fn = options.delete(:key_transform_fn)
98
98
  visit_callable(key_fn) do |fn|
@@ -119,7 +119,7 @@ module Dry
119
119
  else
120
120
  yield header.dup << keys.map { |key|
121
121
  visit(key) { |type| type }
122
- }.join(' ') << '}>'
122
+ }.join(" ") << "}>"
123
123
  end
124
124
  end
125
125
  end
@@ -194,12 +194,12 @@ module Dry
194
194
 
195
195
  visit_options(options) do |opts|
196
196
  if mapping == enum.inverted_mapping
197
- values = mapping.values.map(&:inspect).join(', ')
197
+ values = mapping.values.map(&:inspect).join(", ")
198
198
  yield "Enum<#{type} values={#{values}}#{opts}>"
199
199
  else
200
200
  mapping_str = mapping.map { |key, value|
201
201
  "#{key.inspect}=>#{value.inspect}"
202
- }.join(', ')
202
+ }.join(", ")
203
203
  yield "Enum<#{type} mapping={#{mapping_str}}#{opts}>"
204
204
  end
205
205
  end
@@ -234,7 +234,7 @@ module Dry
234
234
 
235
235
  def visit_hash(hash)
236
236
  options = hash.options.dup
237
- type_fn_str = ''
237
+ type_fn_str = ""
238
238
 
239
239
  if type_fn = options.delete(:type_transform_fn)
240
240
  visit_callable(type_fn) do |fn|
@@ -244,7 +244,7 @@ module Dry
244
244
 
245
245
  visit_options(options, hash.meta) do |opts|
246
246
  if opts.empty? && type_fn_str.empty?
247
- yield 'Hash'
247
+ yield "Hash"
248
248
  else
249
249
  yield "Hash<#{type_fn_str}#{opts}>"
250
250
  end
@@ -255,24 +255,24 @@ module Dry
255
255
  fn = callable.is_a?(String) ? FnContainer[callable] : callable
256
256
 
257
257
  case fn
258
- when Method
258
+ when ::Method
259
259
  yield "#{fn.receiver}.#{fn.name}"
260
- when Proc
260
+ when ::Proc
261
261
  path, line = fn.source_location
262
262
 
263
263
  if line&.zero?
264
264
  yield ".#{path}"
265
265
  elsif path
266
- yield "#{path.sub(Dir.pwd + '/', EMPTY_STRING)}:#{line}"
267
- elsif fn.lambda?
268
- yield '(lambda)'
266
+ yield "#{path.sub(Dir.pwd + "/", EMPTY_STRING)}:#{line}"
269
267
  else
270
- match = fn.to_s.match(/\A#<Proc:0x\h+\(&:(\w+)\)>\z/)
268
+ match = fn.to_s.match(/\A#<Proc:0x\h+\(&:(?<name>\w+)\)(:? \(lambda\))?>\z/)
271
269
 
272
270
  if match
273
- yield ".#{match[1]}"
271
+ yield ".#{match[:name]}"
272
+ elsif fn.lambda?
273
+ yield "(lambda)"
274
274
  else
275
- yield '(proc)'
275
+ yield "(proc)"
276
276
  end
277
277
  end
278
278
  else
@@ -288,9 +288,9 @@ module Dry
288
288
 
289
289
  def visit_options(options, meta = EMPTY_HASH)
290
290
  if options.empty? && meta.empty?
291
- yield ''
291
+ yield ""
292
292
  else
293
- opts = options.empty? ? '' : " options=#{options.inspect}"
293
+ opts = options.empty? ? "" : " options=#{options.inspect}"
294
294
 
295
295
  if meta.empty?
296
296
  yield opts
@@ -304,7 +304,7 @@ module Dry
304
304
  end
305
305
  end
306
306
 
307
- yield "#{opts} meta={#{values.join(', ')}}"
307
+ yield "#{opts} meta={#{values.join(", ")}}"
308
308
  end
309
309
  end
310
310
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/equalizer'
3
+ require "dry/core/equalizer"
4
4
 
5
5
  module Dry
6
6
  module Types
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/types/fn_container'
3
+ require "dry/types/fn_container"
4
4
 
5
5
  module Dry
6
6
  module Types
@@ -83,7 +83,7 @@ module Dry
83
83
  call_unsafe(hash, options)
84
84
  end
85
85
 
86
- # @param [Hash] hash
86
+ # @param input [Hash] hash
87
87
  #
88
88
  # @yieldparam [Failure] failure
89
89
  # @yieldreturn [Result]
@@ -145,18 +145,10 @@ module Dry
145
145
  #
146
146
  # @api public
147
147
  def to_ast(meta: true)
148
- if RUBY_VERSION >= '2.5'
149
- opts = options.slice(:key_transform_fn, :type_transform_fn, :strict)
150
- else
151
- opts = options.select { |k, _|
152
- k == :key_transform_fn || k == :type_transform_fn || k == :strict
153
- }
154
- end
155
-
156
148
  [
157
149
  :schema,
158
150
  [keys.map { |key| key.to_ast(meta: meta) },
159
- opts,
151
+ options.slice(:key_transform_fn, :type_transform_fn, :strict),
160
152
  meta ? self.meta : EMPTY_HASH]
161
153
  ]
162
154
  end
@@ -190,7 +182,7 @@ module Dry
190
182
  def with_key_transform(proc = nil, &block)
191
183
  fn = proc || block
192
184
 
193
- raise ArgumentError, 'a block or callable argument is required' if fn.nil?
185
+ raise ArgumentError, "a block or callable argument is required" if fn.nil?
194
186
 
195
187
  handle = Dry::Types::FnContainer.register(fn)
196
188
  with(key_transform_fn: handle)
@@ -288,7 +280,7 @@ module Dry
288
280
  #
289
281
  # A new instance is returned.
290
282
  #
291
- # @param schema [Schema]
283
+ # @param other [Schema] schema
292
284
  # @return [Schema]
293
285
  #
294
286
  # @api public
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/equalizer'
4
- require 'dry/core/deprecations'
3
+ require "dry/core/equalizer"
4
+ require "dry/core/deprecations"
5
5
 
6
6
  module Dry
7
7
  module Types
@@ -136,8 +136,8 @@ module Dry
136
136
  super
137
137
  else
138
138
  self.class.warn(
139
- 'Using meta for making schema keys is deprecated, ' \
140
- 'please use .omittable or .required(false) instead' \
139
+ "Using meta for making schema keys is deprecated, " \
140
+ "please use .omittable or .required(false) instead" \
141
141
  "\n" + Core::Deprecations::STACK.()
142
142
  )
143
143
  super.required(!data[:omittable])
@@ -1,79 +1,79 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- RSpec.shared_examples_for 'Dry::Types::Nominal without primitive' do
3
+ RSpec.shared_examples_for "Dry::Types::Nominal without primitive" do
4
4
  def be_boolean
5
5
  satisfy { |x| x == true || x == false }
6
6
  end
7
7
 
8
- describe '#constrained?' do
9
- it 'returns a boolean value' do
8
+ describe "#constrained?" do
9
+ it "returns a boolean value" do
10
10
  expect(type.constrained?).to be_boolean
11
11
  end
12
12
  end
13
13
 
14
- describe '#default?' do
15
- it 'returns a boolean value' do
14
+ describe "#default?" do
15
+ it "returns a boolean value" do
16
16
  expect(type.default?).to be_boolean
17
17
  end
18
18
  end
19
19
 
20
- describe '#valid?' do
21
- it 'returns a boolean value' do
20
+ describe "#valid?" do
21
+ it "returns a boolean value" do
22
22
  expect(type.valid?(1)).to be_boolean
23
23
  end
24
24
  end
25
25
 
26
- describe '#eql?' do
27
- it 'has #eql? defined' do
26
+ describe "#eql?" do
27
+ it "has #eql? defined" do
28
28
  expect(type).to eql(type)
29
29
  end
30
30
  end
31
31
 
32
- describe '#==' do
33
- it 'has #== defined' do
32
+ describe "#==" do
33
+ it "has #== defined" do
34
34
  expect(type).to eq(type)
35
35
  end
36
36
  end
37
37
 
38
- describe '#optional?' do
39
- it 'returns a boolean value' do
38
+ describe "#optional?" do
39
+ it "returns a boolean value" do
40
40
  expect(type.optional?).to be_boolean
41
41
  end
42
42
  end
43
43
 
44
- describe '#to_s' do
45
- it 'returns a custom string representation' do
46
- expect(type.to_s).to start_with('#<Dry::Types') if type.class.name.start_with?('Dry::Types')
44
+ describe "#to_s" do
45
+ it "returns a custom string representation" do
46
+ expect(type.to_s).to start_with("#<Dry::Types") if type.class.name.start_with?("Dry::Types")
47
47
  end
48
48
  end
49
49
 
50
- describe '#to_proc' do
50
+ describe "#to_proc" do
51
51
  subject(:callable) { type.to_proc }
52
52
 
53
- it 'converts a type to a proc' do
53
+ it "converts a type to a proc" do
54
54
  expect(callable).to be_a(Proc)
55
55
  end
56
56
  end
57
57
  end
58
58
 
59
- RSpec.shared_examples_for 'Dry::Types::Nominal#meta' do
60
- describe '#meta' do
61
- it 'allows setting meta information' do
62
- with_meta = type.meta(foo: :bar).meta(baz: '1')
59
+ RSpec.shared_examples_for "Dry::Types::Nominal#meta" do
60
+ describe "#meta" do
61
+ it "allows setting meta information" do
62
+ with_meta = type.meta(foo: :bar).meta(baz: "1")
63
63
 
64
64
  expect(with_meta).to be_instance_of(type.class)
65
- expect(with_meta.meta).to eql(foo: :bar, baz: '1')
65
+ expect(with_meta.meta).to eql(foo: :bar, baz: "1")
66
66
  end
67
67
 
68
- it 'equalizes on empty meta' do
68
+ it "equalizes on empty meta" do
69
69
  expect(type).to eql(type.meta({}))
70
70
  end
71
71
 
72
- it 'equalizes on filled meta' do
73
- expect(type).to_not eql(type.meta(i_am: 'different'))
72
+ it "equalizes on filled meta" do
73
+ expect(type).to_not eql(type.meta(i_am: "different"))
74
74
  end
75
75
 
76
- it 'is locally immutable' do
76
+ it "is locally immutable" do
77
77
  expect(type.meta).to be_a ::Hash
78
78
  expect(type.meta).to be_frozen
79
79
  expect(type.meta).not_to have_key :immutable_test
@@ -84,24 +84,24 @@ RSpec.shared_examples_for 'Dry::Types::Nominal#meta' do
84
84
  end
85
85
  end
86
86
 
87
- describe '#pristine' do
88
- it 'erases meta' do
87
+ describe "#pristine" do
88
+ it "erases meta" do
89
89
  expect(type.meta(foo: :bar).pristine).to eql(type)
90
90
  end
91
91
  end
92
92
  end
93
93
 
94
94
  RSpec.shared_examples_for Dry::Types::Nominal do
95
- it_behaves_like 'Dry::Types::Nominal without primitive'
95
+ it_behaves_like "Dry::Types::Nominal without primitive"
96
96
 
97
- describe '#primitive' do
98
- it 'returns a class' do
97
+ describe "#primitive" do
98
+ it "returns a class" do
99
99
  expect(type.primitive).to be_instance_of(Class)
100
100
  end
101
101
  end
102
102
 
103
- describe '#constructor' do
104
- it 'returns a constructor' do
103
+ describe "#constructor" do
104
+ it "returns a constructor" do
105
105
  constructor = type.constructor(&:to_s)
106
106
 
107
107
  expect(constructor).to be_a(Dry::Types::Type)
@@ -109,40 +109,52 @@ RSpec.shared_examples_for Dry::Types::Nominal do
109
109
  end
110
110
  end
111
111
 
112
- RSpec.shared_examples_for 'a constrained type' do |options = { inputs: Object.new }|
112
+ RSpec.shared_examples_for "a constrained type" do |options = {inputs: Object.new}|
113
113
  inputs = options[:inputs]
114
114
 
115
115
  let(:fallback) { Object.new }
116
116
 
117
- describe '#call' do
118
- it 'yields a block on failure' do
117
+ describe "#call" do
118
+ it "yields a block on failure" do
119
119
  Array(inputs).each do |input|
120
120
  expect(type.(input) { fallback }).to be(fallback)
121
121
  end
122
122
  end
123
123
 
124
- it 'throws an error on invalid input' do
124
+ it "throws an error on invalid input" do
125
125
  Array(inputs).each do |input|
126
126
  expect { type.(input) }.to raise_error(Dry::Types::CoercionError)
127
127
  end
128
128
  end
129
129
  end
130
+
131
+ describe "#constructor" do
132
+ let(:wrapping_constructor) do
133
+ type.constructor { |input, type| type.(input) { fallback } }
134
+ end
135
+
136
+ it "can be wrapped" do
137
+ Array(inputs).each do |input|
138
+ expect(wrapping_constructor.(input)).to be(fallback)
139
+ end
140
+ end
141
+ end
130
142
  end
131
143
 
132
- RSpec.shared_examples_for 'a nominal type' do |inputs: Object.new|
133
- describe '#call' do
134
- it 'always returns the input back' do
144
+ RSpec.shared_examples_for "a nominal type" do |inputs: Object.new|
145
+ describe "#call" do
146
+ it "always returns the input back" do
135
147
  Array(inputs).each do |input|
136
- expect(type.(input) { fail }).to be(input)
148
+ expect(type.(input) { raise }).to be(input)
137
149
  expect(type.(input)).to be(input)
138
150
  end
139
151
  end
140
152
  end
141
153
  end
142
154
 
143
- RSpec.shared_examples_for 'a composable constructor' do
144
- describe '#constructor' do
145
- it 'has aliases for composition' do
155
+ RSpec.shared_examples_for "a composable constructor" do
156
+ describe "#constructor" do
157
+ it "has aliases for composition" do
146
158
  expect(type.method(:append)).to eql(type.method(:constructor))
147
159
  expect(type.method(:prepend)).to eql(type.method(:constructor))
148
160
  expect(type.method(:<<)).to eql(type.method(:constructor))