dry-types 1.4.0 → 1.5.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +68 -0
  3. data/LICENSE +1 -1
  4. data/README.md +1 -1
  5. data/dry-types.gemspec +2 -3
  6. data/lib/dry-types.rb +1 -1
  7. data/lib/dry/types.rb +55 -31
  8. data/lib/dry/types/any.rb +2 -2
  9. data/lib/dry/types/array.rb +2 -2
  10. data/lib/dry/types/array/constructor.rb +1 -1
  11. data/lib/dry/types/array/member.rb +1 -1
  12. data/lib/dry/types/builder.rb +66 -18
  13. data/lib/dry/types/builder_methods.rb +1 -2
  14. data/lib/dry/types/coercions/json.rb +5 -5
  15. data/lib/dry/types/coercions/params.rb +3 -3
  16. data/lib/dry/types/compiler.rb +10 -10
  17. data/lib/dry/types/constrained.rb +5 -9
  18. data/lib/dry/types/constraints.rb +3 -3
  19. data/lib/dry/types/constructor.rb +39 -6
  20. data/lib/dry/types/constructor/function.rb +31 -2
  21. data/lib/dry/types/constructor/wrapper.rb +94 -0
  22. data/lib/dry/types/container.rb +1 -1
  23. data/lib/dry/types/core.rb +12 -12
  24. data/lib/dry/types/decorator.rb +2 -2
  25. data/lib/dry/types/default.rb +13 -1
  26. data/lib/dry/types/enum.rb +3 -3
  27. data/lib/dry/types/errors.rb +1 -1
  28. data/lib/dry/types/extensions.rb +2 -2
  29. data/lib/dry/types/extensions/maybe.rb +4 -4
  30. data/lib/dry/types/extensions/monads.rb +1 -1
  31. data/lib/dry/types/fn_container.rb +1 -1
  32. data/lib/dry/types/hash.rb +9 -15
  33. data/lib/dry/types/hash/constructor.rb +1 -1
  34. data/lib/dry/types/inflector.rb +1 -1
  35. data/lib/dry/types/json.rb +15 -15
  36. data/lib/dry/types/lax.rb +2 -2
  37. data/lib/dry/types/map.rb +2 -2
  38. data/lib/dry/types/meta.rb +1 -1
  39. data/lib/dry/types/module.rb +6 -6
  40. data/lib/dry/types/nominal.rb +10 -11
  41. data/lib/dry/types/params.rb +30 -28
  42. data/lib/dry/types/predicate_inferrer.rb +51 -11
  43. data/lib/dry/types/predicate_registry.rb +1 -1
  44. data/lib/dry/types/primitive_inferrer.rb +1 -1
  45. data/lib/dry/types/printer.rb +25 -25
  46. data/lib/dry/types/result.rb +1 -1
  47. data/lib/dry/types/schema.rb +5 -13
  48. data/lib/dry/types/schema/key.rb +4 -4
  49. data/lib/dry/types/spec/types.rb +57 -45
  50. data/lib/dry/types/sum.rb +3 -3
  51. data/lib/dry/types/type.rb +1 -1
  52. data/lib/dry/types/version.rb +1 -1
  53. metadata +9 -22
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'date'
4
- require 'bigdecimal'
5
- require 'bigdecimal/util'
6
- require 'time'
3
+ require "date"
4
+ require "bigdecimal"
5
+ require "bigdecimal/util"
6
+ require "time"
7
7
 
8
8
  module Dry
9
9
  module Types
@@ -38,7 +38,7 @@ module Dry
38
38
  # @raise CoercionError
39
39
  #
40
40
  # @api public
41
- def self.to_decimal(input, &block)
41
+ def self.to_decimal(input, &_block)
42
42
  if input.is_a?(::Float)
43
43
  input.to_d
44
44
  else
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bigdecimal'
4
- require 'bigdecimal/util'
3
+ require "bigdecimal"
4
+ require "bigdecimal/util"
5
5
 
6
6
  module Dry
7
7
  module Types
@@ -112,7 +112,7 @@ module Dry
112
112
  # @raise CoercionError
113
113
  #
114
114
  # @api public
115
- def self.to_decimal(input, &block)
115
+ def self.to_decimal(input, &_block)
116
116
  to_float(input) do
117
117
  if block_given?
118
118
  return yield
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/core/deprecations'
3
+ require "dry/core/deprecations"
4
4
 
5
5
  module Dry
6
6
  module Types
@@ -63,37 +63,37 @@ module Dry
63
63
  def visit_array(node)
64
64
  member, meta = node
65
65
  member = member.is_a?(Class) ? member : visit(member)
66
- registry['nominal.array'].of(member).meta(meta)
66
+ registry["nominal.array"].of(member).meta(meta)
67
67
  end
68
68
 
69
69
  def visit_hash(node)
