rox-rollout 4.7.2 → 5.0.3

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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +34 -13
  3. data/.editorconfig +12 -0
  4. data/.github/workflows/ruby.yml +21 -0
  5. data/.rubocop.yml +17 -0
  6. data/Gemfile +2 -2
  7. data/README.md +32 -0
  8. data/Rakefile +12 -9
  9. data/bin/console +3 -4
  10. data/e2e/container.rb +11 -14
  11. data/e2e/custom_props.rb +9 -9
  12. data/e2e/rox_e2e_test.rb +17 -19
  13. data/e2e/test_vars.rb +3 -6
  14. data/e2e-server/run_server.sh +12 -0
  15. data/e2e-server/server.rb +158 -0
  16. data/example/local.rb +40 -0
  17. data/lib/rox/core/client/buid.rb +8 -8
  18. data/lib/rox/core/client/device_properties.rb +7 -1
  19. data/lib/rox/core/client/dynamic_api.rb +53 -15
  20. data/lib/rox/core/client/internal_flags.rb +14 -2
  21. data/lib/rox/core/client/sdk_settings.rb +1 -1
  22. data/lib/rox/core/configuration/configuration.rb +1 -1
  23. data/lib/rox/core/configuration/configuration_fetched_args.rb +2 -2
  24. data/lib/rox/core/configuration/configuration_fetched_invoker.rb +17 -3
  25. data/lib/rox/core/configuration/configuration_parser.rb +38 -37
  26. data/lib/rox/core/configuration/fetcher_error.rb +1 -1
  27. data/lib/rox/core/configuration/fetcher_status.rb +1 -1
  28. data/lib/rox/core/configuration/models/experiment_model.rb +1 -1
  29. data/lib/rox/core/configuration/models/target_group_model.rb +1 -1
  30. data/lib/rox/core/consts/build.rb +2 -2
  31. data/lib/rox/core/consts/environment.rb +10 -8
  32. data/lib/rox/core/consts/property_type.rb +16 -17
  33. data/lib/rox/core/context/merged_context.rb +2 -1
  34. data/lib/rox/core/core.rb +91 -46
  35. data/lib/rox/core/entities/default_flag_values.rb +10 -0
  36. data/lib/rox/core/entities/flag.rb +22 -7
  37. data/lib/rox/core/entities/flag_setter.rb +6 -6
  38. data/lib/rox/core/entities/rox_double.rb +11 -0
  39. data/lib/rox/core/entities/rox_int.rb +11 -0
  40. data/lib/rox/core/entities/{variant.rb → rox_string.rb} +20 -13
  41. data/lib/rox/core/error_handling/exception_trigger.rb +10 -0
  42. data/lib/rox/core/error_handling/userspace_handler_exception.rb +13 -0
  43. data/lib/rox/core/error_handling/userspace_unhandled_error_invoker.rb +41 -0
  44. data/lib/rox/core/impression/impression_args.rb +2 -2
  45. data/lib/rox/core/impression/impression_invoker.rb +46 -6
  46. data/lib/rox/core/impression/models/experiment.rb +1 -1
  47. data/lib/rox/core/impression/models/reporting_value.rb +10 -2
  48. data/lib/rox/core/logging/logging.rb +3 -3
  49. data/lib/rox/core/logging/no_op_logger.rb +1 -1
  50. data/lib/rox/core/network/configuration_fetcher.rb +2 -2
  51. data/lib/rox/core/network/configuration_fetcher_roxy.rb +2 -2
  52. data/lib/rox/core/network/configuration_fetcher_self_managed.rb +30 -0
  53. data/lib/rox/core/network/configuration_source.rb +1 -1
  54. data/lib/rox/core/network/request.rb +1 -1
  55. data/lib/rox/core/network/request_configuration_builder.rb +5 -5
  56. data/lib/rox/core/network/request_data.rb +1 -1
  57. data/lib/rox/core/network/response.rb +6 -8
  58. data/lib/rox/core/network/state_sender.rb +63 -19
  59. data/lib/rox/core/notifications/notification_listener.rb +4 -4
  60. data/lib/rox/core/properties/custom_property.rb +2 -2
  61. data/lib/rox/core/properties/custom_property_type.rb +1 -1
  62. data/lib/rox/core/properties/device_property.rb +2 -2
  63. data/lib/rox/core/properties/property_factory.rb +132 -0
  64. data/lib/rox/core/register/registerer.rb +13 -12
  65. data/lib/rox/core/reporting/error_reporter.rb +14 -13
  66. data/lib/rox/core/repositories/experiment_repository.rb +2 -4
  67. data/lib/rox/core/repositories/flag_repository.rb +7 -7
  68. data/lib/rox/core/repositories/roxx/experiments_extensions.rb +8 -8
  69. data/lib/rox/core/repositories/roxx/properties_extensions.rb +32 -7
  70. data/lib/rox/core/repositories/target_group_repository.rb +2 -4
  71. data/lib/rox/core/roxx/evaluation_result.rb +2 -0
  72. data/lib/rox/core/roxx/node.rb +1 -1
  73. data/lib/rox/core/roxx/parser.rb +35 -21
  74. data/lib/rox/core/roxx/regular_expression_extensions.rb +6 -3
  75. data/lib/rox/core/roxx/string_tokenizer.rb +3 -1
  76. data/lib/rox/core/roxx/symbols.rb +1 -1
  77. data/lib/rox/core/roxx/token_type.rb +1 -1
  78. data/lib/rox/core/roxx/tokenized_expression.rb +16 -12
  79. data/lib/rox/core/roxx/value_compare_extensions.rb +49 -29
  80. data/lib/rox/core/security/signature_verifier.rb +3 -3
  81. data/lib/rox/core/security/signature_verifier_mock.rb +12 -0
  82. data/lib/rox/core/utils/type_utils.rb +1 -1
  83. data/lib/rox/server/client/server_properties.rb +1 -1
  84. data/lib/rox/server/flags/normalize_flag_type.rb +25 -0
  85. data/lib/rox/server/flags/rox_double.rb +8 -0
  86. data/lib/rox/server/flags/rox_flag.rb +1 -1
  87. data/lib/rox/server/flags/rox_int.rb +8 -0
  88. data/lib/rox/server/flags/rox_string.rb +8 -0
  89. data/lib/rox/server/flags/server_entities_provider.rb +14 -4
  90. data/lib/rox/server/logging/server_logger.rb +2 -2
  91. data/lib/rox/server/rox_options.rb +39 -8
  92. data/lib/rox/server/rox_server.rb +84 -59
  93. data/lib/rox/version.rb +1 -1
  94. data/rox.gemspec +9 -9
  95. metadata +47 -33
  96. data/CODEOWNERS +0 -1
  97. data/README_DEVELOP.md +0 -25
  98. data/_archive/.document +0 -5
  99. data/_archive/.rspec +0 -1
  100. data/_archive/Gemfile +0 -15
  101. data/_archive/Gemfile.lock +0 -87
  102. data/_archive/README.md +0 -32
  103. data/_archive/README.rdoc +0 -19
  104. data/_archive/Rakefile +0 -50
  105. data/_archive/lib/expr_function_definition.rb +0 -52
  106. data/_archive/lib/function_definition.rb +0 -48
  107. data/_archive/lib/function_token.rb +0 -12
  108. data/_archive/lib/object_extends.rb +0 -12
  109. data/_archive/lib/ruby_interpreter.rb +0 -292
  110. data/_archive/lib/stack.rb +0 -48
  111. data/_archive/lib/string_extends.rb +0 -14
  112. data/_archive/spec/ruby_interpreter_spec.rb +0 -203
  113. data/_archive/spec/spec_helper.rb +0 -30
  114. data/_archive/spec/stack_spec.rb +0 -77
  115. data/lib/rox/server/flags/rox_variant.rb +0 -8
