Module: IbmAppconfigurationRubySdk::Utils

Included in:
ConfigFetcher, ConfigurationHandler
Defined in:
lib/ibm_appconfiguration_ruby_sdk/utils.rb

Class Method Summary collapse

Class 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



42
43
44
45
46
47
48
49
50
51
52
# File 'lib/ibm_appconfiguration_ruby_sdk/utils.rb', line 42

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



161
162
163
164
# File 'lib/ibm_appconfiguration_ruby_sdk/utils.rb', line 161

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

Raises:



128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/ibm_appconfiguration_ruby_sdk/utils.rb', line 128

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

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

  raise IbmAppconfigurationRubySdk::Error, "Required collection not found in collections" unless match_found

  # Data in SDK config/export/promote format
  config_data = extract_environment_data(configurations, environment)
  extract_resources(config_data, collection)
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

Raises:



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/ibm_appconfiguration_ruby_sdk/utils.rb', line 58

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 IbmAppconfigurationRubySdk::Error, "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]
    }
    return result
  end

  raise IbmAppconfigurationRubySdk::Error, "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

Raises:



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
# File 'lib/ibm_appconfiguration_ruby_sdk/utils.rb', line 82

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

  # 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 IbmAppconfigurationRubySdk::Error, "Required segment doesn't exist in provided segments" if segment_ids.size.positive?

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

.get_current_rollout_percentage(rollout_hash) ⇒ Integer

Get the current rollout percentage based on the current time. Returns the percentage of the phase whose transition timestamp is the largest value that is still less than or equal to the current time.

Parameters:

  • rollout_hash (Hash)

    Sorted hash mapping timestamp (ms) -> percentage

Returns:

  • (Integer)

    The current rollout percentage (0 if the map is nil/empty)



223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/ibm_appconfiguration_ruby_sdk/utils.rb', line 223

def get_current_rollout_percentage(rollout_hash)
  return 0 if rollout_hash.nil? || rollout_hash.empty?

  current_time_ms = (Time.now.to_f * 1000).to_i
  percentage = 0
  rollout_hash.each do |timestamp, pct|
    break if timestamp > current_time_ms

    percentage = pct
  end
  percentage
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)



170
171
172
173
174
# File 'lib/ibm_appconfiguration_ruby_sdk/utils.rb', line 170

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



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/ibm_appconfiguration_ruby_sdk/utils.rb', line 181

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



144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/ibm_appconfiguration_ruby_sdk/utils.rb', line 144

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)

Raises:



29
30
31
32
33
34
35
36
37
# File 'lib/ibm_appconfiguration_ruby_sdk/utils.rb', line 29

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 IbmAppconfigurationRubySdk::Error, "Improper collection format in resource data" unless collections.is_a?(Array)

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