dry-types 1.1.1 → 1.2.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +2 -5
  3. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +10 -0
  4. data/.github/ISSUE_TEMPLATE/---bug-report.md +34 -0
  5. data/.github/ISSUE_TEMPLATE/---feature-request.md +18 -0
  6. data/.github/workflows/custom_ci.yml +76 -0
  7. data/.github/workflows/docsite.yml +34 -0
  8. data/.github/workflows/sync_configs.yml +34 -0
  9. data/.gitignore +1 -1
  10. data/.rspec +3 -1
  11. data/.rubocop.yml +34 -4
  12. data/CHANGELOG.md +69 -0
  13. data/CODE_OF_CONDUCT.md +13 -0
  14. data/CONTRIBUTING.md +2 -2
  15. data/Gemfile +11 -6
  16. data/LICENSE +17 -17
  17. data/docsite/source/array-with-member.html.md +13 -0
  18. data/docsite/source/built-in-types.html.md +116 -0
  19. data/docsite/source/constraints.html.md +31 -0
  20. data/docsite/source/custom-types.html.md +93 -0
  21. data/docsite/source/default-values.html.md +91 -0
  22. data/docsite/source/enum.html.md +69 -0
  23. data/docsite/source/extensions/maybe.html.md +57 -0
  24. data/docsite/source/extensions/monads.html.md +61 -0
  25. data/docsite/source/extensions.html.md +15 -0
  26. data/docsite/source/getting-started.html.md +57 -0
  27. data/docsite/source/hash-schemas.html.md +169 -0
  28. data/docsite/source/index.html.md +156 -0
  29. data/docsite/source/map.html.md +17 -0
  30. data/docsite/source/optional-values.html.md +35 -0
  31. data/docsite/source/sum.html.md +21 -0
  32. data/lib/dry/types/any.rb +2 -2
  33. data/lib/dry/types/array/member.rb +3 -3
  34. data/lib/dry/types/builder.rb +1 -1
  35. data/lib/dry/types/builder_methods.rb +18 -8
  36. data/lib/dry/types/compiler.rb +2 -2
  37. data/lib/dry/types/constrained.rb +1 -1
  38. data/lib/dry/types/constructor.rb +6 -33
  39. data/lib/dry/types/decorator.rb +3 -2
  40. data/lib/dry/types/enum.rb +1 -1
  41. data/lib/dry/types/extensions/monads.rb +29 -0
  42. data/lib/dry/types/extensions.rb +4 -0
  43. data/lib/dry/types/hash.rb +3 -2
  44. data/lib/dry/types/lax.rb +1 -1
  45. data/lib/dry/types/meta.rb +1 -1
  46. data/lib/dry/types/module.rb +3 -3
  47. data/lib/dry/types/params.rb +5 -0
  48. data/lib/dry/types/predicate_inferrer.rb +197 -0
  49. data/lib/dry/types/predicate_registry.rb +34 -0
  50. data/lib/dry/types/primitive_inferrer.rb +97 -0
  51. data/lib/dry/types/spec/types.rb +3 -1
  52. data/lib/dry/types/sum.rb +2 -2
  53. data/lib/dry/types/version.rb +1 -1
  54. data/lib/dry/types.rb +7 -2
  55. metadata +29 -4
  56. data/.travis.yml +0 -27
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/core/cache'
4
+ require 'dry/types/predicate_registry'
5
+
6
+ module Dry
7
+ module Types
8
+ # PredicateInferrer returns the list of predicates used by a type.
9
+ #
10
+ # @api public
11
+ class PredicateInferrer
12
+ extend Core::Cache
13
+
14
+ TYPE_TO_PREDICATE = {
15
+ DateTime => :date_time?,
16
+ FalseClass => :false?,
17
+ Integer => :int?,
18
+ NilClass => :nil?,
19
+ String => :str?,
20
+ TrueClass => :true?,
21
+ BigDecimal => :decimal?
22
+ }.freeze
23
+
24
+ REDUCED_TYPES = {
25
+ [[[:true?], [:false?]]] => %i[bool?]
26
+ }.freeze
27
+
28
+ HASH = %i[hash?].freeze
29
+
30
+ ARRAY = %i[array?].freeze
31
+
32
+ NIL = %i[nil?].freeze
33
+
34
+ # Compiler reduces type AST into a list of predicates
35
+ #
36
+ # @api private
37
+ class Compiler
38
+ # @return [PredicateRegistry]
39
+ # @api private
40
+ attr_reader :registry
41
+
42
+ # @api private
43
+ def initialize(registry)
44
+ @registry = registry
45
+ end
46
+
47
+ # @api private
48
+ def infer_predicate(type)
49
+ [TYPE_TO_PREDICATE.fetch(type) { :"#{type.name.split('::').last.downcase}?" }]
50
+ end
51
+
52
+ # @api private
53
+ def visit(node)
54
+ meth, rest = node
55
+ public_send(:"visit_#{meth}", rest)
56
+ end
57
+
58
+ # @api private
59
+ def visit_nominal(node)
60
+ type = node[0]
61
+ predicate = infer_predicate(type)
62
+
63
+ if registry.key?(predicate[0])
64
+ predicate
65
+ else
66
+ [type?: type]
67
+ end
68
+ end
69
+
70
+ # @api private
71
+ def visit_hash(_)
72
+ HASH
73
+ end
74
+
75
+ # @api private
76
+ def visit_array(_)
77
+ ARRAY
78
+ end
79
+
80
+ # @api private
81
+ def visit_lax(node)
82
+ visit(node)
83
+ end
84
+
85
+ # @api private
86
+ def visit_constructor(node)
87
+ other, * = node
88
+ visit(other)
89
+ end
90
+
91
+ # @api private
92
+ def visit_enum(node)
93
+ other, * = node
94
+ visit(other)
95
+ end
96
+
97
+ # @api private
98
+ def visit_sum(node)
99
+ left_node, right_node, = node
100
+ left = visit(left_node)
101
+ right = visit(right_node)
102
+
103
+ if left.eql?(NIL)
104
+ right
105
+ else
106
+ [[left, right]]
107
+ end
108
+ end
109
+
110
+ # @api private
111
+ def visit_constrained(node)
112
+ other, rules = node
113
+ predicates = visit(rules)
114
+
115
+ if predicates.empty?
116
+ visit(other)
117
+ else
118
+ [*visit(other), *merge_predicates(predicates)]
119
+ end
120
+ end
121
+
122
+ # @api private
123
+ def visit_any(_)
124
+ EMPTY_ARRAY
125
+ end
126
+
127
+ # @api private
128
+ def visit_and(node)
129
+ left, right = node
130
+ visit(left) + visit(right)
131
+ end
132
+
133
+ # @api private
134
+ def visit_predicate(node)
135
+ pred, args = node
136
+
137
+ if pred.equal?(:type?)
138
+ EMPTY_ARRAY
139
+ elsif registry.key?(pred)
140
+ *curried, _ = args
141
+ values = curried.map { |_, v| v }
142
+
143
+ if values.empty?
144
+ [pred]
145
+ else
146
+ [pred => values[0]]
147
+ end
148
+ else
149
+ EMPTY_ARRAY
150
+ end
151
+ end
152
+
153
+ private
154
+
155
+ # @api private
156
+ def merge_predicates(nodes)
157
+ preds, merged = nodes.each_with_object([[], {}]) do |predicate, (ps, h)|
158
+ if predicate.is_a?(::Hash)
159
+ h.update(predicate)
160
+ else
161
+ ps << predicate
162
+ end
163
+ end
164
+
165
+ merged.empty? ? preds : [*preds, merged]
166
+ end
167
+ end
168
+
169
+ # @return [Compiler]
170
+ # @api private
171
+ attr_reader :compiler
172
+
173
+ # @api private
174
+ def initialize(registry = PredicateRegistry.new)
175
+ @compiler = Compiler.new(registry)
176
+ end
177
+
178
+ # Infer predicate identifier from the provided type
179
+ #
180
+ # @param [Type] type
181
+ # @return [Symbol]
182
+ #
183
+ # @api private
184
+ def [](type)
185
+ self.class.fetch_or_store(type) do
186
+ predicates = compiler.visit(type.to_ast)
187
+
188
+ if predicates.is_a?(::Hash)
189
+ predicates
190
+ else
191
+ REDUCED_TYPES[predicates] || predicates
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
197
+ 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
@@ -109,7 +109,9 @@ RSpec.shared_examples_for Dry::Types::Nominal do
109
109
  end
