wingify-fme-ruby-sdk 1.50.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 (88) hide show
  1. checksums.yaml +7 -0
  2. data/lib/resources/debug_messages.json +21 -0
  3. data/lib/resources/error_messages.json +63 -0
  4. data/lib/resources/info_messages.json +43 -0
  5. data/lib/resources/warn_messages.json +1 -0
  6. data/lib/vwo.rb +40 -0
  7. data/lib/wingify/api/get_flag.rb +244 -0
  8. data/lib/wingify/api/set_attribute.rb +57 -0
  9. data/lib/wingify/api/track_event.rb +80 -0
  10. data/lib/wingify/constants/constants.rb +106 -0
  11. data/lib/wingify/decorators/storage_decorator.rb +82 -0
  12. data/lib/wingify/enums/api_enum.rb +23 -0
  13. data/lib/wingify/enums/campaign_type_enum.rb +19 -0
  14. data/lib/wingify/enums/debug_category_enum.rb +20 -0
  15. data/lib/wingify/enums/decision_types_enum.rb +18 -0
  16. data/lib/wingify/enums/event_enum.rb +22 -0
  17. data/lib/wingify/enums/headers_enum.rb +20 -0
  18. data/lib/wingify/enums/hooks_enum.rb +17 -0
  19. data/lib/wingify/enums/http_method_enum.rb +18 -0
  20. data/lib/wingify/enums/log_level_enum.rb +21 -0
  21. data/lib/wingify/enums/log_level_to_number.rb +27 -0
  22. data/lib/wingify/enums/status_enum.rb +19 -0
  23. data/lib/wingify/enums/storage_enum.rb +22 -0
  24. data/lib/wingify/enums/url_enum.rb +22 -0
  25. data/lib/wingify/models/campaign/campaign_model.rb +192 -0
  26. data/lib/wingify/models/campaign/feature_model.rb +111 -0
  27. data/lib/wingify/models/campaign/impact_campaign_model.rb +38 -0
  28. data/lib/wingify/models/campaign/metric_model.rb +44 -0
  29. data/lib/wingify/models/campaign/rule_model.rb +56 -0
  30. data/lib/wingify/models/campaign/variable_model.rb +51 -0
  31. data/lib/wingify/models/campaign/variation_model.rb +137 -0
  32. data/lib/wingify/models/gateway_service_model.rb +39 -0
  33. data/lib/wingify/models/schemas/settings_schema_validation.rb +104 -0
  34. data/lib/wingify/models/settings/settings_model.rb +101 -0
  35. data/lib/wingify/models/storage/storage_data_model.rb +44 -0
  36. data/lib/wingify/models/user/context_model.rb +154 -0
  37. data/lib/wingify/models/user/context_vwo_model.rb +38 -0
  38. data/lib/wingify/models/user/get_flag_response.rb +50 -0
  39. data/lib/wingify/models/vwo_options_model.rb +129 -0
  40. data/lib/wingify/packages/decision_maker/decision_maker.rb +60 -0
  41. data/lib/wingify/packages/logger/core/log_manager.rb +92 -0
  42. data/lib/wingify/packages/logger/core/transport_manager.rb +76 -0
  43. data/lib/wingify/packages/logger/log_message_builder.rb +70 -0
  44. data/lib/wingify/packages/logger/logger.rb +38 -0
  45. data/lib/wingify/packages/logger/transports/console_transport.rb +49 -0
  46. data/lib/wingify/packages/network_layer/client/network_client.rb +276 -0
  47. data/lib/wingify/packages/network_layer/handlers/request_handler.rb +37 -0
  48. data/lib/wingify/packages/network_layer/manager/network_manager.rb +145 -0
  49. data/lib/wingify/packages/network_layer/models/global_request_model.rb +105 -0
  50. data/lib/wingify/packages/network_layer/models/request_model.rb +179 -0
  51. data/lib/wingify/packages/network_layer/models/response_model.rb +62 -0
  52. data/lib/wingify/packages/segmentation_evaluator/core/segmentation_manager.rb +77 -0
  53. data/lib/wingify/packages/segmentation_evaluator/enums/segment_operand_regex_enum.rb +29 -0
  54. data/lib/wingify/packages/segmentation_evaluator/enums/segment_operand_value_enum.rb +26 -0
  55. data/lib/wingify/packages/segmentation_evaluator/enums/segment_operator_value_enum.rb +33 -0
  56. data/lib/wingify/packages/segmentation_evaluator/evaluators/segment_evaluator.rb +218 -0
  57. data/lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb +415 -0
  58. data/lib/wingify/packages/segmentation_evaluator/utils/segment_util.rb +44 -0
  59. data/lib/wingify/packages/storage/connector.rb +26 -0
  60. data/lib/wingify/packages/storage/storage.rb +47 -0
  61. data/lib/wingify/services/batch_event_queue.rb +179 -0
  62. data/lib/wingify/services/campaign_decision_service.rb +161 -0
  63. data/lib/wingify/services/hooks_service.rb +51 -0
  64. data/lib/wingify/services/logger_service.rb +114 -0
  65. data/lib/wingify/services/settings_service.rb +178 -0
  66. data/lib/wingify/services/storage_service.rb +66 -0
  67. data/lib/wingify/utils/batch_event_dispatcher_util.rb +178 -0
  68. data/lib/wingify/utils/brand_context.rb +33 -0
  69. data/lib/wingify/utils/brand_util.rb +53 -0
  70. data/lib/wingify/utils/campaign_util.rb +284 -0
  71. data/lib/wingify/utils/data_type_util.rb +105 -0
  72. data/lib/wingify/utils/debugger_service_util.rb +40 -0
  73. data/lib/wingify/utils/decision_util.rb +259 -0
  74. data/lib/wingify/utils/event_util.rb +55 -0
  75. data/lib/wingify/utils/function_util.rb +141 -0
  76. data/lib/wingify/utils/gateway_service_util.rb +101 -0
  77. data/lib/wingify/utils/impression_util.rb +66 -0
  78. data/lib/wingify/utils/log_message_util.rb +42 -0
  79. data/lib/wingify/utils/meg_util.rb +357 -0
  80. data/lib/wingify/utils/network_util.rb +503 -0
  81. data/lib/wingify/utils/rule_evaluation_util.rb +57 -0
  82. data/lib/wingify/utils/settings_util.rb +38 -0
  83. data/lib/wingify/utils/usage_stats_util.rb +119 -0
  84. data/lib/wingify/utils/uuid_util.rb +96 -0
  85. data/lib/wingify/wingify_builder.rb +261 -0
  86. data/lib/wingify/wingify_client.rb +227 -0
  87. data/lib/wingify.rb +117 -0
  88. metadata +327 -0
