dry-types 1.2.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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +405 -225
  3. data/LICENSE +1 -1
  4. data/README.md +14 -12
  5. data/dry-types.gemspec +26 -31
  6. data/lib/dry-types.rb +1 -1
  7. data/lib/dry/types.rb +55 -40
  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 +70 -18
  13. data/lib/dry/types/builder_methods.rb +1 -2
  14. data/lib/dry/types/coercions.rb +0 -17
  15. data/lib/dry/types/coercions/json.rb +22 -5
  16. data/lib/dry/types/coercions/params.rb +20 -3
  17. data/lib/dry/types/compiler.rb +10 -10
  18. data/lib/dry/types/constrained.rb +6 -9
  19. data/lib/dry/types/constraints.rb +3 -3
  20. data/lib/dry/types/constructor.rb +40 -6
  21. data/lib/dry/types/constructor/function.rb +48 -32
  22. data/lib/dry/types/constructor/wrapper.rb +94 -0
  23. data/lib/dry/types/container.rb +1 -1
  24. data/lib/dry/types/core.rb +15 -13
  25. data/lib/dry/types/decorator.rb +2 -9
  26. data/lib/dry/types/default.rb +14 -1
  27. data/lib/dry/types/enum.rb +4 -3
  28. data/lib/dry/types/errors.rb +1 -1
  29. data/lib/dry/types/extensions.rb +2 -2
  30. data/lib/dry/types/extensions/maybe.rb +18 -17
  31. data/lib/dry/types/extensions/monads.rb +1 -1
  32. data/lib/dry/types/fn_container.rb +1 -1
  33. data/lib/dry/types/hash.rb +9 -15
  34. data/lib/dry/types/hash/constructor.rb +1 -1
  35. data/lib/dry/types/inflector.rb +1 -1
  36. data/lib/dry/types/json.rb +15 -15
  37. data/lib/dry/types/lax.rb +3 -6
  38. data/lib/dry/types/map.rb +2 -2
  39. data/lib/dry/types/meta.rb +3 -3
  40. data/lib/dry/types/module.rb +6 -6
  41. data/lib/dry/types/nominal.rb +11 -11
  42. data/lib/dry/types/params.rb +31 -28
  43. data/lib/dry/types/predicate_inferrer.rb +52 -11
  44. data/lib/dry/types/predicate_registry.rb +1 -1
  45. data/lib/dry/types/primitive_inferrer.rb +1 -1
  46. data/lib/dry/types/printer.rb +25 -25
  47. data/lib/dry/types/result.rb +3 -3
  48. data/lib/dry/types/schema.rb +26 -13
  49. data/lib/dry/types/schema/key.rb +15 -6
  50. data/lib/dry/types/spec/types.rb +65 -42
  51. data/lib/dry/types/sum.rb +6 -5
  52. data/lib/dry/types/type.rb +1 -1
  53. data/lib/dry/types/version.rb +1 -1
  54. metadata +27 -78
  55. data/.codeclimate.yml +0 -12
  56. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +0 -10
  57. data/.github/ISSUE_TEMPLATE/---bug-report.md +0 -30
  58. data/.github/ISSUE_TEMPLATE/---feature-request.md +0 -18
  59. data/.github/workflows/custom_ci.yml +0 -76
  60. data/.github/workflows/docsite.yml +0 -34
  61. data/.github/workflows/sync_configs.yml +0 -34
  62. data/.gitignore +0 -11
  63. data/.rspec +0 -4
  64. data/.rubocop.yml +0 -92
  65. data/.yardopts +0 -9
  66. data/CODE_OF_CONDUCT.md +0 -13
  67. data/CONTRIBUTING.md +0 -29
  68. data/Gemfile +0 -34
  69. data/Rakefile +0 -22
  70. data/benchmarks/hash_schemas.rb +0 -55
  71. data/benchmarks/lax_schema.rb +0 -15
  72. data/benchmarks/profile_invalid_input.rb +0 -15
  73. data/benchmarks/profile_lax_schema_valid.rb +0 -16
  74. data/benchmarks/profile_valid_input.rb +0 -15
  75. data/benchmarks/schema_valid_vs_invalid.rb +0 -21
  76. data/benchmarks/setup.rb +0 -17
  77. data/docsite/source/array-with-member.html.md +0 -13
  78. data/docsite/source/built-in-types.html.md +0 -116
  79. data/docsite/source/constraints.html.md +0 -31
  80. data/docsite/source/custom-types.html.md +0 -93
  81. data/docsite/source/default-values.html.md +0 -91
  82. data/docsite/source/enum.html.md +0 -69
  83. data/docsite/source/extensions.html.md +0 -15
  84. data/docsite/source/extensions/maybe.html.md +0 -57
  85. data/docsite/source/extensions/monads.html.md +0 -61
  86. data/docsite/source/getting-started.html.md +0 -57
  87. data/docsite/source/hash-schemas.html.md +0 -169
  88. data/docsite/source/index.html.md +0 -156
  89. data/docsite/source/map.html.md +0 -17
  90. data/docsite/source/optional-values.html.md +0 -35
  91. data/docsite/source/sum.html.md +0 -21
@@ -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
@@ -8,23 +8,6 @@ module Dry
8
8
  module Coercions
9
9
  include Dry::Core::Constants
10
10
 
11
- # @param [String, Object] input
12
- #
13
- # @return [nil] if the input is an empty string or nil
14
- #
15
- # @raise CoercionError
16
- #
17
- # @api public
18
- def to_nil(input, &_block)
19
- if input.nil? || empty_str?(input)
20
- nil
21
- elsif block_given?
22
- yield
23
- else
24
- raise CoercionError, "#{input.inspect} is not nil"
25
- end
26
- end
27
-
28
11
  # @param [#to_str, Object] input
