rox-rollout 4.7.3 → 5.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 (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 +20 -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-server/run_server.sh +12 -0
  11. data/e2e-server/server.rb +158 -0
  12. data/e2e/container.rb +11 -14
  13. data/e2e/custom_props.rb +9 -9
  14. data/e2e/rox_e2e_test.rb +17 -19
  15. data/e2e/test_vars.rb +3 -6
  16. data/example/local.rb +40 -0
  17. data/lib/rox/core/analytics.rb +18 -0
  18. data/lib/rox/core/client/buid.rb +8 -8
  19. data/lib/rox/core/client/device_properties.rb +7 -1
  20. data/lib/rox/core/client/dynamic_api.rb +53 -15
  21. data/lib/rox/core/client/internal_flags.rb +14 -2
  22. data/lib/rox/core/client/sdk_settings.rb +1 -1
  23. data/lib/rox/core/configuration/configuration.rb +1 -1
  24. data/lib/rox/core/configuration/configuration_fetched_args.rb +2 -2
  25. data/lib/rox/core/configuration/configuration_fetched_invoker.rb +8 -3
  26. data/lib/rox/core/configuration/configuration_parser.rb +38 -37
  27. data/lib/rox/core/configuration/fetcher_error.rb +1 -1
  28. data/lib/rox/core/configuration/fetcher_status.rb +1 -1
  29. data/lib/rox/core/configuration/models/experiment_model.rb +1 -1
  30. data/lib/rox/core/configuration/models/target_group_model.rb +1 -1
  31. data/lib/rox/core/consts/build.rb +2 -2
  32. data/lib/rox/core/consts/environment.rb +10 -8
  33. data/lib/rox/core/consts/property_type.rb +16 -17
  34. data/lib/rox/core/context/merged_context.rb +2 -1
  35. data/lib/rox/core/core.rb +88 -38
  36. data/lib/rox/core/entities/default_flag_values.rb +10 -0
  37. data/lib/rox/core/entities/flag.rb +22 -7
  38. data/lib/rox/core/entities/flag_setter.rb +6 -6
  39. data/lib/rox/core/entities/rox_double.rb +11 -0
  40. data/lib/rox/core/entities/rox_int.rb +11 -0
  41. data/lib/rox/core/entities/{variant.rb → rox_string.rb} +20 -13
  42. data/lib/rox/core/error_handling/exception_trigger.rb +10 -0
  43. data/lib/rox/core/error_handling/userspace_handler_exception.rb +12 -0
  44. data/lib/rox/core/error_handling/userspace_unhandled_error_invoker.rb +41 -0
  45. data/lib/rox/core/impression/impression_args.rb +2 -2
  46. data/lib/rox/core/impression/impression_invoker.rb +41 -7
  47. data/lib/rox/core/impression/models/experiment.rb +1 -1
  48. data/lib/rox/core/impression/models/reporting_value.rb +10 -2
  49. data/lib/rox/core/logging/logging.rb +3 -3
  50. data/lib/rox/core/logging/no_op_logger.rb +1 -1
  51. data/lib/rox/core/network/configuration_fetcher.rb +2 -2
  52. data/lib/rox/core/network/configuration_fetcher_roxy.rb +2 -2
  53. data/lib/rox/core/network/configuration_fetcher_self_managed.rb +30 -0
  54. data/lib/rox/core/network/configuration_source.rb +1 -1
  55. data/lib/rox/core/network/request.rb +1 -1
  56. data/lib/rox/core/network/request_configuration_builder.rb +5 -5
  57. data/lib/rox/core/network/request_data.rb +1 -1
  58. data/lib/rox/core/network/response.rb +6 -8
  59. data/lib/rox/core/network/state_sender.rb +61 -18
  60. data/lib/rox/core/notifications/notification_listener.rb +4 -4
  61. data/lib/rox/core/properties/custom_property.rb +1 -1
  62. data/lib/rox/core/properties/custom_property_type.rb +1 -1
  63. data/lib/rox/core/properties/device_property.rb +2 -2
  64. data/lib/rox/core/properties/property_factory.rb +132 -0
  65. data/lib/rox/core/register/registerer.rb +13 -12
  66. data/lib/rox/core/reporting/error_reporter.rb +14 -13
  67. data/lib/rox/core/repositories/experiment_repository.rb +2 -4
  68. data/lib/rox/core/repositories/flag_repository.rb +7 -7
  69. data/lib/rox/core/repositories/roxx/experiments_extensions.rb +8 -8
  70. data/lib/rox/core/repositories/roxx/properties_extensions.rb +32 -7
  71. data/lib/rox/core/repositories/target_group_repository.rb +2 -4
  72. data/lib/rox/core/roxx/evaluation_result.rb +2 -0
  73. data/lib/rox/core/roxx/node.rb +1 -1
  74. data/lib/rox/core/roxx/parser.rb +35 -21
  75. data/lib/rox/core/roxx/regular_expression_extensions.rb +6 -3
  76. data/lib/rox/core/roxx/string_tokenizer.rb +3 -1
  77. data/lib/rox/core/roxx/symbols.rb +1 -1
  78. data/lib/rox/core/roxx/token_type.rb +1 -1
  79. data/lib/rox/core/roxx/tokenized_expression.rb +16 -12
  80. data/lib/rox/core/roxx/value_compare_extensions.rb +49 -29
  81. data/lib/rox/core/security/signature_verifier.rb +3 -3
  82. data/lib/rox/core/security/signature_verifier_mock.rb +12 -0
  83. data/lib/rox/core/utils/type_utils.rb +1 -1
  84. data/lib/rox/server/client/server_properties.rb +1 -1
  85. data/lib/rox/server/flags/normalize_flag_type.rb +25 -0
  86. data/lib/rox/server/flags/rox_double.rb +8 -0
  87. data/lib/rox/server/flags/rox_flag.rb +1 -1
  88. data/lib/rox/server/flags/rox_int.rb +8 -0
  89. data/lib/rox/server/flags/rox_string.rb +8 -0
  90. data/lib/rox/server/flags/server_entities_provider.rb +14 -4
  91. data/lib/rox/server/logging/server_logger.rb +2 -2
  92. data/lib/rox/server/rox_options.rb +39 -8
  93. data/lib/rox/server/rox_server.rb +80 -59
  94. data/lib/rox/version.rb +1 -1
  95. data/rox.gemspec +11 -9
  96. metadata +66 -37
  97. data/CODEOWNERS +0 -1
  98. data/README_DEVELOP.md +0 -25
  99. data/_archive/.document +0 -5
  100. data/_archive/.rspec +0 -1
  101. data/_archive/Gemfile +0 -15
  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/e2e/rox_e2e_test.rb CHANGED
@@ -1,4 +1,4 @@
1
- require "minitest/autorun"
1
+ require 'minitest/autorun'
2
2
  require 'rox/server/rox_options'
3
3
  require 'rox/server/rox_server'
4
4
  require_relative 'container'
@@ -7,7 +7,7 @@ require_relative 'test_vars'
7
7
 
8
8
  module E2E
9
9
  class Logger
10
- def debug(message, ex = nil)
10
+ def debug(message, _ex = nil)
11
11
  puts 'Before Rox.Setup', message
12
12
  end
13
13
 
@@ -15,7 +15,7 @@ module E2E
15
15
  puts 'Before Rox.Setup', message, ex
16
16
  end
17
17
 
18
- def warn(message, ex = nil)
18
+ def warn(message, _ex = nil)
19
19
  puts 'Before Rox.Setup', message
20
20
  end
21
21
  end
@@ -30,8 +30,8 @@ module E2E
30
30
  end
31
31
 
32
32
  impression_handler = proc do |e|
33
- if !e.nil? && !e.reporting_value.nil?
34
- TestVars.is_impression_raised = true if e.reporting_value.name == 'flag_for_impression'
33
+ if !e.nil? && !e.reporting_value.nil? && (e.reporting_value.name == 'flag_for_impression')
34
+ TestVars.is_impression_raised = true
35
35
  end
36
36
  TestVars.impression_returned_args = e
37
37
  end
@@ -44,7 +44,7 @@ module E2E
44
44
  )
