dry-types 0.15.0 → 1.0.0

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +18 -2
  4. data/.travis.yml +4 -5
  5. data/.yardopts +6 -2
  6. data/CHANGELOG.md +69 -1
  7. data/Gemfile +3 -0
  8. data/README.md +2 -1
  9. data/Rakefile +2 -0
  10. data/benchmarks/hash_schemas.rb +2 -0
  11. data/benchmarks/lax_schema.rb +16 -0
  12. data/benchmarks/profile_invalid_input.rb +15 -0
  13. data/benchmarks/profile_lax_schema_valid.rb +16 -0
  14. data/benchmarks/profile_valid_input.rb +15 -0
  15. data/benchmarks/schema_valid_vs_invalid.rb +21 -0
  16. data/benchmarks/setup.rb +17 -0
  17. data/dry-types.gemspec +4 -2
  18. data/lib/dry-types.rb +2 -0
  19. data/lib/dry/types.rb +51 -13
  20. data/lib/dry/types/any.rb +21 -10
  21. data/lib/dry/types/array.rb +11 -1
  22. data/lib/dry/types/array/member.rb +65 -13
  23. data/lib/dry/types/builder.rb +48 -4
  24. data/lib/dry/types/builder_methods.rb +9 -8
  25. data/lib/dry/types/coercions.rb +71 -19
  26. data/lib/dry/types/coercions/json.rb +22 -3
  27. data/lib/dry/types/coercions/params.rb +98 -30
  28. data/lib/dry/types/compiler.rb +35 -12
  29. data/lib/dry/types/constrained.rb +73 -27
  30. data/lib/dry/types/constrained/coercible.rb +36 -6
  31. data/lib/dry/types/constraints.rb +15 -1
  32. data/lib/dry/types/constructor.rb +90 -43
  33. data/lib/dry/types/constructor/function.rb +201 -0
  34. data/lib/dry/types/container.rb +5 -0
  35. data/lib/dry/types/core.rb +7 -5
  36. data/lib/dry/types/decorator.rb +36 -9
  37. data/lib/dry/types/default.rb +48 -16
  38. data/lib/dry/types/enum.rb +30 -16
  39. data/lib/dry/types/errors.rb +73 -7
  40. data/lib/dry/types/extensions.rb +2 -0
  41. data/lib/dry/types/extensions/maybe.rb +43 -4
  42. data/lib/dry/types/fn_container.rb +5 -0
  43. data/lib/dry/types/hash.rb +22 -3
  44. data/lib/dry/types/hash/constructor.rb +13 -0
  45. data/lib/dry/types/inflector.rb +2 -0
  46. data/lib/dry/types/json.rb +4 -6
  47. data/lib/dry/types/{safe.rb → lax.rb} +34 -17
  48. data/lib/dry/types/map.rb +63 -29
  49. data/lib/dry/types/meta.rb +51 -0
  50. data/lib/dry/types/module.rb +7 -2
  51. data/lib/dry/types/nominal.rb +105 -13
  52. data/lib/dry/types/options.rb +12 -25
  53. data/lib/dry/types/params.rb +5 -3
  54. data/lib/dry/types/printable.rb +5 -1
  55. data/lib/dry/types/printer.rb +58 -57
  56. data/lib/dry/types/result.rb +26 -0
  57. data/lib/dry/types/schema.rb +169 -66
  58. data/lib/dry/types/schema/key.rb +34 -39
  59. data/lib/dry/types/spec/types.rb +41 -1
  60. data/lib/dry/types/sum.rb +70 -21
  61. data/lib/dry/types/type.rb +49 -0
  62. data/lib/dry/types/version.rb +3 -1
  63. metadata +14 -12
@@ -1,7 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/equalizer'
2
4
 
3
5
  module Dry
4
6
  module Types
7
+ # Schema is a hash with explicit member types defined
8
+ #
9
+ # @api public
5
10
  class Schema < Hash
6
11
  # Proxy type for schema keys. Contains only key name and
7
12
  # whether it's required or not. All other calls deletaged
