dry-types 0.13.2 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +763 -233
  3. data/LICENSE +17 -17
  4. data/README.md +15 -13
  5. data/dry-types.gemspec +28 -28
  6. data/lib/dry-types.rb +3 -1
  7. data/lib/dry/types.rb +156 -76
  8. data/lib/dry/types/any.rb +32 -12
  9. data/lib/dry/types/array.rb +19 -6
  10. data/lib/dry/types/array/constructor.rb +32 -0
  11. data/lib/dry/types/array/member.rb +75 -16
  12. data/lib/dry/types/builder.rb +131 -15
  13. data/lib/dry/types/builder_methods.rb +49 -20
  14. data/lib/dry/types/coercions.rb +76 -22
  15. data/lib/dry/types/coercions/json.rb +43 -7
  16. data/lib/dry/types/coercions/params.rb +118 -31
  17. data/lib/dry/types/compat.rb +0 -2
  18. data/lib/dry/types/compiler.rb +56 -41
  19. data/lib/dry/types/constrained.rb +81 -32
  20. data/lib/dry/types/constrained/coercible.rb +36 -6
  21. data/lib/dry/types/constraints.rb +18 -4
  22. data/lib/dry/types/constructor.rb +127 -54
  23. data/lib/dry/types/constructor/function.rb +216 -0
  24. data/lib/dry/types/constructor/wrapper.rb +94 -0
  25. data/lib/dry/types/container.rb +7 -0
  26. data/lib/dry/types/core.rb +54 -21
  27. data/lib/dry/types/decorator.rb +38 -17
  28. data/lib/dry/types/default.rb +61 -16
  29. data/lib/dry/types/enum.rb +43 -20
  30. data/lib/dry/types/errors.rb +75 -9
  31. data/lib/dry/types/extensions.rb +7 -1
  32. data/lib/dry/types/extensions/maybe.rb +74 -16
  33. data/lib/dry/types/extensions/monads.rb +29 -0
  34. data/lib/dry/types/fn_container.rb +6 -1
  35. data/lib/dry/types/hash.rb +86 -67
  36. data/lib/dry/types/hash/constructor.rb +33 -0
  37. data/lib/dry/types/inflector.rb +3 -1
  38. data/lib/dry/types/json.rb +18 -16
  39. data/lib/dry/types/lax.rb +75 -0
  40. data/lib/dry/types/map.rb +76 -33
  41. data/lib/dry/types/meta.rb +51 -0
  42. data/lib/dry/types/module.rb +120 -0
  43. data/lib/dry/types/nominal.rb +210 -0
  44. data/lib/dry/types/options.rb +13 -26
  45. data/lib/dry/types/params.rb +39 -25
  46. data/lib/dry/types/predicate_inferrer.rb +238 -0
  47. data/lib/dry/types/predicate_registry.rb +34 -0
  48. data/lib/dry/types/primitive_inferrer.rb +97 -0
  49. data/lib/dry/types/printable.rb +16 -0
  50. data/lib/dry/types/printer.rb +315 -0
  51. data/lib/dry/types/result.rb +29 -3
  52. data/lib/dry/types/schema.rb +408 -0
  53. data/lib/dry/types/schema/key.rb +156 -0
  54. data/lib/dry/types/spec/types.rb +103 -33
  55. data/lib/dry/types/sum.rb +84 -35
  56. data/lib/dry/types/type.rb +49 -0
  57. data/lib/dry/types/version.rb +3 -1
  58. metadata +68 -79
  59. data/.gitignore +0 -10
  60. data/.rspec +0 -2
  61. data/.travis.yml +0 -29
  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
@@ -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
@@ -1,5 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/container"
4
+
1
5
  module Dry
2
6
  module Types
7
+ # Internal container for the built-in types
8
+ #
9
+ # @api private
3
10
  class Container
4
11
  include Dry::Container::Mixin
5
12
  end
@@ -1,8 +1,11 @@
1
- require 'dry/types/any'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/types/any"
2
4
 
3
5
  module Dry
4
6
  module Types