45
45
 
46
46
  @@container = Container.new
47
- Rox::Server::RoxServer.register('', @@container)
47
+ Rox::Server::RoxServer.register(@@container)
48
48
  CustomProps.create_custom_props
49
49
  Rox::Server::RoxServer.setup('5b82864ebc3aec37aff1fdd5', option).join
50
50
 
@@ -56,7 +56,7 @@ module E2E
56
56
  assert_equal false, @@container.simple_flag_overwritten.enabled?
57
57
  end
58
58
 
59
- def test_variant
59
+ def test_string
60
60
  assert_equal 'red', @@container.variant.value
61
61
  end
62
62
 
@@ -87,9 +87,9 @@ module E2E
87
87
  end
88
88
  end
89
89
 
90
- def test_variant_with_context
91
- some_positive_context = {'isDuckAndCover' => true}
92
- some_negative_context = {'isDuckAndCover' => false}
90
+ def test_string_with_context
91
+ some_positive_context = { 'isDuckAndCover' => true }
92
+ some_negative_context = { 'isDuckAndCover' => false }
93
93
 
94
94
  assert_equal 'red', @@container.variant_with_context.value
95
95
 
@@ -121,18 +121,16 @@ module E2E
121
121
  assert_equal true, TestVars.is_impression_raised