@@ -28,12 +33,19 @@ module Dry
28
33
  @name = name
29
34
  end
30
35
 
31
- # @see Dry::Types::Nominal#call
32
- def call(input, &block)
33
- type.(input, &block)
36
+ # @api private
37
+ def call_safe(input, &block)
38
+ type.call_safe(input, &block)
39
+ end
40
+
41
+ # @api private
42
+ def call_unsafe(input)
43
+ type.call_unsafe(input)
34
44
  end
35
45
 
36
46
  # @see Dry::Types::Nominal#try
47
+ #
48
+ # @api public
37
49
  def try(input, &block)
38
50
  type.try(input, &block)
39
51
  end
@@ -41,6 +53,8 @@ module Dry
41
53
  # Whether the key is required in schema input
42
54
  #
43
55
  # @return [Boolean]
56
+ #
57
+ # @api public
44
58
  def required?
45
59
  options.fetch(:required)
46
60
  end
@@ -55,6 +69,8 @@ module Dry
55
69
  #
56
70
  # @param [Boolean] required New value
57
71
  # @return [Dry::Types::Schema::Key]
72
+ #
73
+ # @api public
58
74
  def required(required = Undefined)
59
75
  if Undefined.equal?(required)
60
76
  options.fetch(:required)
@@ -66,36 +82,26 @@ module Dry
66
82
  # Make key not required
67
83
  #
68
84
  # @return [Dry::Types::Schema::Key]
85
+ #
86
+ # @api public
69
87
  def omittable
70
88
  required(false)
71
89
  end
72
90
 
73
- # Construct a default type. Default values are
74
- # evaluated/applied when key is absent in schema
75
- # input.
91
+ # Turn key into a lax type. Lax types are not strict hence such keys are not required
76
92
  #
77
- # @see Dry::Types::Default
78
- # @return [Dry::Types::Schema::Key]
79
- def default(input = Undefined, &block)
80
- new(type.default(input, &block))
81
- end
82
-
83
- # Replace the underlying type
84
- # @param [Dry::Types::Type] type
85
- # @return [Dry::Types::Schema::Key]
86
- def new(type)
87
- self.class.new(type, name, options)
88
- end
89
-
90
- # @see Dry::Types::Safe
91
- # @return [Dry::Types::Schema::Key]
92
- def safe
93
- new(type.safe)
93
+ # @return [Lax]
94
+ #
95
+ # @api public
96
+ def lax
97
+ super.required(false)
94
98
  end
95
99
 
96
100
  # Dump to internal AST representation
97
101
  #
98
102
  # @return [Array]
103
+ #
104
+ # @api public
99
105
  def to_ast(meta: true)
100
106
  [
101
107
  :key,
@@ -107,22 +113,11 @@ module Dry
107
113
  ]
108
114
  end
109
115
 
110
- # Get/set type metadata. The Key type doesn't have
111
- # its out meta, it delegates these calls to the underlying
112
- # type.
113
- #
114
- # @overload meta
115
- # @return [Hash] metadata associated with type
116
- #
117
- # @overload meta(data)
118
- # @param [Hash] new metadata to merge into existing metadata
119
- # @return [Type] new type with added metadata
120
- def meta(data = nil)
121
- if data.nil?
122
- type.meta
123
- else
124
- new(type.meta(data))
125
- end
116
+ private
117
+
118
+ # @api private
119
+ def decorate?(response)
120
+ response.is_a?(Type)
126
121
  end
127
122
  end
128
123
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.shared_examples_for 'Dry::Types::Nominal without primitive' do
2
4
  def be_boolean
3
5
  satisfy { |x| x == true || x == false }
@@ -46,6 +48,14 @@ RSpec.shared_examples_for 'Dry::Types::Nominal without primitive' do
46
48
  end
47
49
  end
48
50
  end
51
+
52
+ describe '#to_proc' do
53
+ subject(:callable) { type.to_proc }
54
+
55
+ it 'converts a type to a proc' do
56
+ expect(callable).to be_a(Proc)
57
+ end
58
+ end
49
59
  end
50
60
 