data/lib/rox/core/core.rb CHANGED
@@ -11,6 +11,7 @@ require 'rox/core/network/request_configuration_builder'
11
11
  require 'rox/core/network/request'
12
12
  require 'rox/core/network/configuration_fetcher'
13
13
  require 'rox/core/network/configuration_fetcher_roxy'
14
+ require 'rox/core/network/configuration_fetcher_self_managed'
14
15
  require 'rox/core/network/state_sender'
15
16
  require 'rox/core/notifications/notification_listener'
16
17
  require 'rox/core/register/registerer'
@@ -20,8 +21,10 @@ require 'rox/core/entities/flag_setter'
20
21
  require 'rox/core/impression/impression_invoker'
21
22
  require 'rox/core/reporting/error_reporter'
22
23
  require 'rox/core/security/signature_verifier'
24
+ require 'rox/core/security/signature_verifier_mock'
23
25
  require 'rox/core/utils/periodic_task'
24
26
  require 'rox/core/client/dynamic_api'
27
+ require 'rox/core/error_handling/userspace_unhandled_error_invoker'
25
28
 
26
29
  module Rox
27
30
  module Core
@@ -31,14 +34,10 @@ module Rox
31
34
  @custom_property_repository = CustomPropertyRepository.new
32
35
  @target_group_repository = TargetGroupRepository.new
