featurevisor 1.1.0 → 2.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.
@@ -25,9 +25,9 @@ module Featurevisor
25
25
  def initialize(options = {})
26
26
  # from options
27
27
  @context = options[:context] || {}
28
- @logger = Logger.new(
28
+ @diagnostics = DiagnosticReporter.new(
29
29
  level: options[:log_level] || "info",
30
- handler: method(:handle_internal_log)
30
+ handler: method(:handle_evaluation_diagnostic)
31
31
  )
32
32
  @on_diagnostic = options[:on_diagnostic] || options[:onDiagnostic]
33
33
  @emitter = Featurevisor::Emitter.new
@@ -36,9 +36,9 @@ module Featurevisor
36
36
  @module_diagnostic_subscriptions = []
37
37
 
38
38
  # datafile
39
- @datafile_reader = DatafileReader.new(
39
+ @datafile = InstanceEvaluationDataProvider.new(
40
40
  datafile: EMPTY_DATAFILE,
41
- logger: @logger
41
+ diagnostics: @diagnostics
42
42
  )
43
43
 
44
44
  @modules_manager = Featurevisor::Modules::ModulesManager.new(
@@ -62,21 +62,35 @@ module Featurevisor
62
62
  # Set the log level
63
63
  # @param level [String] Log level
64
64
  def set_log_level(level)
65
- @logger.set_level(level)
65
+ @diagnostics.set_level(level)
66
66
  end
67
67
 
68
- def handle_internal_log(level, message, details = nil)
68
+ def handle_evaluation_diagnostic(level, message, details = nil)
69
69
  details = (details || {}).dup
70
- code = details[:reason] || details["reason"] || message
70
+ code = details.delete(:code) || details.delete("code") || details[:reason] || details["reason"] || message
71
71
  code = "deprecated_feature" if message == "feature is deprecated"
72
72
  code = "deprecated_variable" if message == "variable is deprecated"
73
73
  code = "feature_not_found" if message == "feature not found"
74
74
  code = "variable_not_found" if message == "variable schema not found"
75
75
  code = "no_variations" if message == "no variations"
76
76
  code = "invalid_bucket_by" if message == "invalid bucketBy"
77
- report_diagnostic(level: level, code: code.to_s, message: message, details: details)
77
+ code = "evaluation_error" if message == "Error during evaluation"
78
+ code = "conditions_parse_error" if message == "Error parsing conditions"
79
+ original_error = details.delete(:error) || details.delete("error")
80
+ nested_details = details.delete(:details) || details.delete("details")
81
+ details.merge!(nested_details) if nested_details.is_a?(Hash)
82
+ if details.key?(:feature_key) && details.key?(:reason)
83
+ evaluation = details.dup
84
+ details = {
85
+ featureKey: evaluation[:feature_key],
86
+ variableKey: evaluation[:variable_key],
87
+ reason: evaluation[:reason],
88
+ evaluation: camelize_diagnostic_value(evaluation)
89
+ }
90
+ end
91
+ report_diagnostic(level: level, code: code.to_s, message: message, details: details, originalError: original_error)
78
92
  end
79
- private :handle_internal_log
93
+ private :handle_evaluation_diagnostic
80
94
 
81
95
  # Set the datafile
82
96
  # @param datafile [Hash, String] Datafile content or JSON string
@@ -97,14 +111,14 @@ module Featurevisor
97
111
  parsed_datafile[:features].is_a?(Hash)
98
112
  raise ArgumentError, "Invalid datafile"
99
113
  end
100
- next_datafile = replace ? parsed_datafile : merge_datafiles(@datafile_reader.get_datafile, parsed_datafile)
101
- new_datafile_reader = DatafileReader.new(
114
+ next_datafile = replace ? parsed_datafile : merge_datafiles(@datafile.get_datafile, parsed_datafile)
115
+ new_datafile = InstanceEvaluationDataProvider.new(
102
116
  datafile: next_datafile,
103
- logger: @logger
117
+ diagnostics: @diagnostics
104
118
  )
105
119
 
106
- details = Featurevisor::Events.get_params_for_datafile_set_event(@datafile_reader, new_datafile_reader, replace)
107
- @datafile_reader = new_datafile_reader
120
+ details = Featurevisor::Events.get_params_for_datafile_set_event(@datafile, new_datafile, replace)
121
+ @datafile = new_datafile
108
122
 
109
123
  report_diagnostic(
110
124
  level: "info",
@@ -152,34 +166,34 @@ module Featurevisor
152
166
  # Get the revision
153
167
  # @return [String] Revision string
154
168
  def get_revision
155
- @datafile_reader.get_revision
169
+ @datafile.get_revision
156
170
  end
157
171
 
158
172
  def get_schema_version
159
- @datafile_reader.get_schema_version
173
+ @datafile.get_schema_version
160
174
  end
161
175
 
162
176
  def get_segment(segment_key)
163
- @datafile_reader.get_segment(segment_key)
177
+ @datafile.get_segment(segment_key)
164
178
  end
165
179
 
166
180
  def get_feature_keys
167
- @datafile_reader.get_feature_keys
181
+ @datafile.get_feature_keys
168
182
  end
169
183
 
170
184
  def get_variable_keys(feature_key)
171
- @datafile_reader.get_variable_keys(feature_key)
185
+ @datafile.get_variable_keys(feature_key)
172
186
  end
173
187
 
174
188
  def has_variations?(feature_key)
175
- @datafile_reader.has_variations?(feature_key)
189
+ @datafile.has_variations?(feature_key)
176
190
  end
177
191
 
178
192
  # Get a feature by key
179
193
  # @param feature_key [String] Feature key
180
194
  # @return [Hash, nil] Feature data or nil if not found
181
195
  def get_feature(feature_key)
182
- @datafile_reader.get_feature(feature_key)
196
+ @datafile.get_feature(feature_key)
183
197
  end
184
198
 
185
199
  # Add a module
@@ -189,8 +203,8 @@ module Featurevisor
189
203
  @modules_manager.add(mod)
190
204
  end
191
205
 
192
- def remove_module(name_or_module)
193
- @modules_manager.remove(name_or_module)
206
+ def remove_module(name)
207
+ @modules_manager.remove(name)
194
208
  end
195
209
 
196
210
  # Subscribe to an event
@@ -198,11 +212,15 @@ module Featurevisor
198
212
  # @param callback [Proc] Callback function
199
213
  # @return [Proc] Unsubscribe function
200
214
  def on(event_name, callback)
215
+ return -> {} if @closed
216
+
201
217
  @emitter.on(event_name, callback)
202
218
  end
203
219
 
204
220
  # Close the instance
205
221
  def close
222
+ return if @closed
223
+
206
224
  @closed = true
207
225
  @modules_manager.close_all
208
226
  @module_diagnostic_subscriptions = []
@@ -285,7 +303,7 @@ module Featurevisor
285
303
  evaluation = evaluate_flag(feature_key, context, options)
286
304
  evaluation[:enabled] == true
287
305
  rescue => e
288
- @logger.error("isEnabled", { feature_key: feature_key, error: e })
306
+ report_diagnostic(level: "error", code: "evaluation_error", message: "isEnabled failed", originalError: e, details: { featureKey: feature_key })
289
307
  false
290
308
  end
291
309
  end
@@ -313,7 +331,7 @@ module Featurevisor
313
331
  begin
314
332
  evaluation = evaluate_variation(feature_key, context, options)
315
333
 
316
- if evaluation[:variation_value]
334
+ if evaluation.key?(:variation_value)
317
335
  evaluation[:variation_value]
318
336
  elsif evaluation[:variation]
319
337
  evaluation[:variation][:value]
@@ -321,7 +339,7 @@ module Featurevisor
321
339
  nil
322
340
  end
323
341
  rescue => e
324
- @logger.error("getVariation", { feature_key: feature_key, error: e })
342
+ report_diagnostic(level: "error", code: "evaluation_error", message: "getVariation failed", originalError: e, details: { featureKey: feature_key })
325
343
  nil
326
344
  end
327
345
  end
@@ -352,7 +370,7 @@ module Featurevisor
352
370
  begin
353
371
  evaluation = evaluate_variable(feature_key, variable_key, context, options)
354
372
 
355
- if !evaluation[:variable_value].nil?
373
+ if evaluation.key?(:variable_value)
356
374
  if evaluation[:variable_schema] &&
357
375
  evaluation[:variable_schema][:type] == "json" &&
358
376
  evaluation[:variable_value].is_a?(String)
@@ -364,7 +382,7 @@ module Featurevisor
364
382
  nil
365
383
  end
366
384
  rescue => e
367
- @logger.error("getVariable", { feature_key: feature_key, variable_key: variable_key, error: e })
385
+ report_diagnostic(level: "error", code: "evaluation_error", message: "getVariable failed", originalError: e, details: { featureKey: feature_key, variableKey: variable_key })
368
386
  nil
369
387
  end
370
388
  end
@@ -454,7 +472,7 @@ module Featurevisor
454
472
  def get_all_evaluations(context = {}, feature_keys = [], options = {})
455
473
  result = {}
456
474
 
457
- keys = feature_keys.size > 0 ? feature_keys : @datafile_reader.get_feature_keys
475
+ keys = feature_keys.size > 0 ? feature_keys : @datafile.get_feature_keys
458
476
 
459
477
  keys.each do |feature_key|
460
478
  # Convert symbol keys to strings for evaluation functions
@@ -466,13 +484,13 @@ module Featurevisor
466
484
  }
467
485
 
468
486
  # variation
469
- if @datafile_reader.has_variations?(feature_key_str)
487
+ if @datafile.has_variations?(feature_key_str)
470
488
  variation = get_variation(feature_key_str, context, options)
471
- evaluated_feature[:variation] = variation if variation
489
+ evaluated_feature[:variation] = variation unless variation.nil?
472
490
  end
473
491
 
474
492
  # variables
475
- variable_keys = @datafile_reader.get_variable_keys(feature_key_str)
493
+ variable_keys = @datafile.get_variable_keys(feature_key_str)
476
494
  if variable_keys.size > 0
477
495
  evaluated_feature[:variables] = {}
478
496
 
@@ -501,13 +519,14 @@ module Featurevisor
501
519
  def get_evaluation_dependencies(context, options = {})
502
520
  {
503
521
  context: get_context(context),
504
- logger: @logger,
522
+ diagnostics: @diagnostics,
505
523
  modules_manager: @modules_manager,
506
- datafile_reader: @datafile_reader,
524
+ datafile: @datafile,
507
525
  sticky: options[:__featurevisor_child_sticky] || @sticky,
508
- default_variation_value: options[:default_variation_value],
509
- default_variable_value: options[:default_variable_value]
510
- }
526
+ }.tap do |dependencies|
527
+ dependencies[:default_variation_value] = options[:default_variation_value] if options.key?(:default_variation_value)
528
+ dependencies[:default_variable_value] = options[:default_variable_value] if options.key?(:default_variable_value)
529
+ end
511
530
  end
512
531
 
513
532
  # Get value by type
@@ -584,7 +603,7 @@ module Featurevisor
584
603
  diagnostic = (diagnostic || {}).dup
585
604
  diagnostic[:level] ||= "info"
586
605
  diagnostic[:module] = source_module.name if source_module && source_module.name
587
- details = (diagnostic[:details] || {}).dup
606
+ details = camelize_diagnostic_value((diagnostic[:details] || {}).dup)
588
607
  legacy_module_name = diagnostic.delete(:module_name)
589
608
  legacy_original_error = diagnostic.delete(:original_error)
590
609
  diagnostic[:moduleName] = legacy_module_name if !diagnostic.key?(:moduleName) && !legacy_module_name.nil?
@@ -609,7 +628,7 @@ module Featurevisor
609
628
  end
610
629
 
611
630
  if @on_diagnostic
612
- if should_report_diagnostic?(diagnostic[:level], @logger.level)
631
+ if should_report_diagnostic?(diagnostic[:level], @diagnostics.level)
613
632
  begin
614
633
  @on_diagnostic.call(diagnostic)
615
634
  rescue => e
@@ -617,7 +636,7 @@ module Featurevisor
617
636
  end
618
637
  end
619
638
  else
620
- Logger.new(level: @logger.level).log(
639
+ DiagnosticReporter.new(level: @diagnostics.level).log(
621
640
  diagnostic[:level],
622
641
  diagnostic[:message],
623
642
  diagnostic
@@ -638,6 +657,21 @@ module Featurevisor
638
657
 
639
658
  subscriber_index >= diagnostic_index
640
659
  end
660
+
661
+ def camelize_diagnostic_value(value)
662
+ case value
663
+ when Hash
664
+ value.each_with_object({}) do |(key, item), result|
665
+ normalized_key = key.to_s.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }.to_sym
666
+ result[normalized_key] = camelize_diagnostic_value(item)
667
+ end
668
+ when Array
669
+ value.map { |item| camelize_diagnostic_value(item) }
670
+ else
671
+ value
672
+ end
673
+ end
674
+ private :camelize_diagnostic_value
641
675
  end
642
676
 
643
677
  # Create a new Featurevisor instance
@@ -1,3 +1,3 @@
1
1
  module Featurevisor
2
- VERSION = "1.1.0"
2
+ VERSION = "2.0.0"
3
3
  end
data/lib/featurevisor.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  require_relative "featurevisor/version"
2
2
  require_relative "featurevisor/murmurhash"
3
3
  require_relative "featurevisor/compare_versions"
4
- require_relative "featurevisor/logger"
4
+ require_relative "featurevisor/diagnostics"
5
5
  require_relative "featurevisor/emitter"
6
6
  require_relative "featurevisor/conditions"
7
- require_relative "featurevisor/datafile_reader"
7
+ require_relative "featurevisor/evaluation_data_provider"
8
8
  require_relative "featurevisor/bucketer"
9
9
  require_relative "featurevisor/modules"
10
10
  require_relative "featurevisor/evaluate"
@@ -15,6 +15,6 @@ require_relative "featurevisor/events"
15
15
  module Featurevisor
16
16
  class Error < StandardError; end
17
17
 
18
- private_constant :DatafileReader
19
- private_constant :Logger
18
+ private_constant :InstanceEvaluationDataProvider
19
+ private_constant :DiagnosticReporter
20
20
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: featurevisor
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fahad Heylaal
@@ -76,12 +76,12 @@ files:
76
76
  - lib/featurevisor/child_instance.rb
77
77
  - lib/featurevisor/compare_versions.rb
78
78
  - lib/featurevisor/conditions.rb
79
- - lib/featurevisor/datafile_reader.rb
79
+ - lib/featurevisor/diagnostics.rb
80
80
  - lib/featurevisor/emitter.rb
81
81
  - lib/featurevisor/evaluate.rb
82
+ - lib/featurevisor/evaluation_data_provider.rb
82
83
  - lib/featurevisor/events.rb
83
84
  - lib/featurevisor/instance.rb
84
- - lib/featurevisor/logger.rb
85
85
  - lib/featurevisor/modules.rb
86
86
  - lib/featurevisor/murmurhash.rb
87
87
  - lib/featurevisor/version.rb