ff-ruby-server-sdk 0.0.2 → 1.0.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.run/build.sh.run.xml +17 -0
  3. data/.run/install.sh.run.xml +17 -0
  4. data/.run/openapi.sh.run.xml +17 -0
  5. data/.run/publish.sh.run.xml +17 -0
  6. data/.run/sdk_test.rb.run.xml +3 -3
  7. data/.run/unpublish.sh.run.xml +17 -0
  8. data/CHANGELOG.md +1 -1
  9. data/Gemfile +20 -3
  10. data/README.md +155 -7
  11. data/api.yaml +736 -0
  12. data/example/example.rb +99 -3
  13. data/lib/ff/ruby/server/sdk/api/auth_service.rb +91 -0
  14. data/lib/ff/ruby/server/sdk/api/cf_client.rb +93 -0
  15. data/lib/ff/ruby/server/sdk/api/client_callback.rb +45 -0
  16. data/lib/ff/ruby/server/sdk/api/config.rb +140 -0
  17. data/lib/ff/ruby/server/sdk/api/config_builder.rb +116 -0
  18. data/lib/ff/ruby/server/sdk/api/default_cache.rb +112 -0
  19. data/lib/ff/ruby/server/sdk/api/evaluation.rb +29 -0
  20. data/lib/ff/ruby/server/sdk/api/evaluator.rb +526 -0
  21. data/lib/ff/ruby/server/sdk/api/file_map_store.rb +60 -0
  22. data/lib/ff/ruby/server/sdk/api/flag_evaluate_callback.rb +13 -0
  23. data/lib/ff/ruby/server/sdk/api/inner_client.rb +311 -0
  24. data/lib/ff/ruby/server/sdk/api/inner_client_flag_evaluate_callback.rb +30 -0
  25. data/lib/ff/ruby/server/sdk/api/inner_client_metrics_callback.rb +33 -0
  26. data/lib/ff/ruby/server/sdk/api/inner_client_repository_callback.rb +44 -0
  27. data/lib/ff/ruby/server/sdk/api/inner_client_updater.rb +63 -0
  28. data/lib/ff/ruby/server/sdk/api/metrics_callback.rb +19 -0
  29. data/lib/ff/ruby/server/sdk/api/metrics_event.rb +16 -0
  30. data/lib/ff/ruby/server/sdk/api/metrics_processor.rb +297 -0
  31. data/lib/ff/ruby/server/sdk/api/operators.rb +20 -0
  32. data/lib/ff/ruby/server/sdk/api/polling_processor.rb +164 -0
  33. data/lib/ff/ruby/server/sdk/api/repository_callback.rb +28 -0
  34. data/lib/ff/ruby/server/sdk/api/storage_repository.rb +263 -0
  35. data/lib/ff/ruby/server/sdk/api/summary_metrics.rb +16 -0
  36. data/lib/ff/ruby/server/sdk/api/update_processor.rb +149 -0
  37. data/lib/ff/ruby/server/sdk/common/cache.rb +27 -0
  38. data/lib/ff/ruby/server/sdk/common/closeable.rb +7 -0
  39. data/lib/ff/ruby/server/sdk/common/destroyable.rb +12 -0
  40. data/lib/ff/ruby/server/sdk/common/repository.rb +45 -0
  41. data/lib/ff/ruby/server/sdk/common/storage.rb +29 -0
  42. data/lib/ff/ruby/server/sdk/connector/connector.rb +44 -0
  43. data/lib/ff/ruby/server/sdk/connector/events.rb +118 -0
  44. data/lib/ff/ruby/server/sdk/connector/harness_connector.rb +236 -0
  45. data/lib/ff/ruby/server/sdk/connector/service.rb +19 -0
  46. data/lib/ff/ruby/server/sdk/connector/updater.rb +32 -0
  47. data/lib/ff/ruby/server/sdk/dto/message.rb +13 -0
  48. data/lib/ff/ruby/server/sdk/dto/target.rb +24 -0
  49. data/lib/ff/ruby/server/sdk/version.rb +2 -1
  50. data/lib/ff/ruby/server/sdk.rb +39 -3
  51. data/openapitools.json +7 -0
  52. data/scripts/openapi.sh +35 -0
  53. data/scripts/sdk_specs.sh +1 -1
  54. metadata +46 -3
  55. data/lib/ff/ruby/server/sdk/cf_client.rb +0 -6
