dry-types 0.13.2 → 1.5.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 (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