featurevisor 0.3.0 → 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.
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module Featurevisor
6
+ # Modules extend evaluation behavior and SDK lifecycle.
7
+ module Modules
8
+ class FeaturevisorModule
9
+ attr_reader :id, :name
10
+
11
+ def initialize(options = {})
12
+ @id = SecureRandom.uuid
13
+ @name = options[:name]
14
+ @setup = options[:setup]
15
+ @before = options[:before]
16
+ @bucket_key = options[:bucket_key]
17
+ @bucket_value = options[:bucket_value]
18
+ @after = options[:after]
19
+ @close = options[:close]
20
+ end
21
+
22
+ def call_setup(api)
23
+ @setup.call(api) if @setup
24
+ end
25
+
26
+ def call_before(options)
27
+ return options unless @before
28
+
29
+ @before.call(options)
30
+ end
31
+
32
+ def call_bucket_key(options)
33
+ return options[:bucket_key] unless @bucket_key
34
+
35
+ @bucket_key.call(options)
36
+ end
37
+
38
+ def call_bucket_value(options)
39
+ return options[:bucket_value] unless @bucket_value
40
+
41
+ @bucket_value.call(options)
42
+ end
43
+
44
+ def call_after(evaluation, options)
45
+ return evaluation unless @after
46
+
47
+ @after.call(evaluation, options)
48
+ end
49
+
50
+ def call_close
51
+ @close.call if @close
52
+ end
53
+ end
54
+
55
+ class ModulesManager
56
+ attr_reader :modules
57
+
58
+ def initialize(options = {})
59
+ @modules = []
60
+ @report_diagnostic = options[:report_diagnostic]
61
+ @module_api_factory = options[:module_api_factory]
62
+ @clear_module_diagnostic_subscriptions = options[:clear_module_diagnostic_subscriptions]
63
+
64
+ (options[:modules] || []).each do |mod|
65
+ add(mod)
66
+ end
67
+ end
68
+
69
+ def add(mod)
70
+ mod = FeaturevisorModule.new(mod) if mod.is_a?(Hash)
71
+ return nil unless mod
72
+
73
+ if mod.name && !mod.name.to_s.empty? && @modules.any? { |existing| existing.name == mod.name }
74
+ report(
75
+ {
76
+ level: "error",
77
+ code: "duplicate_module",
78
+ message: "Duplicate module name",
79
+ module_name: mod.name
80
+ },
81
+ nil
82
+ )
83
+ return nil
84
+ end
85
+
86
+ begin
87
+ mod.call_setup(@module_api_factory.call(mod)) if @module_api_factory
88
+ rescue => e
89
+ @clear_module_diagnostic_subscriptions.call(mod) if @clear_module_diagnostic_subscriptions
90
+ report(
91
+ {
92
+ level: "error",
93
+ code: "module_setup_error",
94
+ message: "Module setup failed",
95
+ module_name: mod.name,
96
+ original_error: e
97
+ },
98
+ nil
99
+ )
100
+ close_module(mod)
101
+ return nil
102
+ end
103
+ @modules << mod
104
+
105
+ -> { remove(mod) }
106
+ end
107
+
108
+ def remove(name_or_module)
109
+ removed = []
110
+ @modules = @modules.reject do |mod|
111
+ matches = name_or_module.equal?(mod) || mod.name == name_or_module
112
+ removed << mod if matches
113
+ matches
114
+ end
115
+
116
+ removed.each do |mod|
117
+ @clear_module_diagnostic_subscriptions.call(mod) if @clear_module_diagnostic_subscriptions
118
+ close_module(mod)
119
+ end
120
+ end
121
+
122
+ def get_all
123
+ @modules
124
+ end
125
+
126
+ def run_before_modules(options)
127
+ @modules.reduce(options) do |result, mod|
128
+ mod.call_before(result)
129
+ end
130
+ end
131
+
132
+ def run_bucket_key_modules(options)
133
+ bucket_key = options[:bucket_key]
134
+ @modules.each do |mod|
135
+ bucket_key = mod.call_bucket_key(options.merge(bucket_key: bucket_key))
136
+ end
137
+ bucket_key
138
+ end
139
+
140
+ def run_bucket_value_modules(options)
141
+ bucket_value = options[:bucket_value]
142
+ @modules.each do |mod|
143
+ bucket_value = mod.call_bucket_value(options.merge(bucket_value: bucket_value))
144
+ end
145
+ bucket_value
146
+ end
147
+
148
+ def run_after_modules(evaluation, options)
149
+ @modules.reduce(evaluation) do |result, mod|
150
+ mod.call_after(result, options)
151
+ end
152
+ end
153
+
154
+ def close_all
155
+ @modules.each do |mod|
156
+ @clear_module_diagnostic_subscriptions.call(mod) if @clear_module_diagnostic_subscriptions
157
+ close_module(mod)
158
+ end
159
+ @modules = []
160
+ end
161
+
162
+ private
163
+
164
+ def close_module(mod)
165
+ mod.call_close
166
+ rescue => e
167
+ report(
168
+ {
169
+ level: "error",
170
+ code: "module_close_error",
171
+ message: "Module close failed",
172
+ module_name: mod.name,
173
+ original_error: e
174
+ },
175
+ nil
176
+ )
177
+ end
178
+
179
+ def report(diagnostic, mod)
180
+ @report_diagnostic.call(diagnostic, mod) if @report_diagnostic
181
+ end
182
+ end
183
+ end
184
+ end
@@ -1,3 +1,3 @@
1
1
  module Featurevisor