70
70
  opts, meta = node
71
- registry['nominal.hash'].with(**opts, meta: meta)
71
+ registry["nominal.hash"].with(**opts, meta: meta)
72
72
  end
73
73
 
74
74
  def visit_schema(node)
75
75
  keys, options, meta = node
76
- registry['nominal.hash'].schema(keys.map { |key| visit(key) }).with(**options, meta: meta)
76
+ registry["nominal.hash"].schema(keys.map { |key| visit(key) }).with(**options, meta: meta)
77
77
  end
78
78
 
79
79
  def visit_json_hash(node)
80
80
  keys, meta = node
81
- registry['json.hash'].schema(keys.map { |key| visit(key) }, meta)
81
+ registry["json.hash"].schema(keys.map { |key| visit(key) }, meta)
82
82
  end
83
83
 
84
84
  def visit_json_array(node)
85
85
  member, meta = node
86
- registry['json.array'].of(visit(member)).meta(meta)
86
+ registry["json.array"].of(visit(member)).meta(meta)
87
87
  end
88
88
 
89
89
  def visit_params_hash(node)
90
90
  keys, meta = node
91
- registry['params.hash'].schema(keys.map { |key| visit(key) }, meta)
91
+ registry["params.hash"].schema(keys.map { |key| visit(key) }, meta)
92
92
  end
93
93
 
94
94
  def visit_params_array(node)
95
95
  member, meta = node
96
- registry['params.array'].of(visit(member)).meta(meta)
96
+ registry["params.array"].of(visit(member)).meta(meta)
97
97
  end
98
98
 
99
99
  def visit_key(node)
@@ -108,11 +108,11 @@ module Dry
108
108
 
109
109
  def visit_map(node)
110
110
  key_type, value_type, meta = node
111
- registry['nominal.hash'].map(visit(key_type), visit(value_type)).meta(meta)
111
+ registry["nominal.hash"].map(visit(key_type), visit(value_type)).meta(meta)
112
112
  end
113
113
 
114
114
  def visit_any(meta)
115
- registry['any'].meta(meta)
115
+ registry["any"].meta(meta)
116
116
  end
117
117
 
118
118
  def compile_fn(fn)
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/types/decorator'
4
- require 'dry/types/constraints'
5
- require 'dry/types/constrained/coercible'
3
+ require "dry/types/decorator"
4
+ require "dry/types/constraints"
5
+ require "dry/types/constrained/coercible"
6
6
 
7
7
  module Dry
8
8
  module Types
@@ -29,11 +29,9 @@ module Dry
29
29
  @rule = options.fetch(:rule)
30
30
  end
31
31
 
32
- # @api private
33
- #
34
32
  # @return [Object]
35
33
  #
36
- # @api public
34
+ # @api private
37
35
  def call_unsafe(input)
38
36
  result = rule.(input)
39
37
 
@@ -44,11 +42,9 @@ module Dry
44
42
  end
45
43
  end
46
44
 
47
- # @api private
48
- #
49
45
  # @return [Object]
50
46
  #
51
- # @api public
47
+ # @api private
52
48
  def call_safe(input, &block)
53
49
  if rule[input]
54
50
  type.call_safe(input, &block)
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/logic/rule_compiler'
4
- require 'dry/logic/predicates'
5
- require 'dry/logic/rule/predicate'
3
+ require "dry/logic/rule_compiler"
4
+ require "dry/logic/predicates"
5
+ require "dry/logic/rule/predicate"
6
6
 
7
7
  module Dry
8
8
  # Helper methods for constraint types
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/types/fn_container'
4
- require 'dry/types/constructor/function'
3
+ require "dry/types/fn_container"
4
+ require "dry/types/constructor/function"
5
+ require "dry/types/constructor/wrapper"
5
6
 
6
7
  module Dry
7
8
  module Types
@@ -30,6 +31,32 @@ module Dry
30
31
  super(type, **options, fn: Function[options.fetch(:fn, block)])
31
32
  end
32
33
 
34
+ # @param [Builder, Object] input
35
+ # @param [Hash] options
36
+ # @param [#call, nil] block
37
+ #
38
+ # @api public
39
+ def self.[](type, fn:, **options)
40
+ function = Function[fn]
41
+
42
+ if function.wrapper?
43
+ wrapper_type.new(type, fn: function, **options)
44
+ else
45
+ new(type, fn: function, **options)
46
+ end
47
+ end
48
+
49
+ # @api private
50
+ def self.wrapper_type
51
+ @wrapper_type ||= begin
52
+ if self < Wrapper
53
+ self
54
+ else
55
+ const_set(:Wrapping, ::Class.new(self).include(Wrapper))
56
+ end
57
+ end
58
+ end
59
+
33
60
  # Instantiate a new constructor type instance
34
61
  #
