dry-types 0.15.0 → 1.5.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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +547 -161
  3. data/LICENSE +17 -17
  4. data/README.md +15 -13
  5. data/dry-types.gemspec +27 -30
  6. data/lib/dry/types/any.rb +23 -12
  7. data/lib/dry/types/array/constructor.rb +32 -0
  8. data/lib/dry/types/array/member.rb +74 -15
  9. data/lib/dry/types/array.rb +18 -2
  10. data/lib/dry/types/builder.rb +118 -22
  11. data/lib/dry/types/builder_methods.rb +46 -16
  12. data/lib/dry/types/coercions/json.rb +43 -7
  13. data/lib/dry/types/coercions/params.rb +117 -32
  14. data/lib/dry/types/coercions.rb +76 -22
  15. data/lib/dry/types/compiler.rb +44 -21
  16. data/lib/dry/types/constrained/coercible.rb +36 -6
  17. data/lib/dry/types/constrained.rb +79 -31
  18. data/lib/dry/types/constraints.rb +18 -4
  19. data/lib/dry/types/constructor/function.rb +216 -0
  20. data/lib/dry/types/constructor/wrapper.rb +94 -0
  21. data/lib/dry/types/constructor.rb +110 -61
  22. data/lib/dry/types/container.rb +6 -1
  23. data/lib/dry/types/core.rb +34 -11
  24. data/lib/dry/types/decorator.rb +38 -17
  25. data/lib/dry/types/default.rb +61 -16
  26. data/lib/dry/types/enum.rb +36 -20
  27. data/lib/dry/types/errors.rb +74 -8
  28. data/lib/dry/types/extensions/maybe.rb +65 -17
  29. data/lib/dry/types/extensions/monads.rb +29 -0
  30. data/lib/dry/types/extensions.rb +7 -1
  31. data/lib/dry/types/fn_container.rb +6 -1
  32. data/lib/dry/types/hash/constructor.rb +17 -4
  33. data/lib/dry/types/hash.rb +32 -20
  34. data/lib/dry/types/inflector.rb +3 -1
  35. data/lib/dry/types/json.rb +18 -16
  36. data/lib/dry/types/lax.rb +75 -0
  37. data/lib/dry/types/map.rb +70 -32
  38. data/lib/dry/types/meta.rb +51 -0
  39. data/lib/dry/types/module.rb +16 -11
  40. data/lib/dry/types/nominal.rb +113 -22
  41. data/lib/dry/types/options.rb +12 -25
  42. data/lib/dry/types/params.rb +39 -25
  43. data/lib/dry/types/predicate_inferrer.rb +238 -0
  44. data/lib/dry/types/predicate_registry.rb +34 -0
  45. data/lib/dry/types/primitive_inferrer.rb +97 -0
  46. data/lib/dry/types/printable.rb +5 -1
  47. data/lib/dry/types/printer.rb +63 -57
  48. data/lib/dry/types/result.rb +29 -3
  49. data/lib/dry/types/schema/key.rb +62 -36
  50. data/lib/dry/types/schema.rb +201 -91
  51. data/lib/dry/types/spec/types.rb +99 -37
  52. data/lib/dry/types/sum.rb +75 -25
  53. data/lib/dry/types/type.rb +49 -0
  54. data/lib/dry/types/version.rb +3 -1
  55. data/lib/dry/types.rb +106 -48
  56. data/lib/dry-types.rb +3 -1
  57. metadata +55 -78
  58. data/.codeclimate.yml +0 -15
  59. data/.gitignore +0 -10
  60. data/.rspec +0 -2
  61. data/.rubocop.yml +0 -43
  62. data/.travis.yml +0 -28
  63. data/.yardopts +0 -5
  64. data/CONTRIBUTING.md +0 -29
  65. data/Gemfile +0 -23
  66. data/Rakefile +0 -20
  67. data/benchmarks/hash_schemas.rb +0 -51
  68. data/lib/dry/types/safe.rb +0 -61
  69. data/log/.gitkeep +0 -0
