super-smart-tool 0.0.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.
- checksums.yaml +7 -0
- data/dry-types-1.9.1/CHANGELOG.md +1145 -0
- data/dry-types-1.9.1/LICENSE +20 -0
- data/dry-types-1.9.1/README.md +17 -0
- data/dry-types-1.9.1/dry-types.gemspec +45 -0
- data/dry-types-1.9.1/lib/dry/types/any.rb +41 -0
- data/dry-types-1.9.1/lib/dry/types/array/constructor.rb +24 -0
- data/dry-types-1.9.1/lib/dry/types/array/member.rb +118 -0
- data/dry-types-1.9.1/lib/dry/types/array.rb +32 -0
- data/dry-types-1.9.1/lib/dry/types/builder.rb +221 -0
- data/dry-types-1.9.1/lib/dry/types/builder_methods.rb +138 -0
- data/dry-types-1.9.1/lib/dry/types/coercions/json.rb +57 -0
- data/dry-types-1.9.1/lib/dry/types/coercions/params.rb +168 -0
- data/dry-types-1.9.1/lib/dry/types/coercions.rb +105 -0
- data/dry-types-1.9.1/lib/dry/types/compat.rb +1 -0
- data/dry-types-1.9.1/lib/dry/types/compiler.rb +138 -0
- data/dry-types-1.9.1/lib/dry/types/composition.rb +138 -0
- data/dry-types-1.9.1/lib/dry/types/constrained/coercible.rb +59 -0
- data/dry-types-1.9.1/lib/dry/types/constrained.rb +146 -0
- data/dry-types-1.9.1/lib/dry/types/constraints.rb +30 -0
- data/dry-types-1.9.1/lib/dry/types/constructor/function.rb +203 -0
- data/dry-types-1.9.1/lib/dry/types/constructor/wrapper.rb +88 -0
- data/dry-types-1.9.1/lib/dry/types/constructor.rb +195 -0
- data/dry-types-1.9.1/lib/dry/types/container.rb +12 -0
- data/dry-types-1.9.1/lib/dry/types/core.rb +106 -0
- data/dry-types-1.9.1/lib/dry/types/decorator.rb +94 -0
- data/dry-types-1.9.1/lib/dry/types/default.rb +122 -0
- data/dry-types-1.9.1/lib/dry/types/enum.rb +121 -0
- data/dry-types-1.9.1/lib/dry/types/errors.rb +138 -0
- data/dry-types-1.9.1/lib/dry/types/extensions/maybe.rb +128 -0
- data/dry-types-1.9.1/lib/dry/types/extensions/monads.rb +34 -0
- data/dry-types-1.9.1/lib/dry/types/extensions.rb +9 -0
- data/dry-types-1.9.1/lib/dry/types/fn_container.rb +37 -0
- data/dry-types-1.9.1/lib/dry/types/hash/constructor.rb +25 -0
- data/dry-types-1.9.1/lib/dry/types/hash.rb +134 -0
- data/dry-types-1.9.1/lib/dry/types/implication.rb +64 -0
- data/dry-types-1.9.1/lib/dry/types/inflector.rb +9 -0
- data/dry-types-1.9.1/lib/dry/types/intersection.rb +102 -0
- data/dry-types-1.9.1/lib/dry/types/json.rb +35 -0
- data/dry-types-1.9.1/lib/dry/types/lax.rb +66 -0
- data/dry-types-1.9.1/lib/dry/types/map.rb +134 -0
- data/dry-types-1.9.1/lib/dry/types/meta.rb +53 -0
- data/dry-types-1.9.1/lib/dry/types/module.rb +127 -0
- data/dry-types-1.9.1/lib/dry/types/nominal.rb +201 -0
- data/dry-types-1.9.1/lib/dry/types/options.rb +34 -0
- data/dry-types-1.9.1/lib/dry/types/params.rb +38 -0
- data/dry-types-1.9.1/lib/dry/types/predicate_inferrer.rb +235 -0
- data/dry-types-1.9.1/lib/dry/types/predicate_registry.rb +32 -0
- data/dry-types-1.9.1/lib/dry/types/primitive_inferrer.rb +75 -0
- data/dry-types-1.9.1/lib/dry/types/printable.rb +14 -0
- data/dry-types-1.9.1/lib/dry/types/printer/composition.rb +44 -0
- data/dry-types-1.9.1/lib/dry/types/printer.rb +313 -0
- data/dry-types-1.9.1/lib/dry/types/result.rb +72 -0
- data/dry-types-1.9.1/lib/dry/types/schema/key.rb +138 -0
- data/dry-types-1.9.1/lib/dry/types/schema.rb +408 -0
- data/dry-types-1.9.1/lib/dry/types/spec/types.rb +164 -0
- data/dry-types-1.9.1/lib/dry/types/sum.rb +105 -0
- data/dry-types-1.9.1/lib/dry/types/type.rb +53 -0
- data/dry-types-1.9.1/lib/dry/types/version.rb +7 -0
- data/dry-types-1.9.1/lib/dry/types.rb +259 -0
- data/dry-types-1.9.1/lib/dry-types.rb +3 -0
- data/super-smart-tool.gemspec +11 -0
- metadata +101 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
# Constrained types apply rules to the input
|
|
6
|
+
#
|
|
7
|
+
# @api public
|
|
8
|
+
class Constrained
|
|
9
|
+
include Type
|
|
10
|
+
include Decorator
|
|
11
|
+
include Builder
|
|
12
|
+
include Printable
|
|
13
|
+
include ::Dry::Equalizer(:type, :rule, inspect: false, immutable: true)
|
|
14
|
+
|
|
15
|
+
# @return [Dry::Logic::Rule]
|
|
16
|
+
attr_reader :rule
|
|
17
|
+
|
|
18
|
+
# @param [Type] type
|
|
19
|
+
#
|
|
20
|
+
# @param [Hash] options
|
|
21
|
+
#
|
|
22
|
+
# @api public
|
|
23
|
+
def initialize(type, **options)
|
|
24
|
+
super
|
|
25
|
+
@rule = options.fetch(:rule)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @return [Object]
|
|
29
|
+
#
|
|
30
|
+
# @api private
|
|
31
|
+
def call_unsafe(input)
|
|
32
|
+
result = rule.(input)
|
|
33
|
+
|
|
34
|
+
if result.success?
|
|
35
|
+
type.call_unsafe(input)
|
|
36
|
+
else
|
|
37
|
+
raise ConstraintError.new(result, input)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Object]
|
|
42
|
+
#
|
|
43
|
+
# @api private
|
|
44
|
+
def call_safe(input, &)
|
|
45
|
+
if rule[input]
|
|
46
|
+
type.call_safe(input, &)
|
|
47
|
+
else
|
|
48
|
+
yield
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Safe coercion attempt. It is similar to #call with a
|
|
53
|
+
# block given but returns a Result instance with metadata
|
|
54
|
+
# about errors (if any).
|
|
55
|
+
#
|
|
56
|
+
# @overload try(input)
|
|
57
|
+
# @param [Object] input
|
|
58
|
+
# @return [Logic::Result]
|
|
59
|
+
#
|
|
60
|
+
# @overload try(input)
|
|
61
|
+
# @param [Object] input
|
|
62
|
+
# @yieldparam [Failure] failure
|
|
63
|
+
# @yieldreturn [Object]
|
|
64
|
+
# @return [Object]
|
|
65
|
+
#
|
|
66
|
+
# @api public
|
|
67
|
+
def try(input, &)
|
|
68
|
+
result = rule.(input)
|
|
69
|
+
|
|
70
|
+
if result.success?
|
|
71
|
+
type.try(input, &)
|
|
72
|
+
else
|
|
73
|
+
failure = failure(input, ConstraintError.new(result, input))
|
|
74
|
+
block_given? ? yield(failure) : failure
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @param *nullary_rules [Array<Symbol>] a list of rules that do not require an additional
|
|
79
|
+
# argument (e.g., :odd)
|
|
80
|
+
# @param **unary_rules [Hash] a list of rules that require an additional argument
|
|
81
|
+
# (e.g., gt: 0)
|
|
82
|
+
# The parameters are merger to create a rules hash provided to {Types.Rule} and combined
|
|
83
|
+
# using {&} with previous {#rule}
|
|
84
|
+
#
|
|
85
|
+
# @return [Constrained]
|
|
86
|
+
#
|
|
87
|
+
# @see Dry::Logic::Operators#and
|
|
88
|
+
#
|
|
89
|
+
# @api public
|
|
90
|
+
def constrained(*nullary_rules, **unary_rules)
|
|
91
|
+
nullary_rules_hash = parse_arguments(nullary_rules)
|
|
92
|
+
|
|
93
|
+
rules = nullary_rules_hash.merge(unary_rules)
|
|
94
|
+
|
|
95
|
+
with(rule: rule & Types.Rule(rules))
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @return [true]
|
|
99
|
+
#
|
|
100
|
+
# @api public
|
|
101
|
+
def constrained? = true
|
|
102
|
+
|
|
103
|
+
# @param [Object] value
|
|
104
|
+
#
|
|
105
|
+
# @return [Boolean]
|
|
106
|
+
#
|
|
107
|
+
# @api public
|
|
108
|
+
def ===(value) = valid?(value)
|
|
109
|
+
|
|
110
|
+
# Build lax type. Constraints are not applicable to lax types hence unwrapping
|
|
111
|
+
#
|
|
112
|
+
# @return [Lax]
|
|
113
|
+
# @api public
|
|
114
|
+
def lax = type.lax
|
|
115
|
+
|
|
116
|
+
# @see Nominal#to_ast
|
|
117
|
+
# @api public
|
|
118
|
+
def to_ast(meta: true)
|
|
119
|
+
[:constrained, [type.to_ast(meta: meta), rule.to_ast]]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# @api private
|
|
123
|
+
def constructor_type = type.constructor_type
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
# @param [Object] response
|
|
128
|
+
#
|
|
129
|
+
# @return [Boolean]
|
|
130
|
+
#
|
|
131
|
+
# @api private
|
|
132
|
+
def decorate?(response) = super || response.is_a?(Constructor)
|
|
133
|
+
|
|
134
|
+
# @param [Array] positional_args
|
|
135
|
+
#
|
|
136
|
+
# @return [Hash]
|
|
137
|
+
#
|
|
138
|
+
# @api private
|
|
139
|
+
def parse_arguments(positional_arguments)
|
|
140
|
+
return positional_arguments.first if positional_arguments.first.is_a?(::Hash)
|
|
141
|
+
|
|
142
|
+
positional_arguments.flatten.zip([]).to_h
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
# Helper methods for constraint types
|
|
5
|
+
#
|
|
6
|
+
# @api public
|
|
7
|
+
module Types
|
|
8
|
+
# @param [Hash] options
|
|
9
|
+
#
|
|
10
|
+
# @return [Dry::Logic::Rule]
|
|
11
|
+
#
|
|
12
|
+
# @api public
|
|
13
|
+
def self.Rule(options)
|
|
14
|
+
rule_compiler.(
|
|
15
|
+
options.map { |key, val|
|
|
16
|
+
::Dry::Logic::Rule::Predicate.build(
|
|
17
|
+
::Dry::Logic::Predicates[:"#{key}?"]
|
|
18
|
+
).curry(val).to_ast
|
|
19
|
+
}
|
|
20
|
+
).reduce(:and)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [Dry::Logic::RuleCompiler]
|
|
24
|
+
#
|
|
25
|
+
# @api private
|
|
26
|
+
def self.rule_compiler
|
|
27
|
+
@rule_compiler ||= ::Dry::Logic::RuleCompiler.new(::Dry::Logic::Predicates)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent/map"
|
|
4
|
+
|
|
5
|
+
module Dry
|
|
6
|
+
module Types
|
|
7
|
+
class Constructor < Nominal
|
|
8
|
+
# Function is used internally by Constructor types
|
|
9
|
+
#
|
|
10
|
+
# @api private
|
|
11
|
+
class Function
|
|
12
|
+
# Wrapper for unsafe coercion functions
|
|
13
|
+
#
|
|
14
|
+
# @api private
|
|
15
|
+
class Safe < Function
|
|
16
|
+
def call(input, &)
|
|
17
|
+
@fn.(input)
|
|
18
|
+
rescue ::NoMethodError, ::TypeError, ::ArgumentError => exception
|
|
19
|
+
CoercionError.handle(exception, &)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Coercion via a method call on a known object
|
|
24
|
+
#
|
|
25
|
+
# @api private
|
|
26
|
+
class MethodCall < Function
|
|
27
|
+
@cache = ::Concurrent::Map.new
|
|
28
|
+
|
|
29
|
+
# Choose or build the base class
|
|
30
|
+
#
|
|
31
|
+
# @return [Function]
|
|
32
|
+
def self.call_class(method, public, safe)
|
|
33
|
+
@cache.fetch_or_store([method, public, safe]) do
|
|
34
|
+
if public
|
|
35
|
+
::Class.new(PublicCall) do
|
|
36
|
+
include PublicCall.call_interface(method, safe)
|
|
37
|
+
|
|
38
|
+
define_method(:__to_s__) do
|
|
39
|
+
"#<PublicCall for :#{method}>"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
elsif safe
|
|
43
|
+
PrivateCall
|
|
44
|
+
else
|
|
45
|
+
PrivateSafeCall
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Coercion with a publicly accessible method call
|
|
51
|
+
#
|
|
52
|
+
# @api private
|
|
53
|
+
class PublicCall < MethodCall
|
|
54
|
+
@interfaces = ::Concurrent::Map.new
|
|
55
|
+
|
|
56
|
+
# Choose or build the interface
|
|
57
|
+
#
|
|
58
|
+
# @return [::Module]
|
|
59
|
+
def self.call_interface(method, safe)
|
|
60
|
+
@interfaces.fetch_or_store([method, safe]) do
|
|
61
|
+
::Module.new do
|
|
62
|
+
if safe
|
|
63
|
+
module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
|
64
|
+
def call(input, &) # def call(input, &)
|
|
65
|
+
@target.#{method}(input, &) # @target.coerce(input, &)
|
|
66
|
+
end # end
|
|
67
|
+
RUBY
|
|
68
|
+
else
|
|
69
|
+
module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
|
70
|
+
def call(input, &) # def call(input, &)
|
|
71
|
+
@target.#{method}(input) # @target.coerce(input)
|
|
72
|
+
rescue ::NoMethodError, ::TypeError, ::ArgumentError => error # rescue ::NoMethodError, ::TypeError, ::ArgumentError => error
|
|
73
|
+
CoercionError.handle(error, &) # CoercionError.handle(error, &)
|
|
74
|
+
end # end
|
|
75
|
+
RUBY
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Coercion via a private method call
|
|
83
|
+
#
|
|
84
|
+
# @api private
|
|
85
|
+
class PrivateCall < MethodCall
|
|
86
|
+
def call(input, &) = @target.send(@name, input, &)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Coercion via an unsafe private method call
|
|
90
|
+
#
|
|
91
|
+
# @api private
|
|
92
|
+
class PrivateSafeCall < PrivateCall
|
|
93
|
+
def call(input, &)
|
|
94
|
+
@target.send(@name, input)
|
|
95
|
+
rescue ::NoMethodError, ::TypeError, ::ArgumentError => exception
|
|
96
|
+
CoercionError.handle(exception, &)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @api private
|
|
101
|
+
#
|
|
102
|
+
# @return [MethodCall]
|
|
103
|
+
def self.[](fn, safe)
|
|
104
|
+
public = fn.receiver.respond_to?(fn.name)
|
|
105
|
+
MethodCall.call_class(fn.name, public, safe).new(fn)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
attr_reader :target, :name
|
|
109
|
+
|
|
110
|
+
def initialize(fn)
|
|
111
|
+
super
|
|
112
|
+
@target = fn.receiver
|
|
113
|
+
@name = fn.name
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def to_ast = [:method, target, name]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
class Wrapper < Function
|
|
120
|
+
# @return [Object]
|
|
121
|
+
def call(input, type, &)
|
|
122
|
+
@fn.(input, type, &)
|
|
123
|
+
rescue ::NoMethodError, ::TypeError, ::ArgumentError => exception
|
|
124
|
+
CoercionError.handle(exception, &)
|
|
125
|
+
end
|
|
126
|
+
alias_method :[], :call
|
|
127
|
+
|
|
128
|
+
def arity = 2
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Choose or build specialized invokation code for a callable
|
|
132
|
+
#
|
|
133
|
+
# @param [#call] fn
|
|
134
|
+
# @return [Function]
|
|
135
|
+
def self.[](fn)
|
|
136
|
+
raise ::ArgumentError, "Missing constructor block" if fn.nil?
|
|
137
|
+
|
|
138
|
+
if fn.is_a?(Function)
|
|
139
|
+
fn
|
|
140
|
+
elsif fn.respond_to?(:arity) && fn.arity.equal?(2)
|
|
141
|
+
Wrapper.new(fn)
|
|
142
|
+
elsif fn.is_a?(::Method)
|
|
143
|
+
MethodCall[fn, yields_block?(fn)]
|
|
144
|
+
elsif yields_block?(fn)
|
|
145
|
+
new(fn)
|
|
146
|
+
else
|
|
147
|
+
Safe.new(fn)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# @return [Boolean]
|
|
152
|
+
def self.yields_block?(fn)
|
|
153
|
+
*, (last_arg,) =
|
|
154
|
+
if fn.respond_to?(:parameters)
|
|
155
|
+
fn.parameters
|
|
156
|
+
else
|
|
157
|
+
fn.method(:call).parameters
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
last_arg.equal?(:block)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
include ::Dry::Equalizer(:fn, immutable: true)
|
|
164
|
+
|
|
165
|
+
attr_reader :fn
|
|
166
|
+
|
|
167
|
+
def initialize(fn)
|
|
168
|
+
@fn = fn
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# @return [Object]
|
|
172
|
+
def call(input, &) = @fn.(input, &)
|
|
173
|
+
alias_method :[], :call
|
|
174
|
+
|
|
175
|
+
# @return [Integer]
|
|
176
|
+
def arity = 1
|
|
177
|
+
|
|
178
|
+
def wrapper? = arity.equal?(2)
|
|
179
|
+
|
|
180
|
+
# @return [Array]
|
|
181
|
+
def to_ast
|
|
182
|
+
if fn.is_a?(::Proc)
|
|
183
|
+
[:id, FnContainer.register(fn)]
|
|
184
|
+
else
|
|
185
|
+
[:callable, fn]
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# @return [Function]
|
|
190
|
+
def >>(other)
|
|
191
|
+
f = Function[other]
|
|
192
|
+
Function[-> x, &b { f.(self.(x, &b), &b) }]
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# @return [Function]
|
|
196
|
+
def <<(other)
|
|
197
|
+
f = Function[other]
|
|
198
|
+
Function[-> x, &b { self.(f.(x, &b), &b) }]
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
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, &) = fn.(input, type, &)
|
|
12
|
+
|
|
13
|
+
# @return [Object]
|
|
14
|
+
#
|
|
15
|
+
# @api private
|
|
16
|
+
def call_unsafe(input) = fn.(input, type)
|
|
17
|
+
|
|
18
|
+
# @param [Object] input
|
|
19
|
+
# @param [#call,nil] block
|
|
20
|
+
#
|
|
21
|
+
# @return [Logic::Result, Types::Result]
|
|
22
|
+
# @return [Object] if block given and try fails
|
|
23
|
+
#
|
|
24
|
+
# @api public
|
|
25
|
+
def try(input, &)
|
|
26
|
+
value = fn.(input, type)
|
|
27
|
+
rescue CoercionError => exception
|
|
28
|
+
failure = failure(input, exception)
|
|
29
|
+
block_given? ? yield(failure) : failure
|
|
30
|
+
else
|
|
31
|
+
type.try(value, &)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Define a constructor for the type
|
|
35
|
+
#
|
|
36
|
+
# @param [#call,nil] constructor
|
|
37
|
+
# @param [Hash] options
|
|
38
|
+
# @param [#call,nil] block
|
|
39
|
+
#
|
|
40
|
+
# @return [Constructor]
|
|
41
|
+
#
|
|
42
|
+
# @api public
|
|
43
|
+
define_method(:constructor, Builder.instance_method(:constructor))
|
|
44
|
+
alias_method :append, :constructor
|
|
45
|
+
alias_method :>>, :constructor
|
|
46
|
+
|
|
47
|
+
# Build a new constructor by prepending a block to the coercion function
|
|
48
|
+
#
|
|
49
|
+
# @param [#call, nil] new_fn
|
|
50
|
+
# @param [Hash] options
|
|
51
|
+
# @param [#call, nil] block
|
|
52
|
+
#
|
|
53
|
+
# @return [Constructor]
|
|
54
|
+
#
|
|
55
|
+
# @api public
|
|
56
|
+
def prepend(new_fn = nil, **options, &block)
|
|
57
|
+
prep_fn = Function[new_fn || block]
|
|
58
|
+
|
|
59
|
+
decorated =
|
|
60
|
+
if prep_fn.wrapper?
|
|
61
|
+
type.constructor(prep_fn, **options)
|
|
62
|
+
else
|
|
63
|
+
type.prepend(prep_fn, **options)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
__new__(decorated)
|
|
67
|
+
end
|
|
68
|
+
alias_method :<<, :prepend
|
|
69
|
+
|
|
70
|
+
# @return [Constructor]
|
|
71
|
+
#
|
|
72
|
+
# @api public
|
|
73
|
+
def lax
|
|
74
|
+
# return self back because wrapping function
|
|
75
|
+
# can handle failed type check
|
|
76
|
+
self
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
# Replace underlying type
|
|
82
|
+
#
|
|
83
|
+
# @api private
|
|
84
|
+
def __new__(type) = self.class.new(type, *@__args__.drop(1), **@options)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
# Constructor types apply a function to the input that is supposed to return
|
|
6
|
+
# a new value. Coercion is a common use case for constructor types.
|
|
7
|
+
#
|
|
8
|
+
# @api public
|
|
9
|
+
class Constructor < Nominal
|
|
10
|
+
include ::Dry::Equalizer(:type, :options, inspect: false, immutable: true)
|
|
11
|
+
|
|
12
|
+
# @return [#call]
|
|
13
|
+
attr_reader :fn
|
|
14
|
+
|
|
15
|
+
# @return [Type]
|
|
16
|
+
attr_reader :type
|
|
17
|
+
|
|
18
|
+
undef :constrained?, :meta, :optional?, :primitive, :primitive?, :default?, :name, :namespace
|
|
19
|
+
|
|
20
|
+
# @param [Builder, Object] input
|
|
21
|
+
# @param [Hash] options
|
|
22
|
+
# @param [#call, nil] block
|
|
23
|
+
#
|
|
24
|
+
# @api public
|
|
25
|
+
def self.new(input, fn: Undefined, **options, &block)
|
|
26
|
+
type = input.is_a?(Builder) ? input : Nominal.new(input)
|
|
27
|
+
super(type, **options, fn: Function[Undefined.default(fn, block)])
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @param [Builder, Object] input
|
|
31
|
+
# @param [Hash] options
|
|
32
|
+
# @param [#call, nil] block
|
|
33
|
+
#
|
|
34
|
+
# @api public
|
|
35
|
+
def self.[](type, fn:, **options)
|
|
36
|
+
function = Function[fn]
|
|
37
|
+
|
|
38
|
+
if function.wrapper?
|
|
39
|
+
wrapper_type.new(type, fn: function, **options)
|
|
40
|
+
else
|
|
41
|
+
new(type, fn: function, **options)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @api private
|
|
46
|
+
def self.wrapper_type
|
|
47
|
+
@wrapper_type ||=
|
|
48
|
+
if self < Wrapper
|
|
49
|
+
self
|
|
50
|
+
else
|
|
51
|
+
const_set(:Wrapping, ::Class.new(self).include(Wrapper))
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Instantiate a new constructor type instance
|
|
56
|
+
#
|
|
57
|
+
# @param [Type] type
|
|
58
|
+
# @option [Function] fn
|
|
59
|
+
# @param [Hash] options
|
|
60
|
+
#
|
|
61
|
+
# @api private
|
|
62
|
+
def initialize(type, fn: nil, **options)
|
|
63
|
+
@type = type
|
|
64
|
+
@fn = fn
|
|
65
|
+
|
|
66
|
+
super(type, **options, fn: fn)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @return [Object]
|
|
70
|
+
#
|
|
71
|
+
# @api private
|
|
72
|
+
def call_safe(input, &block)
|
|
73
|
+
# block.call is slower in JRuby than yield, but in JRuby 10.0.2.0 there is a bug
|
|
74
|
+
# with default block arguments, so block.call is chosen for correctness.
|
|
75
|
+
# This was introduced in PR https://github.com/dry-rb/dry-types/pull/493, might be reverted
|
|
76
|
+
# in the bug is fixed in JRuby.
|
|
77
|
+
coerced = fn.(input) { |output = input| return block.call(output) }
|
|
78
|
+
type.call_safe(coerced) { |output = coerced| block.call(output) }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @return [Object]
|
|
82
|
+
#
|
|
83
|
+
# @api private
|
|
84
|
+
def call_unsafe(input) = type.call_unsafe(fn.(input))
|
|
85
|
+
|
|
86
|
+
# @param [Object] input
|
|
87
|
+
# @param [#call,nil] block
|
|
88
|
+
#
|
|
89
|
+
# @return [Logic::Result, Types::Result]
|
|
90
|
+
# @return [Object] if block given and try fails
|
|
91
|
+
#
|
|
92
|
+
# @api public
|
|
93
|
+
def try(input, &)
|
|
94
|
+
value = fn.(input)
|
|
95
|
+
rescue CoercionError => exception
|
|
96
|
+
failure = failure(input, exception)
|
|
97
|
+
block_given? ? yield(failure) : failure
|
|
98
|
+
else
|
|
99
|
+
type.try(value, &)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Build a new constructor by appending a block to the coercion function
|
|
103
|
+
#
|
|
104
|
+
# @param [#call, nil] new_fn
|
|
105
|
+
# @param [Hash] options
|
|
106
|
+
# @param [#call, nil] block
|
|
107
|
+
#
|
|
108
|
+
# @return [Constructor]
|
|
109
|
+
#
|
|
110
|
+
# @api public
|
|
111
|
+
def constructor(new_fn = nil, **options, &block)
|
|
112
|
+
next_fn = Function[new_fn || block]
|
|
113
|
+
|
|
114
|
+
if next_fn.wrapper?
|
|
115
|
+
self.class.wrapper_type.new(with(**options), fn: next_fn)
|
|
116
|
+
else
|
|
117
|
+
with(**options, fn: fn >> next_fn)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
alias_method :append, :constructor
|
|
121
|
+
alias_method :>>, :constructor
|
|
122
|
+
|
|
123
|
+
# @return [Class]
|
|
124
|
+
#
|
|
125
|
+
# @api private
|
|
126
|
+
def constrained_type = Constrained::Coercible
|
|
127
|
+
|
|
128
|
+
# @see Nominal#to_ast
|
|
129
|
+
#
|
|
130
|
+
# @api public
|
|
131
|
+
def to_ast(meta: true)
|
|
132
|
+
[:constructor, [type.to_ast(meta: meta), fn.to_ast]]
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Build a new constructor by prepending a block to the coercion function
|
|
136
|
+
#
|
|
137
|
+
# @param [#call, nil] new_fn
|
|
138
|
+
# @param [Hash] options
|
|
139
|
+
# @param [#call, nil] block
|
|
140
|
+
#
|
|
141
|
+
# @return [Constructor]
|
|
142
|
+
#
|
|
143
|
+
# @api public
|
|
144
|
+
def prepend(new_fn = nil, **options, &block)
|
|
145
|
+
with(**options, fn: fn << (new_fn || block))
|
|
146
|
+
end
|
|
147
|
+
alias_method :<<, :prepend
|
|
148
|
+
|
|
149
|
+
# Build a lax type
|
|
150
|
+
#
|
|
151
|
+
# @return [Lax]
|
|
152
|
+
# @api public
|
|
153
|
+
def lax = Lax.new(constructor_type[type.lax, **options])
|
|
154
|
+
|
|
155
|
+
# Wrap the type with a proc
|
|
156
|
+
#
|
|
157
|
+
# @return [Proc]
|
|
158
|
+
#
|
|
159
|
+
# @api public
|
|
160
|
+
def to_proc = proc { self.(_1) }
|
|
161
|
+
|
|
162
|
+
private
|
|
163
|
+
|
|
164
|
+
# @param [Symbol] meth
|
|
165
|
+
# @param [Boolean] include_private
|
|
166
|
+
# @return [Boolean]
|
|
167
|
+
#
|
|
168
|
+
# @api private
|
|
169
|
+
def respond_to_missing?(meth, include_private = false)
|
|
170
|
+
super || type.respond_to?(meth)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Delegates missing methods to {#type}
|
|
174
|
+
#
|
|
175
|
+
# @param [Symbol] method
|
|
176
|
+
# @param [Array] args
|
|
177
|
+
# @param [#call, nil] block
|
|
178
|
+
#
|
|
179
|
+
# @api private
|
|
180
|
+
def method_missing(method, ...)
|
|
181
|
+
if type.respond_to?(method)
|
|
182
|
+
response = type.public_send(method, ...)
|
|
183
|
+
|
|
184
|
+
if response.is_a?(Type) && response.instance_of?(type.class)
|
|
185
|
+
response.constructor_type[response, **options]
|
|
186
|
+
else
|
|
187
|
+
response
|
|
188
|
+
end
|
|
189
|
+
else
|
|
190
|
+
super
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|