51
61
  RSpec.shared_examples_for 'Dry::Types::Nominal#meta' do
@@ -69,7 +79,7 @@ RSpec.shared_examples_for 'Dry::Types::Nominal#meta' do
69
79
  expect(type.meta).to be_a ::Hash
70
80
  expect(type.meta).to be_frozen
71
81
  expect(type.meta).not_to have_key :immutable_test
72
- derived = type.with(meta: {immutable_test: 1})
82
+ derived = type.meta(immutable_test: 1)
73
83
  expect(derived.meta).to be_frozen
74
84
  expect(derived.meta).to eql({immutable_test: 1})
75
85
  expect(type.meta).not_to have_key :immutable_test
@@ -100,3 +110,33 @@ RSpec.shared_examples_for Dry::Types::Nominal do
100
110
  end
101
111
  end
102
112
  end
113
+
114
+ RSpec.shared_examples_for 'a constrained type' do |inputs: Object.new|
115
+ let(:fallback) { Object.new }
116
+
117
+ describe '#call' do
118
+ it 'yields a block on failure' do
119
+ Array(inputs).each do |input|
120
+ expect(type.(input) { fallback }).to be(fallback)
121
+ end
122
+ end
123
+
124
+ it 'throws an error on invalid input' do
125
+ Array(inputs).each do |input|
126
+ expect { type.(input) }.to raise_error(Dry::Types::CoercionError)
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ RSpec.shared_examples_for 'a nominal type' do |inputs: Object.new|
133
+ describe '#call' do
134
+ it 'always returns the input back' do
135
+ Array(inputs).each do |input|
136
+ expect(type.(input) { fail }).to be(input)
137
+ expect(type.(input)).to be(input)
138
+ end
139
+ end
140
+ end
141
+ end
142
+
@@ -1,11 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/types/options'
4
+ require 'dry/types/meta'
2
5
 
3
6
  module Dry
4
7
  module Types
8
+ # Sum type
9
+ #
10
+ # @api public
5
11
  class Sum
6
12
  include Type
7
13
  include Builder
8
14
  include Options
15
+ include Meta
9
16
  include Printable
10
17
  include Dry::Equalizer(:left, :right, :options, :meta, inspect: false)
11
18
 
@@ -15,6 +22,7 @@ module Dry
15
22
  # @return [Type]
16
23
  attr_reader :right
17
24
 
25
+ # @api private
18
26
  class Constrained < Sum
19
27
  # @return [Dry::Logic::Operations::Or]
20
28
  def rule
@@ -25,21 +33,13 @@ module Dry
25
33
  def constrained?
26
34
  true
27
35
  end
28
-
29
- # @param [Object] input
30
- # @return [Object]
31
- # @raise [ConstraintError] if given +input+ not passing {#try}
32
- def call(input)
33
- try(input) { |result|
34
- raise ConstraintError.new(result, input)
35
- }.input
36
- end
37
- alias_method :[], :call
38
36
  end
39
37
 
40
38
  # @param [Type] left
41
39
  # @param [Type] right
42
40
  # @param [Hash] options
41
+ #
42
+ # @api private
43
43
  def initialize(left, right, options = {})
44
44
  super
45
45
  @left, @right = left, right
@@ -47,33 +47,55 @@ module Dry
47
47
  end
48
48
 
49
49
  # @return [String]
50
+ #
51
+ # @api public
50
52
  def name
51
53
  [left, right].map(&:name).join(' | ')
52
54
  end
53
55
 
54
56
  # @return [false]
57
+ #
58
+ # @api public
55
59
  def default?
56
60
  false
57
61
  end
58
62
 
59
63
  # @return [false]
64
+ #
65
+ # @api public
60
66
  def constrained?
61
67
  false
62
68
  end
63
69
 
64
70
  # @return [Boolean]
71
+ #
72
+ # @api public
65
73
  def optional?
66
74
  primitive?(nil)
67
75
  end
68
76
 
69
77
  # @param [Object] input
78
+ #
70
79
  # @return [Object]