@@ -0,0 +1,70 @@
1
+ # Copyright 2024-2026 Wingify Software Pvt. Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ class LogMessageBuilder
16
+ ANSI_COLOR_ENUM = {
17
+ bold: "\x1b[1m",
18
+ cyan: "\x1b[36m",
19
+ green: "\x1b[32m",
20
+ lightblue: "\x1b[94m",
21
+ red: "\x1b[31m",
22
+ reset: "\x1b[0m",
23
+ white: "\x1b[30m",
24
+ yellow: "\x1b[33m"
25
+ }
26
+
27
+ LOG_LEVEL_COLOR = {
28
+ 'TRACE' => ANSI_COLOR_ENUM[:white],
29
+ 'DEBUG' => ANSI_COLOR_ENUM[:lightblue],
30
+ 'INFO' => ANSI_COLOR_ENUM[:cyan],
31
+ 'WARN' => ANSI_COLOR_ENUM[:yellow],
32
+ 'ERROR' => ANSI_COLOR_ENUM[:red]
33
+ }
34
+
35
+ def initialize(logger_config, transport_config)
36
+ @logger_config = logger_config
37
+ @transport_config = transport_config
38
+
39
+ # Access the values directly from transport_config if available.
40
+ @prefix = transport_config.instance_variable_get(:@prefix) || logger_config[:prefix] || ''
41
+ @date_time_format = transport_config.respond_to?(:date_time_format) ? transport_config.date_time_format : logger_config[:date_time_format] || lambda { Time.now.iso8601 }
42
+ end
43
+
44
+ def format_message(level, message)
45
+ "[#{get_formatted_level(level)}]: #{get_formatted_prefix(@prefix)} #{get_formatted_date_time} #{message}"
46
+ end
47
+
48
+ private
49
+
50
+ def get_formatted_prefix(prefix)
51
+ if @logger_config[:is_ansi_color_enabled]
52
+ "#{ANSI_COLOR_ENUM[:bold]}#{ANSI_COLOR_ENUM[:green]}#{prefix}#{ANSI_COLOR_ENUM[:reset]}"
53
+ else
54
+ prefix
55
+ end
56
+ end
57
+
58
+ def get_formatted_level(level)
59
+ color = LOG_LEVEL_COLOR[level.upcase] || ''
60
+ level_string = level.upcase
61
+ return "#{color}#{level_string}#{ANSI_COLOR_ENUM[:reset]}" if @logger_config[:is_ansi_color_enabled]
62
+
63
+ level_string
64
+ end
65
+
66
+ def get_formatted_date_time
67
+ @date_time_format.call
68
+ end
69
+ end
70
+
@@ -0,0 +1,38 @@
1
+ # Copyright 2024-2026 Wingify Software Pvt. Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # logger.rb
16
+ class VWOLogger
17
+ # Abstract method definitions for logging at different levels
18
+ def trace(message)
19
+ raise NotImplementedError, "You must implement the trace method"
20
+ end
21
+
22
+ def debug(message)
23
+ raise NotImplementedError, "You must implement the debug method"
24
+ end
25
+
26
+ def info(message)
27
+ raise NotImplementedError, "You must implement the info method"
28
+ end
29
+
30
+ def warn(message)
31
+ raise NotImplementedError, "You must implement the warn method"
32
+ end
33
+
34
+ def error(message)
35
+ raise NotImplementedError, "You must implement the error method"
36
+ end
37
+ end
38
+
@@ -0,0 +1,49 @@
1
+ # Copyright 2024-2026 Wingify Software Pvt. Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ class ConsoleTransport
16
+ attr_reader :level, :prefix
17
+
18
+ def initialize(config = {})
19
+ @config = config
20
+ @level = @config[:level] || 'ERROR'
21
+ @prefix = @config[:prefix] || 'VWO-SDK' # Ensure `@prefix` is set here
22
+ end
23
+
24
+ # Define the logging methods
25
+ def trace(message)
26
+ console_log('TRACE', message)
27
+ end
28
+
29
+ def debug(message)
30
+ console_log('DEBUG', message)
31
+ end
32
+
33
+ def info(message)
34
+ console_log('INFO', message)
35
+ end
36
+
37
+ def warn(message)
38
+ console_log('WARN', message)
39
+ end
40
+
41
+ def error(message)
42
+ console_log('ERROR', message)
43
+ end
44
+
45
+ def console_log(level, message)
46
+ puts "#{message}" # Output the log to console
47
+ end
48
+ end
49
+
@@ -0,0 +1,276 @@
1
+ # Copyright 2024-2026 Wingify Software Pvt. Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'json'
16
+ require 'uri'
17
+ require_relative '../models/request_model'
18
+ require_relative '../models/response_model'
19
+ require_relative '../../../utils/network_util'
20
+ require 'net/http'
21
+ require 'concurrent-ruby'
22
+ require_relative '../../../constants/constants'
23
+ require_relative '../../../services/logger_service'
24
+ require_relative '../../../enums/log_level_enum'
25
+ require_relative '../../../enums/event_enum'
26
+ require_relative '../../../utils/function_util'
27
+
28
+ class NetworkClient
29
+ HTTPS_SCHEME = 'https'
30
+
31
+ def initialize(options = {})
32
+ # options for threading
33
+ @should_use_threading = options.key?(:enabled) ? options[:enabled] : Constants::SHOULD_USE_THREADING
34
+ @thread_pool = Concurrent::ThreadPoolExecutor.new(
35
+ # Minimum number of threads to keep alive in the pool
36
+ min_threads: 1,
37
+ # Maximum number of threads allowed in the pool, configurable via options or defaults to MAX_POOL_SIZE constant
38
+ max_threads: options.key?(:max_pool_size) ? options[:max_pool_size] : Constants::MAX_POOL_SIZE,
39
+ # Maximum number of tasks that can be queued when all threads are busy
40
+ max_queue: options.key?(:max_queue_size) ? options[:max_queue_size] : Constants::MAX_QUEUE_SIZE,
41
+ # When queue is full, execute task in the caller's thread rather than rejecting it
42
+ fallback_policy: :caller_runs
43
+ )
44
+ end
45
+
46
+ def get_thread_pool
47
+ @thread_pool
48
+ end
49
+
50
+ def get_should_use_threading
51
+ @should_use_threading
52
+ end
53
+
54
+ def get(request_model)
55
+ execute_with_retry(request_model, :get_request)
56
+ end
57
+
58
+ def post(request_model)
59
+ execute_with_retry(request_model, :post_request)
60
+ end
61
+
62
+ private
63
+
64
+ def execute_with_retry(request_model, request_type)
65
+ url = request_model.get_url + request_model.get_path
66
+ uri = URI(url)
67
+ attempt = 0
68
+
69
+ # Get retry config from request model or use defaults
70
+ retry_config = request_model.get_retry_config || Constants::DEFAULT_RETRY_CONFIG.dup
71
+ extra_data = request_model.get_extra_info
72
+ endpoint = request_model.get_path.split('?')[0]
73
+
74
+ # If retry is disabled, execute without retry logic
75
+ unless retry_config[:should_retry]
76
+ response_model, _should_retry, _last_error_message =
77
+ perform_single_attempt(uri, request_model, request_type, attempt, retry_config)
78
+ return response_model
79
+ end
80
+
81
+ # Attempt loop: initial attempt (0) + configured retries
82
+ last_error_message = nil
83
+ last_response_model = nil
84
+
85
+ (0..retry_config[:max_retries] - 1).each do |attempt_index|
86
+ attempt = attempt_index
87
+
88
+ # Perform a single attempt
89
+ response_model, should_retry, last_error_message =
90
+ perform_single_attempt(uri, request_model, request_type, attempt, retry_config, last_error_message)
91
+
92
+ last_response_model = response_model
93
+
94
+ # If there is no retry needed, return immediately
95
+ return response_model unless should_retry
96
+
97
+ # Calculate delay before next retry (in seconds)
98
+ delay_seconds = calculate_retry_delay(attempt_index, retry_config)
99
+
100
+ # Log retry attempt
101
+ LoggerService.log(
102
+ LogLevelEnum::ERROR,
103
+ "ATTEMPTING_RETRY_FOR_FAILED_NETWORK_CALL",
104
+ {
105
+ endPoint: endpoint,
106
+ err: last_error_message,
107
+ delay: delay_seconds,
108
+ attempt: attempt_index + 1,
109
+ maxRetries: retry_config[:max_retries]
110
+ }.merge(extra_data), false
111
+ )
112
+
113
+ # Store last error on request for diagnostics
114
+ request_model.set_last_error(last_error_message)
115
+
116
+ sleep(delay_seconds)
117
+ end
118
+
119
+ # All attempts exhausted
120
+ total_attempts = retry_config[:max_retries]
121
+ final_error_message = last_error_message || 'Unknown error'
122
+
123
+ # Log failure after max retries (skip for debugger events)
124
+ unless endpoint.include?(EventEnum::DEBUGGER_EVENT)
125
+ LoggerService.log(
126
+ LogLevelEnum::ERROR,
127
+ "NETWORK_CALL_FAILURE_AFTER_MAX_RETRIES",
128
+ {
129
+ extraData: endpoint,
130
+ attempts: total_attempts,
131
+ err: final_error_message
132
+ }.merge(extra_data),
133
+ false
134
+ )
135
+ end
136
+
137
+ if last_response_model
138
+ last_response_model.set_total_attempts(total_attempts)
139
+ last_response_model.set_error(final_error_message)
140
+ last_response_model
141
+ else
142
+ response_model = ResponseModel.new
143
+ response_model.set_error(final_error_message)
144
+ response_model.set_total_attempts(total_attempts)
145
+ response_model
146
+ end
147
+ end
148
+
149
+ # Performs a single HTTP attempt (GET/POST) and returns:
150
+ # [ResponseModel, should_retry (Boolean), last_error_message (String or nil)]
151
+ def perform_single_attempt(uri, request_model, request_type, attempt, retry_config, prev_error_message = nil)
152
+ begin
153
+ # Low-level HTTP call
154
+ response = if request_type == :get_request
155
+ perform_get_request(uri, request_model)
156
+ else
157
+ perform_post_request(uri, request_model)
158
+ end
159
+
160
+ response_model = build_response_model(response)
161
+
162
+ # Success (2xx)
163
+ if response.is_a?(Net::HTTPSuccess)
164
+ # On retries, echo back last error and attempts (for diagnostics)
165
+ if attempt.positive?
166
+ response_model.set_total_attempts(attempt)
167
+ response_model.set_error(request_model.get_last_error)
168
+ end
169
+ return [response_model, false, nil]
170
+ end
171
+
172
+ # Client error 400 → do not retry
173
+ if response.code.to_i == 400
174
+ error_message = "#{response.body}, Status Code: #{response.code.to_i}"
175
+ response_model.set_error(error_message)
176
+ response_model.set_total_attempts(attempt)
177
+ return [response_model, false, error_message]
178
+ end
179
+
180
+ # Non-2xx/400 status → decide if we should retry based on status code
181
+ should_retry = should_retry(response, attempt, retry_config)
182
+ error_message = "#{response.body}, Status Code: #{response.code.to_i}"
183
+ response_model.set_error(error_message)
184
+ response_model.set_total_attempts(attempt)
185
+ [response_model, should_retry, error_message]
186
+ rescue StandardError => e
187
+ # Network / timeout / other exceptions
188
+ error_message = get_formatted_error_message(e)
189
+ response_model = ResponseModel.new
190
+ response_model.set_error(error_message)
191
+ response_model.set_total_attempts(attempt)
192
+
193
+ should_retry = should_retry_on_error(e, attempt, retry_config)
194
+ [response_model, should_retry, error_message]
195
+ end
196
+ end
197
+
198
+ def perform_get_request(uri, request_model)
199
+ request = Net::HTTP::Get.new(uri)
200
+ request_model.get_headers.each { |k, v| request[k] = v }
201
+
202
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https', open_timeout: Constants::REQUEST_TIMEOUT, read_timeout: Constants::REQUEST_TIMEOUT) do |http|
203
+ http.request(request)
204
+ end
205
+ end
206
+
207
+ def perform_post_request(uri, request_model)
208
+ headers = request_model.get_headers
209
+ body = JSON.dump(request_model.get_body)
210
+
211
+ request = Net::HTTP::Post.new(uri, headers)
212
+ request.body = body
213
+
214
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https', open_timeout: Constants::REQUEST_TIMEOUT, read_timeout: Constants::REQUEST_TIMEOUT) do |http|
215
+ http.request(request)
216
+ end
217
+ end
218
+
219
+ def build_response_model(response)
220
+ response_model = ResponseModel.new
221
+ response_model.set_status_code(response.code.to_i)
222
+
223
+ if response.is_a?(Net::HTTPSuccess) && !response.body.strip.empty?
224
+ content_type = response['Content-Type']&.downcase
225
+ if content_type&.include?('application/json')
226
+ begin
227
+ parsed_data = JSON.parse(response.body)
228
+ response_model.set_data(parsed_data)
229
+ rescue JSON::ParserError => e
230
+ response_model.set_error("Invalid JSON response: #{e.message}")
231
+ end
232
+ else
233
+ response_model.set_data(response.body)
234
+ end
235
+ end
236
+
237
+ response_model
238
+ end
239
+
240
+ def should_retry(response, attempt, retry_config)
241
+ # Retry on server errors (5xx) or network timeouts
242
+ status_code = response.code.to_i
243
+ status_code < 200 || status_code > 300
244
+ end
245
+
246
+ def should_retry_on_error(error, attempt, retry_config)
247
+ return false if attempt >= retry_config[:max_retries]
248
+
249
+ # Retry on network errors, timeouts, and connection errors
250
+ # Check for specific error types that indicate transient network issues
251
+ retryable = error.is_a?(Net::OpenTimeout) ||
252
+ error.is_a?(Net::ReadTimeout) ||
253
+ error.is_a?(Errno::ECONNREFUSED) ||
254
+ error.is_a?(Errno::ETIMEDOUT) ||
255
+ error.is_a?(Errno::EHOSTUNREACH) ||
256
+ error.is_a?(Errno::ENETUNREACH) ||
257
+ error.is_a?(Errno::ECONNRESET) ||
258
+ error.is_a?(Errno::EPIPE) ||
259
+ error.is_a?(SocketError) ||
260
+ (error.respond_to?(:message) && error.message &&
261
+ (error.message.downcase.include?('timeout') ||
262
+ error.message.downcase.include?('connection') ||
263
+ error.message.downcase.include?('connection refused') ||
264
+ error.message.downcase.include?('getaddrinfo') ||
265
+ error.message.downcase.include?('connection reset') ||
266
+ error.message.downcase.include?('broken pipe')))
267
+
268
+ retryable
269
+ end
270
+
271
+ def calculate_retry_delay(attempt, retry_config)
272
+ retry_config[:initial_delay] * (retry_config[:backoff_multiplier] ** attempt)
273
+ end
274
+
275
+
276
+ end
@@ -0,0 +1,37 @@
1
+ # Copyright 2024-2026 Wingify Software Pvt. Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require_relative '../models/global_request_model'
16
+ require_relative '../models/request_model'
17
+
18
+ class RequestHandler
19
+ def create_request(request, config)
20
+ return nil if config.get_base_url.nil? && request.get_url.nil?
21
+
22
+ request.set_url(request.get_url || config.get_base_url)
23
+ request.set_timeout(request.get_timeout || config.get_timeout)
24
+ request.set_body(request.get_body || config.get_body)
25
+ request.set_headers(request.get_headers || config.get_headers)
26
+
27
+ request_query_params = request.get_query || {}
28
+ config_query_params = config.get_query || {}
29
+
30
+ config_query_params.each do |key, value|
31
+ request_query_params[key] ||= value
32
+ end
33
+
34
+ request.set_query(request_query_params)
35
+ request
36
+ end
37
+ end
@@ -0,0 +1,145 @@
1
+ # Copyright 2024-2026 Wingify Software Pvt. Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require_relative '../client/network_client'
16
+ require_relative '../handlers/request_handler'
17
+ require_relative '../models/global_request_model'
18
+ require_relative '../../../services/logger_service'
19
+ require_relative '../../../enums/log_level_enum'
20
+ require_relative '../../../constants/constants'
21
+ require_relative '../../../enums/api_enum'
22
+ require_relative '../../../utils/data_type_util'
23
+
24
+ class NetworkManager
25
+ @instance = nil
26
+
27
+ def initialize(options = {})
28
+ @client = NetworkClient.new(options)
29
+ @config = GlobalRequestModel.new(nil, {}, {}, {})
30
+ @should_use_threading = options.key?(:enabled) ? options[:enabled] : Constants::SHOULD_USE_THREADING
31
+ @retry_config = nil
32
+ end
33
+
34
+ def self.instance(options = {})
35
+ @instance ||= new(options)
36
+ end
37
+
38
+ def attach_client(client = nil, retry_config = nil)
39
+ # Only set retry configuration if it's not already initialized or if a new config is provided
40
+ if !@retry_config || retry_config
41
+ # Define default retry configuration
42
+ default_retry_config = Constants::DEFAULT_RETRY_CONFIG.dup
43
+
44
+ # Merge provided retry_config with defaults, giving priority to provided values
45
+ merged_config = default_retry_config.merge(retry_config || {})
46
+
47
+ # Validate the merged configuration
48
+ @retry_config = validate_retry_config(merged_config)
49
+ end
50
+ end
51
+
52
+ def get_retry_config
53
+ @retry_config ? @retry_config.dup : nil
54
+ end
55
+
56
+ def validate_retry_config(retry_config)
57
+ validated_config = retry_config.dup
58
+ is_invalid_config = false
59
+
60
+ # Validate should_retry: should be a boolean value
61
+ if !DataTypeUtil.is_boolean(validated_config[:should_retry])
62
+ validated_config[:should_retry] = Constants::DEFAULT_RETRY_CONFIG[:should_retry]
63
+ is_invalid_config = true
64
+ end
65
+
66
+ # Validate max_retries: should be a non-negative integer and should not be less than 1
67
+ if !DataTypeUtil.is_number(validated_config[:max_retries]) ||
68
+ !validated_config[:max_retries].is_a?(Integer) ||
69
+ validated_config[:max_retries] < 1
70
+ validated_config[:max_retries] = Constants::DEFAULT_RETRY_CONFIG[:max_retries]
71
+ is_invalid_config = true
72
+ end
73
+
74
+ # Validate initial_delay: should be a non-negative integer and should not be less than 1
75
+ if !DataTypeUtil.is_number(validated_config[:initial_delay]) ||
76
+ !validated_config[:initial_delay].is_a?(Integer) ||
77
+ validated_config[:initial_delay] < 1
78
+ validated_config[:initial_delay] = Constants::DEFAULT_RETRY_CONFIG[:initial_delay]
79
+ is_invalid_config = true
80
+ end
81
+
82
+ # Validate backoff_multiplier: should be a non-negative integer and should not be less than 2
83
+ if !DataTypeUtil.is_number(validated_config[:backoff_multiplier]) ||
84
+ !validated_config[:backoff_multiplier].is_a?(Integer) ||
85
+ validated_config[:backoff_multiplier] < 2
86
+ validated_config[:backoff_multiplier] = Constants::DEFAULT_RETRY_CONFIG[:backoff_multiplier]
87
+ is_invalid_config = true
88
+ end
89
+
90
+ if is_invalid_config
91
+ LoggerService.log(LogLevelEnum::ERROR, "INVALID_RETRY_CONFIG", {
92
+ retryConfig: validated_config.to_json,
93
+ an: ApiEnum::INIT
94
+ })
95
+ end
96
+
97
+ is_invalid_config ? Constants::DEFAULT_RETRY_CONFIG.dup : validated_config
98
+ end
99
+
100
+ def get_client
101
+ @client
102
+ end
103
+
104
+ def set_config(config)
105
+ @config = config
106
+ end
107
+
108
+ def get_config
109
+ @config
110
+ end
111
+
112
+ def create_request(request)
113
+ network_request = RequestHandler.new.create_request(request, @config)
114
+ # Set retry config from network manager if not already set in request
115
+ if @retry_config && (!network_request.get_retry_config || network_request.get_retry_config.nil?)
116
+ network_request.set_retry_config(@retry_config.dup)
117
+ end
118
+ network_request
119
+ end
120
+
121
+ def get(request)
122
+ begin
123
+ network_options = create_request(request)
124
+ raise 'No URL found' if network_options.get_url.nil?
125
+
126
+ response = @client.get(network_options)
127
+ response
128
+ rescue => e
129
+ return ResponseModel.new.set_error(e.message)
130
+ end
131
+ end
132
+
133
+
134
+ def post(request)
135
+ begin
136
+ network_options = create_request(request)
137
+ raise 'No URL found' if network_options.get_url.nil?
138
+
139
+ response = @client.post(network_options) # Return the response
140
+ response
141
+ rescue => e
142
+ return ResponseModel.new.set_error(e.message) # Return error response
143
+ end
144
+ end
145
+ end