1+ # frozen_string_literal: true
2+
3+ module Datadog
4+ module OpenFeature
5+ module Binding
6+ # Variation types supported by UFC
7+ module VariationType
8+ STRING = 'STRING'
9+ INTEGER = 'INTEGER'
10+ NUMERIC = 'NUMERIC'
11+ BOOLEAN = 'BOOLEAN'
12+ JSON = 'JSON'
13+ end
14+
15+ # Condition operators for rule evaluation
16+ module ConditionOperator
17+ MATCHES = 'MATCHES'
18+ NOT_MATCHES = 'NOT_MATCHES'
19+ GTE = 'GTE'
20+ GT = 'GT'
21+ LTE = 'LTE'
22+ LT = 'LT'
23+ ONE_OF = 'ONE_OF'
24+ NOT_ONE_OF = 'NOT_ONE_OF'
25+ IS_NULL = 'IS_NULL'
26+ end
27+
28+ # Assignment reasons returned in ResolutionDetails
29+ module AssignmentReason
30+ TARGETING_MATCH = 'TARGETING_MATCH'
31+ SPLIT = 'SPLIT'
32+ STATIC = 'STATIC'
33+ end
34+
35+ # Represents a feature flag configuration
36+ class Flag
37+ attr_reader :key , :enabled , :variation_type , :variations , :allocations
38+
39+ def initialize ( key :, enabled :, variation_type :, variations :, allocations :)
40+ @key = key
41+ @enabled = enabled
42+ @variation_type = variation_type
43+ @variations = variations || { }
44+ @allocations = allocations || [ ]
45+ end
46+
47+ def self . from_json ( key , flag_data )
48+ new (
49+ key : key ,
50+ enabled : flag_data [ 'enabled' ] || false ,
51+ variation_type : flag_data [ 'variationType' ] ,
52+ variations : parse_variations ( flag_data [ 'variations' ] || { } ) ,
53+ allocations : parse_allocations ( flag_data [ 'allocations' ] || [ ] )
54+ )
55+ end
56+
57+ private
58+
59+ def self . parse_variations ( variations_data )
60+ variations_data . transform_values do |variation_data |
61+ Variation . from_json ( variation_data )
62+ end
63+ end
64+
65+ def self . parse_allocations ( allocations_data )
66+ allocations_data . map { |allocation_data | Allocation . from_json ( allocation_data ) }
67+ end
68+ end
69+
70+ # Represents a variation value for a flag
71+ class Variation
72+ attr_reader :key , :value
73+
74+ def initialize ( key :, value :)
75+ @key = key
76+ @value = value
77+ end
78+
79+ def self . from_json ( variation_data )
80+ new (
81+ key : variation_data [ 'key' ] ,
82+ value : variation_data [ 'value' ]
83+ )
84+ end
85+ end
86+
87+ # Represents an allocation rule with traffic splits
88+ class Allocation
89+ attr_reader :key , :rules , :start_at , :end_at , :splits , :do_log
90+
91+ def initialize ( key :, rules : nil , start_at : nil , end_at : nil , splits :, do_log : true )
92+ @key = key
93+ @rules = rules
94+ @start_at = start_at
95+ @end_at = end_at
96+ @splits = splits || [ ]
97+ @do_log = do_log
98+ end
99+
100+ def self . from_json ( allocation_data )
101+ new (
102+ key : allocation_data [ 'key' ] ,
103+ rules : parse_rules ( allocation_data [ 'rules' ] ) ,
104+ start_at : parse_timestamp ( allocation_data [ 'startAt' ] ) ,
105+ end_at : parse_timestamp ( allocation_data [ 'endAt' ] ) ,
106+ splits : parse_splits ( allocation_data [ 'splits' ] || [ ] ) ,
107+ do_log : allocation_data . fetch ( 'doLog' , true )
108+ )
109+ end
110+
111+ private
112+
113+ def self . parse_rules ( rules_data )
114+ return nil if rules_data . nil? || rules_data . empty?
115+
116+ rules_data . map { |rule_data | Rule . from_json ( rule_data ) }
117+ end
118+
119+ def self . parse_splits ( splits_data )
120+ splits_data . map { |split_data | Split . from_json ( split_data ) }
121+ end
122+
123+ def self . parse_timestamp ( timestamp_data )
124+ return nil if timestamp_data . nil?
125+
126+ # Handle both Unix timestamps and ISO8601 strings
127+ case timestamp_data
128+ when Numeric
129+ Time . at ( timestamp_data )
130+ when String
131+ Time . parse ( timestamp_data )
132+ else
133+ nil
134+ end
135+ rescue StandardError
136+ nil
137+ end
138+ end
139+
140+ # Represents a traffic split within an allocation
141+ class Split
142+ attr_reader :shards , :variation_key , :extra_logging
143+
144+ def initialize ( shards :, variation_key :, extra_logging : nil )
145+ @shards = shards || [ ]
146+ @variation_key = variation_key
147+ @extra_logging = extra_logging || { }
148+ end
149+
150+ def self . from_json ( split_data )
151+ new (
152+ shards : parse_shards ( split_data [ 'shards' ] || [ ] ) ,
153+ variation_key : split_data [ 'variationKey' ] ,
154+ extra_logging : split_data [ 'extraLogging' ] || { }
155+ )
156+ end
157+
158+ private
159+
160+ def self . parse_shards ( shards_data )
161+ shards_data . map { |shard_data | Shard . from_json ( shard_data ) }
162+ end
163+ end
164+
165+ # Represents a shard configuration for traffic splitting
166+ class Shard
167+ attr_reader :salt , :total_shards , :ranges
168+
169+ def initialize ( salt :, total_shards :, ranges :)
170+ @salt = salt
171+ @total_shards = total_shards
172+ @ranges = ranges || [ ]
173+ end
174+
175+ def self . from_json ( shard_data )
176+ new (
177+ salt : shard_data [ 'salt' ] ,
178+ total_shards : shard_data [ 'totalShards' ] ,
179+ ranges : parse_ranges ( shard_data [ 'ranges' ] || [ ] )
180+ )
181+ end
182+
183+ private
184+
185+ def self . parse_ranges ( ranges_data )
186+ ranges_data . map { |range_data | ShardRange . from_json ( range_data ) }
187+ end
188+ end
189+
190+ # Represents a shard range for traffic allocation
191+ class ShardRange
192+ attr_reader :start , :end_value
193+
194+ def initialize ( start :, end_value :)
195+ @start = start
196+ @end_value = end_value
197+ end
198+
199+ def self . from_json ( range_data )
200+ new (
201+ start : range_data [ 'start' ] ,
202+ end_value : range_data [ 'end' ]
203+ )
204+ end
205+
206+ # Alias for backward compatibility
207+ def end
208+ @end_value
209+ end
210+ end
211+
212+ # Represents a targeting rule
213+ class Rule
214+ attr_reader :conditions
215+
216+ def initialize ( conditions :)
217+ @conditions = conditions || [ ]
218+ end
219+
220+ def self . from_json ( rule_data )
221+ new (
222+ conditions : parse_conditions ( rule_data [ 'conditions' ] || [ ] )
223+ )
224+ end
225+
226+ private
227+
228+ def self . parse_conditions ( conditions_data )
229+ conditions_data . map { |condition_data | Condition . from_json ( condition_data ) }
230+ end
231+ end
232+
233+ # Represents a single condition within a rule
234+ class Condition
235+ attr_reader :attribute , :operator , :value
236+
237+ def initialize ( attribute :, operator :, value :)
238+ @attribute = attribute
239+ @operator = operator
240+ @value = value
241+ end
242+
243+ def self . from_json ( condition_data )
244+ new (
245+ attribute : condition_data [ 'attribute' ] ,
246+ operator : condition_data [ 'operator' ] ,
247+ value : parse_condition_value ( condition_data [ 'value' ] )
248+ )
249+ end
250+
251+ private
252+
253+ def self . parse_condition_value ( value_data )
254+ # Handle both single values and arrays for ONE_OF/NOT_ONE_OF operators
255+ case value_data
256+ when Array
257+ value_data
258+ else
259+ value_data
260+ end
261+ end
262+ end
263+
264+ # Main configuration container
265+ class Configuration
266+ attr_reader :flags , :schema_version
267+
268+ def initialize ( flags :, schema_version : nil )
269+ @flags = flags || { }
270+ @schema_version = schema_version
271+ end
272+
273+ def self . from_json ( config_data )
274+ flags_data = config_data [ 'flags' ] || config_data [ 'flagsV1' ] || { }
275+
276+ parsed_flags = flags_data . transform_values do |flag_data |
277+ Flag . from_json ( flag_data [ 'key' ] || '' , flag_data )
278+ end
279+
280+ new (
281+ flags : parsed_flags ,
282+ schema_version : config_data [ 'schemaVersion' ]
283+ )
284+ end
285+
286+ def get_flag ( flag_key )
287+ @flags [ flag_key ]
288+ end
289+ end
290+ end
291+ end
292+ end
0 commit comments