optimizely-sdk 5.0.0.pre.beta → 5.0.1

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +202 -202
  3. data/lib/optimizely/audience.rb +127 -127
  4. data/lib/optimizely/bucketer.rb +156 -156
  5. data/lib/optimizely/condition_tree_evaluator.rb +123 -123
  6. data/lib/optimizely/config/datafile_project_config.rb +48 -32
  7. data/lib/optimizely/config/proxy_config.rb +34 -34
  8. data/lib/optimizely/config_manager/async_scheduler.rb +95 -95
  9. data/lib/optimizely/config_manager/http_project_config_manager.rb +340 -338
  10. data/lib/optimizely/config_manager/project_config_manager.rb +25 -25
  11. data/lib/optimizely/config_manager/static_project_config_manager.rb +55 -54
  12. data/lib/optimizely/decide/optimizely_decide_option.rb +28 -28
  13. data/lib/optimizely/decide/optimizely_decision.rb +60 -60
  14. data/lib/optimizely/decide/optimizely_decision_message.rb +26 -26
  15. data/lib/optimizely/decision_service.rb +563 -563
  16. data/lib/optimizely/error_handler.rb +39 -39
  17. data/lib/optimizely/event/batch_event_processor.rb +235 -235
  18. data/lib/optimizely/event/entity/conversion_event.rb +44 -44
  19. data/lib/optimizely/event/entity/decision.rb +38 -38
  20. data/lib/optimizely/event/entity/event_batch.rb +86 -86
  21. data/lib/optimizely/event/entity/event_context.rb +50 -50
  22. data/lib/optimizely/event/entity/impression_event.rb +48 -48
  23. data/lib/optimizely/event/entity/snapshot.rb +33 -33
  24. data/lib/optimizely/event/entity/snapshot_event.rb +48 -48
  25. data/lib/optimizely/event/entity/user_event.rb +22 -22
  26. data/lib/optimizely/event/entity/visitor.rb +36 -36
  27. data/lib/optimizely/event/entity/visitor_attribute.rb +38 -38
  28. data/lib/optimizely/event/event_factory.rb +156 -156
  29. data/lib/optimizely/event/event_processor.rb +25 -25
  30. data/lib/optimizely/event/forwarding_event_processor.rb +44 -44
  31. data/lib/optimizely/event/user_event_factory.rb +88 -88
  32. data/lib/optimizely/event_builder.rb +221 -221
  33. data/lib/optimizely/event_dispatcher.rb +69 -69
  34. data/lib/optimizely/exceptions.rb +193 -149
  35. data/lib/optimizely/helpers/constants.rb +459 -459
  36. data/lib/optimizely/helpers/date_time_utils.rb +30 -30
  37. data/lib/optimizely/helpers/event_tag_utils.rb +132 -132
  38. data/lib/optimizely/helpers/group.rb +31 -31
  39. data/lib/optimizely/helpers/http_utils.rb +68 -68
  40. data/lib/optimizely/helpers/sdk_settings.rb +61 -61
  41. data/lib/optimizely/helpers/validator.rb +236 -238
  42. data/lib/optimizely/helpers/variable_type.rb +67 -67
  43. data/lib/optimizely/logger.rb +46 -46
  44. data/lib/optimizely/notification_center.rb +174 -174
  45. data/lib/optimizely/notification_center_registry.rb +71 -71
  46. data/lib/optimizely/odp/lru_cache.rb +114 -114
  47. data/lib/optimizely/odp/odp_config.rb +102 -102
  48. data/lib/optimizely/odp/odp_event.rb +75 -75
  49. data/lib/optimizely/odp/odp_event_api_manager.rb +70 -70
  50. data/lib/optimizely/odp/odp_event_manager.rb +286 -286
  51. data/lib/optimizely/odp/odp_manager.rb +159 -159
  52. data/lib/optimizely/odp/odp_segment_api_manager.rb +122 -122
  53. data/lib/optimizely/odp/odp_segment_manager.rb +97 -97
  54. data/lib/optimizely/optimizely_config.rb +273 -271
  55. data/lib/optimizely/optimizely_factory.rb +184 -186
  56. data/lib/optimizely/optimizely_user_context.rb +238 -238
  57. data/lib/optimizely/params.rb +31 -31
  58. data/lib/optimizely/project_config.rb +99 -99
  59. data/lib/optimizely/semantic_version.rb +166 -166
  60. data/lib/optimizely/user_condition_evaluator.rb +391 -391
  61. data/lib/optimizely/user_profile_service.rb +35 -35
  62. data/lib/optimizely/version.rb +21 -21
  63. data/lib/optimizely.rb +1262 -1262
  64. metadata +12 -10
