Top Level Namespace

Defined Under Namespace

Modules: Connectivity, Constants, IbmAppconfigurationRubySdk, State Classes: ApiManager, BackgroundRetryManager, ConfigFetcher, ConfigurationHandler, ConnectionManager, DriverSocket, Feature, FileManager, Logger, Metering, Property, RetryPolicy, Rule, SecretProperty, Segment, SegmentRules, UrlBuilder, Watchdog, WebSocketClient

Instance Method Summary collapse

Instance Method Details

#append_segment_id(resource, segment_ids) ⇒ Object

Appends segment ids to the provided set

Parameters:

  • resource (Hash)

    The resource (feature or property)

  • segment_ids (Set)

    Set to store segment IDs



36
37
38
39
40
41
42
43
44
45
46
# File 'lib/ibm_appconfiguration_ruby_sdk/configurations/internal/utils.rb', line 36

def append_segment_id(resource, segment_ids)
  return unless resource[:segment_rules]

  resource[:segment_rules].each do |segment_rule|
    segment_rule[:rules].each do |rule|
      rule[:segments].each do |segment_id|
        segment_ids.add(segment_id)
      end
    end
  end
end

#compute_hash(str) ⇒ Integer

Compute hash using MurmurHash3

Parameters:

  • str (String)

    String to hash

Returns:

  • (Integer)

    Hash value



186
187
188
189
# File 'lib/ibm_appconfiguration_ruby_sdk/configurations/internal/utils.rb', line 186

def compute_hash(str)
  seed = 0
  MurmurHash3::V32.str_hash(str, seed)
end

#extract_configurations(configurations, environment, collection) ⇒ Hash

Unified parser for app-config data for new sdk-config format, export and promote data format

Parameters:

  • configurations (Hash)

    Configuration JSON data (with symbol keys)

  • environment (String)

    Environment ID

  • collection (String)

    Collection ID

Returns:

  • (Hash)

    Hash containing features, properties, and segments



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/ibm_appconfiguration_ruby_sdk/configurations/internal/utils.rb', line 131

def extract_configurations(configurations, environment, collection)
  puts "🔍 extract_configurations called"
  puts "   Environment: #{environment}"
  puts "   Collection: #{collection}"

  # Check if data belongs to correct collection
  raise "Improper/Missing collections in configuration" unless configurations.key?(:collections) && configurations[:collections].is_a?(Array)

  match_found = false
  configurations[:collections].each do |coll|
    puts "   Checking collection: #{coll[:collection_id]}"
    if coll[:collection_id] == collection
      match_found = true
      break
    end
  end

  raise "Required collection not found in collections" unless match_found

  puts "   Collection match found!"

  # Data in SDK config/export/promote format
  config_data = extract_environment_data(configurations, environment)
  puts "   After extract_environment_data: features=#{config_data[:features]&.length}"

  result = extract_resources(config_data, collection)
  puts "   After extract_resources: features=#{result[:features]&.length}"

  result
rescue StandardError => e
  puts "❌ ERROR in extract_configurations: #{e.message}"
  puts e.backtrace.first(5).join("\n")
  raise "Extraction of configurations failed with error:\n #{e.message}"
end

#extract_environment_data(data, environment_id) ⇒ Hash

Prepares config data for extraction with validation

Parameters:

  • data (Hash)

    Configuration data

  • environment_id (String)

    Environment ID

Returns:

  • (Hash)

    Hash containing features, properties, and segments



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/ibm_appconfiguration_ruby_sdk/configurations/internal/utils.rb', line 52

def extract_environment_data(data, environment_id)
  unless data.key?(:segments) && data[:segments].is_a?(Array) &&
         data.key?(:environments) && data[:environments].is_a?(Array)
    raise "Improper Data format present in configuration"
  end

  data[:environments].each do |environment|
    next unless environment[:environment_id] == environment_id

    result = {
      features: environment[:features] || [],
      properties: environment[:properties] || [],
      segments: data[:segments]
    }
    puts "🔍 extract_environment_data: Found environment '#{environment_id}'"
    puts "   Features: #{result[:features].length}"
    puts "   Properties: #{result[:properties].length}"
    puts "   Segments: #{result[:segments].length}"
    return result
  end

  raise "Matching environment not found in configuration"
end

#extract_resources(resource_data, collection) ⇒ Hash

Returns object containing features, properties, segments after validation

Parameters:

  • resource_data (Hash)

    Resource data containing features, properties, and segments

  • collection (String)

    Collection ID

Returns:

  • (Hash)

    Hash containing validated features, properties, and segments



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/ibm_appconfiguration_ruby_sdk/configurations/internal/utils.rb', line 80

