featurevisor 1.1.0 → 3.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.
@@ -19,6 +19,7 @@ module Featurevisor
19
19
  VARIABLE_DISABLED = "variable_disabled" # feature is disabled, and variable's disabledValue is used
20
20
  VARIABLE_OVERRIDE_VARIATION = "variable_override_variation" # variable overridden from inside a variation
21
21
  VARIABLE_OVERRIDE_RULE = "variable_override_rule" # variable overridden from inside a rule
22
+ REQUIRED_FEATURES_UNMET = "required_features_unmet"
22
23
 
23
24
  # Common
24
25
  NO_MATCH = "no_match" # no rules matched
@@ -35,6 +36,50 @@ module Featurevisor
35
36
 
36
37
  # Evaluation module for feature flag evaluation
37
38
  module Evaluate
39
+ def self.required_features_are_matched(requirements, datafile, options)
40
+ return true if requirements.nil?
41
+
42
+ items = requirements.is_a?(Array) ? requirements : [requirements]
43
+ clean_options = options.reject do |key, _|
44
+ %i[feature_key variable_key default_variation_value default_variable_value].include?(key)
45
+ end
46
+ items.all? do |required|
47
+ if required.is_a?(String)
48
+ key = required
49
+ enabled = true
50
+ variation = nil
51
+ elsif required.key?(:feature)
52
+ key = required[:feature]
53
+ enabled = required.key?(:enabled) ? required[:enabled] : true
54
+ variation = required[:variation]
55
+ else
56
+ key = required[:key]
57
+ enabled = true
58
+ variation = required[:variation]
59
+ end
60
+
61
+ flag = evaluate_with_modules(clean_options.merge(type: "flag", feature_key: key, datafile: datafile))
62
+ next false unless (flag[:enabled] == true) == enabled
63
+ next true if variation.nil?
64
+
65
+ evaluated_variation = evaluate_with_modules(clean_options.merge(type: "variation", feature_key: key, datafile: datafile))
66
+ value = evaluated_variation.key?(:variation_value) ? evaluated_variation[:variation_value] : evaluated_variation.dig(:variation, :value)
67
+ value == variation
68
+ end
69
+ end
70
+
71
+ def self.variable_override_matches?(override, datafile, context, options)
72
+ return false unless required_features_are_matched(override[:requiredFeatures], datafile, options)
73
+
74
+ conditions_match = !override[:conditions] || datafile.all_conditions_are_matched(
75
+ datafile.parse_conditions_if_stringified(override[:conditions]), context
76
+ )
77
+ segments_match = !override[:segments] || datafile.all_segments_are_matched(
78
+ datafile.parse_segments_if_stringified(override[:segments]), context
79
+ )
80
+ conditions_match && segments_match &&
81
+ (override.key?(:conditions) || override.key?(:segments) || override.key?(:requiredFeatures))
82
+ end
38
83
 
39
84
  # Evaluate with modules
40
85
  # @param options [Hash] Evaluation options
@@ -53,17 +98,18 @@ module Featurevisor
53
98
  evaluation = evaluate(result_options)
54
99
 
55
100
  # Default: variation
56
- if options.key?(:default_variation_value) &&
101
+ if result_options.key?(:default_variation_value) &&
57
102
  evaluation[:type] == "variation" &&
58
- evaluation[:variation_value].nil?
59
- evaluation[:variation_value] = options[:default_variation_value]
103
+ !evaluation.key?(:variation_value) &&
104
+ !evaluation.key?(:variation)
105
+ evaluation[:variation_value] = result_options[:default_variation_value]
60
106
  end
61
107
 
62
108
  # Default: variable
63
- if options.key?(:default_variable_value) &&
109
+ if result_options.key?(:default_variable_value) &&
64
110
  evaluation[:type] == "variable" &&
65
- evaluation[:variable_value].nil?
66
- evaluation[:variable_value] = options[:default_variable_value]
111
+ !evaluation.key?(:variable_value)
112
+ evaluation[:variable_value] = result_options[:default_variable_value]
67
113
  end
