ibm_appconfiguration_ruby_sdk 0.1.0.pre.rc.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 (37) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/CODE_OF_CONDUCT.md +76 -0
  4. data/CONTRIBUTING.md +9 -0
  5. data/LICENSE +201 -0
  6. data/README.md +474 -0
  7. data/Rakefile +8 -0
  8. data/examples/README.md +60 -0
  9. data/examples/app.rb +104 -0
  10. data/lib/ibm_appconfiguration_ruby_sdk/app_configuration.rb +291 -0
  11. data/lib/ibm_appconfiguration_ruby_sdk/configurations/configuration_handler.rb +828 -0
  12. data/lib/ibm_appconfiguration_ruby_sdk/configurations/internal/constants.rb +89 -0
  13. data/lib/ibm_appconfiguration_ruby_sdk/configurations/internal/file_manager.rb +72 -0
  14. data/lib/ibm_appconfiguration_ruby_sdk/configurations/internal/logger.rb +98 -0
  15. data/lib/ibm_appconfiguration_ruby_sdk/configurations/internal/retry_manager/background_retry_manager.rb +284 -0
  16. data/lib/ibm_appconfiguration_ruby_sdk/configurations/internal/retry_manager/config_fetcher.rb +254 -0
  17. data/lib/ibm_appconfiguration_ruby_sdk/configurations/internal/utils.rb +240 -0
  18. data/lib/ibm_appconfiguration_ruby_sdk/configurations/internal/websocket_client/connection_manager.rb +501 -0
  19. data/lib/ibm_appconfiguration_ruby_sdk/configurations/internal/websocket_client/connectivity.rb +30 -0
  20. data/lib/ibm_appconfiguration_ruby_sdk/configurations/internal/websocket_client/driver_socket.rb +28 -0
  21. data/lib/ibm_appconfiguration_ruby_sdk/configurations/internal/websocket_client/retry_policy.rb +42 -0
  22. data/lib/ibm_appconfiguration_ruby_sdk/configurations/internal/websocket_client/state.rb +24 -0
  23. data/lib/ibm_appconfiguration_ruby_sdk/configurations/internal/websocket_client/watchdog.rb +50 -0
  24. data/lib/ibm_appconfiguration_ruby_sdk/configurations/internal/websocket_client/websocket_client.rb +43 -0
  25. data/lib/ibm_appconfiguration_ruby_sdk/configurations/models/feature.rb +121 -0
  26. data/lib/ibm_appconfiguration_ruby_sdk/configurations/models/property.rb +107 -0
  27. data/lib/ibm_appconfiguration_ruby_sdk/configurations/models/rule.rb +87 -0
  28. data/lib/ibm_appconfiguration_ruby_sdk/configurations/models/secret_property.rb +81 -0
  29. data/lib/ibm_appconfiguration_ruby_sdk/configurations/models/segment.rb +39 -0
  30. data/lib/ibm_appconfiguration_ruby_sdk/configurations/models/segment_rules.rb +57 -0
  31. data/lib/ibm_appconfiguration_ruby_sdk/core/api_manager.rb +269 -0
  32. data/lib/ibm_appconfiguration_ruby_sdk/core/metering.rb +400 -0
  33. data/lib/ibm_appconfiguration_ruby_sdk/core/url_builder.rb +252 -0
  34. data/lib/ibm_appconfiguration_ruby_sdk/version.rb +20 -0
  35. data/lib/ibm_appconfiguration_ruby_sdk.rb +20 -0
  36. data/sig/ibm_appconfiguration_ruby_sdk.rbs +4 -0
  37. metadata +209 -0
