dry-types 0.14.1 → 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 (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