unleash 5.1.1 → 6.0.0.pre

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.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unleash
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.1.1
4
+ version: 6.0.0.pre
5
5
  platform: ruby
6
6
  authors:
7
7
  - Renato Arruda
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 1980-01-01 00:00:00.000000000 Z
11
+ date: 2024-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: murmurhash3
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.1.7
27
+ - !ruby/object:Gem::Dependency
28
+ name: yggdrasil-engine
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.0.5
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.0.5
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -166,7 +180,6 @@ files:
166
180
  - examples/extending_unleash_with_opentelemetry.rb
167
181
  - examples/simple.rb
168
182
  - lib/unleash.rb
169
- - lib/unleash/activation_strategy.rb
170
183
  - lib/unleash/bootstrap/configuration.rb
171
184
  - lib/unleash/bootstrap/handler.rb
172
185
  - lib/unleash/bootstrap/provider/base.rb
@@ -174,36 +187,21 @@ files:
174
187
  - lib/unleash/bootstrap/provider/from_url.rb
175
188
  - lib/unleash/client.rb
176
189
  - lib/unleash/configuration.rb
177
- - lib/unleash/constraint.rb
178
190
  - lib/unleash/context.rb
179
- - lib/unleash/feature_toggle.rb
180
- - lib/unleash/metrics.rb
181
191
  - lib/unleash/metrics_reporter.rb
182
192
  - lib/unleash/scheduled_executor.rb
183
193
  - lib/unleash/spec_version.rb
184
194
  - lib/unleash/strategies.rb
185
- - lib/unleash/strategy/application_hostname.rb
186
- - lib/unleash/strategy/base.rb
187
- - lib/unleash/strategy/default.rb
188
- - lib/unleash/strategy/flexible_rollout.rb
189
- - lib/unleash/strategy/gradual_rollout_random.rb
190
- - lib/unleash/strategy/gradual_rollout_sessionid.rb
191
- - lib/unleash/strategy/gradual_rollout_userid.rb
192
- - lib/unleash/strategy/remote_address.rb
193
- - lib/unleash/strategy/user_with_id.rb
194
- - lib/unleash/strategy/util.rb
195
195
  - lib/unleash/toggle_fetcher.rb
196
196
  - lib/unleash/util/http.rb
197
197
  - lib/unleash/variant.rb
198
- - lib/unleash/variant_definition.rb
199
- - lib/unleash/variant_override.rb
200
198
  - lib/unleash/version.rb
201
199
  - unleash-client.gemspec
202
200
  homepage: https://github.com/unleash/unleash-client-ruby
203
201
  licenses:
204
202
  - Apache-2.0
205
203
  metadata: {}
206
- post_install_message:
204
+ post_install_message:
207
205
  rdoc_options: []
208
206
  require_paths:
209
207
  - lib
@@ -218,8 +216,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
218
216
  - !ruby/object:Gem::Version
219
217
  version: '0'
220
218
  requirements: []
221
- rubygems_version: 3.5.16
222
- signing_key:
219
+ rubygems_version: 3.5.6
220
+ signing_key:
223
221
  specification_version: 4
224
222
  summary: Unleash feature toggle client.
225
223
  test_files: []
