dry-types 1.4.0 → 1.5.0

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