2
- VERSION = "0.3.0"
2
+ VERSION = "1.0.0"
3
3
  end
data/lib/featurevisor.rb CHANGED
@@ -6,7 +6,7 @@ require_relative "featurevisor/emitter"
6
6
  require_relative "featurevisor/conditions"
7
7
  require_relative "featurevisor/datafile_reader"
8
8
  require_relative "featurevisor/bucketer"
9
- require_relative "featurevisor/hooks"
9
+ require_relative "featurevisor/modules"
10
10
  require_relative "featurevisor/evaluate"
11
11
  require_relative "featurevisor/instance"
12
12
  require_relative "featurevisor/child_instance"
@@ -14,4 +14,7 @@ require_relative "featurevisor/events"
14
14
 
15
15
  module Featurevisor
16
16
  class Error < StandardError; end
17
+
18
+ private_constant :DatafileReader
19
+ private_constant :Logger
17
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: 0.3.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fahad Heylaal
@@ -9,6 +9,20 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: benchmark
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: rspec
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -60,9 +74,9 @@ files:
60
74
  - lib/featurevisor/emitter.rb
61
75
  - lib/featurevisor/evaluate.rb
62
76
  - lib/featurevisor/events.rb
63
- - lib/featurevisor/hooks.rb
64
77
  - lib/featurevisor/instance.rb
65
78
  - lib/featurevisor/logger.rb
79
+ - lib/featurevisor/modules.rb
66
80
  - lib/featurevisor/murmurhash.rb
67
81
  - lib/featurevisor/version.rb
68
82
  homepage: https://featurevisor.com