@@ -7,7 +7,6 @@ module Dry
7
7
  MAPPING = {
8
8
  Nominal => :visit_nominal,
9
9
  Constructor => :visit_constructor,
10
- Hash::Constructor => :visit_constructor,
11
10
  Constrained => :visit_constrained,
12
11
  Constrained::Coercible => :visit_constrained,
13
12
  Hash => :visit_hash,
@@ -16,7 +15,7 @@ module Dry
16
15
  Map => :visit_map,
17
16
  Array => :visit_array,
18
17
  Array::Member => :visit_array_member,
19
- Safe => :visit_safe,
18
+ Lax => :visit_lax,
20
19
  Enum => :visit_enum,
21
20
  Default => :visit_default,
22
21
  Default::Callable => :visit_default,
@@ -28,15 +27,17 @@ module Dry
28
27
  def call(type)
29
28
  output = "".dup
30
29
  visit(type) { |str| output << str }
31
- "#<Dry::Types[#{ output }]>"
30
+ "#<Dry::Types[#{output}]>"
32
31
  end
33
32
 
34
33
  def visit(type, &block)
35
34
  print_with = MAPPING.fetch(type.class) do
36
- if type.is_a?(Type)
35
+ if type.class < Constructor
36
+ :visit_constructor
37
+ elsif type.is_a?(Type)
37
38
  return yield type.inspect
38
39
  else
39
- raise ArgumentError, "Do not know how to print #{ type.class }"
40
+ raise ArgumentError, "Do not know how to print #{type.class}"
40
41
  end
41
42
  end
42
43
  send(print_with, type, &block)
@@ -46,24 +47,28 @@ module Dry
46
47
  yield "Any"
47
48
  end
48
49
 
49
- def visit_array(_)
50
- yield "Array"
50
+ def visit_array(type)
51
+ visit_options(EMPTY_HASH, type.meta) do |opts|
52
+ yield "Array#{opts}"
53
+ end
51
54
  end
52
55
 
53
56
  def visit_array_member(array)
54
57
  visit(array.member) do |type|
55
- yield "Array<#{ type }>"
58
+ visit_options(EMPTY_HASH, array.meta) do |opts|
59
+ yield "Array<#{type}#{opts}>"
60
+ end
56
61
  end
57
62
  end
58
63
 
59
64
  def visit_constructor(constructor)
60
65
  visit(constructor.type) do |type|
61
- visit_callable(constructor.fn) do |fn|
66
+ visit_callable(constructor.fn.fn) do |fn|
62
67
  options = constructor.options.dup
63
68
  options.delete(:fn)
64
69
 
65
- visit_options(options, constructor.meta) do |opts|
66
- yield "Constructor<#{ type } fn=#{ fn }#{ opts }>"
70
+ visit_options(options) do |opts|
71
+ yield "Constructor<#{type} fn=#{fn}#{opts}>"
67
72
  end
68
73
  end
69
74
  end
@@ -74,8 +79,8 @@ module Dry
74
79
  options = constrained.options.dup
75
80
  rule = options.delete(:rule)
76
81
 
77
- visit_options(options, constrained.meta) do |opts|
78
- yield "Constrained<#{ type } rule=[#{ rule.to_s }]>"
82
+ visit_options(options) do |_opts|
83
+ yield "Constrained<#{type} rule=[#{rule}]>"
79
84
  end
80
85
  end
81
86
  end
@@ -91,25 +96,26 @@ module Dry
91
96
 
92
97
  if key_fn = options.delete(:key_transform_fn)
93
98
  visit_callable(key_fn) do |fn|
94
- key_fn_str = "key_fn=#{ fn } "
99
+ key_fn_str = "key_fn=#{fn} "
95
100
  end
96
101
  end
97
102
 
98
103
  if type_fn = options.delete(:type_transform_fn)
99
104
  visit_callable(type_fn) do |fn|
100
- type_fn_str = "type_fn=#{ fn } "
105
+ type_fn_str = "type_fn=#{fn} "
101
106
  end
102
107
  end
103
108
 
104
109
  keys = options.delete(:keys)
105
110
 
106
111
  visit_options(options, schema.meta) do |opts|
107
- schema_parameters = "#{ key_fn_str }#{ type_fn_str }#{ strict_str }#{ opts }"
112
+ opts = "#{opts[1..-1]} " unless opts.empty?
113
+ schema_parameters = "#{key_fn_str}#{type_fn_str}#{strict_str}#{opts}"
108
114
 
109
- header = "Schema<#{ schema_parameters }keys={"
115
+ header = "Schema<#{schema_parameters}keys={"
110
116
 
111
117
  if size.zero?
112
- yield "#{ header}}>"
118
+ yield "#{header}}>"
113
119
  else
114
120
  yield header.dup << keys.map { |key|
115
121
  visit(key) { |type| type }
@@ -125,8 +131,8 @@ module Dry
125
131
  options.delete(:key_type)
126
132
  options.delete(:value_type)
127
133
 
128
- visit_options(options, map.meta) do |opts|
129
- yield "Map<#{ key } => #{ value }>"
134
+ visit_options(options) do |_opts|
135
+ yield "Map<#{key} => #{value}>"
130
136
  end
131
137
  end
132
138
  end
@@ -135,9 +141,9 @@ module Dry
135
141
  def visit_key(key)
136
142
  visit(key.type) do |type|
137
143
  if key.required?
138
- yield "#{ key.name }: #{ type }"
144
+ yield "#{key.name}: #{type}"
139
145
  else
140
- yield "#{ key.name }?: #{ type }"
146
+ yield "#{key.name}?: #{type}"
141
147
  end
142
148
  end
143
149
  end
@@ -145,7 +151,7 @@ module Dry
145
151
  def visit_sum(sum)
146
152
  visit_sum_constructors(sum) do |constructors|
147
153
  visit_options(sum.options, sum.meta) do |opts|
148
- yield "Sum<#{ constructors }#{ opts }>"
154
+ yield "Sum<#{constructors}#{opts}>"
149
155
  end
150
156
  end
151
157
  end
@@ -157,11 +163,11 @@ module Dry
157
163
  case sum.right
158
164
  when Sum
159
165
  visit_sum_constructors(sum.right) do |right|
160
- yield "#{ left } | #{ right }"
166
+ yield "#{left} | #{right}"
161
167
  end
162
168
  else
163
169
  visit(sum.right) do |right|
164
- yield "#{ left } | #{ right }"
170
+ yield "#{left} | #{right}"
165
171
  end
166
172
  end
167
173
  end
@@ -170,11 +176,11 @@ module Dry
170
176
  case sum.right
171
177
  when Sum
172
178
  visit_sum_constructors(sum.right) do |right|
173
- yield "#{ left } | #{ right }"
179
+ yield "#{left} | #{right}"
174
180
  end
175
181
  else
176
182
  visit(sum.right) do |right|
177
- yield "#{ left } | #{ right }"
183
+ yield "#{left} | #{right}"
178
184
  end
179
185
  end
180
186
  end
@@ -186,15 +192,15 @@ module Dry
186
192
  options = enum.options.dup
187
193
  mapping = options.delete(:mapping)
188
194
 
189
- visit_options(options, enum.meta) do |opts|
195
+ visit_options(options) do |opts|
190
196
  if mapping == enum.inverted_mapping
191
197
  values = mapping.values.map(&:inspect).join(", ")
192
- yield "Enum<#{ type } values={#{ values }}#{ opts }>"
198
+ yield "Enum<#{type} values={#{values}}#{opts}>"
193
199
  else
194
200
  mapping_str = mapping.map { |key, value|
195
- "#{ key.inspect }=>#{ value.inspect }"
201
+ "#{key.inspect}=>#{value.inspect}"
196
202
  }.join(", ")
197
- yield "Enum<#{ type } mapping={#{ mapping_str }}#{ opts }>"
203
+ yield "Enum<#{type} mapping={#{mapping_str}}#{opts}>"
198
204
  end
199
205
  end
200
206
  end
@@ -202,13 +208,13 @@ module Dry
202
208
 
203
209
  def visit_default(default)
204
210
  visit(default.type) do |type|
205
- visit_options(default.options, default.meta) do |opts|
211
+ visit_options(default.options) do |opts|
206
212
  if default.is_a?(Default::Callable)
207
213
  visit_callable(default.value) do |fn|
208
- yield "Default<#{ type } value_fn=#{ fn }#{ opts }>"
214
+ yield "Default<#{type} value_fn=#{fn}#{opts}>"
209
215
  end
210
216
  else
211
- yield "Default<#{ type } value=#{ default.value.inspect }#{ opts }>"
217
+ yield "Default<#{type} value=#{default.value.inspect}#{opts}>"
212
218
  end
213
219
  end
214
220
  end
@@ -216,13 +222,13 @@ module Dry
216
222
 
217
223
  def visit_nominal(type)
218
224
  visit_options(type.options, type.meta) do |opts|
219
- yield "Nominal<#{ type.primitive }#{ opts }>"
225
+ yield "Nominal<#{type.primitive}#{opts}>"
220
226
  end
221
227
  end
222
228
 
223
- def visit_safe(safe)
224
- visit(safe.type) do |type|
225
- yield "Safe<#{ type }>"
229
+ def visit_lax(lax)
230
+ visit(lax.type) do |type|
231
+ yield "Lax<#{type}>"
226
232
  end
227
233
  end
228
234
 
@@ -232,7 +238,7 @@ module Dry
232
238
 
233
239
  if type_fn = options.delete(:type_transform_fn)
234
240
  visit_callable(type_fn) do |fn|
235
- type_fn_str = "type_fn=#{ fn }"
241
+ type_fn_str = "type_fn=#{fn}"
236
242
  end
237
243
  end
238
244
 
@@ -240,7 +246,7 @@ module Dry
240
246
  if opts.empty? && type_fn_str.empty?
241
247
  yield "Hash"
242
248
  else
243
- yield "Hash<#{ type_fn_str }#{ opts }>"
249
+ yield "Hash<#{type_fn_str}#{opts}>"
244
250
  end
245
251
  end
246
252
  end
@@ -249,22 +255,22 @@ module Dry
249
255
  fn = callable.is_a?(String) ? FnContainer[callable] : callable
250
256
 
251
257
  case fn
252
- when Method
253
- yield "#{ fn.receiver }.#{ fn.name }"
254
- when Proc
258
+ when ::Method
259
+ yield "#{fn.receiver}.#{fn.name}"
260
+ when ::Proc
255
261
  path, line = fn.source_location
256
262
 
257
- if line && line.zero?
258
- yield ".#{ path }"
263
+ if line&.zero?
264
+ yield ".#{path}"
259
265
  elsif path
260
- yield "#{ path.sub(Dir.pwd + "/", EMPTY_STRING) }:#{ line }"
261
- elsif fn.lambda?
262
- yield "(lambda)"
266
+ yield "#{path.sub(Dir.pwd + "/", EMPTY_STRING)}:#{line}"
263
267
  else
264
- 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/)
265
269
 
266
270
  if match
267
- yield ".#{ match[1] }"
271
+ yield ".#{match[:name]}"
272
+ elsif fn.lambda?
273
+ yield "(lambda)"
268
274
  else
269
275
  yield "(proc)"
270
276
  end
@@ -273,18 +279,18 @@ module Dry
273
279
  call = fn.method(:call)
274
280
 
275
281
  if call.owner == fn.class
276
- yield "#{ fn.class.to_s }#call"
282
+ yield "#{fn.class}#call"
277
283
  else
278
- yield "#{ fn.to_s }.call"
284
+ yield "#{fn}.call"
279
285
  end
280
286
  end
281
287
  end
282
288
 
283
- def visit_options(options, meta)
289
+ def visit_options(options, meta = EMPTY_HASH)
284
290
  if options.empty? && meta.empty?
285
291
  yield ""
286
292
  else
287
- opts = options.empty? ? "" : " options=#{ options.inspect }"
293
+ opts = options.empty? ? "" : " options=#{options.inspect}"
288
294
 
289
295
  if meta.empty?
290
296
  yield opts
@@ -292,13 +298,13 @@ module Dry
292
298
  values = meta.map do |key, value|
293
299
  case key
294
300
  when Symbol
295
- "#{ key }: #{ value.inspect }"
301
+ "#{key}: #{value.inspect}"
296
302
  else
297
- "#{ key.inspect }=>#{ value.inspect }"
303
+ "#{key.inspect}=>#{value.inspect}"
298
304
  end
299
305
  end
300
306
 
301
- yield "#{ opts } meta={#{ values.join(", ") }}"
307
+ yield "#{opts} meta={#{values.join(", ")}}"
302
308
  end
303
309
  end
304
310
  end
@@ -1,54 +1,80 @@
1
- require 'dry/equalizer'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/equalizer"
2
4
 
3
5
  module Dry
4
6
  module Types
7
+ # Result class used by {Type#try}
8
+ #
9
+ # @api public
5
10
  class Result
6
- include Dry::Equalizer(:input, inspect: false)
11
+ include ::Dry::Equalizer(:input, immutable: true)
7
12
 
8
13
  # @return [Object]
9
14
  attr_reader :input
10
15
 
11
16
  # @param [Object] input
17
+ #
18
+ # @api private
12
19
  def initialize(input)
13
20
  @input = input
14
21
  end
15
22
 
23
+ # Success result
24
+ #
25
+ # @api public
16
26
  class Success < Result
17
27
  # @return [true]
28
+ #
29
+ # @api public
18
30
  def success?
19
31
  true
20
32
  end
21
33
 
22
34
  # @return [false]
35
+ #
36
+ # @api public
23
37
  def failure?
24
38
  false
25
39
  end
26
40
  end
27
41
 
42
+ # Failure result
43
+ #
44
+ # @api public
28
45
  class Failure < Result
29
- include Dry::Equalizer(:input, :error, inspect: false)
46
+ include ::Dry::Equalizer(:input, :error, immutable: true)
30
47
 
31
48
  # @return [#to_s]
32
49
  attr_reader :error
33
50
 
34
51
  # @param [Object] input
52
+ #
35
53
  # @param [#to_s] error
54
+ #
55
+ # @api private
36
56
  def initialize(input, error)
37
57
  super(input)
38
58
  @error = error
39
59
  end
40
60
 
41
61
  # @return [String]
62
+ #
63
+ # @api private
42
64
  def to_s
43
65
  error.to_s
44
66
  end
45
67
 
46
68
  # @return [false]
69
+ #
70
+ # @api public
47
71
  def success?
48
72
  false
49
73
  end
50
74
 
51
75
  # @return [true]
76
+ #
77
+ # @api public
52
78
  def failure?
53
79
  true
54
80
  end
@@ -1,7 +1,13 @@
1
- require 'dry/equalizer'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/equalizer"
4
+ require "dry/core/deprecations"
2
5
 
3
6
  module Dry
4
7
  module Types
8
+ # Schema is a hash with explicit member types defined
9
+ #
10
+ # @api public
5
11
  class Schema < Hash
6
12
  # Proxy type for schema keys. Contains only key name and
7
13
  # whether it's required or not. All other calls deletaged
@@ -9,8 +15,9 @@ module Dry
9
15
  #
10
16
  # @see Dry::Types::Schema
11
17
  class Key
18
+ extend ::Dry::Core::Deprecations[:'dry-types']
12
19
  include Type
13
- include Dry::Equalizer(:name, :type, :options, inspect: false)
20
+ include Dry::Equalizer(:name, :type, :options, inspect: false, immutable: true)
14
21
  include Decorator
15
22
  include Builder
16
23
  include Printable
@@ -24,16 +31,27 @@ module Dry
24
31
  type.meta.fetch(:required) { !type.meta.fetch(:omittable, false) }
25
32
  end
26
33
 
34
+ unless name.is_a?(::Symbol)
35
+ raise ArgumentError, "Schemas can only contain symbol keys, #{name.inspect} given"
36
+ end
37
+
27
38
  super(type, name, required: required, **options)
28
39
  @name = name
29
40
  end
30
41
 
31
- # @see Dry::Types::Nominal#call
32
- def call(input, &block)
33
- type.(input, &block)
42
+ # @api private
43
+ def call_safe(input, &block)
44
+ type.call_safe(input, &block)
45
+ end
46
+
47
+ # @api private
48
+ def call_unsafe(input)
49
+ type.call_unsafe(input)
34
50
  end
35
51
 
36
52
  # @see Dry::Types::Nominal#try
53
+ #
54
+ # @api public
37
55
  def try(input, &block)
38
56
  type.try(input, &block)
39
57
  end
@@ -41,6 +59,8 @@ module Dry
41
59
  # Whether the key is required in schema input
42
60
  #
43
61
  # @return [Boolean]
62
+ #
63
+ # @api public
44
64
  def required?
45
65
  options.fetch(:required)
46
66
  end
@@ -55,6 +75,8 @@ module Dry
55
75
  #
56
76
  # @param [Boolean] required New value
57
77
  # @return [Dry::Types::Schema::Key]
78
+ #
79
+ # @api public
58
80
  def required(required = Undefined)
59
81
  if Undefined.equal?(required)
60
82
  options.fetch(:required)
@@ -66,36 +88,35 @@ module Dry
66
88
  # Make key not required
67
89
  #
68
90
  # @return [Dry::Types::Schema::Key]
91
+ #
92
+ # @api public
69
93
  def omittable
70
94
  required(false)
71
95
  end
72
96
 
73
- # Construct a default type. Default values are
74
- # evaluated/applied when key is absent in schema
75
- # input.
97
+ # Turn key into a lax type. Lax types are not strict hence such keys are not required
76
98
  #
77
- # @see Dry::Types::Default
78
- # @return [Dry::Types::Schema::Key]
79
- def default(input = Undefined, &block)
80
- new(type.default(input, &block))
81
- end
82
-
83
- # Replace the underlying type
84
- # @param [Dry::Types::Type] type
85
- # @return [Dry::Types::Schema::Key]
86
- def new(type)
87
- self.class.new(type, name, options)
99
+ # @return [Lax]
100
+ #
101
+ # @api public
102
+ def lax
103
+ __new__(type.lax).required(false)
88
104
  end
89
105
 
90
- # @see Dry::Types::Safe
91
- # @return [Dry::Types::Schema::Key]
92
- def safe
93
- new(type.safe)
106
+ # Make wrapped type optional
107
+ #
108
+ # @return [Key]
109
+ #
110
+ # @api public
111
+ def optional
112
+ __new__(type.optional)
94
113
  end
95
114
 
96
115
  # Dump to internal AST representation
97
116
  #
98
117
  # @return [Array]
118
+ #
119
+ # @api public
99
120
  def to_ast(meta: true)
100
121
  [
101
122
  :key,
@@ -107,23 +128,28 @@ module Dry
107
128
  ]
108
129
  end
109
130
 
110
- # Get/set type metadata. The Key type doesn't have
111
- # its out meta, it delegates these calls to the underlying
112
- # type.
113
- #
114
- # @overload meta
115
- # @return [Hash] metadata associated with type
131
+ # @see Dry::Types::Meta#meta
116
132
  #
117
- # @overload meta(data)
118
- # @param [Hash] new metadata to merge into existing metadata
119
- # @return [Type] new type with added metadata
120
- def meta(data = nil)
121
- if data.nil?
122
- type.meta
133
+ # @api public
134
+ def meta(data = Undefined)
135
+ if Undefined.equal?(data) || !data.key?(:omittable)
136
+ super
123
137
  else
124
- new(type.meta(data))
138
+ self.class.warn(
139
+ "Using meta for making schema keys is deprecated, " \
140
+ "please use .omittable or .required(false) instead" \
141
+ "\n" + Core::Deprecations::STACK.()
142
+ )
143
+ super.required(!data[:omittable])
125
144
  end
126
145
  end
146
+
147
+ private
148
+
149
+ # @api private
150
+ def decorate?(response)
151
+ response.is_a?(Type)
152
+ end
127
153
  end
128
154
  end
129
155
  end