dry-types 0.15.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
  - - ">="