solid-result 2.0.0
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/.rubocop.yml +98 -0
- data/.rubocop_todo.yml +12 -0
- data/CHANGELOG.md +600 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +2691 -0
- data/Rakefile +28 -0
- data/Steepfile +31 -0
- data/examples/multiple_listeners/Rakefile +55 -0
- data/examples/multiple_listeners/app/models/account/member.rb +10 -0
- data/examples/multiple_listeners/app/models/account/owner_creation.rb +62 -0
- data/examples/multiple_listeners/app/models/account.rb +11 -0
- data/examples/multiple_listeners/app/models/user/creation.rb +67 -0
- data/examples/multiple_listeners/app/models/user/token/creation.rb +51 -0
- data/examples/multiple_listeners/app/models/user/token.rb +7 -0
- data/examples/multiple_listeners/app/models/user.rb +15 -0
- data/examples/multiple_listeners/config/boot.rb +16 -0
- data/examples/multiple_listeners/config/initializers/solid_result.rb +9 -0
- data/examples/multiple_listeners/config.rb +27 -0
- data/examples/multiple_listeners/db/setup.rb +60 -0
- data/examples/multiple_listeners/lib/event_logs_listener/stdout.rb +60 -0
- data/examples/multiple_listeners/lib/runtime_breaker.rb +11 -0
- data/examples/multiple_listeners/lib/solid/result/event_logs_record.rb +27 -0
- data/examples/multiple_listeners/lib/solid/result/rollback_on_failure.rb +15 -0
- data/examples/service_objects/Rakefile +36 -0
- data/examples/service_objects/app/models/account/member.rb +10 -0
- data/examples/service_objects/app/models/account.rb +11 -0
- data/examples/service_objects/app/models/user/token.rb +7 -0
- data/examples/service_objects/app/models/user.rb +15 -0
- data/examples/service_objects/app/services/account/owner_creation.rb +47 -0
- data/examples/service_objects/app/services/application_service.rb +79 -0
- data/examples/service_objects/app/services/user/creation.rb +56 -0
- data/examples/service_objects/app/services/user/token/creation.rb +37 -0
- data/examples/service_objects/config/boot.rb +17 -0
- data/examples/service_objects/config/initializers/solid_result.rb +9 -0
- data/examples/service_objects/config.rb +20 -0
- data/examples/service_objects/db/setup.rb +49 -0
- data/examples/single_listener/Rakefile +92 -0
- data/examples/single_listener/app/models/account/member.rb +10 -0
- data/examples/single_listener/app/models/account/owner_creation.rb +62 -0
- data/examples/single_listener/app/models/account.rb +11 -0
- data/examples/single_listener/app/models/user/creation.rb +67 -0
- data/examples/single_listener/app/models/user/token/creation.rb +51 -0
- data/examples/single_listener/app/models/user/token.rb +7 -0
- data/examples/single_listener/app/models/user.rb +15 -0
- data/examples/single_listener/config/boot.rb +16 -0
- data/examples/single_listener/config/initializers/solid_result.rb +9 -0
- data/examples/single_listener/config.rb +23 -0
- data/examples/single_listener/db/setup.rb +49 -0
- data/examples/single_listener/lib/runtime_breaker.rb +11 -0
- data/examples/single_listener/lib/single_event_logs_listener.rb +117 -0
- data/examples/single_listener/lib/solid/result/rollback_on_failure.rb +15 -0
- data/lib/solid/failure.rb +23 -0
- data/lib/solid/output/callable_and_then.rb +40 -0
- data/lib/solid/output/expectations/mixin.rb +31 -0
- data/lib/solid/output/expectations.rb +25 -0
- data/lib/solid/output/failure.rb +9 -0
- data/lib/solid/output/mixin.rb +57 -0
- data/lib/solid/output/success.rb +37 -0
- data/lib/solid/output.rb +115 -0
- data/lib/solid/result/_self.rb +198 -0
- data/lib/solid/result/callable_and_then/caller.rb +49 -0
- data/lib/solid/result/callable_and_then/config.rb +15 -0
- data/lib/solid/result/callable_and_then/error.rb +11 -0
- data/lib/solid/result/callable_and_then.rb +9 -0
- data/lib/solid/result/config/options.rb +27 -0
- data/lib/solid/result/config/switcher.rb +82 -0
- data/lib/solid/result/config/switchers/addons.rb +25 -0
- data/lib/solid/result/config/switchers/constant_aliases.rb +33 -0
- data/lib/solid/result/config/switchers/features.rb +32 -0
- data/lib/solid/result/config/switchers/pattern_matching.rb +20 -0
- data/lib/solid/result/config.rb +64 -0
- data/lib/solid/result/contract/disabled.rb +25 -0
- data/lib/solid/result/contract/error.rb +17 -0
- data/lib/solid/result/contract/evaluator.rb +45 -0
- data/lib/solid/result/contract/for_types.rb +29 -0
- data/lib/solid/result/contract/for_types_and_values.rb +46 -0
- data/lib/solid/result/contract/interface.rb +21 -0
- data/lib/solid/result/contract/type_checker.rb +37 -0
- data/lib/solid/result/contract.rb +33 -0
- data/lib/solid/result/data.rb +33 -0
- data/lib/solid/result/error.rb +59 -0
- data/lib/solid/result/event_logs/config.rb +28 -0
- data/lib/solid/result/event_logs/listener.rb +51 -0
- data/lib/solid/result/event_logs/listeners.rb +87 -0
- data/lib/solid/result/event_logs/tracking/disabled.rb +15 -0
- data/lib/solid/result/event_logs/tracking/enabled.rb +161 -0
- data/lib/solid/result/event_logs/tracking.rb +26 -0
- data/lib/solid/result/event_logs/tree.rb +141 -0
- data/lib/solid/result/event_logs.rb +27 -0
- data/lib/solid/result/expectations/mixin.rb +58 -0
- data/lib/solid/result/expectations.rb +75 -0
- data/lib/solid/result/failure.rb +11 -0
- data/lib/solid/result/handler/allowed_types.rb +45 -0
- data/lib/solid/result/handler.rb +57 -0
- data/lib/solid/result/ignored_types.rb +14 -0
- data/lib/solid/result/mixin.rb +72 -0
- data/lib/solid/result/success.rb +11 -0
- data/lib/solid/result/version.rb +7 -0
- data/lib/solid/result.rb +27 -0
- data/lib/solid/success.rb +23 -0
- data/lib/solid-result.rb +3 -0
- data/sig/solid/failure.rbs +13 -0
- data/sig/solid/output.rbs +175 -0
- data/sig/solid/result/callable_and_then.rbs +60 -0
- data/sig/solid/result/config.rbs +102 -0
- data/sig/solid/result/contract.rbs +120 -0
- data/sig/solid/result/data.rbs +16 -0
- data/sig/solid/result/error.rbs +34 -0
- data/sig/solid/result/event_logs.rbs +189 -0
- data/sig/solid/result/expectations.rbs +71 -0
- data/sig/solid/result/handler.rbs +47 -0
- data/sig/solid/result/ignored_types.rbs +9 -0
- data/sig/solid/result/mixin.rbs +45 -0
- data/sig/solid/result/version.rbs +5 -0
- data/sig/solid/result.rbs +85 -0
- data/sig/solid/success.rbs +13 -0
- metadata +167 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Solid::Result
|
|
4
|
+
attr_accessor :unknown, :event_logs
|
|
5
|
+
|
|
6
|
+
attr_reader :source, :data, :type_checker, :terminal
|
|
7
|
+
|
|
8
|
+
protected :source
|
|
9
|
+
|
|
10
|
+
private :unknown, :unknown=, :type_checker, :event_logs=
|
|
11
|
+
|
|
12
|
+
def self.config
|
|
13
|
+
Config.instance
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.configuration(freeze: true)
|
|
17
|
+
yield(config)
|
|
18
|
+
|
|
19
|
+
freeze and config.freeze
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def initialize(type:, value:, source: nil, expectations: nil, terminal: nil)
|
|
23
|
+
data = Data.new(kind, type, value)
|
|
24
|
+
|
|
25
|
+
@type_checker = Contract.evaluate(data, expectations)
|
|
26
|
+
@source = source
|
|
27
|
+
@terminal = kind == :failure || (terminal && !IgnoredTypes.include?(type))
|
|
28
|
+
@data = data
|
|
29
|
+
|
|
30
|
+
self.unknown = true
|
|
31
|
+
self.event_logs = EventLogs::Tracking::EMPTY
|
|
32
|
+
|
|
33
|
+
EventLogs.tracking.record(self)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def terminal?
|
|
37
|
+
terminal
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def type
|
|
41
|
+
data.type
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def value
|
|
45
|
+
data.value
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def type?(arg)
|
|
49
|
+
type_checker.allow!(arg.to_sym) == type
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def success?(_type = nil)
|
|
53
|
+
raise Error::NotImplemented
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def failure?(_type = nil)
|
|
57
|
+
raise Error::NotImplemented
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def value_or(&_block)
|
|
61
|
+
raise Error::NotImplemented
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def on(*types, &block)
|
|
65
|
+
raise Error::MissingTypeArgument if types.empty?
|
|
66
|
+
|
|
67
|
+
tap { known(block) if type_checker.allow?(types) }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def on_success(*types, &block)
|
|
71
|
+
tap { known(block) if type_checker.allow_success?(types) && success? }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def on_failure(*types, &block)
|
|
75
|
+
tap { known(block) if type_checker.allow_failure?(types) && failure? }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def on_unknown
|
|
79
|
+
tap { yield(value, type) if unknown }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def and_then(method_name = nil, injected_value = nil, &block)
|
|
83
|
+
return self if terminal?
|
|
84
|
+
|
|
85
|
+
method_name && block and raise ::ArgumentError, 'method_name and block are mutually exclusive'
|
|
86
|
+
|
|
87
|
+
method_name ? call_and_then_source_method(method_name, injected_value) : call_and_then_block(block)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def and_then!(source, injected_value = nil, _call: nil)
|
|
91
|
+
raise Error::CallableAndThenDisabled unless Config.instance.feature.enabled?(:and_then!)
|
|
92
|
+
|
|
93
|
+
return self if terminal?
|
|
94
|
+
|
|
95
|
+
call_and_then_callable!(source, value: value, injected_value: injected_value, method_name: _call)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def handle
|
|
99
|
+
handler = Handler.new(self, type_checker: type_checker)
|
|
100
|
+
|
|
101
|
+
yield handler
|
|
102
|
+
|
|
103
|
+
handler.send(:outcome)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def ==(other)
|
|
107
|
+
self.class == other.class && type == other.type && value == other.value
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def hash
|
|
111
|
+
[self.class, type, value].hash
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def inspect
|
|
115
|
+
format('#<%<class_name>s type=%<type>p value=%<value>p>', class_name: self.class.name, type: type, value: value)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def deconstruct
|
|
119
|
+
[type, value]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
TYPE_AND_VALUE = %i[type value].freeze
|
|
123
|
+
|
|
124
|
+
def deconstruct_keys(keys)
|
|
125
|
+
output = TYPE_AND_VALUE.each_with_object({}) do |key, hash|
|
|
126
|
+
hash[key] = send(key) if keys.include?(key)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
output.empty? ? value : output
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def method_missing(name, *args, &block)
|
|
133
|
+
name.end_with?('?') ? is?(name.to_s.chomp('?')) : super
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def respond_to_missing?(name, include_private = false)
|
|
137
|
+
name.end_with?('?') || super
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
alias is? type?
|
|
141
|
+
alias eql? ==
|
|
142
|
+
alias on_type on
|
|
143
|
+
|
|
144
|
+
private
|
|
145
|
+
|
|
146
|
+
def kind
|
|
147
|
+
:unknown
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def known(block)
|
|
151
|
+
self.unknown = false
|
|
152
|
+
|
|
153
|
+
block.call(value, type)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def call_and_then_source_method(method_name, injected_value)
|
|
157
|
+
method = source.method(method_name)
|
|
158
|
+
|
|
159
|
+
EventLogs.tracking.record_and_then(method, injected_value) do
|
|
160
|
+
result = call_and_then_source_method!(method, injected_value)
|
|
161
|
+
|
|
162
|
+
ensure_result_object(result, origin: :method)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def call_and_then_source_method!(method, injected_value)
|
|
167
|
+
case method.arity
|
|
168
|
+
when 0 then source.send(method.name)
|
|
169
|
+
when 1 then source.send(method.name, value)
|
|
170
|
+
when 2 then source.send(method.name, value, injected_value)
|
|
171
|
+
else raise Error::InvalidSourceMethodArity.build(source: source, method: method, max_arity: 2)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def call_and_then_block(block)
|
|
176
|
+
EventLogs.tracking.record_and_then(:block, nil) do
|
|
177
|
+
result = call_and_then_block!(block)
|
|
178
|
+
|
|
179
|
+
ensure_result_object(result, origin: :block)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def call_and_then_block!(block)
|
|
184
|
+
block.call(value)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def call_and_then_callable!(source, value:, injected_value:, method_name:)
|
|
188
|
+
CallableAndThen::Caller.call(source, value: value, injected_value: injected_value, method_name: method_name)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def ensure_result_object(result, origin:)
|
|
192
|
+
raise Error::UnexpectedOutcome.build(outcome: result, origin: origin) unless result.is_a?(::Solid::Result)
|
|
193
|
+
|
|
194
|
+
return result if result.source.equal?(source)
|
|
195
|
+
|
|
196
|
+
raise Error::InvalidResultSource.build(given_result: result, expected_source: source)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Solid::Result
|
|
4
|
+
class CallableAndThen::Caller
|
|
5
|
+
def self.call(source, value:, injected_value:, method_name:)
|
|
6
|
+
method = callable_method(source, method_name)
|
|
7
|
+
|
|
8
|
+
EventLogs.tracking.record_and_then(method, injected_value) do
|
|
9
|
+
result =
|
|
10
|
+
if source.is_a?(::Proc)
|
|
11
|
+
call_proc!(source, value, injected_value)
|
|
12
|
+
else
|
|
13
|
+
call_method!(source, method, value, injected_value)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
ensure_result_object(source, value, result)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.call_proc!(source, value, injected_value)
|
|
21
|
+
case source.arity
|
|
22
|
+
when 1 then source.call(value)
|
|
23
|
+
when 2 then source.call(value, injected_value)
|
|
24
|
+
else raise CallableAndThen::Error::InvalidArity.build(source: source, method: :call, arity: '1..2')
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.call_method!(source, method, value, injected_value)
|
|
29
|
+
case method.arity
|
|
30
|
+
when 1 then source.send(method.name, value)
|
|
31
|
+
when 2 then source.send(method.name, value, injected_value)
|
|
32
|
+
else raise CallableAndThen::Error::InvalidArity.build(source: source, method: method.name, arity: '1..2')
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.callable_method(source, method_name)
|
|
37
|
+
source.method(method_name || Config.instance.and_then!.default_method_name_to_call)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.ensure_result_object(source, _value, result)
|
|
41
|
+
return result if result.is_a?(::Solid::Result)
|
|
42
|
+
|
|
43
|
+
raise Error::UnexpectedOutcome.build(outcome: result, origin: source)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private_class_method :new, :allocate
|
|
47
|
+
private_class_method :call_proc!, :call_method!, :callable_method, :ensure_result_object
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Solid::Result
|
|
4
|
+
class CallableAndThen::Config
|
|
5
|
+
attr_accessor :default_method_name_to_call
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
self.default_method_name_to_call = :call
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def options
|
|
12
|
+
{ default_method_name_to_call: default_method_name_to_call }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Solid::Result
|
|
4
|
+
class CallableAndThen::Error < Error
|
|
5
|
+
class InvalidArity < self
|
|
6
|
+
def self.build(source:, method:, arity:)
|
|
7
|
+
new("Invalid arity for #{source.class}##{method} method. Expected arity: #{arity}")
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Solid::Result
|
|
4
|
+
class Config
|
|
5
|
+
module Options
|
|
6
|
+
def self.with_defaults(all_flags, config)
|
|
7
|
+
all_flags ||= {}
|
|
8
|
+
|
|
9
|
+
default_flags = Config.instance.to_h.fetch(config)
|
|
10
|
+
|
|
11
|
+
config_flags = all_flags.fetch(config, {})
|
|
12
|
+
|
|
13
|
+
default_flags.merge(config_flags).slice(*default_flags.keys)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.select(all_flags, config:, from:)
|
|
17
|
+
with_defaults(all_flags, config)
|
|
18
|
+
.filter_map { |name, truthy| [name, from[name]] if truthy }
|
|
19
|
+
.to_h
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.addon(map:, from:)
|
|
23
|
+
select(map, config: :addon, from: from)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Solid::Result
|
|
4
|
+
class Config
|
|
5
|
+
class Switcher
|
|
6
|
+
attr_reader :_options, :_affects, :listener
|
|
7
|
+
|
|
8
|
+
private :_options, :_affects, :listener
|
|
9
|
+
|
|
10
|
+
def initialize(options:, listener: nil)
|
|
11
|
+
@_options = options.transform_values { _1.fetch(:default) }
|
|
12
|
+
@_affects = options.transform_values { _1.fetch(:affects) }
|
|
13
|
+
@listener = listener
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def inspect
|
|
17
|
+
"#<#{self.class.name} options=#{_options.inspect}>"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def freeze
|
|
21
|
+
_options.freeze
|
|
22
|
+
super
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_h
|
|
26
|
+
_options.dup
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def options
|
|
30
|
+
_affects.to_h { |name, affects| [name, { enabled: _options[name], affects: affects }] }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def enabled?(name)
|
|
34
|
+
_options[name] || false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def enable!(*names)
|
|
38
|
+
set_many(names, to: true)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def disable!(*names)
|
|
42
|
+
set_many(names, to: false)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def set_many(names, to:)
|
|
48
|
+
require_option!(names)
|
|
49
|
+
|
|
50
|
+
names.each do |name|
|
|
51
|
+
set_one(name, to)
|
|
52
|
+
|
|
53
|
+
listener&.call(name, to)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
options.slice(*names)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def set_one(name, boolean)
|
|
60
|
+
validate_option!(name)
|
|
61
|
+
|
|
62
|
+
_options[name] = boolean
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def require_option!(names)
|
|
66
|
+
raise ::ArgumentError, "One or more options required. #{available_options_message}" if names.empty?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def validate_option!(name)
|
|
70
|
+
return if _options.key?(name)
|
|
71
|
+
|
|
72
|
+
raise ::ArgumentError, "Invalid option: #{name.inspect}. #{available_options_message}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def available_options_message
|
|
76
|
+
"Available options: #{_options.keys.map(&:inspect).join(', ')}"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private_constant :Switcher
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Solid::Result
|
|
4
|
+
class Config
|
|
5
|
+
module Addons
|
|
6
|
+
AFFECTS = %w[
|
|
7
|
+
Solid::Result.mixin
|
|
8
|
+
Solid::Output.mixin
|
|
9
|
+
Solid::Result::Expectations.mixin
|
|
10
|
+
Solid::Output::Expectations.mixin
|
|
11
|
+
].freeze
|
|
12
|
+
|
|
13
|
+
OPTIONS = {
|
|
14
|
+
continue: { default: false, affects: AFFECTS },
|
|
15
|
+
given: { default: true, affects: AFFECTS }
|
|
16
|
+
}.transform_values!(&:freeze).freeze
|
|
17
|
+
|
|
18
|
+
def self.switcher
|
|
19
|
+
Switcher.new(options: OPTIONS)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private_constant :Addons
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Solid::Result
|
|
4
|
+
class Config
|
|
5
|
+
module ConstantAliases
|
|
6
|
+
MAPPING = {
|
|
7
|
+
'Result' => { target: ::Object, name: :Result, value: ::Solid::Result }
|
|
8
|
+
}.transform_values!(&:freeze).freeze
|
|
9
|
+
|
|
10
|
+
OPTIONS = MAPPING.to_h do |option_name, mapping|
|
|
11
|
+
affects = mapping.fetch(:target).name.freeze
|
|
12
|
+
|
|
13
|
+
[option_name, { default: false, affects: [affects].freeze }]
|
|
14
|
+
end.freeze
|
|
15
|
+
|
|
16
|
+
Listener = ->(option_name, bool) do
|
|
17
|
+
mapping = MAPPING.fetch(option_name)
|
|
18
|
+
|
|
19
|
+
target, name, value = mapping.fetch_values(:target, :name, :value)
|
|
20
|
+
|
|
21
|
+
defined = target.const_defined?(name, false)
|
|
22
|
+
|
|
23
|
+
bool ? defined || target.const_set(name, value) : defined && target.send(:remove_const, name)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.switcher
|
|
27
|
+
Switcher.new(options: OPTIONS, listener: Listener)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private_constant :ConstantAliases
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Solid::Result
|
|
4
|
+
class Config
|
|
5
|
+
module Features
|
|
6
|
+
OPTIONS = {
|
|
7
|
+
expectations: {
|
|
8
|
+
default: true,
|
|
9
|
+
affects: %w[Solid::Result::Expectations Solid::Output::Expectations]
|
|
10
|
+
},
|
|
11
|
+
event_logs: {
|
|
12
|
+
default: true,
|
|
13
|
+
affects: %w[Solid::Result Solid::Output Solid::Result::Expectations Solid::Output::Expectations]
|
|
14
|
+
},
|
|
15
|
+
and_then!: {
|
|
16
|
+
default: false,
|
|
17
|
+
affects: %w[Solid::Result Solid::Output Solid::Result::Expectations Solid::Output::Expectations]
|
|
18
|
+
}
|
|
19
|
+
}.transform_values!(&:freeze).freeze
|
|
20
|
+
|
|
21
|
+
Listener = ->(option_name, _bool) do
|
|
22
|
+
Thread.current[EventLogs::THREAD_VAR_NAME] = nil if option_name == :event_logs
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.switcher
|
|
26
|
+
Switcher.new(options: OPTIONS, listener: Listener)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private_constant :Features
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Solid::Result
|
|
4
|
+
class Config
|
|
5
|
+
module PatternMatching
|
|
6
|
+
OPTIONS = {
|
|
7
|
+
nil_as_valid_value_checking: {
|
|
8
|
+
default: false,
|
|
9
|
+
affects: %w[Solid::Result::Expectations Solid::Output::Expectations]
|
|
10
|
+
}
|
|
11
|
+
}.transform_values!(&:freeze).freeze
|
|
12
|
+
|
|
13
|
+
def self.switcher
|
|
14
|
+
Switcher.new(options: OPTIONS)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private_constant :PatternMatching
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'config/options'
|
|
4
|
+
require_relative 'config/switcher'
|
|
5
|
+
require_relative 'config/switchers/addons'
|
|
6
|
+
require_relative 'config/switchers/constant_aliases'
|
|
7
|
+
require_relative 'config/switchers/features'
|
|
8
|
+
require_relative 'config/switchers/pattern_matching'
|
|
9
|
+
|
|
10
|
+
class Solid::Result
|
|
11
|
+
class Config
|
|
12
|
+
attr_reader :addon, :feature, :constant_alias, :pattern_matching
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@addon = Addons.switcher
|
|
16
|
+
@feature = Features.switcher
|
|
17
|
+
@constant_alias = ConstantAliases.switcher
|
|
18
|
+
@pattern_matching = PatternMatching.switcher
|
|
19
|
+
@and_then_ = CallableAndThen::Config.new
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def event_logs
|
|
23
|
+
EventLogs::Config.instance
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def and_then!
|
|
27
|
+
@and_then_
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def freeze
|
|
31
|
+
addon.freeze
|
|
32
|
+
feature.freeze
|
|
33
|
+
constant_alias.freeze
|
|
34
|
+
pattern_matching.freeze
|
|
35
|
+
and_then!.freeze
|
|
36
|
+
event_logs.freeze
|
|
37
|
+
|
|
38
|
+
super
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def options
|
|
42
|
+
{
|
|
43
|
+
addon: addon,
|
|
44
|
+
feature: feature,
|
|
45
|
+
constant_alias: constant_alias,
|
|
46
|
+
pattern_matching: pattern_matching
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def to_h
|
|
51
|
+
options.transform_values(&:to_h)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def inspect
|
|
55
|
+
"#<#{self.class.name} " \
|
|
56
|
+
"options=#{options.keys.sort.inspect} " \
|
|
57
|
+
"and_then!=#{and_then!.options.inspect}>"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
@instance = new
|
|
61
|
+
|
|
62
|
+
singleton_class.send(:attr_reader, :instance)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Solid::Result
|
|
4
|
+
module Contract::Disabled
|
|
5
|
+
extend Contract::Interface
|
|
6
|
+
|
|
7
|
+
EMPTY_SET = ::Set.new.freeze
|
|
8
|
+
|
|
9
|
+
def self.allowed_types
|
|
10
|
+
EMPTY_SET
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.type?(_type)
|
|
14
|
+
true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.type!(type)
|
|
18
|
+
type
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.type_and_value!(_data); end
|
|
22
|
+
|
|
23
|
+
private_constant :EMPTY_SET
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Solid::Result::Contract::Error < Solid::Result::Error
|
|
4
|
+
class UnexpectedType < self
|
|
5
|
+
def self.build(type:, allowed_types:)
|
|
6
|
+
new("type :#{type} is not allowed. Allowed types: #{allowed_types.map(&:inspect).join(', ')}")
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class UnexpectedValue < self
|
|
11
|
+
def self.build(type:, value:, cause: nil)
|
|
12
|
+
cause_message = " (#{cause.message})" if cause
|
|
13
|
+
|
|
14
|
+
new("value #{value.inspect} is not allowed for :#{type} type#{cause_message}")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Solid::Result
|
|
4
|
+
class Contract::Evaluator
|
|
5
|
+
include Contract::Interface
|
|
6
|
+
|
|
7
|
+
attr_reader :allowed_types, :success, :failure
|
|
8
|
+
|
|
9
|
+
def initialize(success, failure)
|
|
10
|
+
@success = success
|
|
11
|
+
@failure = failure
|
|
12
|
+
|
|
13
|
+
@allowed_types = (success.allowed_types | failure.allowed_types).freeze
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def type?(type)
|
|
17
|
+
success_disabled = success == Contract::Disabled
|
|
18
|
+
failure_disabled = failure == Contract::Disabled
|
|
19
|
+
|
|
20
|
+
return Contract::Disabled.type?(type) if success_disabled && failure_disabled
|
|
21
|
+
|
|
22
|
+
(!success_disabled && success.type?(type)) || (!failure_disabled && failure.type?(type))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def type!(type)
|
|
26
|
+
return type if type?(type)
|
|
27
|
+
|
|
28
|
+
raise Contract::Error::UnexpectedType.build(type: type, allowed_types: allowed_types)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def type_and_value!(data)
|
|
32
|
+
self.for(data).type_and_value!(data)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def for(data)
|
|
38
|
+
case data.kind
|
|
39
|
+
when :unknown then Contract::Disabled
|
|
40
|
+
when :success then success
|
|
41
|
+
else failure
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Solid::Result
|
|
4
|
+
class Contract::ForTypes
|
|
5
|
+
include Contract::Interface
|
|
6
|
+
|
|
7
|
+
attr_reader :allowed_types
|
|
8
|
+
|
|
9
|
+
def initialize(types)
|
|
10
|
+
@allowed_types = Array(types).map(&:to_sym).to_set.freeze
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def type?(type)
|
|
14
|
+
IgnoredTypes.include?(type) || allowed_types.member?(type)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def type!(type)
|
|
18
|
+
return type if type?(type)
|
|
19
|
+
|
|
20
|
+
raise Contract::Error::UnexpectedType.build(type: type, allowed_types: allowed_types)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def type_and_value!(data)
|
|
24
|
+
type!(data.type)
|
|
25
|
+
|
|
26
|
+
nil
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|