122
122
  TestVars.is_impression_raised = false
123
123
 
124
- context = {'var' => 'val'}
124
+ context = { 'var' => 'val' }
125
125
  flag_impression_value = @@container.flag_for_impression_with_experiment_and_context.enabled?(context)
126
126
  refute_nil TestVars.impression_returned_args
127
127
  refute_nil TestVars.impression_returned_args.reporting_value
128
- assert_equal 'true', TestVars.impression_returned_args.reporting_value.value
128
+ assert_equal true, TestVars.impression_returned_args.reporting_value.value
129
129
  assert_equal true, flag_impression_value
130
- assert_equal 'flag_for_impression_with_experiment_and_context', TestVars.impression_returned_args.reporting_value.name
130
+ assert_equal 'flag_for_impression_with_experiment_and_context',
131
+ TestVars.impression_returned_args.reporting_value.name
131
132
 
132
133
  refute_nil TestVars.impression_returned_args
133
- refute_nil TestVars.impression_returned_args.experiment
134
- assert_equal '5b8f85ecbc3aec37aff20841', TestVars.impression_returned_args.experiment.identifier
135
- assert_equal 'flag for impression with experiment and context', TestVars.impression_returned_args.experiment.name
136
134
 
137
135
  assert_equal 'val', TestVars.impression_returned_args.context['var']
138
136
  end
@@ -147,9 +145,9 @@ module E2E
147
145
  assert_equal false, @@container.flag_for_dependency.enabled?
148
146
  end
149
147
 
150
- def test_variant_dependency_with_context
151
- some_positive_context = {'isDuckAndCover' => true}
152
- some_negative_context = {'isDuckAndCover' => false}
148
+ def test_string_dependency_with_context
149
+ some_positive_context = { 'isDuckAndCover' => true }
150
+ some_negative_context = { 'isDuckAndCover' => false }
153
151
 
154
152
  assert_equal 'White', @@container.flag_color_dependent_with_context.value
155
153
  assert_equal 'White', @@container.flag_color_dependent_with_context.value(some_negative_context)
data/e2e/test_vars.rb CHANGED
@@ -1,11 +1,8 @@
1
1
  module E2E
2
2
  module TestVars
3
3
  class << self
4
- attr_accessor :is_computed_boolean_prop_called, :is_computed_string_prop_called, :is_computed_int_prop_called, :is_computed_float_prop_called, :is_computed_semver_prop_called
5
- attr_accessor :target_group1, :target_group2
6
- attr_accessor :is_impression_raised, :is_prop_for_target_group_for_dependency
7
-
8
- attr_accessor :configuration_fetched_count, :impression_returned_args
4
+ attr_accessor :is_computed_boolean_prop_called, :is_computed_string_prop_called, :is_computed_int_prop_called,
5
+ :is_computed_float_prop_called, :is_computed_semver_prop_called, :target_group1, :target_group2, :is_impression_raised, :is_prop_for_target_group_for_dependency, :configuration_fetched_count, :impression_returned_args
9
6
  end
10
7
 
11
8
  @is_computed_boolean_prop_called = false
@@ -21,4 +18,4 @@ module E2E
21
18
  @configuration_fetched_count = 0
22
19
  @impression_returned_args = nil
23
20
  end
