dry-types 0.14.1 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +631 -134
  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 +32 -12
  7. data/lib/dry/types/array/constructor.rb +32 -0
  8. data/lib/dry/types/array/member.rb +75 -16
  9. data/lib/dry/types/array.rb +19 -6
  10. data/lib/dry/types/builder.rb +131 -15
  11. data/lib/dry/types/builder_methods.rb +49 -20
  12. data/lib/dry/types/coercions/json.rb +43 -7
  13. data/lib/dry/types/coercions/params.rb +118 -31
  14. data/lib/dry/types/coercions.rb +76 -22
  15. data/lib/dry/types/compat.rb +0 -2
  16. data/lib/dry/types/compiler.rb +56 -41
  17. data/lib/dry/types/constrained/coercible.rb +36 -6
  18. data/lib/dry/types/constrained.rb +81 -32
  19. data/lib/dry/types/constraints.rb +18 -4
  20. data/lib/dry/types/constructor/function.rb +216 -0
  21. data/lib/dry/types/constructor/wrapper.rb +94 -0
  22. data/lib/dry/types/constructor.rb +126 -56
  23. data/lib/dry/types/container.rb +7 -0
  24. data/lib/dry/types/core.rb +54 -21
  25. data/lib/dry/types/decorator.rb +38 -17
  26. data/lib/dry/types/default.rb +61 -16
  27. data/lib/dry/types/enum.rb +43 -20
  28. data/lib/dry/types/errors.rb +75 -9
  29. data/lib/dry/types/extensions/maybe.rb +74 -16
  30. data/lib/dry/types/extensions/monads.rb +29 -0
  31. data/lib/dry/types/extensions.rb +7 -1
  32. data/lib/dry/types/fn_container.rb +6 -1
  33. data/lib/dry/types/hash/constructor.rb +33 -0
  34. data/lib/dry/types/hash.rb +86 -67
  35. data/lib/dry/types/inflector.rb +3 -1
  36. data/lib/dry/types/json.rb +18 -16
  37. data/lib/dry/types/lax.rb +75 -0
  38. data/lib/dry/types/map.rb +76 -33
  39. data/lib/dry/types/meta.rb +51 -0
  40. data/lib/dry/types/module.rb +120 -0
  41. data/lib/dry/types/nominal.rb +210 -0
  42. data/lib/dry/types/options.rb +13 -26
  43. data/lib/dry/types/params.rb +39 -25
  44. data/lib/dry/types/predicate_inferrer.rb +238 -0
  45. data/lib/dry/types/predicate_registry.rb +34 -0
  46. data/lib/dry/types/primitive_inferrer.rb +97 -0
  47. data/lib/dry/types/printable.rb +16 -0
  48. data/lib/dry/types/printer.rb +315 -0
  49. data/lib/dry/types/result.rb +29 -3
  50. data/lib/dry/types/schema/key.rb +156 -0
  51. data/lib/dry/types/schema.rb +408 -0
  52. data/lib/dry/types/spec/types.rb +103 -33
  53. data/lib/dry/types/sum.rb +84 -35
  54. data/lib/dry/types/type.rb +49 -0
  55. data/lib/dry/types/version.rb +3 -1
  56. data/lib/dry/types.rb +156 -76
  57. data/lib/dry-types.rb +3 -1
  58. metadata +65 -78
  59. data/.gitignore +0 -10
  60. data/.rspec +0 -2
  61. data/.travis.yml +0 -27
  62. data/.yardopts +0 -5
  63. data/CONTRIBUTING.md +0 -29
  64. data/Gemfile +0 -24
  65. data/Rakefile +0 -20
  66. data/benchmarks/hash_schemas.rb +0 -51
  67. data/lib/dry/types/compat/form_types.rb +0 -27
  68. data/lib/dry/types/compat/int.rb +0 -14
  69. data/lib/dry/types/definition.rb +0 -113
  70. data/lib/dry/types/hash/schema.rb +0 -199
  71. data/lib/dry/types/hash/schema_builder.rb +0 -75
  72. data/lib/dry/types/safe.rb +0 -59
  73. data/log/.gitkeep +0 -0
