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.
Files changed (119) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +98 -0
  3. data/.rubocop_todo.yml +12 -0
  4. data/CHANGELOG.md +600 -0
  5. data/CODE_OF_CONDUCT.md +84 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +2691 -0
  8. data/Rakefile +28 -0
  9. data/Steepfile +31 -0
  10. data/examples/multiple_listeners/Rakefile +55 -0
  11. data/examples/multiple_listeners/app/models/account/member.rb +10 -0
  12. data/examples/multiple_listeners/app/models/account/owner_creation.rb +62 -0
  13. data/examples/multiple_listeners/app/models/account.rb +11 -0
  14. data/examples/multiple_listeners/app/models/user/creation.rb +67 -0
  15. data/examples/multiple_listeners/app/models/user/token/creation.rb +51 -0
  16. data/examples/multiple_listeners/app/models/user/token.rb +7 -0
  17. data/examples/multiple_listeners/app/models/user.rb +15 -0
  18. data/examples/multiple_listeners/config/boot.rb +16 -0
  19. data/examples/multiple_listeners/config/initializers/solid_result.rb +9 -0
  20. data/examples/multiple_listeners/config.rb +27 -0
  21. data/examples/multiple_listeners/db/setup.rb +60 -0
  22. data/examples/multiple_listeners/lib/event_logs_listener/stdout.rb +60 -0
  23. data/examples/multiple_listeners/lib/runtime_breaker.rb +11 -0
  24. data/examples/multiple_listeners/lib/solid/result/event_logs_record.rb +27 -0
  25. data/examples/multiple_listeners/lib/solid/result/rollback_on_failure.rb +15 -0
  26. data/examples/service_objects/Rakefile +36 -0
  27. data/examples/service_objects/app/models/account/member.rb +10 -0
  28. data/examples/service_objects/app/models/account.rb +11 -0
  29. data/examples/service_objects/app/models/user/token.rb +7 -0
  30. data/examples/service_objects/app/models/user.rb +15 -0
  31. data/examples/service_objects/app/services/account/owner_creation.rb +47 -0
  32. data/examples/service_objects/app/services/application_service.rb +79 -0
  33. data/examples/service_objects/app/services/user/creation.rb +56 -0
  34. data/examples/service_objects/app/services/user/token/creation.rb +37 -0
  35. data/examples/service_objects/config/boot.rb +17 -0
  36. data/examples/service_objects/config/initializers/solid_result.rb +9 -0
  37. data/examples/service_objects/config.rb +20 -0
  38. data/examples/service_objects/db/setup.rb +49 -0
  39. data/examples/single_listener/Rakefile +92 -0
  40. data/examples/single_listener/app/models/account/member.rb +10 -0
  41. data/examples/single_listener/app/models/account/owner_creation.rb +62 -0
  42. data/examples/single_listener/app/models/account.rb +11 -0
  43. data/examples/single_listener/app/models/user/creation.rb +67 -0
  44. data/examples/single_listener/app/models/user/token/creation.rb +51 -0
  45. data/examples/single_listener/app/models/user/token.rb +7 -0
  46. data/examples/single_listener/app/models/user.rb +15 -0
  47. data/examples/single_listener/config/boot.rb +16 -0
  48. data/examples/single_listener/config/initializers/solid_result.rb +9 -0
  49. data/examples/single_listener/config.rb +23 -0
  50. data/examples/single_listener/db/setup.rb +49 -0
  51. data/examples/single_listener/lib/runtime_breaker.rb +11 -0
  52. data/examples/single_listener/lib/single_event_logs_listener.rb +117 -0
  53. data/examples/single_listener/lib/solid/result/rollback_on_failure.rb +15 -0
  54. data/lib/solid/failure.rb +23 -0
  55. data/lib/solid/output/callable_and_then.rb +40 -0
  56. data/lib/solid/output/expectations/mixin.rb +31 -0
  57. data/lib/solid/output/expectations.rb +25 -0
  58. data/lib/solid/output/failure.rb +9 -0
  59. data/lib/solid/output/mixin.rb +57 -0
  60. data/lib/solid/output/success.rb +37 -0
  61. data/lib/solid/output.rb +115 -0
  62. data/lib/solid/result/_self.rb +198 -0
  63. data/lib/solid/result/callable_and_then/caller.rb +49 -0
  64. data/lib/solid/result/callable_and_then/config.rb +15 -0
  65. data/lib/solid/result/callable_and_then/error.rb +11 -0
  66. data/lib/solid/result/callable_and_then.rb +9 -0
  67. data/lib/solid/result/config/options.rb +27 -0
  68. data/lib/solid/result/config/switcher.rb +82 -0
  69. data/lib/solid/result/config/switchers/addons.rb +25 -0
  70. data/lib/solid/result/config/switchers/constant_aliases.rb +33 -0
  71. data/lib/solid/result/config/switchers/features.rb +32 -0
  72. data/lib/solid/result/config/switchers/pattern_matching.rb +20 -0
  73. data/lib/solid/result/config.rb +64 -0
  74. data/lib/solid/result/contract/disabled.rb +25 -0
  75. data/lib/solid/result/contract/error.rb +17 -0
  76. data/lib/solid/result/contract/evaluator.rb +45 -0
  77. data/lib/solid/result/contract/for_types.rb +29 -0
  78. data/lib/solid/result/contract/for_types_and_values.rb +46 -0
  79. data/lib/solid/result/contract/interface.rb +21 -0
  80. data/lib/solid/result/contract/type_checker.rb +37 -0
  81. data/lib/solid/result/contract.rb +33 -0
  82. data/lib/solid/result/data.rb +33 -0
  83. data/lib/solid/result/error.rb +59 -0
  84. data/lib/solid/result/event_logs/config.rb +28 -0
  85. data/lib/solid/result/event_logs/listener.rb +51 -0
  86. data/lib/solid/result/event_logs/listeners.rb +87 -0
  87. data/lib/solid/result/event_logs/tracking/disabled.rb +15 -0
  88. data/lib/solid/result/event_logs/tracking/enabled.rb +161 -0
  89. data/lib/solid/result/event_logs/tracking.rb +26 -0
  90. data/lib/solid/result/event_logs/tree.rb +141 -0
  91. data/lib/solid/result/event_logs.rb +27 -0
  92. data/lib/solid/result/expectations/mixin.rb +58 -0
  93. data/lib/solid/result/expectations.rb +75 -0
  94. data/lib/solid/result/failure.rb +11 -0
  95. data/lib/solid/result/handler/allowed_types.rb +45 -0
  96. data/lib/solid/result/handler.rb +57 -0
  97. data/lib/solid/result/ignored_types.rb +14 -0
  98. data/lib/solid/result/mixin.rb +72 -0
  99. data/lib/solid/result/success.rb +11 -0
  100. data/lib/solid/result/version.rb +7 -0
  101. data/lib/solid/result.rb +27 -0
  102. data/lib/solid/success.rb +23 -0
  103. data/lib/solid-result.rb +3 -0
  104. data/sig/solid/failure.rbs +13 -0
  105. data/sig/solid/output.rbs +175 -0
  106. data/sig/solid/result/callable_and_then.rbs +60 -0
  107. data/sig/solid/result/config.rbs +102 -0
  108. data/sig/solid/result/contract.rbs +120 -0
  109. data/sig/solid/result/data.rbs +16 -0
  110. data/sig/solid/result/error.rbs +34 -0
  111. data/sig/solid/result/event_logs.rbs +189 -0
  112. data/sig/solid/result/expectations.rbs +71 -0
  113. data/sig/solid/result/handler.rbs +47 -0
  114. data/sig/solid/result/ignored_types.rbs +9 -0
  115. data/sig/solid/result/mixin.rbs +45 -0
  116. data/sig/solid/result/version.rbs +5 -0
  117. data/sig/solid/result.rbs +85 -0
  118. data/sig/solid/success.rbs +13 -0
  119. 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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Solid::Result
4
+ module CallableAndThen
5
+ require_relative 'callable_and_then/error'
6
+ require_relative 'callable_and_then/config'
7
+ require_relative 'callable_and_then/caller'
8
+ end
9
+ 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