dry-types 0.14.1 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +631 -134
  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 +32 -12
  7. data/lib/dry/types/array/constructor.rb +32 -0
  8. data/lib/dry/types/array/member.rb +75 -16
  9. data/lib/dry/types/array.rb +19 -6
  10. data/lib/dry/types/builder.rb +131 -15
  11. data/lib/dry/types/builder_methods.rb +49 -20
  12. data/lib/dry/types/coercions/json.rb +43 -7
  13. data/lib/dry/types/coercions/params.rb +118 -31
  14. data/lib/dry/types/coercions.rb +76 -22
  15. data/lib/dry/types/compat.rb +0 -2
  16. data/lib/dry/types/compiler.rb +56 -41
  17. data/lib/dry/types/constrained/coercible.rb +36 -6
  18. data/lib/dry/types/constrained.rb +81 -32
  19. data/lib/dry/types/constraints.rb +18 -4
  20. data/lib/dry/types/constructor/function.rb +216 -0
  21. data/lib/dry/types/constructor/wrapper.rb +94 -0
  22. data/lib/dry/types/constructor.rb +126 -56
  23. data/lib/dry/types/container.rb +7 -0
  24. data/lib/dry/types/core.rb +54 -21
  25. data/lib/dry/types/decorator.rb +38 -17
  26. data/lib/dry/types/default.rb +61 -16
  27. data/lib/dry/types/enum.rb +43 -20
  28. data/lib/dry/types/errors.rb +75 -9
  29. data/lib/dry/types/extensions/maybe.rb +74 -16
  30. data/lib/dry/types/extensions/monads.rb +29 -0
  31. data/lib/dry/types/extensions.rb +7 -1
  32. data/lib/dry/types/fn_container.rb +6 -1
  33. data/lib/dry/types/hash/constructor.rb +33 -0
  34. data/lib/dry/types/hash.rb +86 -67
  35. data/lib/dry/types/inflector.rb +3 -1
  36. data/lib/dry/types/json.rb +18 -16
  37. data/lib/dry/types/lax.rb +75 -0
  38. data/lib/dry/types/map.rb +76 -33
  39. data/lib/dry/types/meta.rb +51 -0
  40. data/lib/dry/types/module.rb +120 -0
  41. data/lib/dry/types/nominal.rb +210 -0
  42. data/lib/dry/types/options.rb +13 -26
  43. data/lib/dry/types/params.rb +39 -25
  44. data/lib/dry/types/predicate_inferrer.rb +238 -0
  45. data/lib/dry/types/predicate_registry.rb +34 -0
  46. data/lib/dry/types/primitive_inferrer.rb +97 -0
  47. data/lib/dry/types/printable.rb +16 -0
  48. data/lib/dry/types/printer.rb +315 -0
  49. data/lib/dry/types/result.rb +29 -3
  50. data/lib/dry/types/schema/key.rb +156 -0
  51. data/lib/dry/types/schema.rb +408 -0
  52. data/lib/dry/types/spec/types.rb +103 -33
  53. data/lib/dry/types/sum.rb +84 -35
  54. data/lib/dry/types/type.rb +49 -0
  55. data/lib/dry/types/version.rb +3 -1
  56. data/lib/dry/types.rb +156 -76
  57. data/lib/dry-types.rb +3 -1
  58. metadata +65 -78
  59. data/.gitignore +0 -10
  60. data/.rspec +0 -2
  61. data/.travis.yml +0 -27
  62. data/.yardopts +0 -5
  63. data/CONTRIBUTING.md +0 -29
  64. data/Gemfile +0 -24
  65. data/Rakefile +0 -20
  66. data/benchmarks/hash_schemas.rb +0 -51
  67. data/lib/dry/types/compat/form_types.rb +0 -27
  68. data/lib/dry/types/compat/int.rb +0 -14
  69. data/lib/dry/types/definition.rb +0 -113
  70. data/lib/dry/types/hash/schema.rb +0 -199
  71. data/lib/dry/types/hash/schema_builder.rb +0 -75
  72. data/lib/dry/types/safe.rb +0 -59
  73. data/log/.gitkeep +0 -0