def extract_resources(resource_data, collection)
  features = []
  properties = []
  segments = []
  segment_ids = Set.new

  puts "🔍 DEBUG extract_resources:"
  puts "  Features in resource_data: #{resource_data[:features]&.length || 0}"
  puts "  Properties in resource_data: #{resource_data[:properties]&.length || 0}"
  puts "  Collection to match: #{collection}"

  # Appending features with validation to features array
  resource_data[:features].each do |feature|
    valid = validate_resource(feature, collection)
    if valid
      append_segment_id(feature, segment_ids)
      features << feature
    end
  end

  # Appending properties with validation to properties array
  resource_data[:properties].each do |property|
    valid = validate_resource(property, collection)
    if valid
      append_segment_id(property, segment_ids)
      properties << property
    end
  end

  # Appending only required segments to segments array and throw error if any required segment is absent
  resource_data[:segments].each do |segment|
    if segment_ids.include?(segment[:segment_id])
      segments << segment
      segment_ids.delete(segment[:segment_id])
    end
  end

  raise "Required segment doesn't exist in provided segments" if segment_ids.size.positive?

  {
    features: features,
    properties: properties,
    segments: segments
  }
end

#get_normalized_value(str) ⇒ Integer

Get normalized value for rollout percentage calculation

Parameters:

  • str (String)

    String to normalize

Returns:

  • (Integer)

    Normalized value (0-100)



195
196
197
198
199
# File 'lib/ibm_appconfiguration_ruby_sdk/configurations/internal/utils.rb', line 195

def get_normalized_value(str)
  max_hash_value = 2**32
  normalizer = 100
  ((compute_hash(str).to_f / max_hash_value) * normalizer).floor
end

#parse_rollout_configuration_phases(configuration) ⇒ Hash

Parse progressive rollout phases into a sorted hash for timestamp-to-percentage lookups.

Parameters:

  • configuration (Hash)

    Rollout config with start_at and phases

Returns:

  • (Hash)

    Sorted hash mapping timestamp (ms) -> percentage

Raises:

  • (ArgumentError)

    If configuration is invalid



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/ibm_appconfiguration_ruby_sdk/configurations/internal/utils.rb', line 206

def parse_rollout_configuration_phases(configuration)
  # Validate input
  raise ArgumentError.new("Invalid rollout configuration") unless configuration&.key?(:start_at) && configuration[:phases].is_a?(Array)

  # Time unit multipliers (to milliseconds)
  multipliers = {
    "days" => 86_400_000,
    "hours" => 3_600_000,
    "minutes" => 60_000
  }

  # Parse start timestamp
  begin
    start_timestamp = Time.parse(configuration[:start_at]).to_i * 1000 # Convert to milliseconds
  rescue ArgumentError
    raise ArgumentError.new("Invalid start_at: #{configuration[:start_at]}")
  end

  # Initialize result hash with initial entry
  result = { 0 => 0 }
  transition_time = start_timestamp

  # Process each phase
  configuration[:phases].each do |phase|
    next unless phase.is_a?(Hash) && phase.key?(:percentage) && phase[:percentage].is_a?(Numeric)

    result[transition_time] = phase[:percentage]

    # Calculate next transition time if duration is specified
    transition_time += multipliers[phase[:duration_type]] * phase[:duration] if phase[:duration] && phase[:duration_type] && multipliers[phase[:duration_type]]
  end

  # Return sorted hash (Ruby hashes maintain insertion order, so we sort by key)
  result.sort.to_h
end

#symbolize_keys(obj) ⇒ Object

Helper method to convert string keys to symbol keys recursively

Parameters:

  • obj (Object)

    The object to convert

Returns:

  • (Object)

    The converted object with symbol keys



169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/ibm_appconfiguration_ruby_sdk/configurations/internal/utils.rb', line 169

def symbolize_keys(obj)
  case obj
  when Hash
    obj.each_with_object({}) do |(key, value), result|
      result[key.to_sym] = symbolize_keys(value)
    end
  when Array
    obj.map { |item| symbolize_keys(item) }
  else
    obj
  end
end

#validate_resource(resource, collection) ⇒ Boolean

Validates feature/property belongs to collection if it contains collections else gives true as default

Parameters:

  • resource (Hash)

    The resource (feature or property)

  • collection (String)

    The collection ID

Returns:

  • (Boolean)


23
24
25
26
27
28
29
30
31
# File 'lib/ibm_appconfiguration_ruby_sdk/configurations/internal/utils.rb', line 23

def validate_resource(resource, collection)
  # If collections is not present the resource data is coming from SDK APIs
  return true unless resource.key?(:collections)

  collections = resource[:collections]
  raise "Improper collection format in resource data" unless collections.is_a?(Array)

  collections.any? { |coll| coll[:collection_id] == collection }
end