33
36
  @experiment_repository = ExperimentRepository.new
34
- @parser = Parser.new
37
+ @user_unhandled_error_invoker = Rox::Core::UserspaceUnhandledErrorInvoker.new
38
+ @parser = Parser.new(@user_unhandled_error_invoker)
35
39
 
36
- experiments_extensions = ExperimentsExtensions.new(@parser, @target_group_repository, @flag_repository, @experiment_repository)
37
- properties_extensions = PropertiesExtensions.new(@parser, @custom_property_repository)
38
- experiments_extensions.extend
39
- properties_extensions.extend
40
-
41
- @configuration_fetched_invoker = ConfigurationFetchedInvoker.new
40
+ @configuration_fetched_invoker = ConfigurationFetchedInvoker.new(@user_unhandled_error_invoker)
42
41
  @registerer = Registerer.new(@flag_repository)
43
42
 
44
43
  @sdk_settings = nil
@@ -51,65 +50,109 @@ module Rox
51
50
  @push_updates_listener = nil
52
51
  end
53
52
 
54
- def setup(sdk_settings, device_properties, rox_options)
53
+ def userspace_unhandled_error_handler=(handler)
54
+ @user_unhandled_error_invoker.handler = handler
55
+ end
56
+
57
+ def setup(sdk_settings, device_properties)
55
58
  @sdk_settings = sdk_settings
59
+ @rox_options = device_properties.rox_options
56
60
 
57
- roxy_path = rox_options.nil? || rox_options.roxy_url.nil? ? nil : rox_options.roxy_url
61
+ experiments_extensions = ExperimentsExtensions.new(@parser, @target_group_repository, @flag_repository,
62
+ @experiment_repository)
63
+ properties_extensions = PropertiesExtensions.new(@parser, @custom_property_repository, @rox_options.dynamic_property_rule_handler)
64
+ experiments_extensions.extend
65
+ properties_extensions.extend
58
66
 
59
- if roxy_path.nil?
60
- validate_api_key(sdk_settings&.api_key)
61
- end
67
+ roxy_path = @rox_options.nil? || @rox_options.roxy_url.nil? ? nil : @rox_options.roxy_url
68
+
69
+ validate_api_key(sdk_settings&.api_key) if roxy_path.nil?
62
70
 
63
71
  # TODO: Analytics.Analytics.Initialize(deviceProperties.RolloutKey, deviceProperties)
64
- @internal_flags = InternalFlags.new(@experiment_repository, @parser)
72
+ @internal_flags = InternalFlags.new(@experiment_repository, @parser, @rox_options)
65
73
 
66
74
  # TODO: impressionInvoker = new ImpressionInvoker(internalFlags, customPropertyRepository, deviceProperties, Analytics.Analytics.Client, roxyPath != null);
67
- @impression_invoker = ImpressionInvoker.new(@internal_flags, @custom_property_repository, device_properties, nil, !roxy_path.nil?)
75
+ @impression_invoker = ImpressionInvoker.new(@internal_flags, @custom_property_repository, device_properties,
76
+ nil, !roxy_path.nil?, @user_unhandled_error_invoker)
68
77
  @flag_setter = FlagSetter.new(@flag_repository, @parser, @experiment_repository, @impression_invoker)
69
78
  buid = BUID.new(sdk_settings, device_properties, @flag_repository, @custom_property_repository)
70
79
 
71
- request_config_builder = RequestConfigurationBuilder.new(sdk_settings, buid, device_properties, roxy_path)
80
+ request_config_builder = RequestConfigurationBuilder.new(sdk_settings, buid, device_properties)
72
81
 
73
82
  client_request = Request.new
74
83
  err_reporter_request = Request.new
75
84
 
76
85
  @error_reporter = ErrorReporter.new(err_reporter_request, device_properties, buid)
77
86
 
