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
@@ -1,53 +1,67 @@
1
- require 'dry/types/coercions/params'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/types/coercions/params"
2
4
 
3
5
  module Dry
4
6
  module Types
5
- register('params.nil') do
6
- self['nominal.nil'].constructor(Coercions::Params.method(:to_nil))
7
+ register("params.nil") do
8
+ self["nominal.nil"].constructor(Coercions::Params.method(:to_nil))
9
+ end
10
+
11
+ register("params.date") do
12
+ self["nominal.date"].constructor(Coercions::Params.method(:to_date))
7
13
  end
8
14
 
9
- register('params.date') do
10
- self['nominal.date'].constructor(Coercions::Params.method(:to_date))
15
+ register("params.date_time") do
16
+ self["nominal.date_time"].constructor(Coercions::Params.method(:to_date_time))
11
17
  end
12
18
 
13
- register('params.date_time') do
14
- self['nominal.date_time'].constructor(Coercions::Params.method(:to_date_time))
19
+ register("params.time") do
20
+ self["nominal.time"].constructor(Coercions::Params.method(:to_time))
15
21
  end
16
22
 
17
- register('params.time') do
18
- self['nominal.time'].constructor(Coercions::Params.method(:to_time))
23
+ register("params.true") do
24
+ self["nominal.true"].constructor(Coercions::Params.method(:to_true))
19
25
  end
20
26
 
21
- register('params.true') do
22
- self['nominal.true'].constructor(Coercions::Params.method(:to_true))
27
+ register("params.false") do
28
+ self["nominal.false"].constructor(Coercions::Params.method(:to_false))
23
29
  end
24
30
 
25
- register('params.false') do
26
- self['nominal.false'].constructor(Coercions::Params.method(:to_false))
31
+ register("params.bool") do
32
+ self["params.true"] | self["params.false"]
27
33
  end
28
34
 
29
- register('params.bool') do
30
- (self['params.true'] | self['params.false']).safe
35
+ register("params.integer") do
36
+ self["nominal.integer"].constructor(Coercions::Params.method(:to_int))
31
37
  end
32
38
 
33
- register('params.integer') do
34
- self['nominal.integer'].constructor(Coercions::Params.method(:to_int))
39
+ register("params.float") do
40
+ self["nominal.float"].constructor(Coercions::Params.method(:to_float))
35
41
  end
36
42
 
37
- register('params.float') do
38
- self['nominal.float'].constructor(Coercions::Params.method(:to_float))
43
+ register("params.decimal") do
44
+ self["nominal.decimal"].constructor(Coercions::Params.method(:to_decimal))
39
45
  end
40
46
 
41
- register('params.decimal') do
42
- self['nominal.decimal'].constructor(Coercions::Params.method(:to_decimal))
47
+ register("params.array") do
48
+ self["nominal.array"].constructor(Coercions::Params.method(:to_ary))
43
49
  end
44
50
 
45
- register('params.array') do
46
- self['nominal.array'].constructor(Coercions::Params.method(:to_ary)).safe
51
+ register("params.hash") do
52
+ self["nominal.hash"].constructor(Coercions::Params.method(:to_hash))
47
53
  end
48
54
 
49
- register('params.hash') do
50
- self['nominal.hash'].constructor(Coercions::Params.method(:to_hash)).safe
55
+ register("params.symbol") do
56
+ self["nominal.symbol"].constructor(Coercions::Params.method(:to_symbol))
57
+ end
58
+
59
+ register("params.string", self["string"])
60
+
61
+ COERCIBLE.each_key do |name|
62
+ next if name.equal?(:string)
63
+
64
+ register("optional.params.#{name}", self["params.nil"] | self["params.#{name}"])
51
65
  end
52
66
  end
53
67
  end