68
114
 
69
115
  # Run after modules
@@ -76,7 +122,7 @@ module Featurevisor
76
122
  type = options[:type]
77
123
  feature_key = options[:feature_key]
78
124
  variable_key = options[:variable_key]
79
- logger = options[:logger]
125
+ diagnostics = options[:diagnostics]
80
126
 
81
127
  evaluation = {
82
128
  type: type,
@@ -86,7 +132,7 @@ module Featurevisor
86
132
  error: e
87
133
  }
88
134
 
89
- logger.error("error during evaluation", evaluation)
135
+ diagnostics.error("Error during evaluation", evaluation)
90
136
 
91
137
  evaluation
92
138
  end
@@ -100,8 +146,8 @@ module Featurevisor
100
146
  feature_key = options[:feature_key]
101
147
  variable_key = options[:variable_key]
102
148
  context = options[:context]
103
- logger = options[:logger]
104
- datafile_reader = options[:datafile_reader]
149
+ diagnostics = options[:diagnostics]
150
+ datafile = options[:datafile]
105
151
  sticky = options[:sticky]
106
152
  modules_manager = options[:modules_manager]
107
153
  evaluation = nil
@@ -120,7 +166,7 @@ module Featurevisor
120
166
  reason: Featurevisor::EvaluationReason::DISABLED
121
167
  }
122
168
 
123
- feature = datafile_reader.get_feature(feature_key)
169
+ feature = datafile.get_feature(feature_key)
124
170
 
125
171
  # serve variable default value if feature is disabled (if explicitly specified)
126
172
  if type == "variable"
@@ -156,7 +202,7 @@ module Featurevisor
156
202
  end
157
203
 
158
204
  # serve disabled variation value if feature is disabled (if explicitly specified)
159
- if type == "variation" && feature && feature[:disabledVariationValue]
205
+ if type == "variation" && feature && feature.key?(:disabledVariationValue)
160
206
  evaluation = {
161
207
  type: type,
162
208
  feature_key: feature_key,
@@ -166,7 +212,7 @@ module Featurevisor
166
212
  }
167
213
  end
168
214
 
169
- logger.debug("feature is disabled", evaluation)
215
+ diagnostics.debug("feature is disabled", evaluation)
170
216
 
171
217
  return evaluation
172
218
  end
@@ -186,7 +232,7 @@ module Featurevisor
186
232
  enabled: sticky_feature[:enabled]
187
233
  }
188
234
 
189
- logger.debug("using sticky enabled", evaluation)
235
+ diagnostics.debug("using sticky enabled", evaluation)
190
236
 
191
237
  return evaluation
192
238
  end
@@ -195,7 +241,7 @@ module Featurevisor
195
241
  if type == "variation"
196
242
  variation_value = sticky_feature[:variation]
197
243
 
198
- unless variation_value.nil?
244
+ if sticky_feature.key?(:variation)
199
245
  evaluation = {
200
246
  type: type,
201
247
  feature_key: feature_key,
@@ -203,7 +249,7 @@ module Featurevisor
203
249
  variation_value: variation_value
204
250
  }
205
251
 
206
- logger.debug("using sticky variation", evaluation)
252
+ diagnostics.debug("using sticky variation", evaluation)
207
253
 
208
254
  return evaluation
209
255
  end
@@ -223,7 +269,7 @@ module Featurevisor
223
269
  variable_value: variable_value
224
270
  }
225
271
 
226
- logger.debug("using sticky variable", evaluation)
272
+ diagnostics.debug("using sticky variable", evaluation)
227
273
 
228
274
  return evaluation
229
275
  end
@@ -231,7 +277,7 @@ module Featurevisor
231
277
  end
232
278
 
233
279
  # Feature
234
- feature = feature_key.is_a?(String) ? datafile_reader.get_feature(feature_key) : feature_key
280
+ feature = feature_key.is_a?(String) ? datafile.get_feature(feature_key) : feature_key
235
281
 
236
282
  # feature: not found