78
- if roxy_path.nil?
79
- @configuration_fetcher = ConfigurationFetcher.new(request_config_builder, client_request, @configuration_fetched_invoker)
80
- @state_sender = StateSender.new(@sdk_settings, device_properties, @flag_repository, @custom_property_repository)
87
+ if @rox_options.self_managed?
88
+ @configuration_fetcher = ConfigurationFetcherSelfManaged.new(request_config_builder, client_request,
89
+ @configuration_fetched_invoker)
90
+ @state_sender = StateSender.new(@sdk_settings, device_properties, @flag_repository,
91
+ @custom_property_repository)
92
+ @flag_repository.register_flag_added_handler { @state_sender.delayed_send }
93
+ @custom_property_repository.register_property_added_handler { @state_sender.delayed_send }
94
+ elsif roxy_path.nil?
95
+ @configuration_fetcher = ConfigurationFetcher.new(request_config_builder, client_request,
96
+ @configuration_fetched_invoker)
97
+ @state_sender = StateSender.new(@sdk_settings, device_properties, @flag_repository,
98
+ @custom_property_repository)
81
99
  @flag_repository.register_flag_added_handler { @state_sender.delayed_send }
82
100
  @custom_property_repository.register_property_added_handler { @state_sender.delayed_send }
83
101
  else
84
- @configuration_fetcher = ConfigurationFetcherRoxy.new(request_config_builder, client_request, @configuration_fetched_invoker)
102
+ @configuration_fetcher = ConfigurationFetcherRoxy.new(request_config_builder, client_request,
103
+ @configuration_fetched_invoker)
85
104
  end
86
105
 
87
106
  configuration_fetched_handler = nil
88
- unless rox_options.nil?
89
- configuration_fetched_handler = rox_options.configuration_fetched_handler
90
- end
107
+ configuration_fetched_handler = @rox_options.configuration_fetched_handler unless @rox_options.nil?
91
108
 
92
- @configuration_fetched_invoker.register_fetched_handler(&wrap_configuration_fetched_handler(&configuration_fetched_handler))
109
+ @configuration_fetched_invoker.register_start_stop_push(proc do |args|
110
+ start_or_stop_push_updated_listener unless args.fetcher_status == FetcherStatus::ERROR_FETCHED_FAILED
111
+ end)
112
+
113
+ @configuration_fetched_invoker.register_fetched_handler(&configuration_fetched_handler)
93
114
 
94
- Thread.new do
115
+ @thread = Thread.new do
95
116
  Thread.current.report_on_exception = false if Thread.current.respond_to?(:report_on_exception)
96
117
  fetch
97
- @state_sender&.delayed_send
118
+ @state_sender&.send
98
119
 
99
- if !rox_options.nil? && !rox_options.impression_handler.nil?
100
- @impression_invoker.register_impression_handler(&rox_options.impression_handler)
120
+ if !@rox_options.nil? && !@rox_options.impression_handler.nil?
121
+ @impression_invoker.register_impression_handler(&@rox_options.impression_handler)
101
122
  end
102
123
 
103
- if !rox_options.nil? && !rox_options.fetch_interval.nil?
104
- PeriodicTask.run(rox_options.fetch_interval) { fetch }
124
+ if !@rox_options.nil? && !@rox_options.fetch_interval.nil?
125
+ PeriodicTask.run(@rox_options.fetch_interval) { fetch }
105
126
  end
106
127
  end
107
128
  end
108
129
 
130
+ def shutdown
131
+ return if @thread.nil?
132
+
133
+ Thread.kill(@thread)
134
+ @thread = nil
135
+
136
+ unless @push_updates_listener.nil?
137
+ @push_updates_listener.stop
138
+ @push_updates_listener = nil
139
+ end
140
+
141
+ #return if @analytics_client.nil?
142
+
143
+ #@analytics_client.flush
144
+ end
145
+
109
146
  def fetch
110
147
  return if @configuration_fetcher.nil?
111
148
 
112
- configuration_parser = ConfigurationParser.new(SignatureVerifier.new, @error_reporter, @configuration_fetched_invoker)
149
+ signature_verifier = if @rox_options.self_managed?
150
+ SignatureVerifierMock.new
151
+ else
152
+ SignatureVerifier.new
153
+ end
154
+ configuration_parser = ConfigurationParser.new(signature_verifier, @error_reporter,
155
+ @configuration_fetched_invoker)
113
156
  result = @configuration_fetcher.fetch
114
157
  return if result.nil?
115
158
 
@@ -122,17 +165,22 @@ module Rox
122
165
 
123
166
  has_changes = @last_configurations.nil? || @last_configurations.data != result.data
124
167
  @last_configurations = result