5
- COERCIBLE = {
7
+ # Primitives with {Kernel} coercion methods
8
+ KERNEL_COERCIBLE = {
6
9
  string: String,
7
10
  integer: Integer,
8
11
  float: Float,
@@ -11,9 +14,19 @@ module Dry
11
14
  hash: ::Hash
12
15
  }.freeze
13
16
 
17
+ # Primitives with coercions through by convention `to_*` methods
18
+ METHOD_COERCIBLE = {
19
+ symbol: Symbol
20
+ }.freeze
21
+
22
+ # By convention methods to coerce {METHOD_COERCIBLE} primitives
23
+ METHOD_COERCIBLE_METHODS = {
24
+ symbol: :to_sym
25
+ }.freeze
26
+
27
+ # Primitives that are non-coercible
14
28
  NON_COERCIBLE = {
15
29
  nil: NilClass,
16
- symbol: Symbol,
17
30
  class: Class,
18
31
  true: TrueClass,
19
32
  false: FalseClass,
@@ -23,44 +36,64 @@ module Dry
23
36
  range: Range
24
37
  }.freeze
25
38
 
26
- ALL_PRIMITIVES = COERCIBLE.merge(NON_COERCIBLE).freeze
39
+ # All built-in primitives
40
+ ALL_PRIMITIVES = [KERNEL_COERCIBLE, METHOD_COERCIBLE, NON_COERCIBLE].reduce(&:merge).freeze
41
+
42
+ # All coercible types
43
+ COERCIBLE = KERNEL_COERCIBLE.merge(METHOD_COERCIBLE).freeze
27
44
 
45
+ # All built-in primitives except {NilClass}
28
46
  NON_NIL = ALL_PRIMITIVES.reject { |name, _| name == :nil }.freeze
29
47
 
30
- # Register built-in types that are non-coercible through kernel methods
48
+ # Register generic types for {ALL_PRIMITIVES}
31
49
  ALL_PRIMITIVES.each do |name, primitive|
32
- register(name.to_s, Definition[primitive].new(primitive))
50
+ type = Nominal[primitive].new(primitive)
51
+ register("nominal.#{name}", type)
33
52
  end
34
53
 
35
- # Register strict built-in types that are non-coercible through kernel methods
54
+ # Register strict types for {ALL_PRIMITIVES}
36
55
  ALL_PRIMITIVES.each do |name, primitive|
37
- register("strict.#{name}", self[name.to_s].constrained(type: primitive))
56
+ type = self["nominal.#{name}"].constrained(type: primitive)
57
+ register(name.to_s, type)
58
+ register("strict.#{name}", type)
59
+ end
60
+
61
+ # Register {KERNEL_COERCIBLE} types
62
+ KERNEL_COERCIBLE.each do |name, primitive|
63
+ register("coercible.#{name}", self["nominal.#{name}"].constructor(Kernel.method(primitive.name)))
38
64
  end
39
65
 
40
- # Register built-in primitive types with kernel coercion methods
41
- COERCIBLE.each do |name, primitive|
42
- register("coercible.#{name}", self[name.to_s].constructor(Kernel.method(primitive.name)))
66
+ # Register {METHOD_COERCIBLE} types
67
+ METHOD_COERCIBLE.each_key do |name|
68
+ register(
69
+ "coercible.#{name}", self["nominal.#{name}"].constructor(&METHOD_COERCIBLE_METHODS[name])
70
+ )
43
71
  end
44
72
 
45
- # Register non-coercible optional types
73
+ # Register optional strict {NON_NIL} types
46
74
  NON_NIL.each_key do |name|
47
- register("optional.strict.#{name}", self["strict.#{name}"].optional)
75
+ type = self[name.to_s].optional
76
+ register("optional.strict.#{name}", type)
77
+ register("optional.#{name}", type)
48
78
  end
49
79
 
50
- # Register coercible optional types
80
+ # Register optional {COERCIBLE} types
51
81
  COERCIBLE.each_key do |name|
52
82
  register("optional.coercible.#{name}", self["coercible.#{name}"].optional)
53
83
  end
54
84
 
55
- # Register :bool since it's common and not a built-in Ruby type :(
56
- register("bool", self["true"] | self["false"])
57
- register("strict.bool", self["strict.true"] | self["strict.false"])
85
+ # Register `:bool` since it's common and not a built-in Ruby type :(
86
+ register("nominal.bool", self["nominal.true"] | self["nominal.false"])
87
+ bool = self["strict.true"] | self["strict.false"]
88
+ register("strict.bool", bool)
89
+ register("bool", bool)
58
90
 
59
91
  register("any", Any)
60
- register("object", self['any'])
92
+ register("nominal.any", Any)
93
+ register("strict.any", Any)
61
94
  end
62
95
  end
63
96
 
64
- require 'dry/types/coercions'
65
- require 'dry/types/params'
66
- require 'dry/types/json'
97
+ require "dry/types/coercions"
98
+ require "dry/types/params"
99
+ require "dry/types/json"
@@ -1,7 +1,12 @@
1
- require 'dry/types/options'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/types/options"
2
4
 
3
5
  module Dry
4
6
  module Types
7
+ # Common API for types
8
+ #
9
+ # @api public
5
10
  module Decorator
6
11
  include Options
7
12
 
@@ -9,63 +14,76 @@ module Dry
9
14
  attr_reader :type
10
15
 
11
16
  # @param [Type] type
12
- def initialize(type, *)
17
+ def initialize(type, *, **)
13
18
  super
14
19
  @type = type
15
20
  end
16
21
 
17
22
  # @param [Object] input
18
23
  # @param [#call, nil] block
24
+ #
19
25
  # @return [Result,Logic::Result]
20
26
  # @return [Object] if block given and try fails
27
+ #
28
+ # @api public
21
29
  def try(input, &block)
22
30
  type.try(input, &block)
23
31
  end
24
32
 
25
- # @param [Object] value
26
- # @return [Boolean]
27
- def valid?(value)
28
- type.valid?(value)
29
- end
30
- alias_method :===, :valid?
31
-
32
33
  # @return [Boolean]
34
+ #
35
+ # @api public
33
36
  def default?
34
37
  type.default?
35
38
  end
36
39
 
37
40
  # @return [Boolean]
41
+ #
42
+ # @api public
38
43
  def constrained?
39
44
  type.constrained?
40
45
  end
41
46
 
42
- # @return [Sum]
43
- def optional
44
- Types['strict.nil'] | self
45
- end
46
-
47
47
  # @param [Symbol] meth
48
48
  # @param [Boolean] include_private
49
+ #
49
50
  # @return [Boolean]
51
+ #
52
+ # @api public
50
53
  def respond_to_missing?(meth, include_private = false)
51
54
  super || type.respond_to?(meth)
52
55
  end
53
56
 
57
+ # Wrap the type with a proc
58
+ #
59
+ # @return [Proc]
60
+ #
61
+ # @api public
62
+ def to_proc
63
+ proc { |value| self.(value) }
64
+ end
65
+
54
66
  private
55
67
 
56
68
  # @param [Object] response
69
+ #
57
70
  # @return [Boolean]
71
+ #
72
+ # @api private
58
73
  def decorate?(response)
59
- response.kind_of?(type.class)
74
+ response.is_a?(type.class)
60
75
  end
61
76
 
62
77
  # Delegates missing methods to {#type}
78
+ #
63
79
  # @param [Symbol] meth
64
80
  # @param [Array] args
65
81
  # @param [#call, nil] block
82
+ #
83
+ # @api private
66
84
  def method_missing(meth, *args, &block)
67
85
  if type.respond_to?(meth)
68
- response = type.__send__(meth, *args, &block)
86
+ response = type.public_send(meth, *args, &block)
69
87
 
70
88
  if decorate?(response)
71
89
  __new__(response)
@@ -76,10 +94,13 @@ module Dry
76
94
  super
77
95
  end
78
96
  end
97
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
79
98
 
80
99
  # Replace underlying type
100
+ #
101
+ # @api private
81
102
  def __new__(type)
82
- self.class.new(type, options)
103
+ self.class.new(type, *@__args__.drop(1), **@options)
83
104
  end
84
105
  end
85
106
  end