29
12
  #
30
13
  # @return [Date, Object]
@@ -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
@@ -14,6 +14,23 @@ module Dry
14
14
  module JSON
15
15
  extend Coercions
16
16
 
17
+ # @param [Object] input
18
+ #
19
+ # @return [nil] if the input is nil
20
+ #
21
+ # @raise CoercionError
22
+ #
23
+ # @api public
24
+ def self.to_nil(input, &_block)
25
+ if input.nil?
26
+ nil
27
+ elsif block_given?
28
+ yield
29
+ else
30
+ raise CoercionError, "#{input.inspect} is not nil"
31
+ end
32
+ end
33
+
17
34
  # @param [#to_d, Object] input
18
35
  #
19
36
  # @return [BigDecimal,nil]
@@ -21,7 +38,7 @@ module Dry
21
38
  # @raise CoercionError
22
39
  #
23
40
  # @api public
24
- def self.to_decimal(input, &block)
41
+ def self.to_decimal(input, &_block)
25
42
  if input.is_a?(::Float)
26
43
  input.to_d
27
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
@@ -18,6 +18,23 @@ module Dry
18
18
 
19
19
  extend Coercions
20
20
 
21
+ # @param [Object] input
22
+ #
23
+ # @return [nil] if the input is an empty string or nil
24
+ #
25
+ # @raise CoercionError
26
+ #
27
+ # @api public
28
+ def self.to_nil(input, &_block)
29
+ if input.nil? || empty_str?(input)
30
+ nil
31
+ elsif block_given?
32
+ yield
33
+ else
34
+ raise CoercionError, "#{input.inspect} is not nil"
35
+ end
36
+ end
37
+
21
38
  # @param [String, Object] input
22
39
  #
23
40
  # @return [Boolean,Object]
@@ -95,7 +112,7 @@ module Dry
95
112
  # @raise CoercionError
96
113
  #
97
114
  # @api public
98
- def self.to_decimal(input, &block)
115
+ def self.to_decimal(input, &_block)
99
116
  to_float(input) do
100
117
  if block_given?
101
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
@@ -15,7 +16,7 @@ module Dry
15
16
  class Safe < Function
16
17
  def call(input, &block)
17
18
  @fn.(input, &block)
18
- rescue NoMethodError, TypeError, ArgumentError => e
19
+ rescue ::NoMethodError, ::TypeError, ::ArgumentError => e
19
20
  CoercionError.handle(e, &block)
20
21
  end
21
22
  end
@@ -30,10 +31,14 @@ module Dry
30
31
  #
31
32
  # @return [Function]
32
33
  def self.call_class(method, public, safe)
33
- @cache.fetch_or_store([method, public, safe].hash) do
34
+ @cache.fetch_or_store([method, public, safe]) do
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
@@ -53,7 +58,7 @@ module Dry
53
58
  #
54
59
  # @return [::Module]
55
60
  def self.call_interface(method, safe)
56
- @interfaces.fetch_or_store([method, safe].hash) do
61
+ @interfaces.fetch_or_store([method, safe]) do
57
62
  ::Module.new do
58
63
  if safe
59
64
  module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
@@ -65,7 +70,7 @@ module Dry
65
70
  module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
66
71
  def call(input, &block)
67
72
  @target.#{method}(input)
68
- rescue NoMethodError, TypeError, ArgumentError => error
73
+ rescue ::NoMethodError, ::TypeError, ::ArgumentError => error
69
74
  CoercionError.handle(error, &block)
70
75
  end
71
76
  RUBY
@@ -90,7 +95,7 @@ module Dry
90
95
  class PrivateSafeCall < PrivateCall
91
96
  def call(input, &block)
92
97
  @target.send(@name, input)
93
- rescue NoMethodError, TypeError, ArgumentError => e
98
+ rescue ::NoMethodError, ::TypeError, ::ArgumentError => e
94
99
  CoercionError.handle(e, &block)
95
100
  end
96
101
  end
@@ -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)
@@ -146,7 +167,7 @@ module Dry
146
167
  last_arg.equal?(:block)
147
168
  end
148
169
 
149
- include Dry::Equalizer(:fn, immutable: true)
170
+ include ::Dry::Equalizer(:fn, immutable: true)
150
171
 
151
172
  attr_reader :fn
152
173
 
@@ -160,39 +181,34 @@ 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)
166
- [:id, Dry::Types::FnContainer.register(fn)]
196
+ [:id, FnContainer.register(fn)]
167
197
  else
168
198
  [:callable, fn]
169
199
  end
170
200
  end
171
201
 
172
- if RUBY_VERSION >= '2.6'
173
- # @return [Function]
174
- def >>(other)
175
- proc = other.is_a?(::Proc) ? other : other.fn
176
- Function[@fn >> proc]
177
- end
178
-
179
- # @return [Function]
180
- def <<(other)
181
- proc = other.is_a?(::Proc) ? other : other.fn
182
- Function[@fn << proc]
183
- end
184
- else
185
- # @return [Function]
186
- def >>(other)
187
- proc = other.is_a?(::Proc) ? other : other.fn
188
- Function[-> x { proc[@fn[x]] }]
189
- end
202
+ # @return [Function]
203
+ def >>(other)
204
+ f = Function[other]
205
+ Function[-> x, &b { f.(self.(x, &b), &b) }]
206
+ end
190
207
 
191
- # @return [Function]
192
- def <<(other)
193
- proc = other.is_a?(::Proc) ? other : other.fn
194
- Function[-> x { @fn[proc[x]] }]
195
- end
208
+ # @return [Function]
209
+ def <<(other)
210
+ f = Function[other]
211
+ Function[-> x, &b { self.(f.(x, &b), &b) }]
196
212
  end
197
213
  end
198
214
  end