ff-ruby-server-sdk 0.0.2 → 1.0.2.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.run/sdk_test.rb.run.xml +3 -3
  3. data/CHANGELOG.md +12 -2
  4. data/Gemfile +17 -3
  5. data/README.md +155 -7
  6. data/api.yaml +736 -0
  7. data/example/example.rb +100 -3
  8. data/lib/ff/ruby/server/generated/lib/openapi_client/api/client_api.rb +545 -0
  9. data/lib/ff/ruby/server/generated/lib/openapi_client/api/metrics_api.rb +89 -0
  10. data/lib/ff/ruby/server/generated/lib/openapi_client/api_client.rb +390 -0
  11. data/lib/ff/ruby/server/generated/lib/openapi_client/api_error.rb +57 -0
  12. data/lib/ff/ruby/server/generated/lib/openapi_client/configuration.rb +282 -0
  13. data/lib/ff/ruby/server/generated/lib/openapi_client/models/authentication_request.rb +232 -0
  14. data/lib/ff/ruby/server/generated/lib/openapi_client/models/authentication_request_target.rb +250 -0
  15. data/lib/ff/ruby/server/generated/lib/openapi_client/models/authentication_response.rb +223 -0
  16. data/lib/ff/ruby/server/generated/lib/openapi_client/models/clause.rb +281 -0
  17. data/lib/ff/ruby/server/generated/lib/openapi_client/models/distribution.rb +239 -0
  18. data/lib/ff/ruby/server/generated/lib/openapi_client/models/error.rb +237 -0
  19. data/lib/ff/ruby/server/generated/lib/openapi_client/models/evaluation.rb +260 -0
  20. data/lib/ff/ruby/server/generated/lib/openapi_client/models/feature_config.rb +418 -0
  21. data/lib/ff/ruby/server/generated/lib/openapi_client/models/feature_state.rb +37 -0
  22. data/lib/ff/ruby/server/generated/lib/openapi_client/models/key_value.rb +237 -0
  23. data/lib/ff/ruby/server/generated/lib/openapi_client/models/metrics.rb +231 -0
  24. data/lib/ff/ruby/server/generated/lib/openapi_client/models/metrics_data.rb +303 -0
  25. data/lib/ff/ruby/server/generated/lib/openapi_client/models/pagination.rb +274 -0
  26. data/lib/ff/ruby/server/generated/lib/openapi_client/models/prerequisite.rb +239 -0
  27. data/lib/ff/ruby/server/generated/lib/openapi_client/models/segment.rb +320 -0
  28. data/lib/ff/ruby/server/generated/lib/openapi_client/models/serve.rb +227 -0
  29. data/lib/ff/ruby/server/generated/lib/openapi_client/models/serving_rule.rb +267 -0
  30. data/lib/ff/ruby/server/generated/lib/openapi_client/models/tag.rb +233 -0
  31. data/lib/ff/ruby/server/generated/lib/openapi_client/models/target.rb +331 -0
  32. data/lib/ff/ruby/server/generated/lib/openapi_client/models/target_data.rb +253 -0
  33. data/lib/ff/ruby/server/generated/lib/openapi_client/models/target_map.rb +232 -0
  34. data/lib/ff/ruby/server/generated/lib/openapi_client/models/variation.rb +255 -0
  35. data/lib/ff/ruby/server/generated/lib/openapi_client/models/variation_map.rb +245 -0
  36. data/lib/ff/ruby/server/generated/lib/openapi_client/models/weighted_variation.rb +237 -0
  37. data/lib/ff/ruby/server/generated/lib/openapi_client/version.rb +15 -0
  38. data/lib/ff/ruby/server/generated/lib/openapi_client.rb +65 -0
  39. data/lib/ff/ruby/server/sdk/api/auth_service.rb +91 -0
  40. data/lib/ff/ruby/server/sdk/api/cf_client.rb +93 -0
  41. data/lib/ff/ruby/server/sdk/api/client_callback.rb +45 -0
  42. data/lib/ff/ruby/server/sdk/api/config.rb +140 -0
  43. data/lib/ff/ruby/server/sdk/api/config_builder.rb +116 -0
  44. data/lib/ff/ruby/server/sdk/api/default_cache.rb +112 -0
  45. data/lib/ff/ruby/server/sdk/api/evaluation.rb +29 -0
  46. data/lib/ff/ruby/server/sdk/api/evaluator.rb +526 -0
  47. data/lib/ff/ruby/server/sdk/api/file_map_store.rb +60 -0
  48. data/lib/ff/ruby/server/sdk/api/flag_evaluate_callback.rb +13 -0
  49. data/lib/ff/ruby/server/sdk/api/inner_client.rb +311 -0
  50. data/lib/ff/ruby/server/sdk/api/inner_client_flag_evaluate_callback.rb +30 -0
  51. data/lib/ff/ruby/server/sdk/api/inner_client_metrics_callback.rb +33 -0
  52. data/lib/ff/ruby/server/sdk/api/inner_client_repository_callback.rb +44 -0
  53. data/lib/ff/ruby/server/sdk/api/inner_client_updater.rb +63 -0
  54. data/lib/ff/ruby/server/sdk/api/metrics_callback.rb +19 -0
  55. data/lib/ff/ruby/server/sdk/api/metrics_event.rb +16 -0
  56. data/lib/ff/ruby/server/sdk/api/metrics_processor.rb +297 -0
  57. data/lib/ff/ruby/server/sdk/api/operators.rb +20 -0
  58. data/lib/ff/ruby/server/sdk/api/polling_processor.rb +164 -0
  59. data/lib/ff/ruby/server/sdk/api/repository_callback.rb +28 -0
  60. data/lib/ff/ruby/server/sdk/api/storage_repository.rb +263 -0
  61. data/lib/ff/ruby/server/sdk/api/summary_metrics.rb +16 -0
  62. data/lib/ff/ruby/server/sdk/api/update_processor.rb +149 -0
  63. data/lib/ff/ruby/server/sdk/common/cache.rb +27 -0
  64. data/lib/ff/ruby/server/sdk/common/closeable.rb +7 -0
  65. data/lib/ff/ruby/server/sdk/common/destroyable.rb +12 -0
  66. data/lib/ff/ruby/server/sdk/common/repository.rb +45 -0
  67. data/lib/ff/ruby/server/sdk/common/storage.rb +29 -0
  68. data/lib/ff/ruby/server/sdk/connector/connector.rb +44 -0
  69. data/lib/ff/ruby/server/sdk/connector/events.rb +118 -0
  70. data/lib/ff/ruby/server/sdk/connector/harness_connector.rb +236 -0
  71. data/lib/ff/ruby/server/sdk/connector/service.rb +19 -0
  72. data/lib/ff/ruby/server/sdk/connector/updater.rb +32 -0
  73. data/lib/ff/ruby/server/sdk/dto/message.rb +13 -0
  74. data/lib/ff/ruby/server/sdk/dto/target.rb +24 -0
  75. data/lib/ff/ruby/server/sdk/version.rb +2 -1
  76. data/lib/ff/ruby/server/sdk.rb +39 -3
  77. data/openapitools.json +7 -0
  78. data/scripts/install.sh +8 -2
  79. data/scripts/openapi.sh +76 -0
  80. data/scripts/publish.sh +1 -1
  81. data/scripts/sdk_specs.sh +1 -1
  82. metadata +263 -11
  83. data/lib/ff/ruby/server/sdk/cf_client.rb +0 -6
@@ -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