71
- def call(input)
72
- try(input).input
80
+ #
81
+ # @api private
82
+ def call_unsafe(input)
83
+ left.call_safe(input) { right.call_unsafe(input) }
73
84
  end
74
- alias_method :[], :call
75
85
 
76
- def try(input, &block)
86
+ # @param [Object] input
87
+ #
88
+ # @return [Object]
89
+ #
90
+ # @api private
91
+ def call_safe(input, &block)
92
+ left.call_safe(input) { right.call_safe(input, &block) }
93
+ end
94
+
95
+ # @param [Object] input
96
+ #
97
+ # @api public
98
+ def try(input)
77
99
  left.try(input) do
78
100
  right.try(input) do |failure|
79
101
  if block_given?
@@ -85,6 +107,7 @@ module Dry
85
107
  end
86
108
  end
87
109
 
110
+ # @api private
88
111
  def success(input)
89
112
  if left.valid?(input)
90
113
  left.success(input)
@@ -95,6 +118,7 @@ module Dry
95
118
  end
96
119
  end
97
120
 
121
+ # @api private
98
122
  def failure(input, _error = nil)
99
123
  if !left.valid?(input)
100
124
  left.failure(input, left.try(input).error)
@@ -104,28 +128,44 @@ module Dry
104
128
  end
105
129
 
106
130
  # @param [Object] value
131
+ #
107
132
  # @return [Boolean]
133
+ #
134
+ # @api private
108
135
  def primitive?(value)
109
136
  left.primitive?(value) || right.primitive?(value)
110
137
  end
111
138
 
112
- # @param [Object] value
113
- # @return [Boolean]
114
- def valid?(value)
115
- left.valid?(value) || right.valid?(value)
139
+ # Manage metadata to the type. If the type is an optional, #meta delegates
140
+ # to the right branch
141
+ #
142
+ # @see [Meta#meta]
143
+ #
144
+ # @api public
145
+ def meta(data = nil)
146
+ if data.nil?
147
+ optional? ? right.meta : super
148
+ elsif optional?
149
+ self.class.new(left, right.meta(data), options)
150
+ else
151
+ super
152
+ end
116
153
  end
117
- alias_method :===, :valid?
118
154
 
119
- # @api public
120
- #
121
155
  # @see Nominal#to_ast
156
+ #
157
+ # @api public
122
158
  def to_ast(meta: true)
123
159
  [:sum, [left.to_ast(meta: meta), right.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]]
124
160
  end
125
161
 
126
162
  # @param [Hash] options
163
+ #
127
164
  # @return [Constrained,Sum]
165
+ #
128
166
  # @see Builder#constrained
167
+ #
168
+ # @api public
129
169
  def constrained(options)
130
170
  if optional?
131
171
  right.constrained(options).optional
@@ -133,6 +173,15 @@ module Dry
133
173
  super
134
174
  end
135
175
  end
176
+
177
+ # Wrap the type with a proc
178
+ #
179
+ # @return [Proc]
180
+ #
181
+ # @api public
182
+ def to_proc
183
+ proc { |value| self.(value) }
184
+ end
136
185
  end
137
186
  end
138
187
  end
@@ -1,6 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/core/deprecations'
4
+
1
5
  module Dry
2
6
  module Types
7
+ # Common Type module denoting an object is a Type
8
+ #
9
+ # @api public
3
10
  module Type
11
+ extend ::Dry::Core::Deprecations[:'dry-types']
12
+
13
+ deprecate(:safe, :lax)
14
+
15
+ # Whether a value is a valid member of the type
16
+ #
17
+ # @return [Boolean]
18
+ #
19
+ # @api private
20
+ def valid?(input = Undefined)
21
+ call_safe(input) { return false }
22
+ true
23
+ end
24
+ # Anything can be coerced matches
25
+ alias_method :===, :valid?
26
+
27
+ # Apply type to a value
28
+ #
29
+ # @overload call(input = Undefined)
30
+ # Possibly unsafe coercion attempt. If a value doesn't
31
+ # match the type, an exception will be raised.
32
+ #
33
+ # @param [Object] input
34
+ # @return [Object]
35
+ #
36
+ # @overload call(input = Undefined)
37
+ # When a block is passed, {#call} will never throw an exception on
38
+ # failed coercion, instead it will call the block.
39
+ #
40
+ # @param [Object] input
41
+ # @yieldparam [Object] output Partially coerced value
42
+ # @return [Object]
43
+ #
44
+ # @api public
45
+ def call(input = Undefined, &block)
46
+ if block_given?
47
+ call_safe(input, &block)
48
+ else
49
+ call_unsafe(input)
50
+ end
51
+ end
52
+ alias_method :[], :call
4
53
  end