35
62
  # @param [Type] type
@@ -85,7 +112,13 @@ module Dry
85
112
  #
86
113
  # @api public
87
114
  def constructor(new_fn = nil, **options, &block)
88
- with(**options, fn: fn >> (new_fn || block))
115
+ next_fn = Function[new_fn || block]
116
+
117
+ if next_fn.wrapper?
118
+ self.class.wrapper_type.new(with(**options), fn: next_fn)
119
+ else
120
+ with(**options, fn: fn >> next_fn)
121
+ end
89
122
  end
90
123
  alias_method :append, :constructor
91
124
  alias_method :>>, :constructor
@@ -123,7 +156,7 @@ module Dry
123
156
  # @return [Lax]
124
157
  # @api public
125
158
  def lax
126
- Lax.new(Constructor.new(type.lax, **options))
159
+ Lax.new(constructor_type[type.lax, **options])
127
160
  end
128
161
 
129
162
  # Wrap the type with a proc
@@ -157,8 +190,8 @@ module Dry
157
190
  if type.respond_to?(method)
158
191
  response = type.public_send(method, *args, &block)
159
192
 
160
- if response.is_a?(Type) && type.class == response.class
161
- response.constructor_type.new(response, **options)
193
+ if response.is_a?(Type) && type.class.equal?(response.class)
194
+ response.constructor_type[response, **options]
162
195
  else
163
196
  response
164
197
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'concurrent/map'
3
+ require "concurrent/map"
4
4
 
5
5
  module Dry
6
6
  module Types
@@ -34,6 +34,10 @@ module Dry
34
34
  if public
35
35
  ::Class.new(PublicCall) do
36
36
  include PublicCall.call_interface(method, safe)
37
+
38
+ define_method(:__to_s__) do
39
+ "#<PublicCall for :#{method}>"
40
+ end
37
41
  end
38
42
  elsif safe
39
43
  PrivateCall
@@ -116,15 +120,31 @@ module Dry
116
120
  end
117
121
  end
118
122
 
123
+ class Wrapper < Function
124
+ # @return [Object]
125
+ def call(input, type, &block)
126
+ @fn.(input, type, &block)
127
+ rescue ::NoMethodError, ::TypeError, ::ArgumentError => e
128
+ CoercionError.handle(e, &block)
129
+ end
130
+ alias_method :[], :call
131
+
132
+ def arity
133
+ 2
134
+ end
135
+ end
136
+
119
137
  # Choose or build specialized invokation code for a callable
120
138
  #
121
139
  # @param [#call] fn
122
140
  # @return [Function]
123
141
  def self.[](fn)
124
- raise ::ArgumentError, 'Missing constructor block' if fn.nil?
142
+ raise ::ArgumentError, "Missing constructor block" if fn.nil?
125
143
 
126
144
  if fn.is_a?(Function)
127
145
  fn
146
+ elsif fn.respond_to?(:arity) && fn.arity.equal?(2)
147
+ Wrapper.new(fn)
128
148
  elsif fn.is_a?(::Method)
129
149
  MethodCall[fn, yields_block?(fn)]
130
150
  elsif yields_block?(fn)
@@ -160,6 +180,15 @@ module Dry
160
180
  end
161
181
  alias_method :[], :call
162
182
 
183
+ # @return [Integer]
184
+ def arity
185
+ 1
186
+ end
187
+
188
+ def wrapper?
189
+ arity.equal?(2)
190
+ end
191
+
163
192
  # @return [Array]
164
193
  def to_ast
165
194
  if fn.is_a?(::Proc)
@@ -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,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/container'
3
+ require "dry/container"
4
4
 
5
5
  module Dry
6
6
  module Types
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/types/any'
3
+ require "dry/types/any"
4
4
 
5
5
  module Dry
6
6
  module Types
@@ -83,17 +83,17 @@ module Dry
83
83
  end
84
84
 
85
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)
90
-
91
- register('any', Any)
92
- register('nominal.any', Any)
93
- register('strict.any', Any)
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)
90
+
91
+ register("any", Any)
92
+ register("nominal.any", Any)
93
+ register("strict.any", Any)
94
94
  end
95
95
  end
96
96
 
97
- require 'dry/types/coercions'
98
- require 'dry/types/params'
99
- require 'dry/types/json'
97
+ require "dry/types/coercions"
98
+ require "dry/types/params"
99
+ require "dry/types/json"
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/types/options'
3
+ require "dry/types/options"
4
4
 
5
5
  module Dry
6
6
  module Types
@@ -100,7 +100,7 @@ module Dry
100
100
  #
101
101
  # @api private
102
102
  def __new__(type)
103
- self.class.new(type, *@__args__[1..-1], **@options)
103
+ self.class.new(type, *@__args__.drop(1), **@options)
104
104
  end
105
105
  end
106
106
  end