1616# limitations under the License.
1717#
1818module Optimizely
19- # Default constants for CMAB requests
19+ # Default constants for CMAB requests
2020 DEFAULT_MAX_RETRIES = 3
21- DEFAULT_INITIAL_BACKOFF = 0.1 # in seconds (100 ms)
22- DEFAULT_MAX_BACKOFF = 10 # in seconds
21+ DEFAULT_INITIAL_BACKOFF = 0.1 # in seconds (100 ms)
22+ DEFAULT_MAX_BACKOFF = 10 # in seconds
2323 DEFAULT_BACKOFF_MULTIPLIER = 2.0
2424 MAX_WAIT_TIME = 10.0
2525
@@ -39,7 +39,7 @@ class DefaultCmabClient
3939 # Client for interacting with the CMAB service.
4040 # Provides methods to fetch decisions with optional retry logic.
4141
42- def initialize ( http_client = nil , retry_config = nil , logger = nil )
42+ def initialize ( http_client = nil , retry_config = nil , logger = nil )
4343 # Initialize the CMAB client.
4444 # Args:
4545 # http_client: HTTP client for making requests.
@@ -61,110 +61,108 @@ def fetch_decision(rule_id, user_id, attributes, cmab_uuid, timeout: MAX_WAIT_TI
6161 # Returns:
6262 # The variation ID.
6363 url = "https://prediction.cmab.optimizely.com/predict/#{ rule_id } "
64- cmab_attributes = attributes . map { |key , value | { id : key , value : value } }
64+ cmab_attributes = attributes . map { |key , value | { id : key , value : value } }
6565
6666 request_body = {
6767 instances : [ {
68- visitorId : user_id ,
69- experimentId : rule_id ,
70- attributes : cmab_attributes ,
71- cmabUUID : cmab_uuid ,
68+ visitorId : user_id ,
69+ experimentId : rule_id ,
70+ attributes : cmab_attributes ,
71+ cmabUUID : cmab_uuid
7272 } ]
7373 }
7474
75- if @retry_config
76- variation_id = _do_fetch_with_retry ( url , request_body , @retry_config , timeout )
75+ variation_id = if @retry_config
76+ _do_fetch_with_retry ( url , request_body , @retry_config , timeout )
7777 else
78- variation_id = _do_fetch ( url , request_body , timeout )
78+ _do_fetch ( url , request_body , timeout )
7979 end
8080 return variation_id
8181 end
8282
8383 def _do_fetch ( url , request_body , timeout )
84- # Perform a single fetch request to the CMAB prediction service.
85-
86- # Args:
87- # url: The endpoint URL.
88- # request_body: The request payload.
89- # timeout: Maximum wait time for the request to respond in seconds.
90- # Returns:
91- # The variation ID from the response.
92-
93- headers = { 'Content-Type' => 'application/json' }
94- begin
95- response = @http_client . post ( url , json : request_body , headers : headers , timeout : timeout )
96- rescue StandardError => e
97- error_message = Errors ::CMAB_FETCH_FAILED % e . message
98- @logger . error ( error_message )
99- raise CmabFetchError , error_message
100- end
84+ # Perform a single fetch request to the CMAB prediction service.
10185
102- if !( 200 ..299 ) . include? ( response . status_code )
103- error_message = Errors ::CMAB_FETCH_FAILED % response . status_code
104- @logger . error ( error_message )
105- raise CmabFetchError , error_message
106- end
86+ # Args:
87+ # url: The endpoint URL.
88+ # request_body: The request payload.
89+ # timeout: Maximum wait time for the request to respond in seconds.
90+ # Returns:
91+ # The variation ID from the response.
10792
108- begin
109- body = response . json ( )
110- rescue JSON ::ParserError => e
111- error_message = Errors ::INVALID_CMAB_FETCH_RESPONSE
112- @logger . error ( error_message )
113- raise CmabInvalidResponseError , error_message
114- end
93+ headers = { 'Content-Type' => 'application/json' }
94+ begin
95+ response = @http_client . post ( url , json : request_body , headers : headers , timeout : timeout )
96+ rescue StandardError => e
97+ error_message = Errors ::CMAB_FETCH_FAILED % e . message
98+ @logger . error ( error_message )
99+ raise CmabFetchError , error_message
100+ end
115101
116- if !validate_response ( body )
117- error_message = Errors ::INVALID_CMAB_FETCH_RESPONSE
118- @logger . error ( error_message )
119- raise CmabInvalidResponseError , error_message
120- end
102+ unless ( 200 ..299 ) . include? ( response . status_code )
103+ error_message = Errors ::CMAB_FETCH_FAILED % response . status_code
104+ @logger . error ( error_message )
105+ raise CmabFetchError , error_message
106+ end
107+
108+ begin
109+ body = response . json
110+ rescue JSON ::ParserError
111+ error_message = Errors ::INVALID_CMAB_FETCH_RESPONSE
112+ @logger . error ( error_message )
113+ raise CmabInvalidResponseError , error_message
114+ end
121115
122- return body [ 'predictions' ] [ 0 ] [ 'variationId' ]
116+ unless validate_response ( body )
117+ error_message = Errors ::INVALID_CMAB_FETCH_RESPONSE
118+ @logger . error ( error_message )
119+ raise CmabInvalidResponseError , error_message
120+ end
121+
122+ body [ 'predictions' ] [ 0 ] [ 'variationId' ]
123123 end
124124
125125 def validate_response ( body )
126- # Validate the response structure from the CMAB service.
127- # Args:
128- # body: The JSON response body to validate.
129- # Returns:
130- # true if valid, false otherwise.
131-
126+ # Validate the response structure from the CMAB service.
127+ # Args:
128+ # body: The JSON response body to validate.
129+ # Returns:
130+ # true if valid, false otherwise.
131+
132132 body . is_a? ( Hash ) &&
133133 body . key? ( 'predictions' ) &&
134134 body [ 'predictions' ] . is_a? ( Array ) &&
135135 !body [ 'predictions' ] . empty? &&
136136 body [ 'predictions' ] [ 0 ] . is_a? ( Hash ) &&
137- body [ 'predictions' ] [ 0 ] . key? ( 'variationId' )
137+ body [ 'predictions' ] [ 0 ] . key? ( 'variationId' )
138138 end
139139
140140 def _do_fetch_with_retry ( url , request_body , retry_config , timeout )
141- # Perform a fetch request with retry logic.
142- # Args:
143- # url: The endpoint URL.
144- # request_body: The request payload.
145- # retry_config: Configuration for retry settings.
146- # timeout: Maximum wait time for the request to respond in seconds.
147- # Returns:
148- # The variation ID from the response.
149-
150- backoff = retry_config . initial_backoff
151-
152- ( 0 ..retry_config . max_retries ) . each do |attempt |
153- begin
154- variation_id = _do_fetch ( url , request_body , timeout )
155- return variation_id
156- rescue => e
157- if attempt < retry_config . max_retries
158- @logger . info ( "Retrying CMAB request (attempt: #{ attempt + 1 } after #{ backoff } seconds)..." )
159- sleep ( backoff )
160- backoff = [ backoff * ( retry_config . backoff_multiplier ** ( attempt + 1 ) ) , retry_config . max_backoff ] . min
161- end
162- end
141+ # Perform a fetch request with retry logic.
142+ # Args:
143+ # url: The endpoint URL.
144+ # request_body: The request payload.
145+ # retry_config: Configuration for retry settings.
146+ # timeout: Maximum wait time for the request to respond in seconds.
147+ # Returns:
148+ # The variation ID from the response.
149+
150+ backoff = retry_config . initial_backoff
151+
152+ ( 0 ..retry_config . max_retries ) . each do |attempt |
153+ variation_id = _do_fetch ( url , request_body , timeout )
154+ return variation_id
155+ rescue
156+ if attempt < retry_config . max_retries
157+ @logger . info ( "Retrying CMAB request (attempt: #{ attempt + 1 } after #{ backoff } seconds)..." )
158+ sleep ( backoff )
159+ backoff = [ backoff * ( retry_config . backoff_multiplier **( attempt + 1 ) ) , retry_config . max_backoff ] . min
163160 end
161+ end
164162
165- error_message = Errors ::CMAB_FETCH_FAILED % " Max retries exceeded for CMAB request."
166- @logger . error ( error_message )
167- raise CmabFetchError , error_message
163+ error_message = Errors ::CMAB_FETCH_FAILED % ' Max retries exceeded for CMAB request.'
164+ @logger . error ( error_message )
165+ raise CmabFetchError , error_message
168166 end
169167 end
170168end
0 commit comments