110
110
  end
111
111
 
112
- RSpec.shared_examples_for 'a constrained type' do |inputs: Object.new|
112
+ RSpec.shared_examples_for 'a constrained type' do |options = { inputs: Object.new }|
113
+ inputs = options[:inputs]
114
+
113
115
  let(:fallback) { Object.new }
114
116
 
115
117
  describe '#call' do
data/lib/dry/types/sum.rb CHANGED
@@ -40,7 +40,7 @@ module Dry
40
40
  # @param [Hash] options
41
41
  #
42
42
  # @api private
43
- def initialize(left, right, options = {})
43
+ def initialize(left, right, **options)
44
44
  super
45
45
  @left, @right = left, right
46
46
  freeze
@@ -146,7 +146,7 @@ module Dry
146
146
  if data.nil?
147
147
  optional? ? right.meta : super
148
148
  elsif optional?
149
- self.class.new(left, right.meta(data), options)
149
+ self.class.new(left, right.meta(data), **options)
150
150
  else
151
151
  super
152
152
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Dry
4
4
  module Types
5
- VERSION = '1.1.1'
5
+ VERSION = '1.2.1'
6
6
  end
7
7
  end
data/lib/dry/types.rb CHANGED
@@ -89,7 +89,7 @@ module Dry
89
89
  def self.[](name)
90
90
  type_map.fetch_or_store(name) do
91
91
  case name
92
- when String
92
+ when ::String
93
93
  result = name.match(TYPE_SPEC_REGEX)