@@ -0,0 +1,315 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module Types
5
+ # @api private
6
+ class Printer
7
+ MAPPING = {
8
+ Nominal => :visit_nominal,
9
+ Constructor => :visit_constructor,
10
+ Constrained => :visit_constrained,
11
+ Constrained::Coercible => :visit_constrained,
12
+ Hash => :visit_hash,
13
+ Schema => :visit_schema,
14
+ Schema::Key => :visit_key,
15
+ Map => :visit_map,
16
+ Array => :visit_array,
17
+ Array::Member => :visit_array_member,
18
+ Lax => :visit_lax,
19
+ Enum => :visit_enum,
20
+ Default => :visit_default,
21
+ Default::Callable => :visit_default,
22
+ Sum => :visit_sum,
23
+ Sum::Constrained => :visit_sum,
24
+ Any.class => :visit_any
25
+ }
26
+
27
+ def call(type)
28
+ output = "".dup
29
+ visit(type) { |str| output << str }
30
+ "#<Dry::Types[#{output}]>"
31
+ end
32
+
33
+ def visit(type, &block)
34
+ print_with = MAPPING.fetch(type.class) do
35
+ if type.class < Constructor
36
+ :visit_constructor
37
+ elsif type.is_a?(Type)
38
+ return yield type.inspect
39
+ else
40
+ raise ArgumentError, "Do not know how to print #{type.class}"
41
+ end
42
+ end
43
+ send(print_with, type, &block)
44
+ end
45
+
46
+ def visit_any(_)
47
+ yield "Any"
48
+ end
49
+
50
+ def visit_array(type)
51
+ visit_options(EMPTY_HASH, type.meta) do |opts|
52
+ yield "Array#{opts}"
53
+ end
54
+ end
55
+
56
+ def visit_array_member(array)
57
+ visit(array.member) do |type|
58
+ visit_options(EMPTY_HASH, array.meta) do |opts|
59
+ yield "Array<#{type}#{opts}>"
60
+ end
61
+ end
62
+ end
63
+
64
+ def visit_constructor(constructor)
65
+ visit(constructor.type) do |type|
66
+ visit_callable(constructor.fn.fn) do |fn|
67
+ options = constructor.options.dup
68
+ options.delete(:fn)
69
+
70
+ visit_options(options) do |opts|
71
+ yield "Constructor<#{type} fn=#{fn}#{opts}>"
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ def visit_constrained(constrained)
78
+ visit(constrained.type) do |type|
79
+ options = constrained.options.dup
80
+ rule = options.delete(:rule)
81
+
82
+ visit_options(options) do |_opts|
83
+ yield "Constrained<#{type} rule=[#{rule}]>"
84
+ end
85
+ end
86
+ end
87
+
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
188
+ end
189
+
190
+ def visit_enum(enum)
191
+ visit(enum.type) do |type|
192
+ options = enum.options.dup
193
+ mapping = options.delete(:mapping)
194
+
195
+ visit_options(options) do |opts|
196
+ if mapping == enum.inverted_mapping
197
+ values = mapping.values.map(&:inspect).join(", ")
198
+ yield "Enum<#{type} values={#{values}}#{opts}>"
199
+ else
200
+ mapping_str = mapping.map { |key, value|
201
+ "#{key.inspect}=>#{value.inspect}"
202
+ }.join(", ")
203
+ yield "Enum<#{type} mapping={#{mapping_str}}#{opts}>"
204
+ end
205
+ end
206
+ end
207
+ end
208
+
209
+ def visit_default(default)
210
+ visit(default.type) do |type|
211
+ visit_options(default.options) do |opts|
212
+ if default.is_a?(Default::Callable)
213
+ visit_callable(default.value) do |fn|
214
+ yield "Default<#{type} value_fn=#{fn}#{opts}>"
215
+ end
216
+ else
217
+ yield "Default<#{type} value=#{default.value.inspect}#{opts}>"
218
+ end
219
+ end
220
+ end
221
+ end
222
+
223
+ def visit_nominal(type)
224
+ visit_options(type.options, type.meta) do |opts|
225
+ yield "Nominal<#{type.primitive}#{opts}>"
226
+ end
227
+ end
228
+
229
+ def visit_lax(lax)
230
+ visit(lax.type) do |type|
231
+ yield "Lax<#{type}>"
232
+ end
233
+ end
234
+
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
+ def visit_callable(callable)
255
+ fn = callable.is_a?(String) ? FnContainer[callable] : callable
256
+
257
+ case fn
258
+ when ::Method
259
+ yield "#{fn.receiver}.#{fn.name}"
260
+ when ::Proc
261
+ path, line = fn.source_location
262
+
263
+ if line&.zero?
264
+ yield ".#{path}"
265
+ elsif path
266
+ yield "#{path.sub(Dir.pwd + "/", EMPTY_STRING)}:#{line}"
267
+ else
268
+ match = fn.to_s.match(/\A#<Proc:0x\h+\(&:(?<name>\w+)\)(:? \(lambda\))?>\z/)
269
+
270
+ if match
271
+ yield ".#{match[:name]}"
272
+ elsif fn.lambda?
273
+ yield "(lambda)"
274
+ else
275
+ yield "(proc)"
276
+ end
277
+ end
278
+ else
279
+ call = fn.method(:call)
280
+
281
+ if call.owner == fn.class
282
+ yield "#{fn.class}#call"
283
+ else
284
+ yield "#{fn}.call"
285
+ end
286
+ end
287
+ end
288
+
289
+ def visit_options(options, meta = EMPTY_HASH)
290
+ if options.empty? && meta.empty?
291
+ yield ""
292
+ else
293
+ opts = options.empty? ? "" : " options=#{options.inspect}"
294
+
295
+ if meta.empty?
296
+ yield opts
297
+ else
298
+ values = meta.map do |key, value|
299
+ case key
300
+ when Symbol
301
+ "#{key}: #{value.inspect}"
302
+ else
303
+ "#{key.inspect}=>#{value.inspect}"
304
+ end
305
+ end
306
+
307
+ yield "#{opts} meta={#{values.join(", ")}}"
308
+ end
309
+ end
310
+ end
311
+ end
312
+
313
+ PRINTER = Printer.new.freeze
314
+ end
315
+ 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)
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)
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
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/equalizer"
4
+ require "dry/core/deprecations"
5
+
6
+ module Dry
7
+ module Types
8
+ # Schema is a hash with explicit member types defined
9
+ #
10
+ # @api public
11
+ class Schema < Hash
12
+ # Proxy type for schema keys. Contains only key name and
13
+ # whether it's required or not. All other calls deletaged
14
+ # to the wrapped type.
15
+ #
16
+ # @see Dry::Types::Schema
17
+ class Key
18
+ extend ::Dry::Core::Deprecations[:'dry-types']
19
+ include Type
20
+ include Dry::Equalizer(:name, :type, :options, inspect: false, immutable: true)
21
+ include Decorator
22
+ include Builder
23
+ include Printable
24
+
25
+ # @return [Symbol]
26
+ attr_reader :name
27
+
28
+ # @api private
29
+ def initialize(type, name, required: Undefined, **options)
30
+ required = Undefined.default(required) do
31
+ type.meta.fetch(:required) { !type.meta.fetch(:omittable, false) }
32
+ end
33
+
34
+ unless name.is_a?(::Symbol)
35
+ raise ArgumentError, "Schemas can only contain symbol keys, #{name.inspect} given"
36
+ end
37
+
38
+ super(type, name, required: required, **options)
39
+ @name = name
40
+ end
41
+
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)
50
+ end
51
+
52
+ # @see Dry::Types::Nominal#try
53
+ #
54
+ # @api public
55
+ def try(input, &block)
56
+ type.try(input, &block)
57
+ end
58
+
59
+ # Whether the key is required in schema input
60
+ #
61
+ # @return [Boolean]
62
+ #
63
+ # @api public
64
+ def required?
65
+ options.fetch(:required)
66
+ end
67
+
68
+ # Control whether the key is required
69
+ #
70
+ # @overload required
71
+ # @return [Boolean]
72
+ #
73
+ # @overload required(required)
74
+ # Change key's "requireness"
75
+ #
76
+ # @param [Boolean] required New value
77
+ # @return [Dry::Types::Schema::Key]
78
+ #
79
+ # @api public
80
+ def required(required = Undefined)
81
+ if Undefined.equal?(required)
82
+ options.fetch(:required)
83
+ else
84
+ with(required: required)
85
+ end
86
+ end
87
+
88
+ # Make key not required
89
+ #
90
+ # @return [Dry::Types::Schema::Key]
91
+ #
92
+ # @api public
93
+ def omittable
94
+ required(false)
95
+ end
96
+
97
+ # Turn key into a lax type. Lax types are not strict hence such keys are not required
98
+ #
99
+ # @return [Lax]
100
+ #
101
+ # @api public
102
+ def lax
103
+ __new__(type.lax).required(false)
104
+ end
105
+
106
+ # Make wrapped type optional
107
+ #
108
+ # @return [Key]
109
+ #
110
+ # @api public
111
+ def optional
112
+ __new__(type.optional)
113
+ end
114
+
115
+ # Dump to internal AST representation
116
+ #
117
+ # @return [Array]
118
+ #
119
+ # @api public
120
+ def to_ast(meta: true)
121
+ [
122
+ :key,
123
+ [
124
+ name,
125
+ required,
126
+ type.to_ast(meta: meta)
127
+ ]
128
+ ]
129
+ end
130
+
131
+ # @see Dry::Types::Meta#meta
132
+ #
133
+ # @api public
134
+ def meta(data = Undefined)
135
+ if Undefined.equal?(data) || !data.key?(:omittable)
136
+ super
137
+ else
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])
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ # @api private
150
+ def decorate?(response)
151
+ response.is_a?(Type)
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end