@@ -1,159 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Featurevisor
4
- # Hooks module for extending evaluation behavior
5
- module Hooks
6
- # Hook interface for extending evaluation behavior
7
- class Hook
8
- attr_reader :name
9
-
10
- # Initialize a new hook
11
- # @param options [Hash] Hook options
12
- # @option options [String] :name Hook name
13
- # @option options [Proc, nil] :before Before evaluation hook
14
- # @option options [Proc, nil] :bucket_key Bucket key configuration hook
15
- # @option options [Proc, nil] :bucket_value Bucket value configuration hook
16
- # @option options [Proc, nil] :after After evaluation hook
17
- def initialize(options)
18
- @name = options[:name]
19
- @before = options[:before]
20
- @bucket_key = options[:bucket_key]
21
- @bucket_value = options[:bucket_value]
22
- @after = options[:after]
23
- end
24
-
25
- # Call the before hook if defined
26
- # @param options [Hash] Evaluation options
27
- # @return [Hash] Modified evaluation options
28
- def call_before(options)
29
- return options unless @before
30
-
31
- @before.call(options)
32
- end
33
-
34
- # Call the bucket key hook if defined
35
- # @param options [Hash] Bucket key options
36
- # @return [String] Modified bucket key
37
- def call_bucket_key(options)
38
- return options[:bucket_key] unless @bucket_key
39
-
40
- @bucket_key.call(options)
41
- end
42
-
43
- # Call the bucket value hook if defined
44
- # @param options [Hash] Bucket value options
45
- # @return [Integer] Modified bucket value
46
- def call_bucket_value(options)
47
- return options[:bucket_value] unless @bucket_value
48
-
49
- @bucket_value.call(options)
50
- end
51
-
52
- # Call the after hook if defined
53
- # @param evaluation [Hash] Evaluation result
54
- # @param options [Hash] Evaluation options
55
- # @return [Hash] Modified evaluation result
56
- def call_after(evaluation, options)
57
- return evaluation unless @after
58
-
59
- @after.call(evaluation, options)
60
- end
61
- end
62
-
63
- # HooksManager class for managing hooks
64
- class HooksManager
65
- attr_reader :hooks, :logger
66
-
67
- # Initialize a new HooksManager
68
- # @param options [Hash] Options hash containing hooks and logger
69
- # @option options [Array<Hook>] :hooks Array of hooks
70
- # @option options [Logger] :logger Logger instance
71
- def initialize(options)
72
- @logger = options[:logger]
73
- @hooks = []
74
-
75
- if options[:hooks]
76
- options[:hooks].each do |hook|
77
- add(hook)
78
- end
79
- end
80
- end
81
-
82
- # Add a hook to the manager
83
- # @param hook [Hook] Hook to add
84
- # @return [Proc, nil] Remove function or nil if hook already exists
85
- def add(hook)
86
- if @hooks.any? { |existing_hook| existing_hook.name == hook.name }
87
- @logger.error("Hook with name \"#{hook.name}\" already exists.", {
88
- name: hook.name,
89
- hook: hook
90
- })
91
-
92
- return nil
93
- end
94
-
95
- @hooks << hook
96
-
97
- # Return a remove function
98
- -> { remove(hook.name) }
99
- end
100
-
101
- # Remove a hook by name
102
- # @param name [String] Hook name to remove
103
- def remove(name)
104
- @hooks = @hooks.reject { |hook| hook.name == name }
105
- end
106
-
107
- # Get all hooks
108
- # @return [Array<Hook>] Array of all hooks
109
- def get_all
110
- @hooks
111
- end
112
-
113
- # Run before hooks
114
- # @param options [Hash] Evaluation options
115
- # @return [Hash] Modified evaluation options
116
- def run_before_hooks(options)
117
- result = options
118
- @hooks.each do |hook|
119
- result = hook.call_before(result)
120
- end
121
- result
122
- end
123
-
124
- # Run bucket key hooks
125
- # @param options [Hash] Bucket key options
126
- # @return [String] Modified bucket key
127
- def run_bucket_key_hooks(options)
128
- bucket_key = options[:bucket_key]
129
- @hooks.each do |hook|
130
- bucket_key = hook.call_bucket_key(options.merge(bucket_key: bucket_key))
131
- end
132
- bucket_key
133
- end
134
-
135
- # Run bucket value hooks
136
- # @param options [Hash] Bucket value options
137
- # @return [Integer] Modified bucket value
138
- def run_bucket_value_hooks(options)
139
- bucket_value = options[:bucket_value]
140
- @hooks.each do |hook|
141
- bucket_value = hook.call_bucket_value(options.merge(bucket_value: bucket_value))
142
- end
143
- bucket_value
144
- end
145
-
146
- # Run after hooks
147
- # @param evaluation [Hash] Evaluation result
148
- # @param options [Hash] Evaluation options
149
- # @return [Hash] Modified evaluation result
150
- def run_after_hooks(evaluation, options)
151
- result = evaluation
152
- @hooks.each do |hook|
153
- result = hook.call_after(result, options)
154
- end
155
- result
156
- end
157
- end
158
- end
159
- end