237
283
  unless feature
@@ -241,14 +287,14 @@ module Featurevisor
241
287
  reason: Featurevisor::EvaluationReason::FEATURE_NOT_FOUND
242
288
  }
243
289
 
244
- logger.warn("feature not found", evaluation)
290
+ diagnostics.warn("feature not found", evaluation)
245
291
 
246
292
  return evaluation
247
293
  end
248
294
 
249
295
  # feature: deprecated
250
296
  if type == "flag" && feature[:deprecated]
251
- logger.warn("feature is deprecated", { feature_key: feature_key })
297
+ diagnostics.warn("feature is deprecated", { feature_key: feature_key })
252
298
  end
253
299
 
254
300
  # variableSchema
@@ -268,13 +314,13 @@ module Featurevisor
268
314
  variable_key: variable_key
269
315
  }
270
316
 
271
- logger.warn("variable schema not found", evaluation)
317
+ diagnostics.warn("variable schema not found", evaluation)
272
318
 
273
319
  return evaluation
274
320
  end
275
321
 
276
322
  if variable_schema[:deprecated]
277
- logger.warn("variable is deprecated", {
323
+ diagnostics.warn("variable is deprecated", {
278
324
  feature_key: feature_key,
279
325
  variable_key: variable_key
280
326
  })
@@ -289,13 +335,13 @@ module Featurevisor
289
335
  reason: Featurevisor::EvaluationReason::NO_VARIATIONS
290
336
  }
291
337
 
292
- logger.warn("no variations", evaluation)
338
+ diagnostics.warn("no variations", evaluation)
293
339
 
294
340
  return evaluation
295
341
  end
296
342
 
297
343
  # Forced
298
- force_result = datafile_reader.get_matched_force(feature, context)
344
+ force_result = datafile.get_matched_force(feature, context)
299
345
  force = force_result[:force]
300
346
  force_index = force_result[:forceIndex]
301
347
 
@@ -311,7 +357,7 @@ module Featurevisor
311
357
  enabled: force[:enabled]
312
358
  }
313
359
 
314
- logger.debug("forced enabled found", evaluation)
360
+ diagnostics.debug("forced enabled found", evaluation)
315
361
 
316
362
  return evaluation
317
363
  end
@@ -330,7 +376,7 @@ module Featurevisor
330
376
  variation: variation
331
377
  }
332
378
 
333
- logger.debug("forced variation found", evaluation)
379
+ diagnostics.debug("forced variation found", evaluation)
334
380
 
335
381
  return evaluation
336
382
  end
@@ -350,46 +396,16 @@ module Featurevisor
350
396
  variable_value: variable_value
351
397
  }
352
398
 
353
- logger.debug("forced variable", evaluation)
399
+ diagnostics.debug("forced variable", evaluation)
354
400
 
355
401
  return evaluation
356
402
  end
357
403
  end
358
404
 
359
405
  # Required
360
- if type == "flag" && feature[:required] && feature[:required].length > 0
361
- required_features_are_enabled = feature[:required].all? do |required|
362
- required_key = nil
363
- required_variation = nil
364
-
365
- if required.is_a?(String)
366
- required_key = required
367
- else
368
- required_key = required[:key]
369
- required_variation = required[:variation]
370
- end
371
-
372
- required_evaluation = evaluate(options.merge(type: "flag", feature_key: required_key))
373
- required_is_enabled = required_evaluation[:enabled]
374
-
375
- next false unless required_is_enabled
376
-
377
- if required_variation
378
- required_variation_evaluation = evaluate(options.merge(type: "variation", feature_key: required_key))
379
-
380
- required_variation_value = nil
381
-
382
- if has_key?(required_variation_evaluation, :variation_value)
383
- required_variation_value = fetch_with_symbol_key(required_variation_evaluation, :variation_value)
384
- elsif required_variation_evaluation[:variation]
385
- required_variation_value = required_variation_evaluation[:variation][:value]
386
- end
387
-
388
- next required_variation_value == required_variation
389
- end
390
-
391
- true
392
- end
406
+ required_features = feature[:requiredFeatures] || feature[:required]
407
+ if type == "flag" && required_features && !Array(required_features).empty?
408
+ required_features_are_enabled = required_features_are_matched(required_features, datafile, options)
393
409
 