24
- end
21
+ end
data/example/local.rb ADDED
@@ -0,0 +1,40 @@
1
+ $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
2
+
3
+ require 'rox/server/rox_server'
4
+ require 'rox/server/rox_options'
5
+
6
+ API_HOST = 'http://localhost:8557'.freeze
7
+ APP_KEY = '600571e330819d4842999e4f'.freeze
8
+ DEV_MODE_SECRET = 'e56cda16749d8d0a9b91d34c'.freeze
9
+
10
+ class Flags
11
+ attr_accessor :boolean_flag, :string_flag, :int_flag, :double_flag
12
+
13
+ def initialize
14
+ # Define the feature flags
15
+ @boolean_flag = Rox::Server::RoxFlag.new(true)
16
+ @string_flag = Rox::Server::RoxString.new('option 1', ['option 1', 'option 2', 'option 3'])
17
+ @int_flag = Rox::Server::RoxInt.new(1, [1, 2, 3])
18
+ @double_flag = Rox::Server::RoxDouble.new(4.0, [5.0, 6.0])
19
+ end
20
+ end
21
+
22
+ flags = Flags.new
23
+ Rox::Server::RoxServer.register(flags)
24
+
25
+ options = Rox::Server::RoxOptions.new(
26
+ self_managed_options: Rox::Server::SelfManagedOptions.new(
27
+ server_url: API_HOST,
28
+ analytics_url: 'http://127.0.0.1:8787'
29
+ ),
30
+ dev_mode_key: DEV_MODE_SECRET
31
+ )
32
+
33
+ Rox::Server::RoxServer.setup(APP_KEY, options)
34
+
35
+ puts "boolean_flag is #{flags.boolean_flag.enabled?}"
36
+ puts "boolean_flag is #{Rox::Server::RoxServer.dynamic_api.enabled?('boolean_flag', true)} (dynamic api)"
37
+ puts "boolean_flag is #{Rox::Server::RoxServer.dynamic_api.enabled?('boolean_flag', true)} (dynamic api)"
38
+ puts "string_flag is #{flags.string_flag.value}"
39
+ puts "int_flag is #{flags.int_flag.value}"
40
+ puts "double_flag is #{flags.double_flag.value}"
@@ -0,0 +1,18 @@
1
+ require 'segment/analytics'
2
+
3
+ module Rox
4
+ module Core
5
+ # Analytics based on segment client
6
+ # https://segment.com/docs/connections/sources/catalog/libraries/server/ruby/
7
+ class Analytics
8
+ attr_reader :client
9
+
10
+ def initialize(key)
11
+ @client = Segment::Analytics.new({
12
+ write_key: key,
13
+ on_error: proc { |_status, msg| print msg }
14
+ })
15
+ end
16
+ end
17
+ end
18
+ end
@@ -6,13 +6,13 @@ module Rox
6
6
  module Core
7
7
  class BUID
8
8
  BUID_GENERATORS = [
9
- PropertyType::PLATFORM,
10
- PropertyType::APP_KEY,
11
- PropertyType::LIB_VERSION,
12
- PropertyType::API_VERSION
9
+ PropertyType::PLATFORM,
10
+ PropertyType::APP_KEY,
11
+ PropertyType::LIB_VERSION,
12
+ PropertyType::API_VERSION
13
13
  ].freeze
14
14
 
15
- def initialize(sdk_settings, device_properties, flag_repository, custom_property_repository)
15
+ def initialize(sdk_settings, device_properties, _flag_repository, _custom_property_repository)
16
16
  @sdk_settings = sdk_settings
17
17
  @device_properties = device_properties
18
18
  @buid = nil
@@ -31,11 +31,11 @@ module Rox
31
31
  end
32
32
 
33
33
  def query_string_parts
34
- generators = BUID::BUID_GENERATORS.map {|pt, _| pt.name}
34
+ generators = BUID::BUID_GENERATORS.map { |pt, _| pt.name }
35
35
 
36
36
  {
37
- PropertyType::BUID.name => value,
38
- PropertyType::BUID_GENERATORS_LIST.name => generators.join(','),
37
+ PropertyType::BUID.name => value,
38
+ PropertyType::BUID_GENERATORS_LIST.name => generators.join(',')
39
39
  }
40
40
  end
41
41
 
@@ -4,11 +4,17 @@ require 'rox/core/consts/build'
4
4
  module Rox
5
5
  module Core
6
6
  class DeviceProperties
7
+ attr_reader :rox_options
8
+
7
9
  def initialize(sdk_settings, rox_options)
8
10
  @sdk_settings = sdk_settings
9
11
  @rox_options = rox_options
10
12
  end
11
13
 
14
+ def get(property)
15
+ all_properties[property.name]
16
+ end
17
+
12
18
  def all_properties