5
54
  end
6
55
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dry
2
4
  module Types
3
- VERSION = '0.15.0'.freeze
5
+ VERSION = '1.0.0'
4
6
  end
5
7
  end
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: 0.15.0
4
+ version: 1.0.0
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-03-22 00:00:00.000000000 Z
11
+ date: 2019-04-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -102,22 +102,16 @@ dependencies:
102
102
  name: dry-logic
103
103
  requirement: !ruby/object:Gem::Requirement
104
104
  requirements:
105
- - - ">="
106
- - !ruby/object:Gem::Version
107
- version: '0.5'
108
105
  - - "~>"
109
106
  - !ruby/object:Gem::Version
110
- version: '0.5'
107
+ version: '1.0'
111
108
  type: :runtime
112
109
  prerelease: false
113
110
  version_requirements: !ruby/object:Gem::Requirement
114
111
  requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0.5'
118
112
  - - "~>"
119
113
  - !ruby/object:Gem::Version
120
- version: '0.5'
114
+ version: '1.0'
121
115
  - !ruby/object:Gem::Dependency
122
116
  name: bundler
123
117
  requirement: !ruby/object:Gem::Requirement
@@ -209,6 +203,12 @@ files:
209
203
  - README.md
210
204
  - Rakefile
211
205
  - benchmarks/hash_schemas.rb
206
+ - benchmarks/lax_schema.rb
207
+ - benchmarks/profile_invalid_input.rb
208
+ - benchmarks/profile_lax_schema_valid.rb
209
+ - benchmarks/profile_valid_input.rb
210
+ - benchmarks/schema_valid_vs_invalid.rb
211
+ - benchmarks/setup.rb
212
212
  - dry-types.gemspec
213
213
  - lib/dry-types.rb
214
214
  - lib/dry/types.rb
@@ -226,6 +226,7 @@ files:
226
226
  - lib/dry/types/constrained/coercible.rb
227
227
  - lib/dry/types/constraints.rb
228
228
  - lib/dry/types/constructor.rb
229
+ - lib/dry/types/constructor/function.rb
229
230
  - lib/dry/types/container.rb
230
231
  - lib/dry/types/core.rb
231
232
  - lib/dry/types/decorator.rb
@@ -239,7 +240,9 @@ files:
239
240
  - lib/dry/types/hash/constructor.rb
240
241
  - lib/dry/types/inflector.rb
241
242
  - lib/dry/types/json.rb
243
+ - lib/dry/types/lax.rb
242
244
  - lib/dry/types/map.rb
245
+ - lib/dry/types/meta.rb
243
246
  - lib/dry/types/module.rb
244
247
  - lib/dry/types/nominal.rb
245
248
  - lib/dry/types/options.rb
@@ -247,7 +250,6 @@ files:
247
250
  - lib/dry/types/printable.rb
248
251
  - lib/dry/types/printer.rb
249
252
  - lib/dry/types/result.rb
250
- - lib/dry/types/safe.rb
251
253
  - lib/dry/types/schema.rb
252
254
  - lib/dry/types/schema/key.rb
253
255
  - lib/dry/types/spec/types.rb
@@ -271,7 +273,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
271
273
  requirements:
272
274
  - - ">="
273
275
  - !ruby/object:Gem::Version
274
- version: 2.3.0
276
+ version: 2.4.0
275
277
  required_rubygems_version: !ruby/object:Gem::Requirement
276
278
  requirements:
277
279
  - - ">="