@@ -1,44 +0,0 @@
1
- module Unleash
2
- class ActivationStrategy
3
- attr_accessor :name, :params, :constraints, :disabled, :variant_definitions
4
-
5
- def initialize(name, params, constraints = [], variant_definitions = [])
6
- self.name = name
7
- self.disabled = false
8
-
9
- if params.is_a?(Hash)
10
- self.params = params
11
- elsif params.nil?
12
- self.params = {}
13
- else
14
- Unleash.logger.warn "Invalid params provided for ActivationStrategy (params:#{params})"
15
- self.params = {}
16
- end
17
-
18
- if constraints.is_a?(Array) && constraints.all?{ |c| c.is_a?(Constraint) }
19
- self.constraints = constraints
20
- else
21
- Unleash.logger.warn "Invalid constraints provided for ActivationStrategy (constraints: #{constraints})"
22
- self.disabled = true
23
- self.constraints = []
24
- end
25
-
26
- self.variant_definitions = valid_variant_definitions(variant_definitions)
27
- end
28
-
29
- def matches_context?(context)
30
- self.constraints.any?{ |c| c.matches_context? context }
31
- end
32
-
33
- private
34
-
35
- def valid_variant_definitions(variant_definitions)
36
- if variant_definitions.is_a?(Array) && variant_definitions.all?{ |variant_definition| variant_definition.is_a?(VariantDefinition) }
37
- variant_definitions
38
- else
39
- Unleash.logger.warn "Invalid variant_definitions provided for ActivationStrategy (variant_definitions: #{variant_definitions})"
40
- []
41
- end
42
- end
43
- end
44
- end
@@ -1,117 +0,0 @@
1
- require 'date'
2
- module Unleash
3
- class Constraint
4
- attr_accessor :context_name, :operator, :value, :inverted, :case_insensitive
5
-
6
- OPERATORS = {
7
- IN: ->(context_v, constraint_v){ constraint_v.include? context_v.to_s },
8
- NOT_IN: ->(context_v, constraint_v){ !constraint_v.include? context_v.to_s },
9
- STR_STARTS_WITH: ->(context_v, constraint_v){ constraint_v.any?{ |v| context_v.start_with? v } },
10
- STR_ENDS_WITH: ->(context_v, constraint_v){ constraint_v.any?{ |v| context_v.end_with? v } },
11
- STR_CONTAINS: ->(context_v, constraint_v){ constraint_v.any?{ |v| context_v.include? v } },
12
- NUM_EQ: ->(context_v, constraint_v){ on_valid_float(constraint_v, context_v){ |x, y| (x - y).abs < Float::EPSILON } },
13
- NUM_LT: ->(context_v, constraint_v){ on_valid_float(constraint_v, context_v){ |x, y| (x > y) } },
14
- NUM_LTE: ->(context_v, constraint_v){ on_valid_float(constraint_v, context_v){ |x, y| (x >= y) } },
15
- NUM_GT: ->(context_v, constraint_v){ on_valid_float(constraint_v, context_v){ |x, y| (x < y) } },
16
- NUM_GTE: ->(context_v, constraint_v){ on_valid_float(constraint_v, context_v){ |x, y| (x <= y) } },
17
- DATE_AFTER: ->(context_v, constraint_v){ on_valid_date(constraint_v, context_v){ |x, y| (x < y) } },
18
- DATE_BEFORE: ->(context_v, constraint_v){ on_valid_date(constraint_v, context_v){ |x, y| (x > y) } },
19
- SEMVER_EQ: ->(context_v, constraint_v){ on_valid_version(constraint_v, context_v){ |x, y| (x == y) } },
20
- SEMVER_GT: ->(context_v, constraint_v){ on_valid_version(constraint_v, context_v){ |x, y| (x < y) } },
21
- SEMVER_LT: ->(context_v, constraint_v){ on_valid_version(constraint_v, context_v){ |x, y| (x > y) } },
22
- FALLBACK_VALIDATOR: ->(_context_v, _constraint_v){ false }
23
- }.freeze
24
-
25
- STRING_OPERATORS = [:STR_STARTS_WITH, :STR_ENDS_WITH, :STR_CONTAINS].freeze
26
-
27
- LIST_OPERATORS = [:IN, :NOT_IN, :STR_STARTS_WITH, :STR_ENDS_WITH, :STR_CONTAINS].freeze
28
-
29
- def initialize(context_name, operator, value = [], inverted: false, case_insensitive: false)
30
- raise ArgumentError, "context_name is not a String" unless context_name.is_a?(String)
31
-
32
- unless OPERATORS.include? operator.to_sym
33
- Unleash.logger.warn "Operator #{operator} is not a supported operator, " \
34
- "falling back to FALLBACK_VALIDATOR which skips this constraint."
35
- operator = "FALLBACK_VALIDATOR"
36
- end
37
- self.log_inconsistent_constraint_configuration(operator.to_sym, value)
38
-
39
- self.context_name = context_name
40
- self.operator = operator.to_sym
41
- self.value = value
42
- self.inverted = !!inverted
43
- self.case_insensitive = !!case_insensitive
44
- end
45
-
46
- def matches_context?(context)
47
- Unleash.logger.debug "Unleash::Constraint matches_context? value: #{self.value} context.get_by_name(#{self.context_name})"
48
- return false if context.nil?
49
-
50
- match = matches_constraint?(context)
51
- self.inverted ? !match : match
52
- rescue KeyError
53
- Unleash.logger.warn "Attemped to resolve a context key during constraint resolution: #{self.context_name} but it wasn't \
54
- found on the context"
55
- false
56
- end
57
-
58
- def self.on_valid_date(val1, val2)
59
- val1 = DateTime.parse(val1)
60
- val2 = val2.is_a?(DateTime) ? val2 : DateTime.parse(val2)
61
- yield(val1, val2)
62
- rescue ArgumentError, TypeError
63
- Unleash.logger.warn "Unleash::ConstraintMatcher unable to parse either context_value (#{val1}) \
64
- or constraint_value (#{val2}) into a date. Returning false!"
65
- false
66
- end
67
-
68
- def self.on_valid_float(val1, val2)
69
- val1 = Float(val1)
70
- val2 = Float(val2)
71
- yield(val1, val2)
72
- rescue ArgumentError
73
- Unleash.logger.warn "Unleash::ConstraintMatcher unable to parse either context_value (#{val1}) \
74
- or constraint_value (#{val2}) into a number. Returning false!"
75
- false
76
- end
77
-
78
- def self.on_valid_version(val1, val2)
79
- val1 = Gem::Version.new(val1)
80
- val2 = Gem::Version.new(val2)
81
- yield(val1, val2)
82
- rescue ArgumentError
83
- Unleash.logger.warn "Unleash::ConstraintMatcher unable to parse either context_value (#{val1}) \
84
- or constraint_value (#{val2}) into a version. Return false!"
85
- false
86
- end
87
-
88
- # This should be a private method but for some reason this fails on Ruby 2.5
89
- def log_inconsistent_constraint_configuration(operator, value)
90
- Unleash.logger.warn "value is a String, operator is expecting an Array" if LIST_OPERATORS.include?(operator) && value.is_a?(String)
91
- Unleash.logger.warn "value is an Array, operator is expecting a String" if !LIST_OPERATORS.include?(operator) && value.is_a?(Array)
92
- end
93
-
94
- private
95
-
96
- def matches_constraint?(context)
97
- Unleash.logger.debug "Unleash::Constraint matches_constraint? value: #{self.value} operator: #{self.operator} " \
98
- " context.get_by_name(#{self.context_name})"
99
-
100
- unless OPERATORS.include?(self.operator)
101
- Unleash.logger.warn "Invalid constraint operator: #{self.operator}, this should be unreachable. Always returning false."
102
- false
103
- end
104
-
105
- # when the operator is NOT_IN and there is no data, return true. In all other cases the operator doesn't match.
106
- return self.operator == :NOT_IN unless context.include?(self.context_name)
107
-
108
- v = self.value.dup
109
- context_value = context.get_by_name(self.context_name)
110
-
111
- # always return false, if we are comparing a non string with a string operator:
112
- return false if !context_value.is_a?(String) && STRING_OPERATORS.include?(self.operator)
113
-
114
- OPERATORS[self.operator].call(*self.case_insensitive ? [context_value.upcase, v.map(&:upcase)] : [context_value, v])
115
- end
116
- end
117
- end
@@ -1,253 +0,0 @@
1
- require 'unleash/activation_strategy'
2
- require 'unleash/constraint'
3
- require 'unleash/variant_definition'
4
- require 'unleash/variant'
5
- require 'unleash/strategy/util'
6
- require 'securerandom'
7
-
8
- module Unleash
9
- class FeatureToggle
10
- attr_accessor :name, :enabled, :dependencies, :strategies, :variant_definitions
11
-
12
- FeatureEvaluationResult = Struct.new(:enabled?, :strategy)
13
-
14
- def initialize(params = {}, segment_map = {})
15
- params = {} if params.nil?
16
-
17
- self.name = params.fetch('name', nil)
18
- self.enabled = params.fetch('enabled', false)
19
- self.dependencies = params.fetch('dependencies', [])
20
-
21
- self.strategies = initialize_strategies(params, segment_map)
22
- self.variant_definitions = initialize_variant_definitions(params)
23
- end
24
-
25
- def to_s
26
- "<FeatureToggle: name=#{name},enabled=#{enabled},strategies=#{strategies},variant_definitions=#{variant_definitions}>"
27
- end
28
-
29
- def is_enabled?(context)
30
- result = am_enabled?(context)
31
-
32
- choice = result ? :yes : :no
33
- Unleash.toggle_metrics.increment(name, choice) unless Unleash.configuration.disable_metrics
34
-
35
- result
36
- end
37
-
38
- def get_variant(context, fallback_variant = Unleash::FeatureToggle.disabled_variant)
39
- raise ArgumentError, "Provided fallback_variant is not of type Unleash::Variant" if fallback_variant.class.name != 'Unleash::Variant'
40
-
41
- context = ensure_valid_context(context)
42
-
43
- evaluation_result = evaluate(context)
44
-
45
- group_id = evaluation_result.strategy&.params.to_h['groupId'] || self.name
46
-
47
- variant = resolve_variant(context, evaluation_result, group_id)
48
-
49
- choice = evaluation_result.enabled? ? :yes : :no
50
- Unleash.toggle_metrics.increment_variant(self.name, choice, variant.name) unless Unleash.configuration.disable_metrics
51
-
52
- variant.feature_enabled = evaluation_result.enabled?
53
-
54
- variant
55
- end
56
-
57
- def self.disabled_variant
58
- Unleash::Variant.new(name: 'disabled', enabled: false, feature_enabled: false)
59
- end
60
-
61
- private
62
-
63
- def resolve_variant(context, evaluation_result, group_id)
64
- variant_strategy_stickiness = evaluation_result.strategy&.params.to_h['stickiness'] || 'default'
65
- variant_definitions = evaluation_result.strategy&.variant_definitions
66
- variant_definitions = self.variant_definitions if variant_definitions.nil? || variant_definitions.empty?
67
- return Unleash::FeatureToggle.disabled_variant unless evaluation_result.enabled?
68
- return Unleash::FeatureToggle.disabled_variant if sum_variant_defs_weights(variant_definitions) <= 0
69
-
70
- variant_from_override_match(context, variant_definitions) ||
71
- variant_from_weights(context, resolve_stickiness(variant_definitions, variant_strategy_stickiness), variant_definitions, group_id)
72
- end
73
-
74
- def resolve_stickiness(variant_definitions, variant_strategy_stickiness)
75
- variant_definitions&.map(&:stickiness)&.compact&.first || variant_strategy_stickiness
76
- end
77
-
78
- # only check if it is enabled, do not do metrics
79
- def am_enabled?(context)
80
- evaluate(context).enabled?
81
- end
82
-
83
- def parent_dependencies_satisfied?(context)
84
- dependencies.empty? || dependencies.all?{ |parent| evaluate_parent(parent, context) }
85
- end
86
-
87
- def evaluate_parent(parent, context)
88
- parent_toggle = get_parent(parent["feature"])
89
- return false if parent_toggle.nil? || !parent_toggle.dependencies.empty?
90
-
91
- evaluation_result = parent_toggle.is_enabled?(context)
92
- return !evaluation_result if parent["enabled"] == false
93
-
94
- return false unless evaluation_result
95
-
96
- return evaluation_result if parent["variants"].nil? || parent["variants"].empty?
97
-
98
- parent["variants"].include?(parent_toggle.get_variant(context).name)
99
- end
100
-
101
- def get_parent(feature)
102
- toggle_as_hash = Unleash&.toggles&.find{ |toggle| toggle['name'] == feature }
103
- if toggle_as_hash.nil?
104
- Unleash.logger.debug "Unleash::Client.is_enabled? feature: #{feature} not found"
105
- return nil
106
- end
107
-
108
- Unleash::FeatureToggle.new(toggle_as_hash, Unleash&.segment_cache)
109
- end
110
-
111
- def evaluate(context)
112
- evaluation_result =
113
- if !parent_dependencies_satisfied?(context)
114
- FeatureEvaluationResult.new(false, nil)
115
- elsif !self.enabled
116
- FeatureEvaluationResult.new(false, nil)
117
- elsif self.strategies.empty?
118
- FeatureEvaluationResult.new(true, nil)
119
- else
120
- strategy = self.strategies.find{ |s| strategy_enabled?(s, context) && strategy_constraint_matches?(s, context) }
121
- FeatureEvaluationResult.new(!strategy.nil?, strategy)
122
- end
123
-
124
- Unleash.logger.debug "Unleash::FeatureToggle (enabled:#{self.enabled}) " \
125
- "and Strategies combined with constraints returned #{evaluation_result})"
126
- evaluation_result
127
- end
128
-
129
- def strategy_enabled?(strategy, context)
130
- r = Unleash.strategies.fetch(strategy.name).is_enabled?(strategy.params, context)
131
- Unleash.logger.debug "Unleash::FeatureToggle.strategy_enabled? Strategy #{strategy.name} returned #{r} with context: #{context}"
132
- r
133
- end
134
-
135
- def strategy_constraint_matches?(strategy, context)
136
- return false if strategy.disabled
137
-
138
- strategy.constraints.empty? || strategy.constraints.all?{ |c| c.matches_context?(context) }
139
- end
140
-
141
- def sum_variant_defs_weights(variant_definitions)
142
- variant_definitions.map(&:weight).reduce(0, :+)
143
- end
144
-
145
- def variant_salt(context, stickiness = "default")
146
- begin
147
- return context.get_by_name(stickiness) if !context.nil? && stickiness != "default"
148
- rescue KeyError
149
- Unleash.logger.warn "Custom stickiness key (#{stickiness}) not found in the provided context #{context}. " \
150
- "Falling back to default behavior."
151
- end
152
- return context.user_id unless context&.user_id.to_s.empty?
153
- return context.session_id unless context&.session_id.to_s.empty?
154
- return context.remote_address unless context&.remote_address.to_s.empty?
155
-
156
- SecureRandom.random_number
157
- end
158
-
159
- def variant_from_override_match(context, variant_definitions)
160
- variant_definition = variant_definitions.find{ |vd| vd.override_matches_context?(context) }
161
- return nil if variant_definition.nil?
162
-
163
- Unleash::Variant.new(name: variant_definition.name, enabled: true, payload: variant_definition.payload)
164
- end
165
-
166
- def variant_from_weights(context, stickiness, variant_definitions, group_id)
167
- variant_weight = Unleash::Strategy::Util.get_normalized_number(
168
- variant_salt(context, stickiness),
169
- group_id,
170
- Unleash::Strategy::Util::VARIANT_NORMALIZER_SEED,
171
- sum_variant_defs_weights(variant_definitions)
172
- )
173
- prev_weights = 0
174
-
175
- variant_definition = variant_definitions
176
- .find do |v|
177
- res = (prev_weights + v.weight >= variant_weight)
178
- prev_weights += v.weight
179
- res
180
- end
181
- return self.disabled_variant if variant_definition.nil?
182
-
183
- Unleash::Variant.new(name: variant_definition.name, enabled: true, payload: variant_definition.payload)
184
- end
185
-
186
- def ensure_valid_context(context)
187
- unless ['NilClass', 'Unleash::Context'].include? context.class.name
188
- Unleash.logger.error "Provided context is not of the correct type #{context.class.name}, " \
189
- "please use Unleash::Context. Context set to nil."
190
- context = nil
191
- end
192
- context
193
- end
194
-
195
- def initialize_strategies(params, segment_map)
196
- (params.fetch('strategies', []) || [])
197
- .select{ |s| s.has_key?('name') && Unleash.strategies.includes?(s['name']) }
198
- .map do |s|
199
- ActivationStrategy.new(
200
- s['name'],
201
- s['parameters'],
202
- resolve_constraints(s, segment_map),
203
- resolve_variants(s)
204
- )
205
- end || []
206
- end
207
-
208
- def resolve_variants(strategy)
209
- (strategy.fetch("variants", []) || [])
210
- .select{ |variant| variant.is_a?(Hash) && variant.has_key?("name") }
211
- .map do |variant|
212
- VariantDefinition.new(
213
- variant.fetch("name", ""),
214
- variant.fetch("weight", 0),
215
- variant.fetch("payload", nil),
216
- variant.fetch("stickiness", nil),
217
- variant.fetch("overrides", [])
218
- )
219
- end
220
- end
221
-
222
- def resolve_constraints(strategy, segment_map)
223
- segment_constraints = (strategy["segments"] || []).map do |segment_id|
224
- segment_map[segment_id]&.fetch("constraints")
225
- end
226
- (strategy.fetch("constraints", []) + segment_constraints).flatten.map do |constraint|
227
- return nil if constraint.nil?
228
-
229
- Constraint.new(
230
- constraint.fetch('contextName'),
231
- constraint.fetch('operator'),
232
- constraint.fetch('value', nil) || constraint.fetch('values', nil),
233
- inverted: constraint.fetch('inverted', false),
234
- case_insensitive: constraint.fetch('caseInsensitive', false)
235
- )
236
- end
237
- end
238
-
239
- def initialize_variant_definitions(params)
240
- (params.fetch('variants', []) || [])
241
- .select{ |v| v.is_a?(Hash) && v.has_key?('name') }
242
- .map do |v|
243
- VariantDefinition.new(
244
- v.fetch('name', ''),
245
- v.fetch('weight', 0),
246
- v.fetch('payload', nil),
247
- v.fetch('stickiness', nil),
248
- v.fetch('overrides', [])
249
- )
250
- end || []
251
- end
252
- end
253
- end
@@ -1,41 +0,0 @@
1
- module Unleash
2
- class Metrics
3
- attr_accessor :features, :features_lock
4
-
5
- def initialize
6
- self.features = {}
7
- self.features_lock = Mutex.new
8
- end
9
-
10
- def to_s
11
- self.features_lock.synchronize do
12
- return self.features.to_json
13
- end
14
- end
15
-
16
- def increment(feature, choice)
17
- raise "InvalidArgument choice must be :yes or :no" unless [:yes, :no].include? choice
18
-
19
- self.features_lock.synchronize do
20
- self.features[feature] = { yes: 0, no: 0 } unless self.features.include? feature
21
- self.features[feature][choice] += 1
22
- end
23
- end
24
-
25
- def increment_variant(feature, choice, variant)
26
- self.features_lock.synchronize do
27
- self.features[feature] = { yes: 0, no: 0 } unless self.features.include? feature
28
- self.features[feature][choice] += 1
29
- self.features[feature]['variants'] = {} unless self.features[feature].include? 'variants'
30
- self.features[feature]['variants'][variant] = 0 unless self.features[feature]['variants'].include? variant
31
- self.features[feature]['variants'][variant] += 1
32
- end
33
- end
34
-
35
- def reset
36
- self.features_lock.synchronize do
37
- self.features = {}
38
- end
39
- end
40
- end
41
- end
@@ -1,26 +0,0 @@
1
- require 'socket'
2
-
3
- module Unleash
4
- module Strategy
5
- class ApplicationHostname < Base
6
- attr_accessor :hostname
7
-
8
- PARAM = 'hostnames'.freeze
9
-
10
- def initialize
11
- self.hostname = Socket.gethostname || 'undefined'
12
- end
13
-
14
- def name
15
- 'applicationHostname'
16
- end
17
-
18
- # need: :params['hostnames']
19
- def is_enabled?(params = {}, _context = nil)
20
- return false unless params.is_a?(Hash) && params.has_key?(PARAM)
21
-
22
- params[PARAM].split(",").map(&:strip).map(&:downcase).include?(self.hostname)
23
- end
24
- end
25
- end
26
- end
@@ -1,16 +0,0 @@
1
- module Unleash
2
- module Strategy
3
- class NotImplemented < RuntimeError
4
- end
5
-
6
- class Base
7
- def name
8
- raise NotImplemented, "Strategy is not implemented"
9
- end
10
-
11
- def is_enabled?(_params = {}, _context = nil)
12
- raise NotImplemented, "Strategy is not implemented"
13
- end
14
- end
15
- end
16
- end
@@ -1,13 +0,0 @@
1
- module Unleash
2
- module Strategy
3
- class Default < Base
4
- def name
5
- 'default'
6
- end
7
-
8
- def is_enabled?(_params = {}, _context = nil)
9
- true
10
- end
11
- end
12
- end
13
- end
@@ -1,64 +0,0 @@
1
- require 'unleash/strategy/util'
2
-
3
- module Unleash
4
- module Strategy
5
- class FlexibleRollout < Base
6
- def name
7
- 'flexibleRollout'
8
- end
9
-
10
- # need: params['percentage']
11
- def is_enabled?(params = {}, context = nil)
12
- return false unless params.is_a?(Hash)
13
-
14
- stickiness = params.fetch('stickiness', 'default')
15
- return false if context_invalid?(stickiness, context)
16
-
17
- stickiness_id = resolve_stickiness(stickiness, context)
18
-
19
- begin
20
- percentage = Integer(params.fetch('rollout', 0))
21
- percentage = 0 if percentage > 100 || percentage.negative?
22
- rescue ArgumentError
23
- return false
24
- end
25
-
26
- group_id = params.fetch('groupId', '')
27
- normalized_number = Util.get_normalized_number(stickiness_id, group_id, 0)
28
-
29
- return false if stickiness_id.nil?
30
-
31
- (percentage.positive? && normalized_number <= percentage)
32
- end
33
-
34
- private
35
-
36
- def context_invalid?(stickiness, context)
37
- return false if ['random', 'default'].include?(stickiness)
38
-
39
- !context.instance_of?(Unleash::Context)
40
- end
41
-
42
- def random
43
- Random.rand(0..10_000)
44
- end
45
-
46
- def resolve_stickiness(stickiness, context)
47
- case stickiness
48
- when 'random'
49
- random
50
- when 'default'
51
- return random unless context.instance_of?(Unleash::Context)
52
-
53
- context&.user_id || context&.session_id || random
54
- else
55
- begin
56
- context.get_by_name(stickiness)
57
- rescue KeyError
58
- nil
59
- end
60
- end
61
- end
62
- end
63
- end
64
- end