125
- @configuration_fetched_invoker.invoke(FetcherStatus::APPLIED_FROM_NETWORK, configuration.signature_date, has_changes)
168
+ @configuration_fetched_invoker.invoke(FetcherStatus::APPLIED_FROM_NETWORK, configuration.signature_date,
169
+ has_changes)
170
+ end
171
+
172
+ def register_with_namespace(namespace, rox_container)
173
+ @registerer.register_instance(rox_container, namespace)
126
174
  end
127
175
 
128
- def register(ns, rox_container)
129
- @registerer.register_instance(rox_container, ns)
176
+ def register(*args)
177
+ rox_container = args.pop
178
+ namespace = args.length == 1 ? args.pop : ''
179
+ @registerer.register_instance(rox_container, namespace)
130
180
  end
131
181
 
132
182
  def context=(context)
133
- @flag_repository.all_flags.each do |flag|
134
- flag.context = context
135
- end
183
+ @parser.global_context = context
136
184
  end
137
185
 
138
186
  def add_custom_property(property)
@@ -143,18 +191,11 @@ module Rox
143
191
  @custom_property_repository.add_custom_property_if_not_exists(property)
144
192
  end
145
193
 
146
- def wrap_configuration_fetched_handler(&handler)
147
- lambda do |args|
148
- start_or_stop_push_updated_listener unless args.fetcher_status == FetcherStatus::ERROR_FETCHED_FAILED
149
- handler.call(args) unless handler.nil?
150
- end
151
- end
152
-
153
194
  def start_or_stop_push_updated_listener
154
195
  if @internal_flags.enabled?('rox.internal.pushUpdates')
155
196
  if @push_updates_listener.nil?
156
197
  @push_updates_listener = NotificationListener.new(Environment.notifications_path, @sdk_settings.api_key)
157
- @push_updates_listener.on 'changed' do |data|
198
+ @push_updates_listener.on 'changed' do |_data|
158
199
  fetch
159
200
  end
160
201
  @push_updates_listener.start
@@ -171,12 +212,16 @@ module Rox
171
212
  Rox::Core::DynamicApi.new(@flag_repository, entities_provider)
172
213
  end
173
214
 
215
+ def dump_state
216
+ @state_sender.dump_state
217
+ end
218
+
174
219
  def validate_api_key(api_key)
175
220
  valid_api_key_pattern = /^[a-f\d]{24}$/
176
221
  if api_key&.strip&.empty?
177
- raise ArgumentError.new('Blank Rollout api key - must be specified')
222
+ raise ArgumentError, 'Blank Rollout api key - must be specified'
178
223
  elsif !valid_api_key_pattern.match(api_key)
179
- raise ArgumentError.new('Illegal Rollout api key')
224
+ raise ArgumentError, 'Illegal Rollout api key'
180
225
  end
181
226
  end
182
227
  end
@@ -0,0 +1,10 @@
1
+ module Rox
2
+ module Core
3
+ class DefaultFlagValues
4
+ STRING = ''
5
+ INT = 0
6
+ FLOAT = 0.0
7
+ BOOLEAN = false
8
+ end
9
+ end
10
+ end
@@ -1,20 +1,35 @@
1
- require 'rox/core/entities/variant'
1
+ require 'rox/core/entities/rox_string'
2
+ require 'rox/core/entities/default_flag_values'
2
3
 
3
4
  module Rox
4
5
  module Core
5
- class Flag < Variant
6
+ class Flag < RoxString
6
7
  FLAG_TRUE_VALUE = 'true'.freeze
7
8
  FLAG_FALSE_VALUE = 'false'.freeze
8
9
 
9
- def initialize(default_value = false)
10
- super(default_value ? Flag::FLAG_TRUE_VALUE : Flag::FLAG_FALSE_VALUE, [Flag::FLAG_FALSE_VALUE, Flag::FLAG_TRUE_VALUE])
10
+ def initialize(default_value = DefaultFlagValues::BOOLEAN)
11
+ super(default_value ? Flag::FLAG_TRUE_VALUE : Flag::FLAG_FALSE_VALUE, [Flag::FLAG_FALSE_VALUE,
12
+ Flag::FLAG_TRUE_VALUE])
11
13
  end
12
14
 
13
15
  def enabled?(context)
14
- value(context) == Flag::FLAG_TRUE_VALUE
16
+ merged_context = MergedContext.new(@parser&.global_context, context)
17
+ value = internal_enabled?(merged_context)
18
+ if [true, false].include? value
19
+ send_impressions(value, merged_context)
20
+ return value
21
+ end
22
+
23
+ send_impressions(DefaultFlagValues::BOOLEAN, merged_context)
24
+ DefaultFlagValues.BOOLEAN
25
+ end
26
+
27
+ def value(context = nil)
28
+ merged_context = MergedContext.new(@parser&.global_context, context)
29
+ internal_value(merged_context, false)
15
30
  end