@@ -0,0 +1,291 @@
1
+ # Copyright 2026 IBM Corp. All Rights Reserved.
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
+ # frozen_string_literal: true
16
+
17
+ require "singleton"
18
+ require_relative "configurations/configuration_handler"
19
+ require_relative "configurations/internal/logger"
20
+ require_relative "configurations/internal/constants"
21
+ require_relative "core/url_builder"
22
+
23
+ module IbmAppconfigurationRubySdk
24
+ # AppConfiguration client class implementing singleton pattern
25
+ class AppConfiguration
26
+ include Singleton
27
+
28
+ # Region constants
29
+ REGION_US_SOUTH = "us-south"
30
+ REGION_EU_GB = "eu-gb"
31
+ REGION_AU_SYD = "au-syd"
32
+ REGION_US_EAST = "us-east"
33
+ REGION_EU_DE = "eu-de"
34
+ REGION_CA_TOR = "ca-tor"
35
+ REGION_JP_TOK = "jp-tok"
36
+ REGION_JP_OSA = "jp-osa"
37
+
38
+ class << self
39
+ ##
40
+ # Get the current instance without creating a new one
41
+ # @return [AppConfiguration] The current instance
42
+ # @raise [RuntimeError] If instance doesn't exist
43
+ def current_instance
44
+ raise Constants::SINGLETON_EXCEPTION unless @singleton__instance__
45
+
46
+ instance
47
+ end
48
+
49
+ ##
50
+ # Override the default App Configuration URL
51
+ # This method should be invoked before the SDK initialization
52
+ # NOTE: To be used for development purposes only
53
+ # @param url [String] The base service URL
54
+ def override_service_url(url)
55
+ return unless url
56
+
57
+ url_builder = UrlBuilder.instance
58
+ url_builder.set_base_service_url(url)
59
+ end
60
+ end
61
+
62
+ def initialize
63
+ @is_initialized = false
64
+ @is_initialized_config = false
65
+ @use_private_endpoint = false
66
+ @configuration_handler = nil
67
+ @logger = Logger.instance
68
+ @url_builder = UrlBuilder.instance
69
+ end
70
+
71
+ ##
72
+ # Initialize the SDK to connect with your App Configuration service instance
73
+ # @param region [String] Region name where the App Configuration service instance is created
74
+ # @param guid [String] GUID of the App Configuration service
75
+ # @param apikey [String] API key of the App Configuration service
76
+ # @raise [RuntimeError] If any required parameter is missing or invalid
77
+ def init(region, guid, apikey)
78
+ # init is a SDK initialization method. It is expected to be called only once.
79
+ # This condition ensures the init inputs are taken only once even if called multiple times.
80
+ return if @is_initialized
81
+
82
+ unless region && guid && apikey
83
+ if !region
84
+ report_error(Constants::REGION_ERROR)
85
+ elsif !guid
86
+ report_error(Constants::GUID_ERROR)
87
+ else
88
+ report_error(Constants::APIKEY_ERROR)
89
+ end
90
+ end
91
+
92
+ @configuration_handler = ConfigurationHandler.instance
93
+ @configuration_handler.init(region, guid, apikey, @use_private_endpoint)
94
+ @is_initialized = true
95
+ end
96
+
97
+ ##
98
+ # Set the context of the SDK
99
+ # @param collection_id [String] ID of the collection created in App Configuration service instance
100
+ # @param environment_id [String] ID of the environment created in App Configuration service instance
101
+ # @param options [Hash] Optional configuration parameters
102
+ # @option options [String] :persistent_cache_directory Directory path for persistent cache
103
+ # @option options [String] :bootstrap_file Absolute path of configuration file
104
+ # @option options [Boolean] :live_config_update_enabled Enable live configuration updates (default: true)
105
+ # @raise [RuntimeError] If init was not called or parameters are invalid
106
+ def set_context(collection_id, environment_id, options = {})
107
+ # setContext is also a SDK initialization method. It is expected to be called only once.
108
+ # This condition ensures the setContext inputs are taken only once even if called multiple times.
109
+ return if @is_initialized_config
110
+
111
+ report_error(Constants::COLLECTION_ID_ERROR) unless @is_initialized
112
+
113
+ report_error(Constants::COLLECTION_ID_VALUE_ERROR) unless collection_id
114
+
115
+ report_error(Constants::ENVIRONMENT_ID_VALUE_ERROR) unless environment_id
116
+
117
+ default_options = {
118
+ persistent_cache_directory: nil,
119
+ bootstrap_file: nil,
120
+ live_config_update_enabled: true
121
+ }
122
+
123
+ if options
124
+ report_error(Constants::INVALID_OPTIONS_PARAMETER.to_s) unless options.is_a?(Hash)
125
+
126
+ if options.key?(:persistent_cache_directory)
127
+ given_dir_path = options[:persistent_cache_directory]
128
+ if given_dir_path.is_a?(String) && !given_dir_path.empty?
129
+ default_options[:persistent_cache_directory] = given_dir_path
130
+ else
131
+ report_error("#{Constants::PERSISTENT_CACHE_OPTION_ERROR} #{given_dir_path}")
132
+ end
133
+ end
134
+
135
+ if options.key?(:bootstrap_file)
136
+ given_file_path = options[:bootstrap_file]
137
+ if given_file_path.is_a?(String) && !given_file_path.empty? && File.extname(given_file_path) == ".json"
138
+ default_options[:bootstrap_file] = given_file_path
139
+ else
140
+ report_error("#{Constants::BOOTSTRAP_FILEPATH_OPTION_ERROR} #{given_file_path}")
141
+ end
142
+ end
143
+
144
+ if options.key?(:live_config_update_enabled)
145
+ given_flag_value = options[:live_config_update_enabled]
146
+ if [true, false].include?(given_flag_value)
147
+ default_options[:live_config_update_enabled] = given_flag_value
148
+ else
149
+ report_error("#{Constants::LIVE_CONFIG_UPDATE_OPTION_ERROR} #{given_flag_value}")
150
+ end
151
+ end
152
+
153
+ report_error(Constants::CONFIGURATION_FILE_NOT_FOUND_ERROR) if default_options[:live_config_update_enabled] == false && default_options[:bootstrap_file].nil?
154
+ end
155
+
156
+ @is_initialized_config = true
157
+ @configuration_handler = ConfigurationHandler.instance
158
+ @configuration_handler.set_context(collection_id, environment_id, default_options)
159
+ end
160
+
161
+ ##
162
+ # Set the SDK to connect to App Configuration service using a private endpoint
163
+ # This function must be called before calling the init function
164
+ # @param use_private_endpoint_param [Boolean] Set to true to use private endpoint (default: false)
165
+ def use_private_endpoint(use_private_endpoint_param)
166
+ if [true, false].include?(use_private_endpoint_param)
167
+ @use_private_endpoint = use_private_endpoint_param
168
+ return
169
+ end
170
+ @logger.error(Constants::INPUT_PARAMETER_NOT_BOOLEAN)
171
+ end
172
+
173
+ ##
174
+ # Returns the Feature object with the details of the feature specified by the feature_id
175
+ # @param feature_id [String] The Feature ID
176
+ # @return [Feature, nil] Feature object or nil if invalid
177
+ def get_feature(feature_id)
178
+ return @configuration_handler.get_feature(feature_id) if @is_initialized && @is_initialized_config
179
+
180
+ @logger.error(Constants::COLLECTION_INIT_ERROR)
181
+ nil
182
+ end
183
+
184
+ ##
185
+ # Returns all features associated with the collection_id
186
+ # @return [Hash, nil] Hash of all features or nil
187
+ def get_features
188
+ return @configuration_handler.get_features if @is_initialized && @is_initialized_config
189
+
190
+ @logger.error(Constants::COLLECTION_INIT_ERROR)
191
+ nil
192
+ end
193
+
194
+ ##
195
+ # Returns the Property object with the details of the property specified by the property_id
196
+ # @param property_id [String] The Property ID
197
+ # @return [Property, nil] Property object or nil if invalid
198
+ def get_property(property_id)
199
+ return @configuration_handler.get_property(property_id) if @is_initialized && @is_initialized_config
200
+
201
+ @logger.error(Constants::COLLECTION_INIT_ERROR)
202
+ nil
203
+ end
204
+
205
+ ##
206
+ # Returns all properties associated with the collection_id
207
+ # @return [Hash, nil] Hash of all properties or nil
208
+ def get_properties
209
+ return @configuration_handler.get_properties if @is_initialized && @is_initialized_config
210
+
211
+ @logger.error(Constants::COLLECTION_INIT_ERROR)
212
+ nil
213
+ end
214
+
215
+ ##
216
+ # Returns the SecretProperty object corresponding to the given property_id
217
+ # @param property_id [String] ID of the secret property from App Configuration
218
+ # @param secrets_manager_service [Object] Secret Manager client object
219
+ # @return [SecretProperty, nil] SecretProperty object or nil
220
+ def get_secret(property_id, secrets_manager_service)
221
+ if @is_initialized && @is_initialized_config
222
+ return @configuration_handler.get_secret(property_id, secrets_manager_service) if secrets_manager_service
223
+
224
+ @logger.error(Constants::INVALID_SECRET_MANAGER_CLIENT_MESSAGE)
225
+ return nil
226
+ end
227
+ @logger.error(Constants::COLLECTION_INIT_ERROR)
228
+ nil
229
+ end
230
+
231
+ ##
232
+ # Record custom metric events for an entity_id while running an experiment
233
+ # @param event_key [String] SDK event key
234
+ # @param entity_id [String] The entity ID
235
+ def track(event_key, entity_id)
236
+ return @configuration_handler.track(event_key, entity_id) if @is_initialized && @is_initialized_config
237
+
238
+ @logger.error(Constants::COLLECTION_INIT_ERROR)
239
+ nil
240
+ end
241
+
242
+ ##
243
+ # Enable or disable the logger
244
+ # By default, logger is disabled
245
+ # @param value [Boolean] Enable (true) or disable (false) debug logging
246
+ def set_debug(value = false)
247
+ Logger.set_debug(value)
248
+ end
249
+
250
+ ##
251
+ # Check if the SDK is connected to the service
252
+ # @return [Boolean] Connection status
253
+ def connected?
254
+ return @configuration_handler.connected? if @is_initialized && @is_initialized_config
255
+
256
+ false
257
+ end
258
+
259
+ ##
260
+ # Register a configuration update listener
261
+ # The listener will be invoked whenever configurations are updated (initial load or live updates).
262
+ # Only one listener can be registered at a time - calling this method multiple times will replace the previous listener.
263
+ # @param block [Proc] Callback block to be invoked on configuration updates
264
+ # @example
265
+ # app_config = IbmAppconfigurationRubySdk::AppConfiguration.instance
266
+ # app_config.register_configuration_update_listener do
267
+ # puts "Configurations updated!"
268
+ # feature = app_config.get_feature('my-feature')
269
+ # # React to configuration changes
270
+ # end
271
+ # @return [void]
272
+ def register_configuration_update_listener(&block)
273
+ if @is_initialized && @is_initialized_config
274
+ @configuration_handler.register_configuration_update_listener(&block)
275
+ return
276
+ end
277
+ @logger.error(Constants::COLLECTION_INIT_ERROR)
278
+ end
279
+
280
+ private
281
+
282
+ ##
283
+ # Report error and raise exception
284
+ # @param error [String] Error message
285
+ # @raise [RuntimeError] Always raises with the error message
286
+ def report_error(error)
287
+ @logger.error(error)
288
+ raise error
289
+ end
290
+ end
291
+ end