94
94
 
95
95
  if result
@@ -98,7 +98,12 @@ module Dry
98
98
  else
99
99
  container[name]
100
100
  end
101
- when Class
101
+ when ::Class
102
+ warn(<<~DEPRECATION)
103
+ Using Dry::Types.[] with a class is deprecated, please use string identifiers: Dry::Types[Integer] -> Dry::Types['integer'].
104
+ If you're using dry-struct this means changing `attribute :counter, Integer` to `attribute :counter, Dry::Types['integer']` or to `attribute :counter, 'integer'`.
105
+ DEPRECATION
106
+
102
107
  type_name = identifier(name)
103
108
 
104
109
  if container.key?(type_name)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-types
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-07-26 00:00:00.000000000 Z
11
+ date: 2019-11-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -197,12 +197,18 @@ extensions: []
197
197
  extra_rdoc_files: []
198
198
  files:
199
199
  - ".codeclimate.yml"
200
+ - ".github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md"
201
+ - ".github/ISSUE_TEMPLATE/---bug-report.md"
202
+ - ".github/ISSUE_TEMPLATE/---feature-request.md"
203
+ - ".github/workflows/custom_ci.yml"
204
+ - ".github/workflows/docsite.yml"
205
+ - ".github/workflows/sync_configs.yml"
200
206
  - ".gitignore"
201
207
  - ".rspec"
202
208
  - ".rubocop.yml"
203
- - ".travis.yml"
204
209
  - ".yardopts"
205
210
  - CHANGELOG.md
211
+ - CODE_OF_CONDUCT.md
206
212
  - CONTRIBUTING.md
207
213
  - Gemfile
208
214
  - LICENSE
@@ -215,6 +221,21 @@ files:
215
221
  - benchmarks/profile_valid_input.rb
216
222
  - benchmarks/schema_valid_vs_invalid.rb
217
223
  - benchmarks/setup.rb
224
+ - docsite/source/array-with-member.html.md
225
+ - docsite/source/built-in-types.html.md
226
+ - docsite/source/constraints.html.md
227
+ - docsite/source/custom-types.html.md
228
+ - docsite/source/default-values.html.md
229
+ - docsite/source/enum.html.md
230
+ - docsite/source/extensions.html.md
231
+ - docsite/source/extensions/maybe.html.md
232
+ - docsite/source/extensions/monads.html.md
233
+ - docsite/source/getting-started.html.md
234
+ - docsite/source/hash-schemas.html.md
235
+ - docsite/source/index.html.md
236
+ - docsite/source/map.html.md
237
+ - docsite/source/optional-values.html.md
238
+ - docsite/source/sum.html.md
218
239
  - dry-types.gemspec
219
240
  - lib/dry-types.rb
220
241
  - lib/dry/types.rb
@@ -242,6 +263,7 @@ files:
242
263
  - lib/dry/types/errors.rb
243
264
  - lib/dry/types/extensions.rb
244
265
  - lib/dry/types/extensions/maybe.rb
266
+ - lib/dry/types/extensions/monads.rb
245
267
  - lib/dry/types/fn_container.rb
246
268
  - lib/dry/types/hash.rb
247
269
  - lib/dry/types/hash/constructor.rb
@@ -254,6 +276,9 @@ files:
254
276
  - lib/dry/types/nominal.rb
255
277
  - lib/dry/types/options.rb
256
278
  - lib/dry/types/params.rb
279
+ - lib/dry/types/predicate_inferrer.rb
280
+ - lib/dry/types/predicate_registry.rb
281
+ - lib/dry/types/primitive_inferrer.rb
257
282
  - lib/dry/types/printable.rb
258
283
  - lib/dry/types/printer.rb
259
284
  - lib/dry/types/result.rb
@@ -287,7 +312,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
287
312
  - !ruby/object:Gem::Version
288
313
  version: '0'
289
314
  requirements: []
290
- rubygems_version: 3.0.3
315
+ rubygems_version: 3.0.6
291
316
  signing_key:
292
317
  specification_version: 4
293
318
  summary: Type system for Ruby supporting coercions, constraints and complex types
data/.travis.yml DELETED
@@ -1,27 +0,0 @@
1
- language: ruby
2
- cache: bundler
3
- bundler_args: --without benchmarks tools
4
- before_script:
5
- - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
6
- - chmod +x ./cc-test-reporter
7
- - ./cc-test-reporter before-build
8
- after_script:
9
- - "[ -d coverage ] && ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT"
10
- script:
11
- - bundle exec rake
12
- rvm:
13
- - 2.6.3
14
- - 2.5.5
15
- - 2.4.6
16
- - jruby-9.2.7.0
17
- env:
18
- global:
19
- - COVERAGE=true
20
- notifications:
21
- email: false
22
- webhooks:
23
- urls:
24
- - https://webhooks.gitter.im/e/19098b4253a72c9796db
25
- on_success: change # options: [always|never|change] default: always
26
- on_failure: always # options: [always|never|change] default: always
27
- on_start: false # default: false