394
410
  unless required_features_are_enabled
395
411
  evaluation = {
@@ -397,10 +413,11 @@ module Featurevisor
397
413
  feature_key: feature_key,
398
414
  reason: Featurevisor::EvaluationReason::REQUIRED,
399
415
  required: feature[:required],
416
+ required_features: feature[:requiredFeatures],
400
417
  enabled: required_features_are_enabled
401
418
  }
402
419
 
403
- logger.debug("required features not enabled", evaluation)
420
+ diagnostics.debug("required features not enabled", evaluation)
404
421
 
405
422
  return evaluation
406
423
  end
@@ -412,7 +429,7 @@ module Featurevisor
412
429
  feature_key: feature_key,
413
430
  bucket_by: feature[:bucketBy],
414
431
  context: context,
415
- logger: logger
432
+ diagnostics: diagnostics
416
433
  })
417
434
 
418
435
  # Run bucket key modules
@@ -438,13 +455,13 @@ module Featurevisor
438
455
  matched_allocation = nil
439
456
 
440
457
  if type != "flag"
441
- matched_traffic = datafile_reader.get_matched_traffic(feature[:traffic], context)
458
+ matched_traffic = datafile.get_matched_traffic(feature[:traffic], context)
442
459
 
443
460
  if matched_traffic
444
- matched_allocation = datafile_reader.get_matched_allocation(matched_traffic, bucket_value)
461
+ matched_allocation = datafile.get_matched_allocation(matched_traffic, bucket_value)
445
462
  end
446
463
  else
447
- matched_traffic = datafile_reader.get_matched_traffic(feature[:traffic], context)
464
+ matched_traffic = datafile.get_matched_traffic(feature[:traffic], context)
448
465
  end
449
466
 
450
467
  if matched_traffic
@@ -461,7 +478,7 @@ module Featurevisor
461
478
  enabled: false
462
479
  }
463
480
 
464
- logger.debug("matched rule with 0 percentage", evaluation)
481
+ diagnostics.debug("matched rule with 0 percentage", evaluation)
465
482
 
466
483
  return evaluation
467
484
  end
@@ -487,7 +504,7 @@ module Featurevisor
487
504
  enabled: matched_traffic[:enabled].nil? ? true : matched_traffic[:enabled]
488
505
  }
489
506
 
490
- logger.debug("matched", evaluation)
507
+ diagnostics.debug("matched", evaluation)
491
508
 
492
509
  return evaluation
493
510
  end
@@ -502,7 +519,7 @@ module Featurevisor
502
519
  enabled: false
503
520
  }
504
521
 
505
- logger.debug("not matched", evaluation)
522
+ diagnostics.debug("not matched", evaluation)
506
523
 
507
524
  return evaluation
508
525
  end
@@ -520,7 +537,7 @@ module Featurevisor
520
537
  enabled: matched_traffic[:enabled]
521
538
  }
522
539
 
523
- logger.debug("override from rule", evaluation)
540
+ diagnostics.debug("override from rule", evaluation)
524
541
 
525
542
  return evaluation
526
543
  end
@@ -538,7 +555,7 @@ module Featurevisor
538
555
  enabled: true
539
556
  }
540
557
 
541
- logger.debug("matched traffic", evaluation)
558
+ diagnostics.debug("matched traffic", evaluation)
542
559
 
543
560
  return evaluation
544
561
  end
@@ -562,7 +579,7 @@ module Featurevisor
562
579
  variation: variation
563
580
  }
564
581
 
565
- logger.debug("override from rule", evaluation)
582
+ diagnostics.debug("override from rule", evaluation)
566
583
 
567
584
  return evaluation
568
585
  end
@@ -584,7 +601,7 @@ module Featurevisor
584
601
  variation: variation
585
602
  }
