dry-types 1.2.2 → 1.5.1

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