featurevisor 0.1.1

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.
@@ -0,0 +1,818 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Featurevisor
4
+ # Evaluation reason constants
5
+ module EvaluationReason
6
+ # Feature specific
7
+ FEATURE_NOT_FOUND = "feature_not_found" # feature is not found in datafile
8
+ DISABLED = "disabled" # feature is disabled
9
+ REQUIRED = "required" # required features are not enabled
10
+ OUT_OF_RANGE = "out_of_range" # out of range when mutually exclusive experiments are involved via Groups
11
+
12
+ # Variations specific
13
+ NO_VARIATIONS = "no_variations" # feature has no variations
14
+ VARIATION_DISABLED = "variation_disabled" # feature is disabled, and variation's disabledVariationValue is used
15
+
16
+ # Variable specific
17
+ VARIABLE_NOT_FOUND = "variable_not_found" # variable's schema is not defined in the feature
18
+ VARIABLE_DEFAULT = "variable_default" # default variable value used
19
+ VARIABLE_DISABLED = "variable_disabled" # feature is disabled, and variable's disabledValue is used
20
+ VARIABLE_OVERRIDE = "variable_override" # variable overridden from inside a variation
21
+
22
+ # Common
23
+ NO_MATCH = "no_match" # no rules matched
24
+ FORCED = "forced" # against a forced rule
25
+ STICKY = "sticky" # against a sticky feature
26
+ RULE = "rule" # against a regular rule
27
+ ALLOCATED = "allocated" # regular allocation based on bucketing
28
+
29
+ ERROR = "error" # error
30
+ end
31
+
32
+ # Evaluation types
33
+ EVALUATION_TYPES = %w[flag variation variable].freeze
34
+
35
+ # Evaluation module for feature flag evaluation
36
+ module Evaluate
37
+
38
+ # Evaluate with hooks
39
+ # @param options [Hash] Evaluation options
40
+ # @return [Hash] Evaluation result
41
+ def self.evaluate_with_hooks(options)
42
+ begin
43
+ hooks_manager = options[:hooks_manager]
44
+ hooks = hooks_manager.get_all
45
+
46
+ # Run before hooks
47
+ result_options = options
48
+ hooks.each do |hook|
49
+ if hook.respond_to?(:call_before)
50
+ result_options = hook.call_before(result_options)
51
+ end
52
+ end
53
+
54
+ # Evaluate
55
+ evaluation = evaluate(result_options)
56
+
57
+ # Default: variation
58
+ if options[:default_variation_value] &&
59
+ evaluation[:type] == "variation" &&
60
+ evaluation[:variation_value].nil?
61
+ evaluation[:variation_value] = options[:default_variation_value]
62
+ end
63
+
64
+ # Default: variable
65
+ if options[:default_variable_value] &&
66
+ evaluation[:type] == "variable" &&
67
+ evaluation[:variable_value].nil?
68
+ evaluation[:variable_value] = options[:default_variable_value]
69
+ end
70
+
71
+ # Run after hooks
72
+ hooks.each do |hook|
73
+ if hook.respond_to?(:call_after)
74
+ evaluation = hook.call_after(evaluation, result_options)
75
+ end
76
+ end
77
+
78
+ evaluation
79
+ rescue => e
80
+ type = options[:type]
81
+ feature_key = options[:feature_key]
82
+ variable_key = options[:variable_key]
83
+ logger = options[:logger]
84
+
85
+ evaluation = {
86
+ type: type,
87
+ feature_key: feature_key,
88
+ variable_key: variable_key,
89
+ reason: Featurevisor::EvaluationReason::ERROR,
90
+ error: e
91
+ }
92
+
93
+ logger.error("error during evaluation", evaluation)
94
+
95
+ evaluation
96
+ end
97
+ end
98
+
99
+ # Main evaluation function
100
+ # @param options [Hash] Evaluation options
101
+ # @return [Hash] Evaluation result
102
+ def self.evaluate(options)
103
+ type = options[:type]
104
+ feature_key = options[:feature_key]
105
+ variable_key = options[:variable_key]
106
+ context = options[:context]
107
+ logger = options[:logger]
108
+ datafile_reader = options[:datafile_reader]
109
+ sticky = options[:sticky]
110
+ hooks_manager = options[:hooks_manager]
111
+
112
+ hooks = hooks_manager.get_all
113
+ evaluation = nil
114
+
115
+ begin
116
+ # Root flag evaluation
117
+ flag = nil
118
+ if type != "flag"
119
+ # needed by variation and variable evaluations
120
+ flag = evaluate(options.merge(type: "flag"))
121
+
122
+ if flag[:enabled] == false
123
+ evaluation = {
124
+ type: type,
125
+ feature_key: feature_key,
126
+ reason: Featurevisor::EvaluationReason::DISABLED
127
+ }
128
+
129
+ feature = datafile_reader.get_feature(feature_key)
130
+
131
+ # serve variable default value if feature is disabled (if explicitly specified)
132
+ if type == "variable"
133
+ if feature && variable_key &&
134
+ feature[:variablesSchema] &&
135
+ (feature[:variablesSchema][variable_key] || feature[:variablesSchema][variable_key.to_sym])
136
+ variable_schema = feature[:variablesSchema][variable_key] || feature[:variablesSchema][variable_key.to_sym]
137
+
138
+ if variable_schema[:disabledValue]
139
+ # disabledValue: <value>
140
+ evaluation = {
141
+ type: type,
142
+ feature_key: feature_key,
143
+ reason: Featurevisor::EvaluationReason::VARIABLE_DISABLED,
144
+ variable_key: variable_key,
145
+ variable_value: variable_schema[:disabledValue],
146
+ variable_schema: variable_schema,
147
+ enabled: false
148
+ }
149
+ elsif variable_schema[:useDefaultWhenDisabled]
150
+ # useDefaultWhenDisabled: true
151
+ evaluation = {
152
+ type: type,
153
+ feature_key: feature_key,
154
+ reason: Featurevisor::EvaluationReason::VARIABLE_DEFAULT,
155
+ variable_key: variable_key,
156
+ variable_value: variable_schema[:defaultValue],
157
+ variable_schema: variable_schema,
158
+ enabled: false
159
+ }
160
+ end
161
+ end
162
+ end
163
+
164
+ # serve disabled variation value if feature is disabled (if explicitly specified)
165
+ if type == "variation" && feature && feature[:disabledVariationValue]
166
+ evaluation = {
167
+ type: type,
168
+ feature_key: feature_key,
169
+ reason: Featurevisor::EvaluationReason::VARIATION_DISABLED,
170
+ variation_value: feature[:disabledVariationValue],
171
+ enabled: false
172
+ }
173
+ end
174
+
175
+ logger.debug("feature is disabled", evaluation)
176
+
177
+ return evaluation
178
+ end
179
+ end
180
+
181
+ # Sticky
182
+ if sticky && (sticky[feature_key] || sticky[feature_key.to_sym])
183
+ sticky_feature = sticky[feature_key] || sticky[feature_key.to_sym]
184
+
185
+ # flag
186
+ if type == "flag" && sticky_feature.key?(:enabled)
187
+ evaluation = {
188
+ type: type,
189
+ feature_key: feature_key,
190
+ reason: Featurevisor::EvaluationReason::STICKY,
191
+ sticky: sticky_feature,
192
+ enabled: sticky_feature[:enabled]
193
+ }
194
+
195
+ logger.debug("using sticky enabled", evaluation)
196
+
197
+ return evaluation
198
+ end
199
+
200
+ # variation
201
+ if type == "variation"
202
+ variation_value = sticky_feature[:variation]
203
+
204
+ if variation_value
205
+ evaluation = {
206
+ type: type,
207
+ feature_key: feature_key,
208
+ reason: Featurevisor::EvaluationReason::STICKY,
209
+ variation_value: variation_value
210
+ }
211
+
212
+ logger.debug("using sticky variation", evaluation)
213
+
214
+ return evaluation
215
+ end
216
+ end
217
+
218
+ # variable
219
+ if type == "variable" && variable_key
220
+ variables = sticky_feature[:variables]
221
+
222
+ if variables && (variables[variable_key] || variables[variable_key.to_sym])
223
+ variable_value = variables[variable_key] || variables[variable_key.to_sym]
224
+ evaluation = {
225
+ type: type,
226
+ feature_key: feature_key,
227
+ reason: Featurevisor::EvaluationReason::STICKY,
228
+ variable_key: variable_key,
229
+ variable_value: variable_value
230
+ }
231
+
232
+ logger.debug("using sticky variable", evaluation)
233
+
234
+ return evaluation
235
+ end
236
+ end
237
+ end
238
+
239
+ # Feature
240
+ feature = feature_key.is_a?(String) ? datafile_reader.get_feature(feature_key) : feature_key
241
+
242
+ # feature: not found
243
+ unless feature
244
+ evaluation = {
245
+ type: type,
246
+ feature_key: feature_key,
247
+ reason: Featurevisor::EvaluationReason::FEATURE_NOT_FOUND
248
+ }
249
+
250
+ logger.warn("feature not found", evaluation)
251
+
252
+ return evaluation
253
+ end
254
+
255
+ # feature: deprecated
256
+ if type == "flag" && feature[:deprecated]
257
+ logger.warn("feature is deprecated", { feature_key: feature_key })
258
+ end
259
+
260
+ # variableSchema
261
+ variable_schema = nil
262
+
263
+ if variable_key
264
+ if feature[:variablesSchema] && (feature[:variablesSchema][variable_key] || feature[:variablesSchema][variable_key.to_sym])
265
+ variable_schema = feature[:variablesSchema][variable_key] || feature[:variablesSchema][variable_key.to_sym]
266
+ end
267
+
268
+ # variable schema not found
269
+ unless variable_schema
270
+ evaluation = {
271
+ type: type,
272
+ feature_key: feature_key,
273
+ reason: Featurevisor::EvaluationReason::VARIABLE_NOT_FOUND,
274
+ variable_key: variable_key
275
+ }
276
+
277
+ logger.warn("variable schema not found", evaluation)
278
+
279
+ return evaluation
280
+ end
281
+
282
+ if variable_schema[:deprecated]
283
+ logger.warn("variable is deprecated", {
284
+ feature_key: feature_key,
285
+ variable_key: variable_key
286
+ })
287
+ end
288
+ end
289
+
290
+ # variation: no variations
291
+ if type == "variation" && (!feature[:variations] || feature[:variations].empty?)
292
+ evaluation = {
293
+ type: type,
294
+ feature_key: feature_key,
295
+ reason: Featurevisor::EvaluationReason::NO_VARIATIONS
296
+ }
297
+
298
+ logger.warn("no variations", evaluation)
299
+
300
+ return evaluation
301
+ end
302
+
303
+ # Forced
304
+ force_result = datafile_reader.get_matched_force(feature, context)
305
+ force = force_result[:force]
306
+ force_index = force_result[:forceIndex]
307
+
308
+ if force
309
+ # flag
310
+ if type == "flag" && force.key?(:enabled)
311
+ evaluation = {
312
+ type: type,
313
+ feature_key: feature_key,
314
+ reason: Featurevisor::EvaluationReason::FORCED,
315
+ force_index: force_index,
316
+ force: force,
317
+ enabled: force[:enabled]
318
+ }
319
+
320
+ logger.debug("forced enabled found", evaluation)
321
+
322
+ return evaluation
323
+ end
324
+
325
+ # variation
326
+ if type == "variation" && force[:variation] && feature[:variations]
327
+ variation = feature[:variations].find { |v| v[:value] == force[:variation] }
328
+
329
+ if variation
330
+ evaluation = {
331
+ type: type,
332
+ feature_key: feature_key,
333
+ reason: Featurevisor::EvaluationReason::FORCED,
334
+ force_index: force_index,
335
+ force: force,
336
+ variation: variation
337
+ }
338
+
339
+ logger.debug("forced variation found", evaluation)
340
+
341
+ return evaluation
342
+ end
343
+ end
344
+
345
+ # variable
346
+ if variable_key && force[:variables] && (force[:variables][variable_key] || force[:variables][variable_key.to_sym])
347
+ variable_value = force[:variables][variable_key] || force[:variables][variable_key.to_sym]
348
+ evaluation = {
349
+ type: type,
350
+ feature_key: feature_key,
351
+ reason: Featurevisor::EvaluationReason::FORCED,
352
+ force_index: force_index,
353
+ force: force,
354
+ variable_key: variable_key,
355
+ variable_schema: variable_schema,
356
+ variable_value: variable_value
357
+ }
358
+
359
+ logger.debug("forced variable", evaluation)
360
+
361
+ return evaluation
362
+ end
363
+ end
364
+
365
+ # Required
366
+ if type == "flag" && feature[:required] && feature[:required].length > 0
367
+ required_features_are_enabled = feature[:required].all? do |required|
368
+ required_key = nil
369
+ required_variation = nil
370
+
371
+ if required.is_a?(String)
372
+ required_key = required
373
+ else
374
+ required_key = required[:key]
375
+ required_variation = required[:variation]
376
+ end
377
+
378
+ required_evaluation = evaluate(options.merge(type: "flag", feature_key: required_key))
379
+ required_is_enabled = required_evaluation[:enabled]
380
+
381
+ next false unless required_is_enabled
382
+
383
+ if required_variation
384
+ required_variation_evaluation = evaluate(options.merge(type: "variation", feature_key: required_key))
385
+
386
+ required_variation_value = nil
387
+
388
+ if required_variation_evaluation[:variation_value]
389
+ required_variation_value = required_variation_evaluation[:variation_value]
390
+ elsif required_variation_evaluation[:variation]
391
+ required_variation_value = required_variation_evaluation[:variation][:value]
392
+ end
393
+
394
+ next required_variation_value == required_variation
395
+ end
396
+
397
+ true
398
+ end
399
+
400
+ unless required_features_are_enabled
401
+ evaluation = {
402
+ type: type,
403
+ feature_key: feature_key,
404
+ reason: Featurevisor::EvaluationReason::REQUIRED,
405
+ required: feature[:required],
406
+ enabled: required_features_are_enabled
407
+ }
408
+
409
+ logger.debug("required features not enabled", evaluation)
410
+
411
+ return evaluation
412
+ end
413
+ end
414
+
415
+ # Bucketing
416
+ # bucketKey
417
+ bucket_key = Featurevisor::Bucketer.get_bucket_key({
418
+ feature_key: feature_key,
419
+ bucket_by: feature[:bucketBy],
420
+ context: context,
421
+ logger: logger
422
+ })
423
+
424
+ # Run bucket key hooks
425
+ bucket_key = hooks_manager.run_bucket_key_hooks({
426
+ feature_key: feature_key,
427
+ context: context,
428
+ bucket_by: feature[:bucketBy],
429
+ bucket_key: bucket_key
430
+ })
431
+
432
+ # bucketValue
433
+ bucket_value = Featurevisor::Bucketer.get_bucketed_number(bucket_key)
434
+
435
+ # Run bucket value hooks
436
+ bucket_value = hooks_manager.run_bucket_value_hooks({
437
+ feature_key: feature_key,
438
+ bucket_key: bucket_key,
439
+ context: context,
440
+ bucket_value: bucket_value
441
+ })
442
+
443
+ matched_traffic = nil
444
+ matched_allocation = nil
445
+
446
+ if type != "flag"
447
+ matched_traffic = datafile_reader.get_matched_traffic(feature[:traffic], context)
448
+
449
+ if matched_traffic
450
+ matched_allocation = datafile_reader.get_matched_allocation(matched_traffic, bucket_value)
451
+ end
452
+ else
453
+ matched_traffic = datafile_reader.get_matched_traffic(feature[:traffic], context)
454
+ end
455
+
456
+ if matched_traffic
457
+ # percentage: 0
458
+ if matched_traffic[:percentage] == 0
459
+ evaluation = {
460
+ type: type,
461
+ feature_key: feature_key,
462
+ reason: Featurevisor::EvaluationReason::RULE,
463
+ bucket_key: bucket_key,
464
+ bucket_value: bucket_value,
465
+ rule_key: matched_traffic[:key],
466
+ traffic: matched_traffic,
467
+ enabled: false
468
+ }
469
+
470
+ logger.debug("matched rule with 0 percentage", evaluation)
471
+
472
+ return evaluation
473
+ end
474
+
475
+ # flag
476
+ if type == "flag"
477
+ # flag: check if mutually exclusive
478
+ if feature[:ranges] && feature[:ranges].length > 0
479
+ matched_range = feature[:ranges].find do |range|
480
+ bucket_value >= range[0] && bucket_value < range[1]
481
+ end
482
+
483
+ # matched
484
+ if matched_range
485
+ evaluation = {
486
+ type: type,
487
+ feature_key: feature_key,
488
+ reason: Featurevisor::EvaluationReason::ALLOCATED,
489
+ bucket_key: bucket_key,
490
+ bucket_value: bucket_value,
491
+ rule_key: matched_traffic[:key],
492
+ traffic: matched_traffic,
493
+ enabled: matched_traffic[:enabled].nil? ? true : matched_traffic[:enabled]
494
+ }
495
+
496
+ logger.debug("matched", evaluation)
497
+
498
+ return evaluation
499
+ end
500
+
501
+ # no match
502
+ evaluation = {
503
+ type: type,
504
+ feature_key: feature_key,
505
+ reason: Featurevisor::EvaluationReason::OUT_OF_RANGE,
506
+ bucket_key: bucket_key,
507
+ bucket_value: bucket_value,
508
+ enabled: false
509
+ }
510
+
511
+ logger.debug("not matched", evaluation)
512
+
513
+ return evaluation
514
+ end
515
+
516
+ # flag: override from rule
517
+ if matched_traffic.key?(:enabled)
518
+ evaluation = {
519
+ type: type,
520
+ feature_key: feature_key,
521
+ reason: Featurevisor::EvaluationReason::RULE,
522
+ bucket_key: bucket_key,
523
+ bucket_value: bucket_value,
524
+ rule_key: matched_traffic[:key],
525
+ traffic: matched_traffic,
526
+ enabled: matched_traffic[:enabled]
527
+ }
528
+
529
+ logger.debug("override from rule", evaluation)
530
+
531
+ return evaluation
532
+ end
533
+
534
+ # treated as enabled because of matched traffic
535
+ if bucket_value <= matched_traffic[:percentage]
536
+ evaluation = {
537
+ type: type,
538
+ feature_key: feature_key,
539
+ reason: Featurevisor::EvaluationReason::RULE,
540
+ bucket_key: bucket_key,
541
+ bucket_value: bucket_value,
542
+ rule_key: matched_traffic[:key],
543
+ traffic: matched_traffic,
544
+ enabled: true
545
+ }
546
+
547
+ logger.debug("matched traffic", evaluation)
548
+
549
+ return evaluation
550
+ end
551
+ end
552
+
553
+ # variation
554
+ if type == "variation" && feature[:variations]
555
+ # override from rule
556
+ if matched_traffic[:variation]
557
+ variation = feature[:variations].find { |v| v[:value] == matched_traffic[:variation] }
558
+
559
+ if variation
560
+ evaluation = {
561
+ type: type,
562
+ feature_key: feature_key,
563
+ reason: Featurevisor::EvaluationReason::RULE,
564
+ bucket_key: bucket_key,
565
+ bucket_value: bucket_value,
566
+ rule_key: matched_traffic[:key],
567
+ traffic: matched_traffic,
568
+ variation: variation
569
+ }
570
+
571
+ logger.debug("override from rule", evaluation)
572
+
573
+ return evaluation
574
+ end
575
+ end
576
+
577
+ # regular allocation
578
+ if matched_allocation && matched_allocation[:variation]
579
+ variation = feature[:variations].find { |v| v[:value] == matched_allocation[:variation] }
580
+
581
+ if variation
582
+ evaluation = {
583
+ type: type,
584
+ feature_key: feature_key,
585
+ reason: Featurevisor::EvaluationReason::ALLOCATED,
586
+ bucket_key: bucket_key,
587
+ bucket_value: bucket_value,
588
+ rule_key: matched_traffic[:key],
589
+ traffic: matched_traffic,
590
+ variation: variation
591
+ }
592
+
593
+ logger.debug("allocated variation", evaluation)
594
+
595
+ return evaluation
596
+ end
597
+ end
598
+ end
599
+ end
600
+
601
+ # variable
602
+ if type == "variable" && variable_key
603
+ # override from rule
604
+ if matched_traffic &&
605
+ matched_traffic[:variables] &&
606
+ (matched_traffic[:variables][variable_key] || matched_traffic[:variables][variable_key.to_sym])
607
+ variable_value = matched_traffic[:variables][variable_key] || matched_traffic[:variables][variable_key.to_sym]
608
+ evaluation = {
609
+ type: type,
610
+ feature_key: feature_key,
611
+ reason: Featurevisor::EvaluationReason::RULE,
612
+ bucket_key: bucket_key,
613
+ bucket_value: bucket_value,
614
+ rule_key: matched_traffic[:key],
615
+ traffic: matched_traffic,
616
+ variable_key: variable_key,
617
+ variable_schema: variable_schema,
618
+ variable_value: variable_value
619
+ }
620
+
621
+ logger.debug("override from rule", evaluation)
622
+
623
+ return evaluation
624
+ end
625
+
626
+ # check variations
627
+ variation_value = nil
628
+
629
+ if force && force[:variation]
630
+ variation_value = force[:variation]
631
+ elsif matched_traffic && matched_traffic[:variation]
632
+ variation_value = matched_traffic[:variation]
633
+ elsif matched_allocation && matched_allocation[:variation]
634
+ variation_value = matched_allocation[:variation]
635
+ end
636
+
637
+ if variation_value && feature[:variations].is_a?(Array)
638
+ variation = feature[:variations].find { |v| v[:value] == variation_value }
639
+
640
+ if variation && variation[:variableOverrides] && (variation[:variableOverrides][variable_key] || variation[:variableOverrides][variable_key.to_sym])
641
+ overrides = variation[:variableOverrides][variable_key] || variation[:variableOverrides][variable_key.to_sym]
642
+
643
+ logger.debug("checking variableOverrides", {
644
+ feature_key: feature_key,
645
+ variable_key: variable_key,
646
+ overrides: overrides,
647
+ context: context
648
+ })
649
+
650
+ override = overrides.find do |o|
651
+ logger.debug("evaluating override", {
652
+ feature_key: feature_key,
653
+ variable_key: variable_key,
654
+ override: o,
655
+ context: context
656
+ })
657
+
658
+ result = if o[:conditions]
659
+ matched = datafile_reader.all_conditions_are_matched(
660
+ o[:conditions].is_a?(String) && o[:conditions] != "*" ?
661
+ JSON.parse(o[:conditions]) : o[:conditions],
662
+ context
663
+ )
664
+ logger.debug("conditions match result", {
665
+ feature_key: feature_key,
666
+ variable_key: variable_key,
667
+ conditions: o[:conditions],
668
+ matched: matched
669
+ })
670
+ matched
671
+ elsif o[:segments]
672
+ segments = datafile_reader.parse_segments_if_stringified(o[:segments])
673
+ matched = datafile_reader.all_segments_are_matched(segments, context)
674
+ logger.debug("segments match result", {
675
+ feature_key: feature_key,
676
+ variable_key: variable_key,
677
+ segments: o[:segments],
678
+ parsed_segments: segments,
679
+ matched: matched
680
+ })
681
+ matched
682
+ else
683
+ logger.debug("override has no conditions or segments", {
684
+ feature_key: feature_key,
685
+ variable_key: variable_key,
686
+ override: o
687
+ })
688
+ false
689
+ end
690
+
691
+ logger.debug("override evaluation result", {
692
+ feature_key: feature_key,
693
+ variable_key: variable_key,
694
+ result: result
695
+ })
696
+
697
+ result
698
+ end
699
+
700
+ if override
701
+ evaluation = {
702
+ type: type,
703
+ feature_key: feature_key,
704
+ reason: Featurevisor::EvaluationReason::VARIABLE_OVERRIDE,
705
+ bucket_key: bucket_key,
706
+ bucket_value: bucket_value,
707
+ rule_key: matched_traffic&.[](:key),
708
+ traffic: matched_traffic,
709
+ variable_key: variable_key,
710
+ variable_schema: variable_schema,
711
+ variable_value: override[:value]
712
+ }
713
+
714
+ logger.debug("variable override", evaluation)
715
+
716
+ return evaluation
717
+ end
718
+ end
719
+
720
+ if variation &&
721
+ variation[:variables] &&
722
+ (variation[:variables][variable_key] || variation[:variables][variable_key.to_sym])
723
+ variable_value = variation[:variables][variable_key] || variation[:variables][variable_key.to_sym]
724
+ evaluation = {
725
+ type: type,
726
+ feature_key: feature_key,
727
+ reason: Featurevisor::EvaluationReason::ALLOCATED,
728
+ bucket_key: bucket_key,
729
+ bucket_value: bucket_value,
730
+ rule_key: matched_traffic&.[](:key),
731
+ traffic: matched_traffic,
732
+ variable_key: variable_key,
733
+ variable_schema: variable_schema,
734
+ variable_value: variable_value
735
+ }
736
+
737
+ logger.debug("allocated variable", evaluation)
738
+
739
+ return evaluation
740
+ end
741
+ end
742
+ end
743
+
744
+ # Nothing matched
745
+ if type == "variation"
746
+ evaluation = {
747
+ type: type,
748
+ feature_key: feature_key,
749
+ reason: Featurevisor::EvaluationReason::NO_MATCH,
750
+ bucket_key: bucket_key,
751
+ bucket_value: bucket_value
752
+ }
753
+
754
+ logger.debug("no matched variation", evaluation)
755
+
756
+ return evaluation
757
+ end
758
+
759
+ if type == "variable"
760
+ if variable_schema
761
+ evaluation = {
762
+ type: type,
763
+ feature_key: feature_key,
764
+ reason: Featurevisor::EvaluationReason::VARIABLE_DEFAULT,
765
+ bucket_key: bucket_key,
766
+ bucket_value: bucket_value,
767
+ variable_key: variable_key,
768
+ variable_schema: variable_schema,
769
+ variable_value: variable_schema[:defaultValue]
770
+ }
771
+
772
+ logger.debug("using default value", evaluation)
773
+
774
+ return evaluation
775
+ end
776
+
777
+ evaluation = {
778
+ type: type,
779
+ feature_key: feature_key,
780
+ reason: Featurevisor::EvaluationReason::VARIABLE_NOT_FOUND,
781
+ variable_key: variable_key,
782
+ bucket_key: bucket_key,
783
+ bucket_value: bucket_value
784
+ }
785
+
786
+ logger.debug("variable not found", evaluation)
787
+
788
+ return evaluation
789
+ end
790
+
791
+ evaluation = {
792
+ type: type,
793
+ feature_key: feature_key,
794
+ reason: Featurevisor::EvaluationReason::NO_MATCH,
795
+ bucket_key: bucket_key,
796
+ bucket_value: bucket_value,
797
+ enabled: false
798
+ }
799
+
800
+ logger.debug("nothing matched", evaluation)
801
+
802
+ evaluation
803
+ rescue => e
804
+ evaluation = {
805
+ type: type,
806
+ feature_key: feature_key,
807
+ variable_key: variable_key,
808
+ reason: Featurevisor::EvaluationReason::ERROR,
809
+ error: e
810
+ }
811
+
812
+ logger.error("error", evaluation)
813
+
814
+ evaluation
815
+ end
816
+ end
817
+ end
818
+ end