optimizely-sdk 5.0.0 → 5.0.1

Sign up to get free protection for your applications and to get access to all the features.
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 +558 -558
  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 -340
  10. data/lib/optimizely/config_manager/project_config_manager.rb +25 -25
  11. data/lib/optimizely/config_manager/static_project_config_manager.rb +55 -55
  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 -193
  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 -236
  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 -273
  55. data/lib/optimizely/optimizely_factory.rb +184 -184
  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 +7 -5
@@ -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