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,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Solid::Result
4
+ class Contract::ForTypesAndValues
5
+ include Contract::Interface
6
+
7
+ def initialize(types_and_values, config)
8
+ @nil_as_valid_value_checking =
9
+ Config::Options
10
+ .with_defaults(config, :pattern_matching)
11
+ .fetch(:nil_as_valid_value_checking)
12
+
13
+ @types_and_values = types_and_values.transform_keys(&:to_sym)
14
+
15
+ @types_contract = Contract::ForTypes.new(@types_and_values.keys)
16
+ end
17
+
18
+ def allowed_types
19
+ @types_contract.allowed_types
20
+ end
21
+
22
+ def type?(type)
23
+ @types_contract.type?(type)
24
+ end
25
+
26
+ def type!(type)
27
+ @types_contract.type!(type)
28
+ end
29
+
30
+ def type_and_value!(data)
31
+ type, value = data.type, data.value
32
+
33
+ return value if IgnoredTypes.include?(type)
34
+
35
+ value_checking = @types_and_values[type!(type)]
36
+
37
+ checking_result = value_checking === value
38
+
39
+ return value if checking_result || (checking_result.nil? && @nil_as_valid_value_checking)
40
+
41
+ raise Contract::Error::UnexpectedValue.build(type: type, value: value)
42
+ rescue ::NoMatchingPatternError => e
43
+ raise Contract::Error::UnexpectedValue.build(type: data.type, value: data.value, cause: e)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Solid::Result
4
+ module Contract::Interface
5
+ def allowed_types
6
+ raise Error::NotImplemented
7
+ end
8
+
9
+ def type?(_type)
10
+ raise Error::NotImplemented
11
+ end
12
+
13
+ def type!(_type)
14
+ raise Error::NotImplemented
15
+ end
16
+
17
+ def type_and_value!(_data)
18
+ raise Error::NotImplemented
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solid::Result::Contract
4
+ class TypeChecker
5
+ attr_reader :result_type, :expectations
6
+
7
+ def initialize(result_type, expectations:)
8
+ @result_type = result_type
9
+
10
+ @expectations = expectations
11
+ end
12
+
13
+ def allow!(type)
14
+ expectations.type!(type)
15
+ end
16
+
17
+ def allow?(types)
18
+ validate(types, expected: expectations, allow_empty: false)
19
+ end
20
+
21
+ def allow_success?(types)
22
+ validate(types, expected: expectations.success, allow_empty: true)
23
+ end
24
+
25
+ def allow_failure?(types)
26
+ validate(types, expected: expectations.failure, allow_empty: true)
27
+ end
28
+
29
+ private
30
+
31
+ def validate(types, expected:, allow_empty:)
32
+ (allow_empty && types.empty?) || types.any? { |type| expected.type!(type) == result_type }
33
+ end
34
+ end
35
+
36
+ private_constant :TypeChecker
37
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solid::Result::Contract
4
+ require_relative 'contract/error'
5
+ require_relative 'contract/type_checker'
6
+ require_relative 'contract/interface'
7
+ require_relative 'contract/evaluator'
8
+ require_relative 'contract/disabled'
9
+ require_relative 'contract/for_types'
10
+ require_relative 'contract/for_types_and_values'
11
+
12
+ NONE = Evaluator.new(Disabled, Disabled).freeze
13
+
14
+ def self.evaluate(data, contract)
15
+ contract ||= NONE
16
+
17
+ contract.type_and_value!(data)
18
+
19
+ TypeChecker.new(data.type, expectations: contract)
20
+ end
21
+
22
+ ToEnsure = ->(spec, config) do
23
+ return Disabled if spec.nil?
24
+
25
+ spec.is_a?(::Hash) ? ForTypesAndValues.new(spec, config) : ForTypes.new(Array(spec))
26
+ end
27
+
28
+ def self.new(success:, failure:, config:)
29
+ Evaluator.new(ToEnsure[success, config], ToEnsure[failure, config])
30
+ end
31
+
32
+ private_constant :ToEnsure
33
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Solid::Result
4
+ class Data
5
+ attr_reader :kind, :type, :value
6
+
7
+ def initialize(kind, type, value)
8
+ @kind = kind
9
+ @type = type.to_sym
10
+ @value = value
11
+ end
12
+
13
+ def to_h
14
+ { kind: kind, type: type, value: value }
15
+ end
16
+
17
+ def to_a
18
+ [kind, type, value]
19
+ end
20
+
21
+ def inspect
22
+ format(
23
+ '#<%<class_name>s kind=%<kind>p type=%<type>p value=%<value>p>',
24
+ class_name: self.class.name, kind: kind, type: type, value: value
25
+ )
26
+ end
27
+
28
+ alias to_ary to_a
29
+ alias to_hash to_h
30
+ end
31
+
32
+ private_constant :Data
33
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Solid::Result::Error < StandardError
4
+ def self.build(**_kargs)
5
+ new
6
+ end
7
+
8
+ class NotImplemented < self
9
+ end
10
+
11
+ class MissingTypeArgument < self
12
+ def initialize(_message = nil)
13
+ super('A type (argument) is required to invoke the #on/#on_type method')
14
+ end
15
+ end
16
+
17
+ class UnexpectedOutcome < self
18
+ def self.build(outcome:, origin:, expected: nil)
19
+ expected ||= 'Solid::Result::Success or Solid::Result::Failure'
20
+
21
+ new("Unexpected outcome: #{outcome.inspect}. The #{origin} must return this object wrapped by #{expected}")
22
+ end
23
+ end
24
+
25
+ class InvalidResultSource < self
26
+ def self.build(given_result:, expected_source:)
27
+ message =
28
+ "You cannot call #and_then and return a result that does not belong to the same source!\n" \
29
+ "Expected source: #{expected_source.inspect}\n" \
30
+ "Given source: #{given_result.send(:source).inspect}\n" \
31
+ "Given result: #{given_result.inspect}"
32
+
33
+ new(message)
34
+ end
35
+ end
36
+
37
+ class InvalidSourceMethodArity < self
38
+ def self.build(source:, method:, max_arity:)
39
+ new("#{source.class}##{method.name} has unsupported arity (#{method.arity}). Expected 0..#{max_arity}")
40
+ end
41
+ end
42
+
43
+ class UnhandledTypes < self
44
+ def self.build(types:)
45
+ source = types.size == 1 ? 'This was' : 'These were'
46
+
47
+ new("You must handle all cases. #{source} not handled: #{types.map(&:inspect).join(', ')}")
48
+ end
49
+ end
50
+
51
+ class CallableAndThenDisabled < self
52
+ def initialize(_message = nil)
53
+ super(
54
+ 'You cannot use #and_then! as the feature is disabled. ' \
55
+ 'Please use Solid::Result.config.feature.enable!(:and_then!) to enable it.'
56
+ )
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solid::Result::EventLogs
4
+ class Config
5
+ attr_reader :listener, :trace_id
6
+
7
+ def initialize
8
+ @trace_id = -> {}
9
+ @listener = Listener::Null.new
10
+ end
11
+
12
+ def listener=(arg)
13
+ Listener.kind?(arg) or raise ::ArgumentError, "#{arg.inspect} must be a #{Listener}"
14
+
15
+ @listener = arg
16
+ end
17
+
18
+ def trace_id=(arg)
19
+ raise ::ArgumentError, 'must be a lambda with arity 0' unless arg.is_a?(::Proc) && arg.lambda? && arg.arity.zero?
20
+
21
+ @trace_id = arg
22
+ end
23
+
24
+ @instance = new
25
+
26
+ singleton_class.send(:attr_reader, :instance)
27
+ end
28
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solid::Result::EventLogs
4
+ module Listener
5
+ module ClassMethods
6
+ def around_event_logs?
7
+ false
8
+ end
9
+
10
+ def around_and_then?
11
+ false
12
+ end
13
+ end
14
+
15
+ def self.included(base)
16
+ base.extend(ClassMethods)
17
+ end
18
+
19
+ def self.extended(base)
20
+ base.extend(ClassMethods)
21
+ end
22
+
23
+ def self.kind?(arg)
24
+ (arg.is_a?(::Class) && arg < self) || (arg.is_a?(::Module) && arg.is_a?(self)) || arg.is_a?(Listeners::Chain)
25
+ end
26
+
27
+ def on_start(scope:); end
28
+
29
+ def around_event_logs(scope:)
30
+ yield
31
+ end
32
+
33
+ def around_and_then(scope:, and_then:)
34
+ yield
35
+ end
36
+
37
+ def on_record(record:); end
38
+
39
+ def on_finish(event_logs:); end
40
+
41
+ def before_interruption(exception:, event_logs:); end
42
+ end
43
+
44
+ module Listener::Null
45
+ extend Listener
46
+
47
+ def self.new
48
+ self
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solid::Result::EventLogs
4
+ class Listeners
5
+ class Chain
6
+ include Listener
7
+
8
+ attr_reader :listeners
9
+
10
+ def initialize(list)
11
+ if list.empty? || list.any? { !Listener.kind?(_1) }
12
+ raise ArgumentError, "listeners must be a list of #{Listener}"
13
+ end
14
+
15
+ around_and_then = list.select(&:around_and_then?)
16
+ around_event_logs = list.select(&:around_event_logs?)
17
+
18
+ raise ArgumentError, 'only one listener can have around_and_then? == true' if around_and_then.size > 1
19
+ raise ArgumentError, 'only one listener can have around_event_logs? == true' if around_event_logs.size > 1
20
+
21
+ @listeners = { list: list, around_and_then: around_and_then[0], around_event_logs: around_event_logs[0] }
22
+ end
23
+
24
+ def new
25
+ list, around_and_then, around_event_logs = listeners[:list], nil, nil
26
+
27
+ instances = list.map do |item|
28
+ instance = item.new
29
+ around_and_then = instance if listener?(:around_and_then, instance)
30
+ around_event_logs = instance if listener?(:around_event_logs, instance)
31
+
32
+ instance
33
+ end
34
+
35
+ list.one? ? list[0].new : Listeners.send(:new, instances, around_and_then, around_event_logs)
36
+ end
37
+
38
+ private
39
+
40
+ def listener?(name, obj)
41
+ listener = listeners[name]
42
+
43
+ !listener.nil? && (obj.is_a?(listener) || obj == listener)
44
+ end
45
+ end
46
+
47
+ private_class_method :new
48
+
49
+ def self.[](*listeners)
50
+ Chain.new(listeners)
51
+ end
52
+
53
+ attr_reader :listeners, :around_and_then_listener, :around_event_logs_listener
54
+
55
+ private :listeners, :around_and_then_listener, :around_event_logs_listener
56
+
57
+ def initialize(listeners, around_and_then_listener, around_event_logs_listener)
58
+ @listeners = listeners
59
+ @around_and_then_listener = around_and_then_listener || Listener::Null
60
+ @around_event_logs_listener = around_event_logs_listener || Listener::Null
61
+ end
62
+
63
+ def on_start(scope:)
64
+ listeners.each { _1.on_start(scope: scope) }
65
+ end
66
+
67
+ def around_event_logs(scope:, &block)
68
+ around_event_logs_listener.around_event_logs(scope: scope, &block)
69
+ end
70
+
71
+ def around_and_then(scope:, and_then:, &block)
72
+ around_and_then_listener.around_and_then(scope: scope, and_then: and_then, &block)
73
+ end
74
+
75
+ def on_record(record:)
76
+ listeners.each { _1.on_record(record: record) }
77
+ end
78
+
79
+ def on_finish(event_logs:)
80
+ listeners.each { _1.on_finish(event_logs: event_logs) }
81
+ end
82
+
83
+ def before_interruption(exception:, event_logs:)
84
+ listeners.each { _1.before_interruption(exception: exception, event_logs: event_logs) }
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solid::Result::EventLogs
4
+ module Tracking::Disabled
5
+ def self.exec(_name, _desc)
6
+ EnsureResult[yield]
7
+ end
8
+
9
+ def self.record(result); end
10
+
11
+ def self.record_and_then(_type, _data)
12
+ yield
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solid::Result::EventLogs
4
+ class Tracking::Enabled
5
+ attr_accessor :tree, :records, :root_started_at, :listener
6
+
7
+ private :tree, :tree=, :records, :records=, :root_started_at, :root_started_at=, :listener, :listener=
8
+
9
+ def exec(name, desc)
10
+ event_log_node, scope = start(name, desc)
11
+
12
+ result = nil
13
+
14
+ listener.around_event_logs(scope: scope) do
15
+ result = EnsureResult[yield]
16
+ end
17
+
18
+ tree.move_to_root! if event_log_node.root?
19
+
20
+ finish(result)
21
+
22
+ result
23
+ rescue ::Exception => e
24
+ err!(e, event_log_node)
25
+ end
26
+
27
+ def err!(exception, event_log_node)
28
+ if event_log_node.root?
29
+ listener.before_interruption(exception: exception, event_logs: map_event_logs)
30
+
31
+ reset!
32
+ end
33
+
34
+ raise exception
35
+ end
36
+
37
+ def reset!
38
+ self.tree = Tracking::EMPTY_TREE
39
+ end
40
+
41
+ def record(result)
42
+ return if tree.frozen?
43
+
44
+ track(result, time: ::Time.now.getutc)
45
+ end
46
+
47
+ def record_and_then(type_arg, arg)
48
+ return yield if tree.frozen?
49
+
50
+ type = type_arg.instance_of?(::Method) ? :method : type_arg
51
+
52
+ current_and_then = { type: type, arg: arg }
53
+ current_and_then[:method_name] = type_arg.name if type == :method
54
+
55
+ tree.current.value[1] = current_and_then
56
+
57
+ scope, and_then = tree.current_value
58
+
59
+ result = nil
60
+
61
+ listener.around_and_then(scope: scope, and_then: and_then) { result = yield }
62
+
63
+ result
64
+ end
65
+
66
+ def reset_and_then!
67
+ return if tree.frozen?
68
+
69
+ tree.current.value[1] = Tracking::EMPTY_HASH
70
+ end
71
+
72
+ private
73
+
74
+ def start(name, desc)
75
+ name_and_desc = [name, desc]
76
+
77
+ tree.frozen? ? root_start(name_and_desc) : tree.insert!(name_and_desc)
78
+
79
+ scope = tree.current.value[0]
80
+
81
+ listener.on_start(scope: scope)
82
+
83
+ [tree.current, scope]
84
+ end
85
+
86
+ def finish(result)
87
+ node = tree.current
88
+
89
+ tree.move_up!
90
+
91
+ return unless node.root?
92
+
93
+ event_logs = map_event_logs
94
+
95
+ result.send(:event_logs=, event_logs)
96
+
97
+ listener.on_finish(event_logs: event_logs)
98
+
99
+ reset!
100
+ end
101
+
102
+ TreeNodeValueNormalizer = ->(id, (nam, des)) { [{ id: id, name: nam, desc: des }, Tracking::EMPTY_HASH] }
103
+
104
+ def root_start(name_and_desc)
105
+ self.root_started_at = now_in_milliseconds
106
+
107
+ self.listener = build_listener
108
+
109
+ self.records = []
110
+
111
+ self.tree = Tree.new(name_and_desc, normalizer: TreeNodeValueNormalizer)
112
+ end
113
+
114
+ def track(result, time:)
115
+ record = track_record(result, time)
116
+
117
+ records << record
118
+
119
+ listener.on_record(record: record)
120
+
121
+ record
122
+ end
123
+
124
+ def track_record(result, time)
125
+ result_data = result.data.to_h
126
+ result_data[:source] = result.send(:source)
127
+
128
+ root, = tree.root_value
129
+ parent, = tree.parent_value
130
+ current, and_then = tree.current_value
131
+
132
+ { root: root, parent: parent, current: current, result: result_data, and_then: and_then, time: time }
133
+ end
134
+
135
+ def now_in_milliseconds
136
+ ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)
137
+ end
138
+
139
+ def map_event_logs
140
+ duration = (now_in_milliseconds - root_started_at)
141
+
142
+ trace_id = Config.instance.trace_id.call
143
+
144
+ ids = { tree: tree.ids, matrix: tree.ids_matrix, level_parent: tree.ids_level_parent }
145
+
146
+ metadata = { duration: duration, trace_id: trace_id, ids: ids }
147
+
148
+ { version: Tracking::VERSION, records: records, metadata: metadata }
149
+ end
150
+
151
+ def build_listener
152
+ Config.instance.listener.new
153
+ rescue ::StandardError => e
154
+ err = "#{e.message} (#{e.class}); Backtrace: #{e.backtrace&.join(', ')}"
155
+
156
+ warn("Fallback to #{Listener::Null} because registered listener raised an exception: #{err}")
157
+
158
+ Listener::Null.new
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Solid::Result
4
+ module EventLogs
5
+ module Tracking
6
+ require_relative 'tracking/enabled'
7
+ require_relative 'tracking/disabled'
8
+
9
+ VERSION = 1
10
+
11
+ EMPTY_ARRAY = [].freeze
12
+ EMPTY_HASH = {}.freeze
13
+ EMPTY_TREE = Tree.new(nil).freeze
14
+ EMPTY_IDS = { tree: EMPTY_ARRAY, matrix: EMPTY_HASH, level_parent: EMPTY_HASH }.freeze
15
+ EMPTY = {
16
+ version: VERSION,
17
+ records: EMPTY_ARRAY,
18
+ metadata: { duration: 0, ids: EMPTY_IDS, trace_id: nil }.freeze
19
+ }.freeze
20
+
21
+ def self.instance
22
+ ::Solid::Result::Config.instance.feature.enabled?(:event_logs) ? Tracking::Enabled.new : Tracking::Disabled
23
+ end
24
+ end
25
+ end
26
+ end