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,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
require "bigdecimal"
|
|
5
|
+
require "bigdecimal/util"
|
|
6
|
+
require "time"
|
|
7
|
+
|
|
8
|
+
module Dry
|
|
9
|
+
module Types
|
|
10
|
+
module Coercions
|
|
11
|
+
# JSON-specific coercions
|
|
12
|
+
#
|
|
13
|
+
# @api public
|
|
14
|
+
module JSON
|
|
15
|
+
extend Coercions
|
|
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, &)
|
|
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
|
+
|
|
34
|
+
# @param [#to_d, Object] input
|
|
35
|
+
#
|
|
36
|
+
# @return [BigDecimal,nil]
|
|
37
|
+
#
|
|
38
|
+
# @raise CoercionError
|
|
39
|
+
#
|
|
40
|
+
# @api public
|
|
41
|
+
def self.to_decimal(input, &)
|
|
42
|
+
if input.is_a?(::Float)
|
|
43
|
+
input.to_d
|
|
44
|
+
else
|
|
45
|
+
BigDecimal(input)
|
|
46
|
+
end
|
|
47
|
+
rescue ::ArgumentError, ::TypeError
|
|
48
|
+
if block_given?
|
|
49
|
+
yield
|
|
50
|
+
else
|
|
51
|
+
raise CoercionError, "#{input} cannot be coerced to decimal"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bigdecimal"
|
|
4
|
+
require "bigdecimal/util"
|
|
5
|
+
|
|
6
|
+
module Dry
|
|
7
|
+
module Types
|
|
8
|
+
module Coercions
|
|
9
|
+
# Params-specific coercions
|
|
10
|
+
#
|
|
11
|
+
# @api public
|
|
12
|
+
module Params
|
|
13
|
+
TRUE_VALUES = %w[1 on On ON t true True TRUE T y yes Yes YES Y].freeze
|
|
14
|
+
FALSE_VALUES = %w[0 off Off OFF f false False FALSE F n no No NO N].freeze
|
|
15
|
+
BOOLEAN_MAP = EMPTY_HASH.merge(
|
|
16
|
+
[true, *TRUE_VALUES].to_h { |v| [v, true] },
|
|
17
|
+
[false, *FALSE_VALUES].to_h { |v| [v, false] }
|
|
18
|
+
).freeze
|
|
19
|
+
|
|
20
|
+
extend Coercions
|
|
21
|
+
|
|
22
|
+
# @param [Object] input
|
|
23
|
+
#
|
|
24
|
+
# @return [nil] if the input is an empty string or nil
|
|
25
|
+
#
|
|
26
|
+
# @raise CoercionError
|
|
27
|
+
#
|
|
28
|
+
# @api public
|
|
29
|
+
def self.to_nil(input, &)
|
|
30
|
+
if input.nil? || empty_str?(input)
|
|
31
|
+
nil
|
|
32
|
+
elsif block_given?
|
|
33
|
+
yield
|
|
34
|
+
else
|
|
35
|
+
raise CoercionError, "#{input.inspect} is not nil"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @param [String, Object] input
|
|
40
|
+
#
|
|
41
|
+
# @return [Boolean,Object]
|
|
42
|
+
#
|
|
43
|
+
# @see TRUE_VALUES
|
|
44
|
+
# @see FALSE_VALUES
|
|
45
|
+
#
|
|
46
|
+
# @raise CoercionError
|
|
47
|
+
#
|
|
48
|
+
# @api public
|
|
49
|
+
def self.to_true(input, &)
|
|
50
|
+
BOOLEAN_MAP.fetch(input.to_s) do
|
|
51
|
+
if block_given?
|
|
52
|
+
yield
|
|
53
|
+
else
|
|
54
|
+
raise CoercionError, "#{input} cannot be coerced to true"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @param [String, Object] input
|
|
60
|
+
#
|
|
61
|
+
# @return [Boolean,Object]
|
|
62
|
+
#
|
|
63
|
+
# @see TRUE_VALUES
|
|
64
|
+
# @see FALSE_VALUES
|
|
65
|
+
#
|
|
66
|
+
# @raise CoercionError
|
|
67
|
+
#
|
|
68
|
+
# @api public
|
|
69
|
+
def self.to_false(input, &)
|
|
70
|
+
BOOLEAN_MAP.fetch(input.to_s) do
|
|
71
|
+
if block_given?
|
|
72
|
+
yield
|
|
73
|
+
else
|
|
74
|
+
raise CoercionError, "#{input} cannot be coerced to false"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @param [#to_int, #to_i, Object] input
|
|
80
|
+
#
|
|
81
|
+
# @return [Integer, nil, Object]
|
|
82
|
+
#
|
|
83
|
+
# @raise CoercionError
|
|
84
|
+
#
|
|
85
|
+
# @api public
|
|
86
|
+
def self.to_int(input, &)
|
|
87
|
+
if input.is_a?(::String)
|
|
88
|
+
Integer(input, 10)
|
|
89
|
+
else
|
|
90
|
+
Integer(input)
|
|
91
|
+
end
|
|
92
|
+
rescue ::ArgumentError, ::TypeError => exception
|
|
93
|
+
CoercionError.handle(exception, &)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# @param [#to_f, Object] input
|
|
97
|
+
#
|
|
98
|
+
# @return [Float, nil, Object]
|
|
99
|
+
#
|
|
100
|
+
# @raise CoercionError
|
|
101
|
+
#
|
|
102
|
+
# @api public
|
|
103
|
+
def self.to_float(input, &)
|
|
104
|
+
Float(input)
|
|
105
|
+
rescue ::ArgumentError, ::TypeError => exception
|
|
106
|
+
CoercionError.handle(exception, &)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @param [#to_d, Object] input
|
|
110
|
+
#
|
|
111
|
+
# @return [BigDecimal, nil, Object]
|
|
112
|
+
#
|
|
113
|
+
# @raise CoercionError
|
|
114
|
+
#
|
|
115
|
+
# @api public
|
|
116
|
+
def self.to_decimal(input, &)
|
|
117
|
+
to_float(input) do
|
|
118
|
+
if block_given?
|
|
119
|
+
return yield
|
|
120
|
+
else
|
|
121
|
+
raise CoercionError, "#{input.inspect} cannot be coerced to decimal"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
input.to_d
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# @param [Array, String, Object] input
|
|
129
|
+
#
|
|
130
|
+
# @return [Array, Object]
|
|
131
|
+
#
|
|
132
|
+
# @raise CoercionError
|
|
133
|
+
#
|
|
134
|
+
# @api public
|
|
135
|
+
def self.to_ary(input, &)
|
|
136
|
+
if empty_str?(input)
|
|
137
|
+
[]
|
|
138
|
+
elsif input.is_a?(::Array)
|
|
139
|
+
input
|
|
140
|
+
elsif block_given?
|
|
141
|
+
yield
|
|
142
|
+
else
|
|
143
|
+
raise CoercionError, "#{input.inspect} cannot be coerced to array"
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# @param [Hash, String, Object] input
|
|
148
|
+
#
|
|
149
|
+
# @return [Hash, Object]
|
|
150
|
+
#
|
|
151
|
+
# @raise CoercionError
|
|
152
|
+
#
|
|
153
|
+
# @api public
|
|
154
|
+
def self.to_hash(input, &)
|
|
155
|
+
if empty_str?(input)
|
|
156
|
+
{}
|
|
157
|
+
elsif input.is_a?(::Hash)
|
|
158
|
+
input
|
|
159
|
+
elsif block_given?
|
|
160
|
+
yield
|
|
161
|
+
else
|
|
162
|
+
raise CoercionError, "#{input.inspect} cannot be coerced to hash"
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
# Common coercion functions used by the built-in `Params` and `JSON` types
|
|
6
|
+
#
|
|
7
|
+
# @api public
|
|
8
|
+
module Coercions
|
|
9
|
+
include Dry::Core::Constants
|
|
10
|
+
|
|
11
|
+
# @param [#to_str, Object] input
|
|
12
|
+
#
|
|
13
|
+
# @return [Date, Object]
|
|
14
|
+
#
|
|
15
|
+
# @see Date.parse
|
|
16
|
+
#
|
|
17
|
+
# @api public
|
|
18
|
+
def to_date(input, &)
|
|
19
|
+
if input.respond_to?(:to_str)
|
|
20
|
+
begin
|
|
21
|
+
::Date.parse(input)
|
|
22
|
+
rescue ::ArgumentError, ::RangeError => exception
|
|
23
|
+
CoercionError.handle(exception, &)
|
|
24
|
+
end
|
|
25
|
+
elsif input.is_a?(::Date)
|
|
26
|
+
input
|
|
27
|
+
elsif block_given?
|
|
28
|
+
yield
|
|
29
|
+
else
|
|
30
|
+
raise CoercionError, "#{input.inspect} is not a string"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @param [#to_str, Object] input
|
|
35
|
+
#
|
|
36
|
+
# @return [DateTime, Object]
|
|
37
|
+
#
|
|
38
|
+
# @see DateTime.parse
|
|
39
|
+
#
|
|
40
|
+
# @api public
|
|
41
|
+
def to_date_time(input, &)
|
|
42
|
+
if input.respond_to?(:to_str)
|
|
43
|
+
begin
|
|
44
|
+
::DateTime.parse(input)
|
|
45
|
+
rescue ::ArgumentError => exception
|
|
46
|
+
CoercionError.handle(exception, &)
|
|
47
|
+
end
|
|
48
|
+
elsif input.is_a?(::DateTime)
|
|
49
|
+
input
|
|
50
|
+
elsif block_given?
|
|
51
|
+
yield
|
|
52
|
+
else
|
|
53
|
+
raise CoercionError, "#{input.inspect} is not a string"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @param [#to_str, Object] input
|
|
58
|
+
#
|
|
59
|
+
# @return [Time, Object]
|
|
60
|
+
#
|
|
61
|
+
# @see Time.parse
|
|
62
|
+
#
|
|
63
|
+
# @api public
|
|
64
|
+
def to_time(input, &)
|
|
65
|
+
if input.respond_to?(:to_str)
|
|
66
|
+
begin
|
|
67
|
+
::Time.parse(input)
|
|
68
|
+
rescue ::ArgumentError => exception
|
|
69
|
+
CoercionError.handle(exception, &)
|
|
70
|
+
end
|
|
71
|
+
elsif input.is_a?(::Time)
|
|
72
|
+
input
|
|
73
|
+
elsif block_given?
|
|
74
|
+
yield
|
|
75
|
+
else
|
|
76
|
+
raise CoercionError, "#{input.inspect} is not a string"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @param [#to_sym, Object] input
|
|
81
|
+
#
|
|
82
|
+
# @return [Symbol, Object]
|
|
83
|
+
#
|
|
84
|
+
# @raise CoercionError
|
|
85
|
+
#
|
|
86
|
+
# @api public
|
|
87
|
+
def to_symbol(input, &)
|
|
88
|
+
input.to_sym
|
|
89
|
+
rescue ::NoMethodError => exception
|
|
90
|
+
CoercionError.handle(exception, &)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
# Checks whether String is empty
|
|
96
|
+
#
|
|
97
|
+
# @param [String, Object] value
|
|
98
|
+
#
|
|
99
|
+
# @return [Boolean]
|
|
100
|
+
#
|
|
101
|
+
# @api private
|
|
102
|
+
def empty_str?(value) = EMPTY_STRING.eql?(value)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
# @api private
|
|
6
|
+
class Compiler
|
|
7
|
+
extend ::Dry::Core::Deprecations[:"dry-types"]
|
|
8
|
+
|
|
9
|
+
attr_reader :registry
|
|
10
|
+
|
|
11
|
+
def initialize(registry)
|
|
12
|
+
@registry = registry
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call(ast) = visit(ast)
|
|
16
|
+
|
|
17
|
+
def visit(node)
|
|
18
|
+
type, body = node
|
|
19
|
+
send(:"visit_#{type}", body)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def visit_constrained(node)
|
|
23
|
+
nominal, rule = node
|
|
24
|
+
type = visit(nominal)
|
|
25
|
+
type.constrained_type.new(type, rule: visit_rule(rule))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def visit_constructor(node)
|
|
29
|
+
nominal, fn = node
|
|
30
|
+
primitive = visit(nominal)
|
|
31
|
+
primitive.constructor(compile_fn(fn))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def visit_lax(node)
|
|
35
|
+
Types::Lax.new(visit(node))
|
|
36
|
+
end
|
|
37
|
+
deprecate(:visit_safe, :visit_lax)
|
|
38
|
+
|
|
39
|
+
def visit_nominal(node)
|
|
40
|
+
type, options, meta =
|
|
41
|
+
case node
|
|
42
|
+
in [type, options, meta]
|
|
43
|
+
node
|
|
44
|
+
in [type, meta]
|
|
45
|
+
[type, EMPTY_HASH, meta]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
nominal_name = "nominal.#{Types.identifier(type)}"
|
|
49
|
+
|
|
50
|
+
if registry.registered?(nominal_name)
|
|
51
|
+
registry[nominal_name].with(**options).meta(meta)
|
|
52
|
+
else
|
|
53
|
+
Nominal.new(type, meta: meta, **options)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def visit_rule(node)
|
|
58
|
+
::Dry::Types.rule_compiler.([node])[0]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def visit_sum(node)
|
|
62
|
+
*types, meta = node
|
|
63
|
+
types.map { |type| visit(type) }.reduce(:|).meta(meta)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def visit_array(node)
|
|
67
|
+
member, meta = node
|
|
68
|
+
member = visit(member) unless member.is_a?(::Class)
|
|
69
|
+
registry["nominal.array"].of(member).meta(meta)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def visit_hash(node)
|
|
73
|
+
opts, meta = node
|
|
74
|
+
registry["nominal.hash"].with(**opts, meta: meta)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def visit_schema(node)
|
|
78
|
+
keys, options, meta = node
|
|
79
|
+
registry["nominal.hash"].schema(keys.map { |key| visit(key) }).with(**options, meta: meta)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def visit_json_hash(node)
|
|
83
|
+
keys, meta = node
|
|
84
|
+
registry["json.hash"].schema(keys.map { |key| visit(key) }, meta)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def visit_json_array(node)
|
|
88
|
+
member, meta = node
|
|
89
|
+
registry["json.array"].of(visit(member)).meta(meta)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def visit_params_hash(node)
|
|
93
|
+
keys, meta = node
|
|
94
|
+
registry["params.hash"].schema(keys.map { |key| visit(key) }, meta)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def visit_params_array(node)
|
|
98
|
+
member, meta = node
|
|
99
|
+
registry["params.array"].of(visit(member)).meta(meta)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def visit_key(node)
|
|
103
|
+
name, required, type = node
|
|
104
|
+
Schema::Key.new(visit(type), name, required: required)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def visit_enum(node)
|
|
108
|
+
type, mapping = node
|
|
109
|
+
Enum.new(visit(type), mapping: mapping)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def visit_map(node)
|
|
113
|
+
key_type, value_type, meta = node
|
|
114
|
+
registry["nominal.hash"].map(visit(key_type), visit(value_type)).meta(meta)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def visit_any(meta)
|
|
118
|
+
registry["any"].meta(meta)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def compile_fn(fn)
|
|
122
|
+
type, *node = fn
|
|
123
|
+
|
|
124
|
+
case type
|
|
125
|
+
when :id
|
|
126
|
+
::Dry::Types::FnContainer[node.fetch(0)]
|
|
127
|
+
when :callable
|
|
128
|
+
node.fetch(0)
|
|
129
|
+
when :method
|
|
130
|
+
target, method = node
|
|
131
|
+
target.method(method)
|
|
132
|
+
else
|
|
133
|
+
raise ::ArgumentError, "Cannot build callable from #{fn.inspect}"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/core/equalizer"
|
|
4
|
+
require "dry/types/options"
|
|
5
|
+
require "dry/types/meta"
|
|
6
|
+
|
|
7
|
+
module Dry
|
|
8
|
+
module Types
|
|
9
|
+
module Composition
|
|
10
|
+
include Type
|
|
11
|
+
include Builder
|
|
12
|
+
include Options
|
|
13
|
+
include Meta
|
|
14
|
+
include Printable
|
|
15
|
+
include ::Dry::Equalizer(
|
|
16
|
+
:left, :right, :options, :meta,
|
|
17
|
+
inspect: false,
|
|
18
|
+
immutable: true
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# @return [Type]
|
|
22
|
+
attr_reader :left
|
|
23
|
+
|
|
24
|
+
# @return [Type]
|
|
25
|
+
attr_reader :right
|
|
26
|
+
|
|
27
|
+
module Constrained
|
|
28
|
+
def rule
|
|
29
|
+
left.rule.public_send(self.class.operator, right.rule)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def constrained?
|
|
33
|
+
true
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.included(base)
|
|
38
|
+
composition_name = Inflector.demodulize(base)
|
|
39
|
+
ast_type = Inflector.underscore(composition_name).to_sym
|
|
40
|
+
base.define_singleton_method(:ast_type) { ast_type }
|
|
41
|
+
base.define_singleton_method(:composition_name) { composition_name }
|
|
42
|
+
base.const_set("Constrained", ::Class.new(base) { include Constrained })
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @param [Type] left
|
|
46
|
+
# @param [Type] right
|
|
47
|
+
# @param [Hash] options
|
|
48
|
+
#
|
|
49
|
+
# @api private
|
|
50
|
+
def initialize(left, right, **options)
|
|
51
|
+
super
|
|
52
|
+
@left, @right = left, right
|
|
53
|
+
freeze
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @return [String]
|
|
57
|
+
#
|
|
58
|
+
# @api public
|
|
59
|
+
def name = "#{left.name} #{self.class.operator} #{right.name}"
|
|
60
|
+
|
|
61
|
+
# @return [false]
|
|
62
|
+
#
|
|
63
|
+
# @api public
|
|
64
|
+
def default? = false
|
|
65
|
+
|
|
66
|
+
# @return [false]
|
|
67
|
+
#
|
|
68
|
+
# @api public
|
|
69
|
+
def constrained? = false
|
|
70
|
+
|
|
71
|
+
# @return [Boolean]
|
|
72
|
+
#
|
|
73
|
+
# @api public
|
|
74
|
+
def optional? = false
|
|
75
|
+
|
|
76
|
+
# @param [Object] input
|
|
77
|
+
#
|
|
78
|
+
# @return [Object]
|
|
79
|
+
#
|
|
80
|
+
# @api private
|
|
81
|
+
def call_unsafe(input) = raise ::NotImplementedError
|
|
82
|
+
|
|
83
|
+
# @param [Object] input
|
|
84
|
+
#
|
|
85
|
+
# @return [Object]
|
|
86
|
+
#
|
|
87
|
+
# @api private
|
|
88
|
+
def call_safe(input, &) = raise ::NotImplementedError
|
|
89
|
+
|
|
90
|
+
# @param [Object] input
|
|
91
|
+
#
|
|
92
|
+
# @api public
|
|
93
|
+
def try(input, &) = raise ::NotImplementedError
|
|
94
|
+
|
|
95
|
+
# @api private
|
|
96
|
+
def success(input)
|
|
97
|
+
result = try(input)
|
|
98
|
+
if result.success?
|
|
99
|
+
result
|
|
100
|
+
else
|
|
101
|
+
raise ::ArgumentError, "Invalid success value '#{input}' for #{inspect}"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @api private
|
|
106
|
+
def failure(input, _error = nil)
|
|
107
|
+
result = try(input)
|
|
108
|
+
if result.failure?
|
|
109
|
+
result
|
|
110
|
+
else
|
|
111
|
+
raise ::ArgumentError, "Invalid failure value '#{input}' for #{inspect}"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @param [Object] value
|
|
116
|
+
#
|
|
117
|
+
# @return [Boolean]
|
|
118
|
+
#
|
|
119
|
+
# @api private
|
|
120
|
+
def primitive?(value) = raise ::NotImplementedError
|
|
121
|
+
|
|
122
|
+
# @see Nominal#to_ast
|
|
123
|
+
#
|
|
124
|
+
# @api public
|
|
125
|
+
def to_ast(meta: true)
|
|
126
|
+
[self.class.ast_type,
|
|
127
|
+
[left.to_ast(meta: meta), right.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Wrap the type with a proc
|
|
131
|
+
#
|
|
132
|
+
# @return [Proc]
|
|
133
|
+
#
|
|
134
|
+
# @api public
|
|
135
|
+
def to_proc = proc { |value| self.(value) }
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
class Constrained
|
|
6
|
+
# Common coercion-related API for constrained types
|
|
7
|
+
#
|
|
8
|
+
# @api public
|
|
9
|
+
class Coercible < Constrained
|
|
10
|
+
# @return [Object]
|
|
11
|
+
#
|
|
12
|
+
# @api private
|
|
13
|
+
def call_unsafe(input)
|
|
14
|
+
coerced = type.call_unsafe(input)
|
|
15
|
+
result = rule.(coerced)
|
|
16
|
+
|
|
17
|
+
if result.success?
|
|
18
|
+
coerced
|
|
19
|
+
else
|
|
20
|
+
raise ConstraintError.new(result, input)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @return [Object]
|
|
25
|
+
#
|
|
26
|
+
# @api private
|
|
27
|
+
def call_safe(input)
|
|
28
|
+
coerced = type.call_safe(input) { return yield }
|
|
29
|
+
|
|
30
|
+
if rule[coerced]
|
|
31
|
+
coerced
|
|
32
|
+
else
|
|
33
|
+
yield(coerced)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @see Dry::Types::Constrained#try
|
|
38
|
+
#
|
|
39
|
+
# @api public
|
|
40
|
+
def try(input, &)
|
|
41
|
+
result = type.try(input)
|
|
42
|
+
|
|
43
|
+
if result.success?
|
|
44
|
+
validation = rule.(result.input)
|
|
45
|
+
|
|
46
|
+
if validation.success?
|
|
47
|
+
result
|
|
48
|
+
else
|
|
49
|
+
failure = failure(result.input, ConstraintError.new(validation, input))
|
|
50
|
+
block_given? ? yield(failure) : failure
|
|
51
|
+
end
|
|
52
|
+
else
|
|
53
|
+
block_given? ? yield(result) : result
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|