586
603
 
587
- logger.debug("allocated variation", evaluation)
604
+ diagnostics.debug("allocated variation", evaluation)
588
605
 
589
606
  return evaluation
590
607
  end
@@ -600,17 +617,7 @@ module Featurevisor
600
617
  has_key?(matched_traffic[:variableOverrides], variable_key)
601
618
  overrides = fetch_with_symbol_key(matched_traffic[:variableOverrides], variable_key)
602
619
 
603
- override_index = overrides.find_index do |o|
604
- if o[:conditions]
605
- conditions = o[:conditions].is_a?(String) && o[:conditions] != "*" ? JSON.parse(o[:conditions]) : o[:conditions]
606
- datafile_reader.all_conditions_are_matched(conditions, context)
607
- elsif o[:segments]
608
- segments = datafile_reader.parse_segments_if_stringified(o[:segments])
609
- datafile_reader.all_segments_are_matched(segments, context)
610
- else
611
- false
612
- end
613
- end
620
+ override_index = overrides.find_index { |o| variable_override_matches?(o, datafile, context, options) }
614
621
 
615
622
  unless override_index.nil?
616
623
  override = overrides[override_index]
@@ -626,10 +633,11 @@ module Featurevisor
626
633
  variable_key: variable_key,
627
634
  variable_schema: variable_schema,
628
635
  variable_value: override[:value],
629
- variable_override_index: override_index
636
+ variable_override_index: override_index,
637
+ variable_override_key: override[:key]
630
638
  }
631
639
 
632
- logger.debug("variable override from rule", evaluation)
640
+ diagnostics.debug("variable override from rule", evaluation)
633
641
 
634
642
  return evaluation
635
643
  end
@@ -652,7 +660,7 @@ module Featurevisor
652
660
  variable_value: variable_value
653
661
  }
654
662
 
655
- logger.debug("override from rule", evaluation)
663
+ diagnostics.debug("override from rule", evaluation)
656
664
 
657
665
  return evaluation
658
666
  end
@@ -674,17 +682,7 @@ module Featurevisor
674
682
  if variation && variation[:variableOverrides] && has_key?(variation[:variableOverrides], variable_key)
675
683
  overrides = fetch_with_symbol_key(variation[:variableOverrides], variable_key)
676
684
 
677
- override_index = overrides.find_index do |o|
678
- if o[:conditions]
679
- conditions = o[:conditions].is_a?(String) && o[:conditions] != "*" ? JSON.parse(o[:conditions]) : o[:conditions]
680
- datafile_reader.all_conditions_are_matched(conditions, context)
681
- elsif o[:segments]
682
- segments = datafile_reader.parse_segments_if_stringified(o[:segments])
683
- datafile_reader.all_segments_are_matched(segments, context)
684
- else
685
- false
686
- end
687
- end
685
+ override_index = overrides.find_index { |o| variable_override_matches?(o, datafile, context, options) }
688
686
 
689
687
  unless override_index.nil?
690
688
  override = overrides[override_index]
@@ -699,10 +697,11 @@ module Featurevisor
699
697
  variable_key: variable_key,
700
698
  variable_schema: variable_schema,
701
699
  variable_value: override[:value],
702
- variable_override_index: override_index
700
+ variable_override_index: override_index,
701
+ variable_override_key: override[:key]
703
702
  }
704
703
 
705
- logger.debug("variable override from variation", evaluation)
704
+ diagnostics.debug("variable override from variation", evaluation)
706
705
 
707
706
  return evaluation
708
707
  end
@@ -725,7 +724,7 @@ module Featurevisor
725
724
  variable_value: variable_value
726
725
  }
727
726
 
728
- logger.debug("allocated variable", evaluation)
727
+ diagnostics.debug("allocated variable", evaluation)
729
728
 
730
729
  return evaluation
731
730
  end
@@ -742,7 +741,7 @@ module Featurevisor
742
741
  bucket_value: bucket_value
743
742
  }
744
743
 