@@ -0,0 +1,29 @@
1
+
2
+ class Evaluation
3
+
4
+ def initialize
5
+ super
6
+
7
+ @tbi = "To be implemented"
8
+ end
9
+
10
+ def bool_variation(identifier, target, default_value, callback)
11
+
12
+ raise @tbi
13
+ end
14
+
15
+ def string_variation(identifier, target, default_value, callback)
16
+
17
+ raise @tbi
18
+ end
19
+
20
+ def number_variation(identifier, target, default_value, callback)
21
+
22
+ raise @tbi
23
+ end
24
+
25
+ def json_variation(identifier, target, default_value, callback)
26
+
27
+ raise @tbi
28
+ end
29
+ end
@@ -0,0 +1,526 @@
1
+ require "json"
2
+ require "murmurhash3"
3
+
4
+ require_relative "evaluation"
5
+ require_relative "../common/repository"
6
+
7
+ class Evaluator < Evaluation
8
+
9
+ def initialize(repository, logger = nil)
10
+
11
+ unless repository.kind_of?(Repository)
12
+
13
+ raise "The 'repository' parameter must be of '" + Repository.to_s + "' data type"
14
+ end
15
+
16
+ if logger != nil
17
+
18
+ @logger = logger
19
+ else
20
+
21
+ @logger = Logger.new(STDOUT)
22
+ end
23
+
24
+ @repository = repository
25
+ end
26
+
27
+ def bool_variation(identifier, target, default_value, callback)
28
+
29
+ variation = evaluate(identifier, target, "boolean", callback)
30
+
31
+ if variation != nil
32
+
33
+ return variation.value == "true"
34
+ end
35
+
36
+ default_value
37
+ end
38
+
39
+ def string_variation(identifier, target, default_value, callback)
40
+
41
+ variation = evaluate(identifier, target, "string", callback)
42
+
43
+ if variation != nil
44
+
45
+ return variation.value
46
+ end
47
+
48
+ default_value
49
+ end
50
+
51
+ def number_variation(identifier, target, default_value, callback)
52
+
53
+ variation = evaluate(identifier, target, "int", callback)
54
+
55
+ if variation != nil
56
+
57
+ return variation.value.to_i
58
+ end
59
+
60
+ default_value
61
+ end
62
+
63
+ def json_variation(identifier, target, default_value, callback)
64
+
65
+ variation = evaluate(identifier, target, "json", callback)
66
+
67
+ if variation != nil
68
+
69
+ return JSON.parse(variation.value)
70
+ end
71
+
72
+ default_value
73
+ end
74
+
75
+ def evaluate(identifier, target, expected, callback)
76
+
77
+ if callback != nil
78
+
79
+ unless callback.kind_of?(FlagEvaluateCallback)
80
+
81
+ raise "The 'callback' parameter must be of '" + FlagEvaluateCallback.to_s + "' data type"
82
+ end
83
+ end
84
+
85
+ flag = @repository.get_flag(identifier)
86
+
87
+ if flag != nil && flag.kind == expected
88
+
89
+ unless flag.prerequisites.empty?
90
+
91
+ pre_req = check_pre_requisite(flag, target)
92
+
93
+ unless pre_req
94
+
95
+ return find_variation(flag.variations, flag.off_variation)
96
+ end
97
+ end
98
+
99
+ variation = evaluate_flag(flag, target)
100
+
101
+ if variation != nil
102
+
103
+ if callback != nil
104
+
105
+ callback.process_evaluation(flag, target, variation)
106
+ end
107
+
108
+ return variation
109
+ end
110
+ end
111
+
112
+ nil
113
+ end
114
+
115
+ protected
116
+
117
+ def get_attr_value(target, attribute)
118
+
119
+ if attribute != nil && !attribute.empty?
120
+
121
+ if target.respond_to?(attribute, :include_private)
122
+
123
+ @logger.debug "The attribute " + attribute.to_s + " exists (1)"
124
+
125
+ return target.send(attribute)
126
+ else
127
+
128
+ result = target.attributes.key?(attribute)
129
+
130
+ if result == nil
131
+
132
+ @logger.debug "The attribute " + attribute.to_s + " does not exist"
133
+
134
+ else
135
+
136
+ @logger.debug "The attribute " + attribute.to_s + " exists (2)"
137
+ end
138
+
139
+ return result
140
+ end
141
+ end
142
+
143
+ @logger.debug "The passed attribute is empty"
144
+
145
+ nil
146
+ end
147
+
148
+ def find_variation(variations, identifier)
149
+
150
+ if identifier != nil && !identifier.empty?
151
+
152
+ variations.each do |v|
153
+
154
+ if v.identifier == identifier
155
+
156
+ return v
157
+ end
158
+ end
159
+ end
160
+
161
+ nil
162
+ end
163
+
164
+ def get_normalized_number(property, bucket_by)
165
+
166
+ joined = property.to_s + ":" + bucket_by.to_s
167
+ hash = MurmurHash3::V32.str_hash(joined, joined.length)
168
+ (hash % 100) + 1
169
+ end
170
+
171
+ def is_enabled(target, bucket_by, percentage)
172
+
173
+ property = get_attr_value(target, bucket_by)
174
+
175
+ if property != nil
176
+
177
+ bucket_id = get_normalized_number(property, bucket_by)
178
+
179
+ return percentage > 0 && bucket_id <= percentage
180
+ end
181
+
182
+ false
183
+ end
184
+
185
+ def evaluate_distribution(distribution, target)
186
+
187
+ if distribution != nil
188
+
189
+ variation = nil
190
+
191
+ distribution.variations.each do |weighted_variation|
192
+
193
+ variation = weighted_variation.variation
194
+
195
+ if is_enabled(target, distribution.bucket_by, weighted_variation.weight)
196
+
197
+ return variation
198
+ end
199
+ end
200
+
201
+ return variation
202
+ end
203
+
204
+ nil
205
+ end
206
+
207
+ def evaluate_clauses(clauses, target)
208
+
209
+ clauses.each do |clause|
210
+
211
+ unless evaluate_clause(clause, target)
212
+
213
+ return false
214
+ end
215
+ end
216
+
217
+ true
218
+ end
219
+
220
+ def evaluate_clause(clause, target)
221
+
222
+ if clause == nil
223
+
224
+ return false
225
+ end
226
+
227
+ operator = clause.op
228
+
229
+ if operator == nil || operator.empty?
230
+
231
+ return false
232
+ end
233
+
234
+ if operator == "segmentMatch"
235
+
236
+ return is_target_included_or_excluded_in_segment(clause.values, target)
237
+ end
238
+
239
+ if clause.values.empty?
240
+
241
+ return false
242
+ end
243
+
244
+ value = clause.values[0]
245
+ attr_value = get_attr_value(target, clause.attribute)
246
+
247
+ if attr_value == nil
248
+
249
+ return false
250
+ end
251
+
252
+ object = attr_value.to_s
253
+
254
+ if operator == "starts_with"
255
+
256
+ return object.start_with?(value)
257
+ end
258
+
259
+ if operator == "ends_with"
260
+
261
+ return object.end_with?(value)
262
+ end
263
+
264
+ if operator == "match"
265
+
266
+ match = object.match?(value)
267
+ return match != nil && !match.empty?
268
+ end
269
+
270
+ if operator == "contains"
271
+
272
+ return object.include?(value)
273
+ end
274
+
275
+ if operator == "equal"
276
+
277
+ return object.casecmp?(value)
278
+ end
279
+
280
+ if operator == "equal_sensitive"
281
+
282
+ return object == value
283
+ end
284
+
285
+ if operator == "in"
286
+
287
+ return object.include?(value)
288
+ end
289
+
290
+ if operator == "segmentMatch"
291
+
292
+ return is_target_included_or_excluded_in_segment(clause.values, target)
293
+ end
294
+
295
+ false
296
+ end
297
+
298
+ def is_target_included_or_excluded_in_segment(segment_list, target)
299
+
300
+ segment_list.each do |segment_identifier|
301
+
302
+ segment = @repository.get_segment(segment_identifier)
303
+
304
+ if segment != nil
305
+
306
+ if is_target_in_list(target, segment.excluded)
307
+
308
+ @logger.debug "Target " + target.name.to_s + " excluded from segment " + segment.name.to_s + " via exclude list"
309
+
310
+ return false
311
+ end
312
+
313
+ if is_target_in_list(target, segment.included)
314
+
315
+ @logger.debug "Target " + target.name.to_s + " included in segment " + segment.name.to_s + " via include list"
316
+
317
+ return true
318
+ end
319
+
320
+ rules = segment.rules
321
+
322
+ if rules != nil && !rules.empty? && evaluate_clauses(rules, target)
323
+
324
+ @logger.debug "Target " + target.name.to_s + " included in segment " + segment.name.to_s + " via rules"
325
+
326
+ return true
327
+ end
328
+ end
329
+ end
330
+
331
+ false
332
+ end
333
+
334
+ def evaluate_rules(serving_rules, target)
335
+
336
+ if target == nil || serving_rules == nil
337
+
338
+ return nil
339
+ end
340
+
341
+ sorted = serving_rules.sort do |a, b|
342
+
343
+ b.priority <=> a.priority
344
+ end
345
+
346
+ sorted.each do |rule|
347
+
348
+ next unless evaluate_rule(rule, target)
349
+
350
+ if rule.serve.distribution != nil
351
+
352
+ return evaluate_distribution(rule.serve.distribution, target)
353
+ end
354
+
355
+ if rule.serve.variation != nil
356
+
357
+ return rule.serve.variation
358
+ end
359
+ end
360
+
361
+ nil
362
+ end
363
+
364
+ def evaluate_rule(serving_rule, target)
365
+
366
+ evaluate_clauses(serving_rule.clauses, target)
367
+ end
368
+
369
+ def evaluate_variation_map(variation_maps, target)
370
+
371
+ if target == nil
372
+
373
+ return nil
374
+ end
375
+
376
+ variation_maps.each do |variation_map|
377
+
378
+ targets = variation_map.targets
379
+
380
+ if targets != nil
381
+
382
+ found = nil
383
+
384
+ targets.each do |t|
385
+
386
+ if t.identifier != nil && t.identifier == target.identifier
387
+
388
+ found = t
389
+ break
390
+ end
391
+ end
392
+
393
+ if found != nil
394
+
395
+ return variation_map.variation
396
+ end
397
+ end
398
+
399
+ segment_identifiers = variation_map.target_segments
400
+
401
+ if segment_identifiers != nil && is_target_included_or_excluded_in_segment(segment_identifiers, target)
402
+
403
+ return variation_map.variation
404
+ end
405
+ end
406
+
407
+ nil
408
+ end
409
+
410
+ def evaluate_flag(feature_config, target)
411
+
412
+ variation = feature_config.off_variation
413
+
414
+ if feature_config.state == OpenapiClient::FeatureState::ON
415
+
416
+ variation = nil
417
+
418
+ if feature_config.variation_to_target_map != nil
419
+
420
+ variation = evaluate_variation_map(feature_config.variation_to_target_map, target)
421
+ end
422
+
423
+ if variation == nil
424
+
425
+ variation = evaluate_rules(feature_config.rules, target)
426
+ end
427
+
428
+ if variation == nil
429
+
430
+ variation = evaluate_distribution(feature_config.default_serve.distribution, target)
431
+ end
432
+
433
+ if variation == nil
434
+
435
+ variation = feature_config.default_serve.variation
436
+ end
437
+ end
438
+
439
+ if variation != nil
440
+
441
+ return find_variation(feature_config.variations, variation)
442
+ end
443
+
444
+ nil
445
+ end
446
+
447
+ def check_pre_requisite(parent_feature_config, target)
448
+
449
+ prerequisites = parent_feature_config.prerequisites
450
+
451
+ if prerequisites != nil && !prerequisites.empty?
452
+
453
+ @logger.debug "Checking pre requisites " + prerequisites.to_s + " of parent feature " + parent_feature_config.feature.to_s
454
+
455
+ prerequisites.each do |pqs|
456
+
457
+ pre_req_feature = pqs.feature
458
+
459
+ pre_req_feature_config = @repository.get_flag(pre_req_feature)
460
+
461
+ if pre_req_feature_config == nil
462
+
463
+ @logger.debug "Could not retrieve the pre requisite details of feature flag: " + pre_req_feature.to_s
464
+
465
+ return true
466
+ end
467
+
468
+ pre_req_evaluated_variation = evaluate_flag(pre_req_feature_config, target)
469
+
470
+ if pre_req_evaluated_variation == nil
471
+
472
+ @logger.debug "Could not evaluate the prerequisite details of feature flag: " + pre_req_feature.to_s
473
+
474
+ return true
475
+ end
476
+
477
+ @logger.debug "Pre requisite flag " + pre_req_feature_config.feature + " has variation " +
478
+ pre_req_evaluated_variation.to_s + " for target " + target.to_s
479
+
480
+ valid_pre_req_variations = pqs.variations
481
+
482
+ @logger.debug "Pre requisite flag " + pre_req_feature_config.to_s + " should have the variations " +
483
+ valid_pre_req_variations.to_s
484
+
485
+ none_match = true
486
+
487
+ valid_pre_req_variations.each do |element|
488
+
489
+ if element.include?(pre_req_evaluated_variation.value)
490
+
491
+ none_match = false
492
+ break
493
+ end
494
+ end
495
+
496
+ if none_match
497
+
498
+ return false
499
+ else
500
+
501
+ return check_pre_requisite(pre_req_feature_config, target)
502
+ end
503
+ end
504
+ end
505
+
506
+ true
507
+ end
508
+
509
+ private
510
+
511
+ def is_target_in_list(target, list_of_targets)
512
+
513
+ if list_of_targets != nil
514
+
515
+ list_of_targets.each do |included_target|
516
+
517
+ if included_target.identifier.include?(target.identifier)
518
+
519
+ return true
520
+ end
521
+ end
522
+ end
523
+
524
+ false
525
+ end
526
+ end
@@ -0,0 +1,60 @@
1
+ require "moneta"
2
+ require_relative "../common/storage"
3
+
4
+ class FileMapStore < Storage
5
+
6
+ def initialize
7
+
8
+ @keys = Set[]
9
+ @store = Moneta.new(:File, dir: "moneta")
10
+ end
11
+
12
+ def set(key, value)
13
+
14
+ check_init
15
+
16
+ @store[key] = value
17
+ keys.add(key)
18
+ end
19
+
20
+ def get(key)
21
+
22
+ check_init
23
+
24
+ @store[key]
25
+ end
26
+
27
+ def delete(key)
28
+
29
+ check_init
30
+
31
+ @store.delete(key)
32
+ keys.delete(key)
33
+ end
34
+
35
+ def keys
36
+
37
+ check_init
38
+
39
+ @keys
40
+ end
41
+
42
+ def close
43
+
44
+ if @store != nil
45
+
46
+ @store.close
47
+ @keys = Set[]
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def check_init
54
+
55
+ if @store == nil
56
+
57
+ raise "Not initialized"
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,13 @@
1
+ class FlagEvaluateCallback
2
+
3
+ def initialize
4
+ super
5
+
6
+ @tbi = "To be implemented"
7
+ end
8
+
9
+ def process_evaluation(feature_config, target, variation)
10
+
11
+ raise @tbi
12
+ end
13
+ end