16
31
 
17
- def internal_enabled?(context, nil_instead_of_default)
32
+ def internal_enabled?(context, nil_instead_of_default = false)
18
33
  val = internal_value(context, nil_instead_of_default)
19
34
  nil_instead_of_default && val.nil? ? nil : (val == Flag::FLAG_TRUE_VALUE)
20
35
  end
@@ -28,4 +43,4 @@ module Rox
28
43
  end
29
44
  end
30
45
  end
31
- end
46
+ end
@@ -7,9 +7,9 @@ module Rox
7
7
  @experiment_repository = experiment_repository
8
8
  @impression_invoker = impression_invoker
9
9
 
10
- @flag_repository.register_flag_added_handler do |variant|
11
- exp = @experiment_repository.experiment_by_flag(variant.name)
12
- set_flag_data(variant, exp)
10
+ @flag_repository.register_flag_added_handler do |string|
11
+ exp = @experiment_repository.experiment_by_flag(string.name)
12
+ set_flag_data(string, exp)
13
13
  end
14
14
  end
15
15
 
@@ -31,9 +31,9 @@ module Rox
31
31
  end
32
32
  end
33
33
 
34
- def set_flag_data(variant, experiment = nil)
35
- variant.set_for_evaluation(@parser, experiment, @impression_invoker)
34
+ def set_flag_data(string, experiment = nil)
35
+ string.set_for_evaluation(@parser, experiment, @impression_invoker)
36
36
  end
37
37
  end
38
38
  end
39
- end
39
+ end
@@ -0,0 +1,11 @@
1
+ require 'rox/core/entities/rox_string'
2
+
3
+ module Rox
4
+ module Core
5
+ class RoxDouble < RoxString
6
+ def value(context = nil)
7
+ internal_value(context, false, Float)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'rox/core/entities/rox_string'
2
+
3
+ module Rox
4
+ module Core
5
+ class RoxInt < RoxString
6
+ def value(context = nil)
7
+ internal_value(context, false, Integer)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -4,22 +4,31 @@ require 'rox/core/impression/models/reporting_value'
4
4
 
5
5
  module Rox
6
6
  module Core
7
- class Variant
8
- attr_accessor :default_value, :options, :name, :context, :condition, :parser, :impression_invoker, :client_experiment
7
+ class RoxString
8
+ attr_accessor :default_value, :options, :name, :condition, :parser, :impression_invoker,
9
+ :client_experiment
9
10
 
10
11
  def initialize(default_value, options = [])
11
12
  @default_value = default_value
12
13
  @options = options.clone
14
+ raise ArgumentError, 'options should be an array' unless options.is_a?(Array)
15
+ if options.length > 0 && options.any? { |x| x.class != default_value.class }
16
+ raise ArgumentError, 'options should be the same type as default value'
17
+ end
13
18
  @options << default_value unless options.include?(default_value)
14
19
 
15
20
  @condition = nil
16
21
  @parser = nil
17
- @context = nil
18
22
  @impression_invoker = nil
19
23
  @client_experiment = nil
20
24
  @name = nil
21
25
  end
22
26
 
27
+ def send_impressions(return_value, merged_context)
28
+ reporting_value = ReportingValue.new(@name, return_value, @client_experiment)
29
+ @impression_invoker&.invoke(reporting_value, @client_experiment, merged_context)
30
+ end
31
+
23
32
  def set_for_evaluation(parser, experiment, impression_invoker)
24
33
  if experiment.nil?
25
34
  @client_experiment = nil
@@ -37,24 +46,22 @@ module Rox
37
46
  internal_value(context, false)
38
47
  end
39
48
 
40
- def internal_value(context, nil_instead_of_default)
49
+ def internal_value(context, nil_instead_of_default, evaluated_type = String)
41
50
  return_value = nil_instead_of_default ? nil : @default_value
42
- merged_context = MergedContext.new(@context, context)
51
+ merged_context = MergedContext.new(@parser&.global_context, context)
43
52
 
44
- if !@parser.nil? && !@condition.nil? && !@condition.empty?
53
+ unless @parser.nil? || @condition.nil? || @condition.empty?
45
54
  evaluation_result = @parser.evaluate_expression(@condition, merged_context)