@@ -0,0 +1,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/cache"
4
+ require "dry/core/class_attributes"
5
+ require "dry/types/predicate_registry"
6
+
7
+ module Dry
8
+ module Types
9
+ # PredicateInferrer returns the list of predicates used by a type.
10
+ #
11
+ # @api public
12
+ class PredicateInferrer
13
+ extend Core::Cache
14
+
15
+ TYPE_TO_PREDICATE = {
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?
27
+ }.freeze
28
+
29
+ REDUCED_TYPES = {
30
+ [[[:true?], [:false?]]] => %i[bool?]
31
+ }.freeze
32
+
33
+ HASH = %i[hash?].freeze
34
+
35
+ ARRAY = %i[array?].freeze
36
+
37
+ NIL = %i[nil?].freeze
38
+
39
+ # Compiler reduces type AST into a list of predicates
40
+ #
41
+ # @api private
42
+ class Compiler
43
+ extend Core::ClassAttributes
44
+
45
+ defines :infer_predicate_by_class_name
46
+ infer_predicate_by_class_name nil
47
+
48
+ # @return [PredicateRegistry]
49
+ # @api private
50
+ attr_reader :registry
51
+
52
+ # @api private
53
+ def initialize(registry)
54
+ @registry = registry
55
+ end
56
+
57
+ # @api private
58
+ def infer_predicate(type)
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
90
+ end
91
+
92
+ # @api private
93
+ def visit(node)
94
+ meth, rest = node
95
+ public_send(:"visit_#{meth}", rest)
96
+ end
97
+
98
+ # @api private
99
+ def visit_nominal(node)
100
+ type = node[0]
101
+ predicate = infer_predicate(type)
102
+
103
+ if !predicate.empty? && registry.key?(predicate[0])
104
+ predicate
105
+ else
106
+ [type?: type]
107
+ end
108
+ end
109
+
110
+ # @api private
111
+ def visit_hash(_)
112
+ HASH
113
+ end
114
+ alias_method :visit_schema, :visit_hash
115
+
116
+ # @api private
117
+ def visit_array(_)
118
+ ARRAY
119
+ end
120
+
121
+ # @api private
122
+ def visit_lax(node)
123
+ visit(node)
124
+ end
125
+
126
+ # @api private
127
+ def visit_constructor(node)
128
+ other, * = node
129
+ visit(other)
130
+ end
131
+
132
+ # @api private
133
+ def visit_enum(node)
134
+ other, * = node
135
+ visit(other)
136
+ end
137
+
138
+ # @api private
139
+ def visit_sum(node)
140
+ left_node, right_node, = node
141
+ left = visit(left_node)
142
+ right = visit(right_node)
143
+
144
+ if left.eql?(NIL)
145
+ right
146
+ else
147
+ [[left, right]]
148
+ end
149
+ end
150
+
151
+ # @api private
152
+ def visit_constrained(node)
153
+ other, rules = node
154
+ predicates = visit(rules)
155
+
156
+ if predicates.empty?
157
+ visit(other)
158
+ else
159
+ [*visit(other), *merge_predicates(predicates)]
160
+ end
161
+ end
162
+
163
+ # @api private
164
+ def visit_any(_)
165
+ EMPTY_ARRAY
166
+ end
167
+
168
+ # @api private
169
+ def visit_and(node)
170
+ left, right = node
171
+ visit(left) + visit(right)
172
+ end
173
+
174
+ # @api private
175
+ def visit_predicate(node)
176
+ pred, args = node
177
+
178
+ if pred.equal?(:type?)
179
+ EMPTY_ARRAY
180
+ elsif registry.key?(pred)
181
+ *curried, _ = args
182
+ values = curried.map { |_, v| v }
183
+
184
+ if values.empty?
185
+ [pred]
186
+ else
187
+ [pred => values[0]]
188
+ end
189
+ else
190
+ EMPTY_ARRAY
191
+ end
192
+ end
193
+
194
+ private
195
+
196
+ # @api private
197
+ def merge_predicates(nodes)
198
+ preds, merged = nodes.each_with_object([[], {}]) do |predicate, (ps, h)|
199
+ if predicate.is_a?(::Hash)
200
+ h.update(predicate)
201
+ else
202
+ ps << predicate
203
+ end
204
+ end
205
+
206
+ merged.empty? ? preds : [*preds, merged]
207
+ end
208
+ end
209
+
210
+ # @return [Compiler]
211
+ # @api private
212
+ attr_reader :compiler
213
+
214
+ # @api private
215
+ def initialize(registry = PredicateRegistry.new)
216
+ @compiler = Compiler.new(registry)
217
+ end
218
+
219
+ # Infer predicate identifier from the provided type
220
+ #
221
+ # @param [Type] type
222
+ # @return [Symbol]
223
+ #
224
+ # @api private
225
+ def [](type)
226
+ self.class.fetch_or_store(type) do
227
+ predicates = compiler.visit(type.to_ast)
228
+
229
+ if predicates.is_a?(::Hash)
230
+ predicates
231
+ else
232
+ REDUCED_TYPES[predicates] || predicates
233
+ end
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/logic/predicates"
4
+
5
+ module Dry
6
+ module Types
7
+ # A registry with predicate objects from `Dry::Logic::Predicates`
8
+ #
9
+ # @api private
10
+ class PredicateRegistry
11
+ # @api private
12
+ attr_reader :predicates
13
+
14
+ # @api private
15
+ attr_reader :has_predicate
16
+
17
+ # @api private
18
+ def initialize(predicates = Logic::Predicates)
19
+ @predicates = predicates
20
+ @has_predicate = ::Kernel.instance_method(:respond_to?).bind(@predicates)
21
+ end
22
+
23
+ # @api private
24
+ def [](name)
25
+ predicates[name]
26
+ end
27
+
28
+ # @api private
29
+ def key?(name)
30
+ has_predicate.(name)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/cache"
4
+
5
+ module Dry
6
+ module Types
7
+ # PrimitiveInferrer returns the list of classes matching a type.
8
+ #
9
+ # @api public
10
+ class PrimitiveInferrer
11
+ extend Core::Cache
12
+
13
+ # Compiler reduces type AST into a list of primitives
14
+ #
15
+ # @api private
16
+ class Compiler
17
+ # @api private
18
+ def visit(node)
19
+ meth, rest = node
20
+ public_send(:"visit_#{meth}", rest)
21
+ end
22
+
23
+ # @api private
24
+ def visit_nominal(node)
25
+ type, _ = node
26
+ type
27
+ end
28
+
29
+ # @api private
30
+ def visit_hash(_)
31
+ ::Hash
32
+ end
33
+ alias_method :visit_schema, :visit_hash
34
+
35
+ # @api private
36
+ def visit_array(_)
37
+ ::Array
38
+ end
39
+
40
+ # @api private
41
+ def visit_lax(node)
42
+ visit(node)
43
+ end
44
+
45
+ # @api private
46
+ def visit_constructor(node)
47
+ other, * = node
48
+ visit(other)
49
+ end
50
+
51
+ # @api private
52
+ def visit_enum(node)
53
+ other, * = node
54
+ visit(other)
55
+ end
56
+
57
+ # @api private
58
+ def visit_sum(node)
59
+ left, right = node
60
+
61
+ [visit(left), visit(right)].flatten(1)
62
+ end
63
+
64
+ # @api private
65
+ def visit_constrained(node)
66
+ other, * = node
67
+ visit(other)
68
+ end
69
+
70
+ # @api private
71
+ def visit_any(_)
72
+ ::Object
73
+ end
74
+ end
75
+
76
+ # @return [Compiler]
77
+ # @api private
78
+ attr_reader :compiler
79
+
80
+ # @api private
81
+ def initialize
82
+ @compiler = Compiler.new
83
+ end
84
+
85
+ # Infer primitives from the provided type
86
+ #
87
+ # @return [Array[Class]]
88
+ #
89
+ # @api private
90
+ def [](type)
91
+ self.class.fetch_or_store(type) do
92
+ Array(compiler.visit(type.to_ast)).freeze
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -1,8 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dry
2
4
  module Types
5
+ # @api private
3
6
  module Printable
4
7
  # @return [String]
5
- # @api public
8
+ #
9
+ # @api private
6
10
  def to_s
7
11
  PRINTER.(self) { super }
8
12
  end