@@ -1,159 +1,159 @@
1
- # frozen_string_literal: true
2
-
3
- #
4
- # Copyright 2022, Optimizely and contributors
5
- #
6
- # Licensed under the Apache License, Version 2.0 (the "License");
7
- # you may not use this file except in compliance with the License.
8
- # You may obtain a copy of the License at
9
- #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
- # Unless required by applicable law or agreed to in writing, software
13
- # distributed under the License is distributed on an "AS IS" BASIS,
14
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- # See the License for the specific language governing permissions and
16
- # limitations under the License.
17
- #
18
-
19
- require 'optimizely/logger'
20
- require_relative '../helpers/constants'
21
- require_relative '../helpers/validator'
22
- require_relative '../exceptions'
23
- require_relative 'odp_config'
24
- require_relative 'lru_cache'
25
- require_relative 'odp_segment_manager'
26
- require_relative 'odp_event_manager'
27
-
28
- module Optimizely
29
- class OdpManager
30
- ODP_LOGS = Helpers::Constants::ODP_LOGS
31
- ODP_MANAGER_CONFIG = Helpers::Constants::ODP_MANAGER_CONFIG
32
- ODP_CONFIG_STATE = Helpers::Constants::ODP_CONFIG_STATE
33
-
34
- # update_odp_config must be called to complete initialization
35
- def initialize(
36
- disable:,
37
- segments_cache: nil,
38
- segment_manager: nil,
39
- event_manager: nil,
40
- fetch_segments_timeout: nil,
41
- odp_event_timeout: nil,
42
- odp_flush_interval: nil,
43
- logger: nil
44
- )
45
- @enabled = !disable
46
- @segment_manager = segment_manager
47
- @event_manager = event_manager
48
- @logger = logger || NoOpLogger.new
49
- @odp_config = OdpConfig.new
50
-
51
- unless @enabled
52
- @logger.log(Logger::INFO, ODP_LOGS[:ODP_NOT_ENABLED])
53
- return
54
- end
55
-
56
- unless @segment_manager
57
- segments_cache ||= LRUCache.new(
58
- Helpers::Constants::ODP_SEGMENTS_CACHE_CONFIG[:DEFAULT_CAPACITY],
59
- Helpers::Constants::ODP_SEGMENTS_CACHE_CONFIG[:DEFAULT_TIMEOUT_SECONDS]
60
- )
61
- @segment_manager = Optimizely::OdpSegmentManager.new(segments_cache, nil, @logger, timeout: fetch_segments_timeout)
62
- end
63
-
64
- @event_manager ||= Optimizely::OdpEventManager.new(logger: @logger, request_timeout: odp_event_timeout, flush_interval: odp_flush_interval)
65
-
66
- @segment_manager.odp_config = @odp_config
67
- end
68
-
69
- def fetch_qualified_segments(user_id:, options:)
70
- # Returns qualified segments for the user from the cache or the ODP server if not in the cache.
71
- #
72
- # @param user_id - The user id.
73
- # @param options - An array of OptimizelySegmentOptions used to ignore and/or reset the cache.
74
- #
75
- # @return - Array of qualified segments or nil.
76
- options ||= []
77
- unless @enabled
78
- @logger.log(Logger::ERROR, ODP_LOGS[:ODP_NOT_ENABLED])
79
- return nil
80
- end
81
-
82
- if @odp_config.odp_state == ODP_CONFIG_STATE[:UNDETERMINED]
83
- @logger.log(Logger::ERROR, 'Cannot fetch segments before the datafile has loaded.')
84
- return nil
85
- end
86
-
87
- @segment_manager.fetch_qualified_segments(ODP_MANAGER_CONFIG[:KEY_FOR_USER_ID], user_id, options)
88
- end
89
-
90
- def identify_user(user_id:)
91
- unless @enabled
92
- @logger.log(Logger::DEBUG, 'ODP identify event is not dispatched (ODP disabled).')
93
- return
94
- end
95
-
96
- case @odp_config.odp_state
97
- when ODP_CONFIG_STATE[:UNDETERMINED]
98
- @logger.log(Logger::DEBUG, 'ODP identify event is not dispatched (datafile not ready).')
99
- return
100
- when ODP_CONFIG_STATE[:NOT_INTEGRATED]
101
- @logger.log(Logger::DEBUG, 'ODP identify event is not dispatched (ODP not integrated).')
102
- return
103
- end
104
-
105
- @event_manager.send_event(
106
- type: ODP_MANAGER_CONFIG[:EVENT_TYPE],
107
- action: 'identified',
108
- identifiers: {ODP_MANAGER_CONFIG[:KEY_FOR_USER_ID] => user_id},
109
- data: {}
110
- )
111
- end
112
-
113
- def send_event(type:, action:, identifiers:, data:)
114
- # Send an event to the ODP server.
115
- #
116
- # @param type - the event type.
117
- # @param action - the event action name.
118
- # @param identifiers - a hash for identifiers.
119
- # @param data - a hash for associated data. The default event data will be added to this data before sending to the ODP server.
120
- unless @enabled
121
- @logger.log(Logger::ERROR, ODP_LOGS[:ODP_NOT_ENABLED])
122
- return
123
- end
124
-
125
- unless Helpers::Validator.odp_data_types_valid?(data)
126
- @logger.log(Logger::ERROR, ODP_LOGS[:ODP_INVALID_DATA])
127
- return
128
- end
129
-
130
- @event_manager.send_event(type: type, action: action, identifiers: identifiers, data: data)
131
- end
132
-
133
- def update_odp_config(api_key, api_host, segments_to_check)
134
- # Update the odp config, reset the cache and send signal to the event processor to update its config.
135
- # Start the event manager if odp is integrated.
136
- return unless @enabled
137
-
138
- config_changed = @odp_config.update(api_key, api_host, segments_to_check)
139
- unless config_changed
140
- @logger.log(Logger::DEBUG, 'Odp config was not changed.')
141
- return
142
- end
143
-
144
- @segment_manager.reset
145
-
146
- if @event_manager.running?
147
- @event_manager.update_config
148
- elsif @odp_config.odp_state == ODP_CONFIG_STATE[:INTEGRATED]
149
- @event_manager.start!(@odp_config)
150
- end
151
- end
152
-
153
- def stop!
154
- return unless @enabled
155
-
156
- @event_manager.stop!
157
- end
158
- end
159
- end
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright 2022, Optimizely and contributors
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'optimizely/logger'
20
+ require_relative '../helpers/constants'
21
+ require_relative '../helpers/validator'
22
+ require_relative '../exceptions'
23
+ require_relative 'odp_config'
24
+ require_relative 'lru_cache'
25
+ require_relative 'odp_segment_manager'
26
+ require_relative 'odp_event_manager'
27
+
28
+ module Optimizely
29
+ class OdpManager
30
+ ODP_LOGS = Helpers::Constants::ODP_LOGS
31
+ ODP_MANAGER_CONFIG = Helpers::Constants::ODP_MANAGER_CONFIG
32
+ ODP_CONFIG_STATE = Helpers::Constants::ODP_CONFIG_STATE
33
+
34
+ # update_odp_config must be called to complete initialization
35
+ def initialize(
36
+ disable:,
37
+ segments_cache: nil,
38
+ segment_manager: nil,
39
+ event_manager: nil,
40
+ fetch_segments_timeout: nil,
41
+ odp_event_timeout: nil,
42
+ odp_flush_interval: nil,
43
+ logger: nil
44
+ )
45
+ @enabled = !disable
46
+ @segment_manager = segment_manager
47
+ @event_manager = event_manager
48
+ @logger = logger || NoOpLogger.new
49
+ @odp_config = OdpConfig.new
50
+
51
+ unless @enabled
52
+ @logger.log(Logger::INFO, ODP_LOGS[:ODP_NOT_ENABLED])
53
+ return
54
+ end
55
+
56
+ unless @segment_manager
57
+ segments_cache ||= LRUCache.new(
58
+ Helpers::Constants::ODP_SEGMENTS_CACHE_CONFIG[:DEFAULT_CAPACITY],
59
+ Helpers::Constants::ODP_SEGMENTS_CACHE_CONFIG[:DEFAULT_TIMEOUT_SECONDS]
60
+ )
61
+ @segment_manager = Optimizely::OdpSegmentManager.new(segments_cache, nil, @logger, timeout: fetch_segments_timeout)
62
+ end
63
+
64
+ @event_manager ||= Optimizely::OdpEventManager.new(logger: @logger, request_timeout: odp_event_timeout, flush_interval: odp_flush_interval)
65
+
66
+ @segment_manager.odp_config = @odp_config
67
+ end
68
+
69
+ def fetch_qualified_segments(user_id:, options:)
70
+ # Returns qualified segments for the user from the cache or the ODP server if not in the cache.
71
+ #
72
+ # @param user_id - The user id.
73
+ # @param options - An array of OptimizelySegmentOptions used to ignore and/or reset the cache.
74
+ #
75
+ # @return - Array of qualified segments or nil.
76
+ options ||= []
77
+ unless @enabled
78
+ @logger.log(Logger::ERROR, ODP_LOGS[:ODP_NOT_ENABLED])
79
+ return nil
80
+ end
81
+
82
+ if @odp_config.odp_state == ODP_CONFIG_STATE[:UNDETERMINED]
83
+ @logger.log(Logger::ERROR, 'Cannot fetch segments before the datafile has loaded.')
84
+ return nil
85
+ end
86
+
87
+ @segment_manager.fetch_qualified_segments(ODP_MANAGER_CONFIG[:KEY_FOR_USER_ID], user_id, options)
88
+ end
89
+
90
+ def identify_user(user_id:)
91
+ unless @enabled
92
+ @logger.log(Logger::DEBUG, 'ODP identify event is not dispatched (ODP disabled).')
93
+ return
94
+ end
95
+
96
+ case @odp_config.odp_state
97
+ when ODP_CONFIG_STATE[:UNDETERMINED]
98
+ @logger.log(Logger::DEBUG, 'ODP identify event is not dispatched (datafile not ready).')
99
+ return
100
+ when ODP_CONFIG_STATE[:NOT_INTEGRATED]
101
+ @logger.log(Logger::DEBUG, 'ODP identify event is not dispatched (ODP not integrated).')
102
+ return
103
+ end
104
+
105
+ @event_manager.send_event(
106
+ type: ODP_MANAGER_CONFIG[:EVENT_TYPE],
107
+ action: 'identified',
108
+ identifiers: {ODP_MANAGER_CONFIG[:KEY_FOR_USER_ID] => user_id},
109
+ data: {}
110
+ )
111
+ end
112
+
113
+ def send_event(type:, action:, identifiers:, data:)
114
+ # Send an event to the ODP server.
115
+ #
116
+ # @param type - the event type.
117
+ # @param action - the event action name.
118
+ # @param identifiers - a hash for identifiers.
119
+ # @param data - a hash for associated data. The default event data will be added to this data before sending to the ODP server.
120
+ unless @enabled
121
+ @logger.log(Logger::ERROR, ODP_LOGS[:ODP_NOT_ENABLED])
122
+ return
123
+ end
124
+
125
+ unless Helpers::Validator.odp_data_types_valid?(data)
126
+ @logger.log(Logger::ERROR, ODP_LOGS[:ODP_INVALID_DATA])
127
+ return
128
+ end
129
+
130
+ @event_manager.send_event(type: type, action: action, identifiers: identifiers, data: data)
131
+ end
132
+
133
+ def update_odp_config(api_key, api_host, segments_to_check)
134
+ # Update the odp config, reset the cache and send signal to the event processor to update its config.
135
+ # Start the event manager if odp is integrated.
136
+ return unless @enabled
137
+
138
+ config_changed = @odp_config.update(api_key, api_host, segments_to_check)
139
+ unless config_changed
140
+ @logger.log(Logger::DEBUG, 'Odp config was not changed.')
141
+ return
142
+ end
143
+
144
+ @segment_manager.reset
145
+
146
+ if @event_manager.running?
147
+ @event_manager.update_config
148
+ elsif @odp_config.odp_state == ODP_CONFIG_STATE[:INTEGRATED]
149
+ @event_manager.start!(@odp_config)
150
+ end
151
+ end
152
+
153
+ def stop!
154
+ return unless @enabled
155
+
156
+ @event_manager.stop!
157
+ end
158
+ end
159
+ end
@@ -1,122 +1,122 @@
1
- # frozen_string_literal: true
2
-
3
- #
4
- # Copyright 2022, Optimizely and contributors
5
- #
6
- # Licensed under the Apache License, Version 2.0 (the "License");
7
- # you may not use this file except in compliance with the License.
8
- # You may obtain a copy of the License at
9
- #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
- # Unless required by applicable law or agreed to in writing, software
13
- # distributed under the License is distributed on an "AS IS" BASIS,
14
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- # See the License for the specific language governing permissions and
16
- # limitations under the License.
17
- #
18
-
19
- require 'json'
20
- require_relative '../exceptions'
21
-
22
- module Optimizely
23
- class OdpSegmentApiManager
24
- # Interface that handles fetching audience segments.
25
-
26
- def initialize(logger: nil, proxy_config: nil, timeout: nil)
27
- @logger = logger || NoOpLogger.new
28
- @proxy_config = proxy_config
29
- @timeout = timeout || Optimizely::Helpers::Constants::ODP_GRAPHQL_API_CONFIG[:REQUEST_TIMEOUT]
30
- end
31
-
32
- # Fetch segments from the ODP GraphQL API.
33
- #
34
- # @param api_key - public api key
35
- # @param api_host - domain url of the host
36
- # @param user_key - vuid or fs_user_id (client device id or fullstack id)
37
- # @param user_value - value of user_key
38
- # @param segments_to_check - array of segments to check
39
-
40
- def fetch_segments(api_key, api_host, user_key, user_value, segments_to_check)
41
- url = "#{api_host}/v3/graphql"
42
-
43
- headers = {'Content-Type' => 'application/json', 'x-api-key' => api_key.to_s}
44
-
45
- payload = {
46
- query: 'query($userId: String, $audiences: [String]) {' \
47
- "customer(#{user_key}: $userId) " \
48
- '{audiences(subset: $audiences) {edges {node {name state}}}}}',
49
- variables: {
50
- userId: user_value.to_s,
51
- audiences: segments_to_check || []
52
- }
53
- }.to_json
54
-
55
- begin
56
- response = Helpers::HttpUtils.make_request(
57
- url, :post, payload, headers, @timeout, @proxy_config
58
- )
59
- rescue SocketError, Timeout::Error, Net::ProtocolError, Errno::ECONNRESET => e
60
- @logger.log(Logger::DEBUG, "GraphQL download failed: #{e}")
61
- log_segments_failure('network error')
62
- return nil
63
- rescue Errno::EINVAL, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, HTTPUriError => e
64
- log_segments_failure(e)
65
- return nil
66
- end
67
-
68
- status = response.code.to_i
69
- if status >= 400
70
- log_segments_failure(status)
71
- return nil
72
- end
73
-
74
- begin
75
- response = JSON.parse(response.body)
76
- rescue JSON::ParserError
77
- log_segments_failure('JSON decode error')
78
- return nil
79
- end
80
-
81
- if response.include?('errors')
82
- error = response['errors'].first if response['errors'].is_a? Array
83
- error_code = extract_component(error, 'extensions', 'code')
84
- if error_code == 'INVALID_IDENTIFIER_EXCEPTION'
85
- log_segments_failure('invalid identifier', Logger::WARN)
86
- else
87
- error_class = extract_component(error, 'extensions', 'classification') || 'decode error'
88
- log_segments_failure(error_class)
89
- end
90
- return nil
91
- end
92
-
93
- audiences = extract_component(response, 'data', 'customer', 'audiences', 'edges')
94
- unless audiences
95
- log_segments_failure('decode error')
96
- return nil
97
- end
98
-
99
- audiences.filter_map do |edge|
100
- name = extract_component(edge, 'node', 'name')
101
- state = extract_component(edge, 'node', 'state')
102
- unless name && state
103
- log_segments_failure('decode error')
104
- return nil
105
- end
106
- state == 'qualified' ? name : nil
107
- end
108
- end
109
-
110
- private
111
-
112
- def log_segments_failure(message, level = Logger::ERROR)
113
- @logger.log(level, format(Optimizely::Helpers::Constants::ODP_LOGS[:FETCH_SEGMENTS_FAILED], message))
114
- end
115
-
116
- def extract_component(hash, *components)
117
- hash.dig(*components) if hash.is_a? Hash
118
- rescue TypeError
119
- nil
120
- end
121
- end
122
- end
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright 2022, Optimizely and contributors
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'json'
20
+ require_relative '../exceptions'
21
+
22
+ module Optimizely
23
+ class OdpSegmentApiManager
24
+ # Interface that handles fetching audience segments.
25
+
26
+ def initialize(logger: nil, proxy_config: nil, timeout: nil)
27
+ @logger = logger || NoOpLogger.new
28
+ @proxy_config = proxy_config
29
+ @timeout = timeout || Optimizely::Helpers::Constants::ODP_GRAPHQL_API_CONFIG[:REQUEST_TIMEOUT]
30
+ end
31
+
32
+ # Fetch segments from the ODP GraphQL API.
33
+ #
34
+ # @param api_key - public api key
35
+ # @param api_host - domain url of the host
36
+ # @param user_key - vuid or fs_user_id (client device id or fullstack id)
37
+ # @param user_value - value of user_key
38
+ # @param segments_to_check - array of segments to check
39
+
40
+ def fetch_segments(api_key, api_host, user_key, user_value, segments_to_check)
41
+ url = "#{api_host}/v3/graphql"
42
+
43
+ headers = {'Content-Type' => 'application/json', 'x-api-key' => api_key.to_s}
44
+
45
+ payload = {
46
+ query: 'query($userId: String, $audiences: [String]) {' \
47
+ "customer(#{user_key}: $userId) " \
48
+ '{audiences(subset: $audiences) {edges {node {name state}}}}}',
49
+ variables: {
50
+ userId: user_value.to_s,
51
+ audiences: segments_to_check || []
52
+ }
53
+ }.to_json
54
+
55
+ begin
56
+ response = Helpers::HttpUtils.make_request(
57
+ url, :post, payload, headers, @timeout, @proxy_config
58
+ )
59
+ rescue SocketError, Timeout::Error, Net::ProtocolError, Errno::ECONNRESET => e
60
+ @logger.log(Logger::DEBUG, "GraphQL download failed: #{e}")
61
+ log_segments_failure('network error')
62
+ return nil
63
+ rescue Errno::EINVAL, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, HTTPUriError => e
64
+ log_segments_failure(e)
65
+ return nil
66
+ end
67
+
68
+ status = response.code.to_i
69
+ if status >= 400
70
+ log_segments_failure(status)
71
+ return nil
72
+ end
73
+
74
+ begin
75
+ response = JSON.parse(response.body)
76
+ rescue JSON::ParserError
77
+ log_segments_failure('JSON decode error')
78
+ return nil
79
+ end
80
+
81
+ if response.include?('errors')
82
+ error = response['errors'].first if response['errors'].is_a? Array
83
+ error_code = extract_component(error, 'extensions', 'code')
84
+ if error_code == 'INVALID_IDENTIFIER_EXCEPTION'
85
+ log_segments_failure('invalid identifier', Logger::WARN)
86
+ else
87
+ error_class = extract_component(error, 'extensions', 'classification') || 'decode error'
88
+ log_segments_failure(error_class)
89
+ end
90
+ return nil
91
+ end
92
+
93
+ audiences = extract_component(response, 'data', 'customer', 'audiences', 'edges')
94
+ unless audiences
95
+ log_segments_failure('decode error')
96
+ return nil
97
+ end
98
+
99
+ audiences.filter_map do |edge|
100
+ name = extract_component(edge, 'node', 'name')
101
+ state = extract_component(edge, 'node', 'state')
102
+ unless name && state
103
+ log_segments_failure('decode error')
104
+ return nil
105
+ end
106
+ state == 'qualified' ? name : nil
107
+ end
108
+ end
109
+
110
+ private
111
+
112
+ def log_segments_failure(message, level = Logger::ERROR)
113
+ @logger.log(level, format(Optimizely::Helpers::Constants::ODP_LOGS[:FETCH_SEGMENTS_FAILED], message))
114
+ end
115
+
116
+ def extract_component(hash, *components)
117
+ hash.dig(*components) if hash.is_a? Hash
118
+ rescue TypeError
119
+ nil
120
+ end
121
+ end
122
+ end