@@ -1,12 +1,42 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dry
2
4
  module Types
3
5
  class Constrained
6
+ # Common coercion-related API for constrained types
7
+ #
8
+ # @api public
4
9
  class Coercible < Constrained
5
- # @param [Object] input
6
- # @param [#call,nil] block
7
- # @yieldparam [Failure] failure
8
- # @yieldreturn [Result]
9
- # @return [Result,nil]
10
+ # @return [Object]
11
+ #
12
+ # @api private
13
+ def call_unsafe(input)
14
+ coerced = type.call_unsafe(input)
15
+ result = rule.(coerced)
16
+
17
+ if result.success?
18
+ coerced
19
+ else
20
+ raise ConstraintError.new(result, input)
21
+ end
22
+ end
23
+
24
+ # @return [Object]
25
+ #
26
+ # @api private
27
+ def call_safe(input)
28
+ coerced = type.call_safe(input) { return yield }
29
+
30
+ if rule[coerced]
31
+ coerced
32
+ else
33
+ yield(coerced)
34
+ end
35
+ end
36
+
37
+ # @see Dry::Types::Constrained#try
38
+ #
39
+ # @api public
10
40
  def try(input, &block)
11
41
  result = type.try(input)
12
42
 
@@ -16,7 +46,7 @@ module Dry
16
46
  if validation.success?
17
47
  result
18
48
  else
19
- failure = failure(result.input, validation)
49
+ failure = failure(result.input, ConstraintError.new(validation, input))
20
50
  block ? yield(failure) : failure
21
51
  end
22
52
  else
@@ -1,93 +1,142 @@
1
- require 'dry/types/decorator'
2
- require 'dry/types/constraints'
3
- require 'dry/types/constrained/coercible'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/equalizer"
4
+ require "dry/types/decorator"
5
+ require "dry/types/constraints"
6
+ require "dry/types/constrained/coercible"
4
7
 
5
8
  module Dry
6
9
  module Types
10
+ # Constrained types apply rules to the input
11
+ #
12
+ # @api public
7
13
  class Constrained
8
14
  include Type
9
- include Dry::Equalizer(:type, :options, :rule, :meta)
10
15
  include Decorator
11
16
  include Builder
17
+ include Printable
18
+ include Dry::Equalizer(:type, :rule, inspect: false, immutable: true)
12
19
 
13
20
  # @return [Dry::Logic::Rule]
14
21
  attr_reader :rule
15
22
 
16
23
  # @param [Type] type
24
+ #
17
25
  # @param [Hash] options
18
- def initialize(type, options)
26
+ #
27
+ # @api public
28
+ def initialize(type, **options)
19
29
  super
20
30
  @rule = options.fetch(:rule)
21
31
  end
22
32
 
23
- # @param [Object] input
24
33
  # @return [Object]
25
- # @raise [ConstraintError]
26
- def call(input)
27
- try(input) do |result|
34
+ #
35
+ # @api private
36
+ def call_unsafe(input)
37
+ result = rule.(input)
38
+
39
+ if result.success?
40
+ type.call_unsafe(input)
41
+ else
28
42
  raise ConstraintError.new(result, input)
29
- end.input
43
+ end
44
+ end
45
+
46
+ # @return [Object]
47
+ #
48
+ # @api private
49
+ def call_safe(input, &block)
50
+ if rule[input]
51
+ type.call_safe(input, &block)
52
+ else
53
+ yield
54
+ end
30
55
  end
31
- alias_method :[], :call
32
-
33
- # @param [Object] input
34
- # @param [#call,nil] block
35
- # @yieldparam [Failure] failure
36
- # @yieldreturn [Result]
37
- # @return [Logic::Result, Result]
38
- # @return [Object] if block given and try fails
56
+
57
+ # Safe coercion attempt. It is similar to #call with a
58
+ # block given but returns a Result instance with metadata
59
+ # about errors (if any).
60
+ #
61
+ # @overload try(input)
62
+ # @param [Object] input
63
+ # @return [Logic::Result]
64
+ #
65
+ # @overload try(input)
66
+ # @param [Object] input
67
+ # @yieldparam [Failure] failure
68
+ # @yieldreturn [Object]
69
+ # @return [Object]
70
+ #
71
+ # @api public
39
72
  def try(input, &block)