745
- logger.debug("no matched variation", evaluation)
744
+ diagnostics.debug("no matched variation", evaluation)
746
745
 
747
746
  return evaluation
748
747
  end
@@ -760,7 +759,7 @@ module Featurevisor
760
759
  variable_value: variable_schema[:defaultValue]
761
760
  }
762
761
 
763
- logger.debug("using default value", evaluation)
762
+ diagnostics.debug("using default value", evaluation)
764
763
 
765
764
  return evaluation
766
765
  end
@@ -774,7 +773,7 @@ module Featurevisor
774
773
  bucket_value: bucket_value
775
774
  }
776
775
 
777
- logger.debug("variable not found", evaluation)
776
+ diagnostics.debug("variable not found", evaluation)
778
777
 
779
778
  return evaluation
780
779
  end
@@ -788,7 +787,7 @@ module Featurevisor
788
787
  enabled: false
789
788
  }
790
789
 
791
- logger.debug("nothing matched", evaluation)
790
+ diagnostics.debug("nothing matched", evaluation)
792
791
 
793
792
  evaluation
794
793
  rescue => e
@@ -800,7 +799,7 @@ module Featurevisor
800
799
  error: e
801
800
  }
802
801
 
803
- logger.error("error", evaluation)
802
+ diagnostics.error("Error during evaluation", evaluation)
804
803
 
805
804
  evaluation
806
805
  end
@@ -3,23 +3,24 @@
3
3
  require "json"
4
4
 
5
5
  module Featurevisor
6
- # DatafileReader class for reading and processing Featurevisor datafiles
7
- class DatafileReader
8
- attr_reader :schema_version, :revision, :featurevisor_version, :segments, :features, :logger, :regex_cache
6
+ # Private datafile and matching adapter used by the evaluator.
7
+ class InstanceEvaluationDataProvider
8
+ attr_reader :schema_version, :revision, :featurevisor_version, :segments, :features, :variables, :diagnostics, :regex_cache
9
9
 
10
- # Initialize a new DatafileReader
11
- # @param options [Hash] Options hash containing datafile and logger
10
+ # Initialize a new evaluation data provider.
11
+ # @param options [Hash] Options hash containing datafile and diagnostics
12
12
  # @option options [Hash] :datafile Datafile content
13
- # @option options [Logger] :logger Logger instance
13
+ # @option options [DiagnosticReporter] :diagnostics Diagnostic reporter
14
14
  def initialize(options)
15
15
  datafile = options[:datafile]
16
- @logger = options[:logger]
16
+ @diagnostics = options[:diagnostics]
17
17
 
18
18
  @schema_version = datafile[:schemaVersion]
19
19
  @revision = datafile[:revision]
20
20
  @featurevisor_version = datafile[:featurevisorVersion]
21
21
  @segments = (datafile[:segments] || {}).transform_keys(&:to_sym)
22
22
  @features = (datafile[:features] || {}).transform_keys(&:to_sym)
23
+ @variables = (datafile[:variables] || {}).transform_keys(&:to_sym)
23
24
 
24
25
  # Transform nested structures to use symbol keys
25
26
  @features.each do |_key, feature|
@@ -72,7 +73,8 @@ module Featurevisor
72
73
  revision: @revision,
73
74
  featurevisorVersion: @featurevisor_version,
74
75
  segments: @segments,
75
- features: @features
76
+ features: @features,
77
+ variables: @variables
76
78
  }.compact
77
79
  end
78
80
 
@@ -88,6 +90,10 @@ module Featurevisor
88
90
  segment
89
91
  end
90
92
 
93
+ def get_segment_keys
94
+ @segments.keys
95
+ end
96
+
91
97
  # Get all feature keys
92
98
  # @return [Array<Symbol>] Array of feature keys
93
99
  def get_feature_keys
@@ -104,7 +110,9 @@ module Featurevisor
104
110
  # Get variable keys for a feature
105
111
  # @param feature_key [String] Feature key
106
112
  # @return [Array<String>] Array of variable keys
