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
@@ -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