40
73
  result = rule.(input)
41
74
 
42
75
  if result.success?
43
76
  type.try(input, &block)
44
77
  else
45
- failure = failure(input, result)
46
- block ? yield(failure) : failure
78
+ failure = failure(input, ConstraintError.new(result, input))
79
+ block_given? ? yield(failure) : failure
47
80
  end
48
81
  end
49
82
 
50
- # @param [Object] value
51
- # @return [Boolean]
52
- def valid?(value)
53
- rule.(value).success? && type.valid?(value)
54
- end
55
-
56
83
  # @param [Hash] options
57
84
  # The options hash provided to {Types.Rule} and combined
58
85
  # using {&} with previous {#rule}
86
+ #
59
87
  # @return [Constrained]
88
+ #
60
89
  # @see Dry::Logic::Operators#and
90
+ #
91
+ # @api public
61
92
  def constrained(options)
62
93
  with(rule: rule & Types.Rule(options))
63
94
  end
64
95
 
65
96
  # @return [true]
97
+ #
98
+ # @api public
66
99
  def constrained?
67
100
  true
68
101
  end
69
102
 
70
103
  # @param [Object] value
104
+ #
71
105
  # @return [Boolean]
106
+ #
107
+ # @api public
72
108
  def ===(value)
73
109
  valid?(value)
74
110
  end
75
111
 
76
- # @api public
112
+ # Build lax type. Constraints are not applicable to lax types hence unwrapping
77
113
  #
78
- # @see Definition#to_ast
114
+ # @return [Lax]
115
+ # @api public
116
+ def lax
117
+ type.lax
118
+ end
119
+
120
+ # @see Nominal#to_ast
121
+ # @api public
79
122
  def to_ast(meta: true)
80
- [:constrained, [type.to_ast(meta: meta),
81
- rule.to_ast,
82
- meta ? self.meta : EMPTY_HASH]]
123
+ [:constrained, [type.to_ast(meta: meta), rule.to_ast]]
124
+ end
125
+
126
+ # @api private
127
+ def constructor_type
128
+ type.constructor_type
83
129
  end
84
130
 
85
131
  private
86
132
 
87
133
  # @param [Object] response
134
+ #
88
135
  # @return [Boolean]
136
+ #
137
+ # @api private
89
138
  def decorate?(response)
90
- super || response.kind_of?(Constructor)
139
+ super || response.is_a?(Constructor)
91
140
  end
92
141
  end
93
142
  end
@@ -1,18 +1,32 @@
1
- require 'dry/logic/rule_compiler'
2
- require 'dry/logic/predicates'
3
- require 'dry/logic/rule/predicate'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/logic/rule_compiler"
4
+ require "dry/logic/predicates"
5
+ require "dry/logic/rule/predicate"
4
6
 
5
7
  module Dry
8
+ # Helper methods for constraint types
9
+ #
10
+ # @api public
6
11
  module Types
7
12
  # @param [Hash] options
13
+ #
8
14
  # @return [Dry::Logic::Rule]
15
+ #
16
+ # @api public
9
17
  def self.Rule(options)
10
18
  rule_compiler.(
11
- options.map { |key, val| Logic::Rule::Predicate.new(Logic::Predicates[:"#{key}?"]).curry(val).to_ast }
19
+ options.map { |key, val|
20
+ Logic::Rule::Predicate.build(
21
+ Logic::Predicates[:"#{key}?"]
22
+ ).curry(val).to_ast
23
+ }
12
24
  ).reduce(:and)
13
25
  end
14
26
 
15
27
  # @return [Dry::Logic::RuleCompiler]
28
+ #
29
+ # @api private
16
30
  def self.rule_compiler
17
31
  @rule_compiler ||= Logic::RuleCompiler.new(Logic::Predicates)