107
- def get_variable_keys(feature_key)
113
+ def get_variable_keys(feature_key = nil)
114
+ return @variables.keys if feature_key.nil?
115
+
108
116
  feature = get_feature(feature_key)
109
117
 
110
118
  return [] unless feature
@@ -112,6 +120,10 @@ module Featurevisor
112
120
  (feature[:variablesSchema] || {}).keys
113
121
  end
114
122
 
123
+ def get_global_variable(variable_key)
124
+ @variables[variable_key.to_sym] || @variables[variable_key]
125
+ end
126
+
115
127
  # Check if a feature has variations
116
128
  # @param feature_key [String] Feature key
117
129
  # @return [Boolean] True if feature has variations
@@ -133,7 +145,15 @@ module Featurevisor
133
145
 
134
146
  return @regex_cache[cache_key] if @regex_cache[cache_key]
135
147
 
136
- regex = Regexp.new(regex_string, flags)
148
+ ruby_flags = 0
149
+ ruby_flags |= Regexp::IGNORECASE if flags.include?("i")
150
+ # Ruby's MULTILINE option controls dot matching newlines, which is the
151
+ # JavaScript `s` behavior. Ruby anchors already operate per line.
152
+ ruby_flags |= Regexp::MULTILINE if flags.include?("s")
153
+ invalid_flags = flags.delete("gimsuy")
154
+ raise ArgumentError, "invalid regular expression flags: #{invalid_flags}" unless invalid_flags.empty?
155
+
156
+ regex = Regexp.new(regex_string, ruby_flags)
137
157
  @regex_cache[cache_key] = regex
138
158
  @regex_cache[cache_key]
139
159
  end
@@ -155,12 +175,11 @@ module Featurevisor
155
175
  result = Conditions.condition_is_matched(conditions, context, get_regex_proc)
156
176
  return result
157
177
  rescue => e
158
- @logger.warn("Error in condition evaluation: #{e.message}", {
159
- error: e.class.name,
160
- details: {
161
- condition: conditions,
162
- context: context
163
- }
178
+ @diagnostics.warn(e.message, {
179
+ code: "condition_match_error",
180
+ error: e,
181
+ condition: conditions,
182
+ context: context
164
183
  })
165
184
  return false
166
185
  end
@@ -183,10 +202,12 @@ module Featurevisor
183
202
  end
184
203
 
185
204
  if conditions.is_a?(Hash) && conditions[:not] && conditions[:not].is_a?(Array)
205
+ return false if conditions[:not].empty?
186
206
  return !all_conditions_are_matched({ and: conditions[:not] }, context)
187
207
  end
188
208
 
189
209
  if conditions.is_a?(Hash) && conditions["not"] && conditions["not"].is_a?(Array)
210
+ return false if conditions["not"].empty?
190
211
  return !all_conditions_are_matched({ "and" => conditions["not"] }, context)
191
212
  end
192
213
 
@@ -244,10 +265,12 @@ module Featurevisor
244
265
  end
245
266
 
246
267
  if group_segments[:not] && group_segments[:not].is_a?(Array)
268
+ return false if group_segments[:not].empty?
247
269
  return !all_segments_are_matched({ and: group_segments[:not] }, context)
248
270
  end
249
271
 
250
272
  if group_segments["not"] && group_segments["not"].is_a?(Array)
273
+ return false if group_segments["not"].empty?
251
274
  return !all_segments_are_matched({ "and" => group_segments["not"] }, context)
252
275
  end
253
276
 
@@ -333,7 +356,7 @@ module Featurevisor
333
356
  begin
334
357
  JSON.parse(conditions)
335
358
  rescue => e
336
- @logger.error("Error parsing conditions", {
359
+ @diagnostics.error("Error parsing conditions", {
337
360
  error: e,
338
361
  details: {
339
362
  conditions: conditions
@@ -355,5 +378,5 @@ module Featurevisor
355
378
  end
356
379
  end
357
380
 
358
- private_constant :DatafileReader
381
+ private_constant :InstanceEvaluationDataProvider
359
382
  end