13
19
  {
14
20
  PropertyType::PACKAGE_NAME.name => @rox_options.version,
@@ -20,7 +26,7 @@ module Rox
20
26
  PropertyType::APP_RELEASE.name => @rox_options.version,
21
27
  PropertyType::DISTINCT_ID.name => distinct_id,
22
28
  PropertyType::APP_KEY.name => @sdk_settings.api_key,
23
- PropertyType::PLATFORM.name => Build::PLATFORM,
29
+ PropertyType::PLATFORM.name => Build::PLATFORM
24
30
  }
25
31
  end
26
32
 
@@ -1,4 +1,5 @@
1
1
  require 'rox/core/entities/flag'
2
+ require 'rox/server/flags/normalize_flag_type'
2
3
 
3
4
  module Rox
4
5
  module Core
@@ -9,28 +10,65 @@ module Rox
9
10
  end
10
11
 
11
12
  def enabled?(name, default_value, context = nil)
12
- variant = @flag_repository.flag(name)
13
- if variant.nil?
14
- variant = @entities_provider.create_flag(default_value)
15
- @flag_repository.add_flag(variant, name)
16
- end
13
+ raise ArgumentError, 'flag name should be a string' unless name.is_a?(String)
14
+ raise ArgumentError, 'default value should be boolean' unless [true, false].include?(default_value)
17
15
 
18
- return default_value unless variant.is_a?(Flag)
16
+ string = @flag_repository.flag(name)
17
+ if string.nil?
18
+ string = @entities_provider.create_flag(default_value)
19
+ @flag_repository.add_flag(string, name)
20
+ end
19
21
 
20
- is_enabled = variant.internal_enabled?(context, nil_instead_of_default: true)
21
- is_enabled.nil? ? default_value : is_enabled
22
+ merged_context = MergedContext.new(string.parser&.global_context, context)
23
+ return_value = unless string.is_a?(Flag)
24
+ default_value
25
+ else
26
+ is_enabled = string.internal_enabled?(context, nil_instead_of_default: true)
27
+ is_enabled.nil? ? default_value : is_enabled
28
+ end
29
+ string.send_impressions(return_value, merged_context)
30
+ return_value
22
31
  end
23
32
 
24
33
  def value(name, default_value, context = nil, options = [])
25
- variant = @flag_repository.flag(name)
26
- if variant.nil?
27
- variant = @entities_provider.create_variant(default_value, options)
28
- @flag_repository.add_flag(variant, name)
34
+ generic_value(name, default_value, context, options)
35
+ end
36
+
37
+ def int_value(name, default_value, context = nil, options = [])
38
+ create_method = @entities_provider.method(:create_int)
39
+ normalize_method = Rox::Server::NormalizeFlagType.method(:normalize_int)
40
+ generic_value(name, default_value, context, options, Integer, create_method, normalize_method)
41
+ end
42
+
43
+ def double_value(name, default_value, context = nil, options = [])
44
+ create_method = @entities_provider.method(:create_double)
45
+ normalize_method = Rox::Server::NormalizeFlagType.method(:normalize_float)
46
+ generic_value(name, default_value, context, options, Float, create_method, normalize_method)
47
+ end
48
+
49
+ private
50
+
51
+ def generic_value(name, default_value, context, options, flag_type = String, create_method = nil, normalize_method = Rox::Server::NormalizeFlagType.method(:normalize_string))
52
+ unless name.is_a?(String)
53
+ raise ArgumentError, 'DynamicApi error - name must be string'
54
+ end
55
+
56
+ unless default_value.is_a?(flag_type)
57
+ raise ArgumentError, "DynamicApi default value must be of #{flag_type} type. Received #{default_value}"
58
+ end
59
+
60
+ string = @flag_repository.flag(name)
61
+ if string.nil?
62
+ string = create_method.nil? ? @entities_provider.create_string(default_value, options) : create_method.call(default_value, options)
63
+ @flag_repository.add_flag(string, name)
29
64
  end
30
65
 
31
- value = variant.internal_value(context, nil_instead_of_default: true)
32
- value.nil? ? default_value : value
66
+ merged_context = MergedContext.new(string.parser&.global_context, context)
67
+ value = string.internal_value(context, nil_instead_of_default: true)
68
+ return_value = value.nil? ? normalize_method.call(default_value) : normalize_method.call(value)
69
+ string.send_impressions(return_value, merged_context)
70
+ return_value
33
71
  end