46
55
  unless evaluation_result.nil?
47
- value = evaluation_result.string_value
48
- if !value.nil? && !value.empty?
49
- return_value = value
50
- end
56
+ value = evaluated_type == String ? evaluation_result.string_value : evaluation_result.value
57
+ is_empty = value.is_a?(String) && value.empty?
58
+ return_value = value if !value.nil? && !is_empty
51
59
  end
52
60
  end
53
61
 
54
- @impression_invoker.invoke(ReportingValue.new(@name, return_value), @client_experiment, merged_context) if @impression_invoker != nil
55
-
62
+ send_impressions(return_value, merged_context)
56
63
  return_value
57
64
  end
58
65
  end
59
66
  end
60
- end
67
+ end
@@ -0,0 +1,10 @@
1
+ module Rox
2
+ module Core
3
+ module ExceptionTrigger
4
+ DYNAMIC_PROPERTIES_RULE = 1
5
+ CONFIGURATION_FETCHED_HANDLER = 2
6
+ IMPRESSION_HANDLER = 3
7
+ CUSTOM_PROPERTY_GENERATOR = 4
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ module Rox
2
+ module Core
3
+ class UserspaceHandlerException < StandardError
4
+ attr_accessor :exception_source, :exception_trigger, :original_exception
5
+ def initialize(exception_source, exception_trigger, original_exception)
6
+ @exception_source = exception_source
7
+ @exception_trigger = exception_trigger
8
+ @original_exception = original_exception
9
+ super('user unhandled exception in roxx expression')
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,41 @@
1
+ require 'rox/core/logging/logging'
2
+
3
+ module Rox
4
+ module Core
5
+ class UserspaceUnhandledErrorInvoker
6
+ def initialize(user_unhandler_error_handler = nil)
7
+ @user_unhandler_error_handler = user_unhandler_error_handler
8
+ end
9
+
10
+ def invoke(exception_source, exception_trigger, exception)
11
+ unless @user_unhandler_error_handler
12
+ Logging.logger.error("User Unhandled Error Occurred, no fallback handler was set, exception ignored: #{exception}")
13
+ return
14
+ end
15
+
16
+ begin
17
+ userspace_unhandled_error_args = UserspaceUnhandledErrorArgs.new(exception_source, exception_trigger, exception)
18
+ @user_unhandler_error_handler.call(userspace_unhandled_error_args)
19
+ rescue StandardError => e
20
+ Logging.logger.error("User Unhandled Error Handler itself threw an exception. original exception: #{e}")
21
+ end
22
+ end
23
+
24
+ def handler=(handler)
25
+ @user_unhandler_error_handler = handler
26
+ end
27
+ end
28
+
29
+ class UserspaceUnhandledErrorArgs
30
+ def initialize(exception_source = nil, exception_trigger = nil, exception = nil)
31
+ @exception_source = exception_source
32
+ @exception_trigger = exception_trigger
33
+ @exception = exception
34
+ end
35
+
36
+ def to_s
37
+ "UserspaceUnhandledErrorArgs(#{@exception_source}, #{@exception_trigger}, #{@exception})"
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,5 +1,5 @@
1
1
  module Rox
2
2
  module Core
3
- ImpressionArgs = Struct.new(:reporting_value, :experiment, :context)
3
+ ImpressionArgs = Struct.new(:reporting_value, :context)
4
4
  end
5
- end
5
+ end
@@ -1,23 +1,58 @@
1
1
  require 'rox/core/impression/impression_args'
2
+ require 'rox/core/logging/logging'
3
+ require 'rox/core/error_handling/exception_trigger'
4
+ require 'rox/core/consts/property_type'
2
5
 
3
6
  module Rox
4
7
  module Core
5
8
  class ImpressionInvoker
6
- def initialize(internal_flags, custom_property_repository, device_properties, analytics_client, is_roxy)
9
+ def initialize(internal_flags, custom_property_repository, device_properties, analytics_client, is_roxy, user_unhandled_error_invoker)
7
10
  @internal_flags = internal_flags
8
11
  @custom_property_repository = custom_property_repository
9
12
  @device_properties = device_properties
10
13
  @analytics_client = analytics_client
11
14
  @is_roxy = is_roxy
15
+ @user_unhandled_error_invoker = user_unhandled_error_invoker
12
16
 
13
17
  @impression_handlers = []
14
18
  @mutex = Mutex.new
15
19
  end
16
20
 