18
32
  end
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/equalizer"
4
+ require "concurrent/map"
5
+
6
+ module Dry
7
+ module Types
8
+ class Constructor < Nominal
9
+ # Function is used internally by Constructor types
10
+ #
11
+ # @api private
12
+ class Function
13
+ # Wrapper for unsafe coercion functions
14
+ #
15
+ # @api private
16
+ class Safe < Function
17
+ def call(input, &block)
18
+ @fn.(input, &block)
19
+ rescue ::NoMethodError, ::TypeError, ::ArgumentError => e
20
+ CoercionError.handle(e, &block)
21
+ end
22
+ end
23
+
24
+ # Coercion via a method call on a known object
25
+ #
26
+ # @api private
27
+ class MethodCall < Function
28
+ @cache = ::Concurrent::Map.new
29
+
30
+ # Choose or build the base class
31
+ #
32
+ # @return [Function]
33
+ def self.call_class(method, public, safe)
34
+ @cache.fetch_or_store([method, public, safe]) do
35
+ if public
36
+ ::Class.new(PublicCall) do
37
+ include PublicCall.call_interface(method, safe)
38
+
39
+ define_method(:__to_s__) do
40
+ "#<PublicCall for :#{method}>"
41
+ end
42
+ end
43
+ elsif safe
44
+ PrivateCall
45
+ else
46
+ PrivateSafeCall
47
+ end
48
+ end
49
+ end
50
+
51
+ # Coercion with a publicly accessible method call
52
+ #
53
+ # @api private
54
+ class PublicCall < MethodCall
55
+ @interfaces = ::Concurrent::Map.new
56
+
57
+ # Choose or build the interface
58
+ #
59
+ # @return [::Module]
60
+ def self.call_interface(method, safe)
61
+ @interfaces.fetch_or_store([method, safe]) do
62
+ ::Module.new do
63
+ if safe
64
+ module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
65
+ def call(input, &block)
66
+ @target.#{method}(input, &block)
67
+ end
68
+ RUBY
69
+ else
70
+ module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
71
+ def call(input, &block)
72
+ @target.#{method}(input)
73
+ rescue ::NoMethodError, ::TypeError, ::ArgumentError => error
74
+ CoercionError.handle(error, &block)
75
+ end
76
+ RUBY
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ # Coercion via a private method call
84
+ #
85
+ # @api private
86
+ class PrivateCall < MethodCall
87
+ def call(input, &block)
88
+ @target.send(@name, input, &block)
89
+ end
90
+ end
91
+
92
+ # Coercion via an unsafe private method call
93
+ #
94
+ # @api private
95
+ class PrivateSafeCall < PrivateCall
96
+ def call(input, &block)
97
+ @target.send(@name, input)
98
+ rescue ::NoMethodError, ::TypeError, ::ArgumentError => e
99
+ CoercionError.handle(e, &block)
100
+ end
101
+ end
102
+
103
+ # @api private
104
+ #
105
+ # @return [MethodCall]
106
+ def self.[](fn, safe)
107
+ public = fn.receiver.respond_to?(fn.name)
108
+ MethodCall.call_class(fn.name, public, safe).new(fn)
109
+ end
110
+
111
+ attr_reader :target, :name
112
+
113
+ def initialize(fn)
114
+ super
115
+ @target = fn.receiver
116
+ @name = fn.name
117
+ end
118
+
119
+ def to_ast
120
+ [:method, target, name]
121
+ end
122
+ end
123
+
124
+ class Wrapper < Function
125
+ # @return [Object]
126
+ def call(input, type, &block)
127
+ @fn.(input, type, &block)
128
+ rescue ::NoMethodError, ::TypeError, ::ArgumentError => e
129
+ CoercionError.handle(e, &block)
130
+ end
131
+ alias_method :[], :call
132
+
133
+ def arity
134
+ 2
135
+ end
136
+ end
137
+
138
+ # Choose or build specialized invokation code for a callable
139
+ #
140
+ # @param [#call] fn
141
+ # @return [Function]
142
+ def self.[](fn)
143
+ raise ::ArgumentError, "Missing constructor block" if fn.nil?
144
+
145
+ if fn.is_a?(Function)
146
+ fn
147
+ elsif fn.respond_to?(:arity) && fn.arity.equal?(2)
148
+ Wrapper.new(fn)
149
+ elsif fn.is_a?(::Method)
150
+ MethodCall[fn, yields_block?(fn)]
151
+ elsif yields_block?(fn)
152
+ new(fn)
153
+ else
154
+ Safe.new(fn)
155
+ end
156
+ end
157
+
158
+ # @return [Boolean]
159
+ def self.yields_block?(fn)
160
+ *, (last_arg,) =
161
+ if fn.respond_to?(:parameters)
162
+ fn.parameters
163
+ else
164
+ fn.method(:call).parameters
165
+ end
166
+
167
+ last_arg.equal?(:block)
168
+ end
169
+
170
+ include ::Dry::Equalizer(:fn, immutable: true)
171
+
172
+ attr_reader :fn
173
+
174
+ def initialize(fn)
175
+ @fn = fn
176
+ end
177
+
178
+ # @return [Object]
179
+ def call(input, &block)
180
+ @fn.(input, &block)
181
+ end
182
+ alias_method :[], :call
183
+
184
+ # @return [Integer]
185
+ def arity
186
+ 1
187
+ end
188
+
189
+ def wrapper?
190
+ arity.equal?(2)
191
+ end
192
+
193
+ # @return [Array]
194
+ def to_ast
195
+ if fn.is_a?(::Proc)
196
+ [:id, FnContainer.register(fn)]
197
+ else
198
+ [:callable, fn]
199
+ end
200
+ end
201
+
202
+ # @return [Function]
203
+ def >>(other)
204
+ f = Function[other]
205
+ Function[-> x, &b { f.(self.(x, &b), &b) }]
206
+ end
207
+
208
+ # @return [Function]
209
+ def <<(other)
210
+ f = Function[other]
211
+ Function[-> x, &b { self.(f.(x, &b), &b) }]
212
+ end
213
+ end
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module Types
5
+ # @api public
6
+ class Constructor < Nominal
7
+ module Wrapper
8
+ # @return [Object]
9
+ #
10
+ # @api private
11
+ def call_safe(input, &block)
12
+ fn.(input, type, &block)
13
+ end
14
+
15
+ # @return [Object]
16
+ #
17
+ # @api private
18
+ def call_unsafe(input)
19
+ fn.(input, type)
20
+ end
21
+
22
+ # @param [Object] input
23
+ # @param [#call,nil] block
24
+ #
25
+ # @return [Logic::Result, Types::Result]
26
+ # @return [Object] if block given and try fails
27
+ #
28
+ # @api public
29
+ def try(input, &block)
30
+ value = fn.(input, type)
31
+ rescue CoercionError => e
32
+ failure = failure(input, e)
33
+ block_given? ? yield(failure) : failure
34
+ else
35
+ type.try(value, &block)
36
+ end
37
+
38
+ # Define a constructor for the type
39
+ #
40
+ # @param [#call,nil] constructor
41
+ # @param [Hash] options
42
+ # @param [#call,nil] block
43
+ #
44
+ # @return [Constructor]
45
+ #
46
+ # @api public
47
+ define_method(:constructor, Builder.instance_method(:constructor))
48
+ alias_method :append, :constructor
49
+ alias_method :>>, :constructor
50
+
51
+ # Build a new constructor by prepending a block to the coercion function
52
+ #
53
+ # @param [#call, nil] new_fn
54
+ # @param [Hash] options
55
+ # @param [#call, nil] block
56
+ #
57
+ # @return [Constructor]
58
+ #
59
+ # @api public
60
+ def prepend(new_fn = nil, **options, &block)
61
+ prep_fn = Function[new_fn || block]
62
+
63
+ decorated =
64
+ if prep_fn.wrapper?
65
+ type.constructor(prep_fn, **options)
66
+ else
67
+ type.prepend(prep_fn, **options)
68
+ end
69
+
70
+ __new__(decorated)
71
+ end
72
+ alias_method :<<, :prepend
73
+
74
+ # @return [Constructor]
75
+ #
76
+ # @api public
77
+ def lax
78
+ # return self back because wrapping function
79
+ # can handle failed type check
80
+ self
81
+ end
82
+
83
+ private
84
+
85
+ # Replace underlying type
86
+ #
87
+ # @api private
88
+ def __new__(type)
89
+ self.class.new(type, *@__args__.drop(1), **@options)
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end