dry-types 1.4.0 → 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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +78 -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 +6 -9
  18. data/lib/dry/types/constraints.rb +3 -3
  19. data/lib/dry/types/constructor.rb +40 -6
  20. data/lib/dry/types/constructor/function.rb +32 -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 +14 -1
  26. data/lib/dry/types/enum.rb +4 -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 +5 -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 +11 -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 +4 -3
  51. data/lib/dry/types/type.rb +1 -1
  52. data/lib/dry/types/version.rb +1 -1
  53. metadata +9 -22
@@ -4,7 +4,6 @@ module Dry
4
4
  module Types
5
5
  # Common API for building type objects in a convenient way
6
6
  #
7
- # rubocop:disable Naming/MethodName
8
7
  #
9
8
  # @api public
10
9
  module BuilderMethods
@@ -133,7 +132,7 @@ module Dry
133
132
  #
134
133
  # @return [Dry::Types::Contrained]
135
134
  def Interface(*methods)
136
- methods.reduce(Types['nominal.any']) do |type, method|
135
+ methods.reduce(Types["nominal.any"]) do |type, method|
137
136
  type.constrained(respond_to: method)
138
137
  end
139
138
  end
@@ -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,9 @@
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/core/equalizer"
4
+ require "dry/types/decorator"
5
+ require "dry/types/constraints"
6
+ require "dry/types/constrained/coercible"
6
7
 
7
8
  module Dry
8
9
  module Types
@@ -29,11 +30,9 @@ module Dry
29
30
  @rule = options.fetch(:rule)
30
31
  end
31
32
 
32
- # @api private
33
- #
34
33
  # @return [Object]
35
34
  #
36
- # @api public
35
+ # @api private
37
36
  def call_unsafe(input)
38
37
  result = rule.(input)
39
38
 
@@ -44,11 +43,9 @@ module Dry
44
43
  end
45
44
  end
46
45
 
47
- # @api private
48
- #
49
46
  # @return [Object]
50
47
  #
51
- # @api public
48
+ # @api private
52
49
  def call_safe(input, &block)
53
50
  if rule[input]
54
51
  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,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/types/fn_container'
4
- require 'dry/types/constructor/function'
3
+ require "dry/core/equalizer"
4
+ require "dry/types/fn_container"
5
+ require "dry/types/constructor/function"
6
+ require "dry/types/constructor/wrapper"
5
7
 
6
8
  module Dry
7
9
  module Types
@@ -30,6 +32,32 @@ module Dry
30
32
  super(type, **options, fn: Function[options.fetch(:fn, block)])
31
33
  end
32
34
 
35
+ # @param [Builder, Object] input
36
+ # @param [Hash] options
37
+ # @param [#call, nil] block
38
+ #
39
+ # @api public
40
+ def self.[](type, fn:, **options)
41
+ function = Function[fn]
42
+
43
+ if function.wrapper?
44
+ wrapper_type.new(type, fn: function, **options)
45
+ else
46
+ new(type, fn: function, **options)
47
+ end
48
+ end
49
+
50
+ # @api private
51
+ def self.wrapper_type
52
+ @wrapper_type ||= begin
53
+ if self < Wrapper
54
+ self
55
+ else
56
+ const_set(:Wrapping, ::Class.new(self).include(Wrapper))
57
+ end
58
+ end
59
+ end
60
+
33
61
  # Instantiate a new constructor type instance
34
62
  #
35
63
  # @param [Type] type
@@ -85,7 +113,13 @@ module Dry
85
113
  #
86
114
  # @api public
87
115
  def constructor(new_fn = nil, **options, &block)
88
- with(**options, fn: fn >> (new_fn || block))
116
+ next_fn = Function[new_fn || block]
117
+
118
+ if next_fn.wrapper?
119
+ self.class.wrapper_type.new(with(**options), fn: next_fn)
120
+ else
121
+ with(**options, fn: fn >> next_fn)
122
+ end
89
123
  end
90
124
  alias_method :append, :constructor
91
125
  alias_method :>>, :constructor
@@ -123,7 +157,7 @@ module Dry
123
157
  # @return [Lax]
124
158
  # @api public
125
159
  def lax
126
- Lax.new(Constructor.new(type.lax, **options))
160
+ Lax.new(constructor_type[type.lax, **options])
127
161
  end
128
162
 
129
163
  # Wrap the type with a proc
@@ -157,8 +191,8 @@ module Dry
157
191
  if type.respond_to?(method)
158
192
  response = type.public_send(method, *args, &block)
159
193
 
160
- if response.is_a?(Type) && type.class == response.class
161
- response.constructor_type.new(response, **options)
194
+ if response.is_a?(Type) && type.class.equal?(response.class)
195
+ response.constructor_type[response, **options]
162
196
  else
163
197
  response
164
198
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'concurrent/map'
3
+ require "dry/core/equalizer"
4
+ require "concurrent/map"
4
5
 
5
6
  module Dry
6
7
  module Types
@@ -34,6 +35,10 @@ module Dry
34
35
  if public
35
36
  ::Class.new(PublicCall) do
36
37
  include PublicCall.call_interface(method, safe)
38
+
39
+ define_method(:__to_s__) do
40
+ "#<PublicCall for :#{method}>"
41
+ end
37
42
  end
38
43
  elsif safe
39
44
  PrivateCall
@@ -116,15 +121,31 @@ module Dry
116
121
  end
117
122
  end
118
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
+
119
138
  # Choose or build specialized invokation code for a callable
120
139
  #
121
140
  # @param [#call] fn
122
141
  # @return [Function]
123
142
  def self.[](fn)
124
- raise ::ArgumentError, 'Missing constructor block' if fn.nil?
143
+ raise ::ArgumentError, "Missing constructor block" if fn.nil?
125
144
 
126
145
  if fn.is_a?(Function)
127
146
  fn
147
+ elsif fn.respond_to?(:arity) && fn.arity.equal?(2)
148
+ Wrapper.new(fn)
128
149
  elsif fn.is_a?(::Method)
129
150
  MethodCall[fn, yields_block?(fn)]
130
151
  elsif yields_block?(fn)
@@ -160,6 +181,15 @@ module Dry
160
181
  end
161
182
  alias_method :[], :call
162
183
 
184
+ # @return [Integer]
185
+ def arity
186
+ 1
187
+ end
188
+
189
+ def wrapper?
190
+ arity.equal?(2)
191
+ end
192
+
163
193
  # @return [Array]
164
194
  def to_ast
165
195
  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"