34
72
  end
35
73
  end
36
- end
74
+ end
@@ -3,12 +3,24 @@ require 'rox/core/entities/flag'
3
3
  module Rox
4
4
  module Core
5
5
  class InternalFlags
6
- def initialize(experiment_repository, parser)
6
+ @@defaults_self_managed = {
7
+ 'rox.internal.pushUpdates' => false,
8
+ 'rox.internal.considerThrottleInPush' => false,
9
+ 'rox.internal.throttleFetchInSeconds' => 0
10
+ }
11
+
12
+ def initialize(experiment_repository, parser, rox_options)
7
13
  @experiment_repository = experiment_repository
8
14
  @parser = parser
15
+ @rox_options = rox_options
9
16
  end
10
17
 
11
18
  def enabled?(flag_name)
19
+ if @rox_options.self_managed?
20
+ value = @@defaults_self_managed[flag_name]
21
+ return value unless value.nil?
22
+ end
23
+
12
24
  internal_experiment = @experiment_repository.experiment_by_flag(flag_name)
13
25
  return false if internal_experiment.nil?
14
26
 
@@ -17,4 +29,4 @@ module Rox
17
29
  end
18
30
  end
19
31
  end
20
- end
32
+ end
@@ -2,4 +2,4 @@ module Rox
2
2
  module Core
3
3
  SdkSettings = Struct.new(:api_key, :dev_mode_secret)
4
4
  end
5
- end
5
+ end
@@ -2,4 +2,4 @@ module Rox
2
2
  module Core
3
3
  Configuration = Struct.new(:experiments, :target_groups, :signature_date)
4
4
  end
5
- end
5
+ end
@@ -14,10 +14,10 @@ module Rox
14
14
  end
15
15
 
16
16
  def self.error(error_details)
17
- args = ConfigurationFetchedArgs.new(FetcherStatus::ERROR_FETCHED_FAILED, nil, false )
17
+ args = ConfigurationFetchedArgs.new(FetcherStatus::ERROR_FETCHED_FAILED, nil, false)
18
18
  args.error_details = error_details
19
19
  args
20
20
  end
21
21
  end
22
22
  end
23
- end
23
+ end
@@ -3,9 +3,10 @@ require 'rox/core/configuration/configuration_fetched_args'
3
3
  module Rox
4
4
  module Core
5
5
  class ConfigurationFetchedInvoker
6
- def initialize
6
+ def initialize(user_unhandled_error_invoker)
7
7
  @fetched_handlers = []
8
8
  @mutex = Mutex.new
9
+ @user_unhandled_error_invoker = user_unhandled_error_invoker
9
10
  end
10
11
 
11
12
  def invoke(fetcher_status, creation_date, has_changes)
@@ -29,9 +30,13 @@ module Rox
29
30
  end
30
31
 
31
32
  handlers.each do |handler|
32
- handler.call(args)
33
+ begin
34
+ handler.call(args)
35
+ rescue StandardError => e
36
+ user_unhandled_error_invoker.invoke(handler, Rox::Core::CONFIGURATION_FETCHED_HANDLER, e)
37
+ end
33
38
  end
34
39
  end
35
40
  end
36
41
  end
37
- end
42
+ end
@@ -17,50 +17,51 @@ module Rox
17
17
  end
18
18
 
19
19
  def parse(fetch_result, sdk_settings)
20
- begin
21
- if fetch_result.nil? || fetch_result.data.nil? || fetch_result.data.empty?
22
- @configuration_fetched_invoker.invoke_error(FetcherError::EMPTY_JSON)
23
- @error_reporter.report('Failed to parse JSON configuration - Null Or Empty', ArgumentError.new('data'))
24
- return nil
25
- end
26
-
27
- begin
28
- json_obj = JSON.parse(fetch_result.data)
29
- rescue StandardError => ex
30
- @configuration_fetched_invoker.invoke_error(FetcherError::CORRUPTED_JSON)
31
- @error_reporter.report('Failed to parse JSON configuration', ex)
32
- return nil
33
- end
34
-
35
- if fetch_result.source != ConfigurationSource::ROXY && !@signature_verifier.verify(json_obj['data'], json_obj['signature_v0'])
36
- @configuration_fetched_invoker.invoke_error(FetcherError::SIGNATURE_VERIFICATION_ERROR)
37
- @error_reporter.report('Failed to validate signature', StandardError.new("Data : #{json_obj['data']} Signature : #{json_obj['signature_v0']}"))
38
- return nil
39
- end
20
+ if fetch_result.nil? || fetch_result.data.nil? || fetch_result.data.empty?
21
+ @configuration_fetched_invoker.invoke_error(FetcherError::EMPTY_JSON)
22
+ @error_reporter.report('Failed to parse JSON configuration - Null Or Empty', ArgumentError.new('data'))
23
+ return nil
24
+ end
40
25
 
