solid-result 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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