17
- def invoke(reporting_value, client_experiment, context)
18
- # TODO: Implement analytics logic
21
+ # TODO: write analytics client and initiate it before using it
22
+ def call_analytics_gateway(reporting_value, stickiness_property, context)
23
+ begin
24
+ analytics_enabled = @internal_flags.enabled?('rox.internal.analytics')
25
+ if analytics_enabled && !@is_roxy
26
+ prop = @custom_property_repository.custom_property(stickiness_property) || @custom_property_repository.custom_property("rox.#{Rox::Core::PropertyType::DISTINCT_ID.name}")
27
+ distinct_id = '(null_distinct_id'
28
+ unless prop.nil?
29
+ prop_value = prop.value(context)
30
+ distinct_id = prop_value if prop_value.instance_of?(String)
31
+ end
19
32
 
20
- raise_impression_event(ImpressionArgs.new(reporting_value, client_experiment, context))
33
+ event_time = (Time.now.to_f * 1000.0).to_i
34
+ begin
35
+ event_time = ENV['rox.analytics.ms'].to_i
36
+ rescue StandardError
37
+ end
38
+
39
+ @analytics_client.track({
40
+ flag: reporting_value.name,
41
+ value: reporting_value.value,
42
+ distinctId: distinct_id,
43
+ experimentVersion: '0',
44
+ type: 'IMPRESSION',
45
+ time: event_time
46
+ })
47
+ end
48
+ rescue StandardError => ex
49
+ Logging.logger.error('Failed to send analytics', ex)
50
+ end
51
+ end
52
+
53
+ def invoke(reporting_value, stickiness_property, context)
54
+ #call_analytics_gateway
55
+ raise_impression_event(ImpressionArgs.new(reporting_value, context))
21
56
  end
22
57
 
23
58
  def register_impression_handler(&block)
@@ -33,9 +68,14 @@ module Rox
33
68
  end
34
69
 
35
70
  handlers.each do |handler|
36
- handler.call(args)
71
+ begin
72
+ handler.call(args)
73
+ rescue StandardError => e
74
+ @user_unhandled_error_invoker.invoke(handler, ExceptionTrigger::IMPRESSION_HANDLER, e)
75
+ Logging.logger.error('Impresssion handler exception', e)
76
+ end
37
77
  end
38
78
  end
39
79
  end
40
80
  end
41
- end
81
+ end
@@ -11,4 +11,4 @@ module Rox
11
11
  end
12
12
  end
13
13
  end
14
- end
14
+ end
@@ -1,5 +1,13 @@
1
1
  module Rox
2
2
  module Core
3
- ReportingValue = Struct.new(:name, :value)
3
+ class ReportingValue
4
+ attr_reader :name, :value, :targeting
5
+
6
+ def initialize(name, value, experiment = nil)
7
+ @name = name
8
+ @value = value
9
+ @targeting = !experiment.nil?
10
+ end
11
+ end
4
12
  end
5
- end
13
+ end
@@ -5,8 +5,8 @@ module Rox
5
5
  class Logging
6
6
  @logger = nil
7
7
 
8
- def self.logger=(logger)
9
- @logger = logger
8
+ class << self
9
+ attr_writer :logger
10
10
  end
11
11
 
12
12
  def self.logger
@@ -14,4 +14,4 @@ module Rox
14
14
  end
15
15
  end
16
16
  end
17
- end
17
+ end
@@ -8,4 +8,4 @@ module Rox
8
8
  def warn(message, ex = nil); end
9
9
  end
10
10
  end
11
- end
11
+ end
@@ -19,8 +19,8 @@ module Rox
19
19
  end
20
20
 
21
21
  write_fetch_error_to_log_and_invoke_fetch_handler(source, fetch_result)
22
- rescue StandardError => ex
23
- write_fetch_exception_to_log_and_invoke_fetch_handler(source, ex)
22
+ rescue StandardError => e
23
+ write_fetch_exception_to_log_and_invoke_fetch_handler(source, e)
24
24
  end
25
25
 
26
26
  nil
@@ -14,8 +14,8 @@ module Rox
14
14
  else
15
15
  write_fetch_error_to_log_and_invoke_fetch_handler(source, fetch_roxy)
16
16
  end
17
- rescue StandardError => ex
18
- write_fetch_exception_to_log_and_invoke_fetch_handler(source, ex)
17
+ rescue StandardError => e
18
+ write_fetch_exception_to_log_and_invoke_fetch_handler(source, e)
19
19
  end
20
20
 
21
21
  nil