41
- signature_date = DateTime.rfc3339(json_obj['signed_date']).to_time
42
- internal_data_string = json_obj['data']
43
- internal_data_object = JSON.parse(internal_data_string)
26
+ begin
27
+ json_obj = JSON.parse(fetch_result.data)
28
+ rescue StandardError => e
29
+ @configuration_fetched_invoker.invoke_error(FetcherError::CORRUPTED_JSON)
30
+ @error_reporter.report('Failed to parse JSON configuration', e)
31
+ return nil
32
+ end
44
33
 
45
- if fetch_result.source != ConfigurationSource::ROXY && internal_data_object['application'] != sdk_settings.api_key
46
- @configuration_fetched_invoker.invoke_error(FetcherError::MISMATCH_APP_KEY)
47
- @error_reporter.report('Failed to parse JSON configuration - ', StandardError.new("Internal Data: #{internal_data_object['application']} SdkSettings: #{sdk_settings.api_key}"))
48
- return nil
49
- end
34
+ if fetch_result.source != ConfigurationSource::ROXY && !@signature_verifier.verify(json_obj['data'],
35
+ json_obj['signature_v0'])
36
+ @configuration_fetched_invoker.invoke_error(FetcherError::SIGNATURE_VERIFICATION_ERROR)
37
+ @error_reporter.report('Failed to validate signature',
38
+ StandardError.new("Data : #{json_obj['data']} Signature : #{json_obj['signature_v0']}"))
39
+ return nil
40
+ end
50
41
 
51
- experiments = parse_experiments(internal_data_object)
52
- target_groups = parse_target_groups(internal_data_object)
42
+ signature_date = DateTime.rfc3339(json_obj['signed_date']).to_time
43
+ internal_data_string = json_obj['data']
44
+ internal_data_object = JSON.parse(internal_data_string)
53
45
 
54
- Configuration.new(experiments, target_groups, signature_date)
55
- rescue StandardError => ex
56
- Logging.logger.error('Failed to parse configurations', ex)
57
- @configuration_fetched_invoker.invoke_error(FetcherError::UNKNOWN)
46
+ if fetch_result.source != ConfigurationSource::ROXY && internal_data_object['application'] != sdk_settings.api_key
47
+ @configuration_fetched_invoker.invoke_error(FetcherError::MISMATCH_APP_KEY)
48
+ @error_reporter.report('Failed to parse JSON configuration - ',
49
+ StandardError.new("Internal Data: #{internal_data_object['application']} SdkSettings: #{sdk_settings.api_key}"))
58
50
  return nil
59
51
  end
52
+
53
+ experiments = parse_experiments(internal_data_object)
54
+ target_groups = parse_target_groups(internal_data_object)
55
+
56
+ Configuration.new(experiments, target_groups, signature_date)
57
+ rescue StandardError => e
58
+ Logging.logger.error('Failed to parse configurations', e)
59
+ @configuration_fetched_invoker.invoke_error(FetcherError::UNKNOWN)
60
+ nil
60
61
  end
61
62
 
62
63
  def parse_experiments(data)
63
- data['experiments'].map { |e| parse_experiment(e)}
64
+ data['experiments'].map { |e| parse_experiment(e) }
64
65
  end
65
66
 
66
67
  def parse_experiment(data)
@@ -69,12 +70,12 @@ module Rox
69
70
  name = data['name']
70
71
  id = data['_id']
71
72
  labels = data['labels'] || []
72
- flags = data['featureFlags'].map {|f| f['name']}
73
+ flags = data['featureFlags'].map { |f| f['name'] }
73
74
  ExperimentModel.new(id, name, condition, is_archived, flags, labels)
74
75
  end
75
76
 
76
77
  def parse_target_groups(data)
77
- data['targetGroups'].map { |g| parse_target_group(g)}
78
+ data['targetGroups'].map { |g| parse_target_group(g) }
78
79
  end
79
80
 
80
81
  def parse_target_group(data)