dry-types 0.15.0 → 1.5.1

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