unleash 5.1.1 → 6.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.
@@ -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
@@ -1,24 +0,0 @@
1
- require 'unleash/strategy/util'
2
-
3
- module Unleash
4
- module Strategy
5
- class GradualRolloutRandom < Base
6
- def name
7
- 'gradualRolloutRandom'
8
- end
9
-
10
- # need: params['percentage']
11
- def is_enabled?(params = {}, _context = nil)
12
- return false unless params.is_a?(Hash) && params.has_key?('percentage')
13
-
14
- begin
15
- percentage = Integer(params['percentage'] || 0)
16
- rescue ArgumentError
17
- return false
18
- end
19
-
20
- (percentage >= Random.rand(1..100))
21
- end
22
- end
23
- end
24
- end
@@ -1,21 +0,0 @@
1
- require 'unleash/strategy/util'
2
-
3
- module Unleash
4
- module Strategy
5
- class GradualRolloutSessionId < Base
6
- def name
7
- 'gradualRolloutSessionId'
8
- end
9
-
10
- # need: params['percentage'], params['groupId'], context.user_id,
11
- def is_enabled?(params = {}, context = nil)
12
- return false unless params.is_a?(Hash) && params.has_key?('percentage')
13
- return false unless context.instance_of?(Unleash::Context)
14
- return false if context.session_id.nil? || context.session_id.empty?
15
-
16
- percentage = Integer(params['percentage'] || 0)
17
- (percentage.positive? && Util.get_normalized_number(context.session_id, params['groupId'] || "", 0) <= percentage)
18
- end
19
- end
20
- end
21
- end
@@ -1,21 +0,0 @@
1
- require 'unleash/strategy/util'
2
-
3
- module Unleash
4
- module Strategy
5
- class GradualRolloutUserId < Base
6
- def name
7
- 'gradualRolloutUserId'
8
- end
9
-
10
- # need: params['percentage'], params['groupId'], context.user_id,
11
- def is_enabled?(params = {}, context = nil, _constraints = [])
12
- return false unless params.is_a?(Hash) && params.has_key?('percentage')
13
- return false unless context.instance_of?(Unleash::Context)
14
- return false if context.user_id.nil? || context.user_id.empty?
15
-
16
- percentage = Integer(params['percentage'] || 0)
17
- (percentage.positive? && Util.get_normalized_number(context.user_id, params['groupId'] || "", 0) <= percentage)
18
- end
19
- end
20
- end
21
- end
@@ -1,36 +0,0 @@
1
- module Unleash
2
- module Strategy
3
- class RemoteAddress < Base
4
- PARAM = 'IPs'.freeze
5
-
6
- def name
7
- 'remoteAddress'
8
- end
9
-
10
- # need: params['IPs'], context.remote_address
11
- def is_enabled?(params = {}, context = nil)
12
- return false unless params.is_a?(Hash) && params.has_key?(PARAM)
13
- return false unless params.fetch(PARAM, nil).is_a? String
14
- return false unless context.instance_of?(Unleash::Context)
15
-
16
- remote_address = ipaddr_or_nil_from_str(context.remote_address)
17
-
18
- params[PARAM]
19
- .split(',')
20
- .map(&:strip)
21
- .map{ |ipblock| ipaddr_or_nil_from_str(ipblock) }
22
- .compact
23
- .map{ |ipb| ipb.include? remote_address }
24
- .any?
25
- end
26
-
27
- private
28
-
29
- def ipaddr_or_nil_from_str(ip)
30
- IPAddr.new(ip)
31
- rescue StandardError
32
- nil
33
- end
34
- end
35
- end
36
- end
@@ -1,20 +0,0 @@
1
- module Unleash
2
- module Strategy
3
- class UserWithId < Base
4
- PARAM = 'userIds'.freeze
5
-
6
- def name
7
- 'userWithId'
8
- end
9
-
10
- # requires: params['userIds'], context.user_id,
11
- def is_enabled?(params = {}, context = nil)
12
- return false unless params.is_a?(Hash) && params.has_key?(PARAM)
13
- return false unless params.fetch(PARAM, nil).is_a? String
14
- return false unless context.instance_of?(Unleash::Context)
15
-
16
- params[PARAM].split(",").map(&:strip).include?(context.user_id)
17
- end
18
- end
19
- end
20
- end
@@ -1,17 +0,0 @@
1
- require 'murmurhash3'
2
-
3
- module Unleash
4
- module Strategy
5
- module Util
6
- module_function
7
-
8
- NORMALIZER = 100
9
- VARIANT_NORMALIZER_SEED = 86_028_157
10
-
11
- # convert the two strings () into a number between 1 and base (100 by default)
12
- def get_normalized_number(identifier, group_id, seed, base = NORMALIZER)
13
- MurmurHash3::V32.str_hash("#{group_id}:#{identifier}", seed) % base + 1
14
- end
15
- end
16
- end
17
- end