unleash 4.3.0 → 4.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f05a01bdf6013184b6948fb5c38f6b1da1cde9d18f9a0eeecd69b54cbed8ff18
4
- data.tar.gz: 6343830fd945d00decd331acade205d605e0d496ed1d4afd4736affcb2f538ca
3
+ metadata.gz: 5788566712ad3909e6707041673d359a4d9835b59c79bf0e0fac80c7c8a164ed
4
+ data.tar.gz: 707f6875e73a10ef24fdce900aa7c179b0762eedda07ce83a41ea5ed76ba494f
5
5
  SHA512:
6
- metadata.gz: 1ac4a4b89f787b5ae27acda02865def0eb68f261da10b83c01a6b34f6706b1b29443375eb82418b9834db555fbe54c1a1dfe49e9bf4c761556145edce88f76c9
7
- data.tar.gz: c7a2332c7e6ab54872b08432af4c5c2cf092b7586664eb0267e45e416fb9a72c46246dd6cdedf68b4a9421678e4cb9f9507365e40fc5c2113b9fba8e703a19dd
6
+ metadata.gz: 5d836f1c130e5db08fdac81f75cb938e93c060f997946ec80bd5819ad098a9b6d7a6d5628f88cd70144899bbe72ac1f5a1e802b4973b6ee0d24eda85e783fab3
7
+ data.tar.gz: 69280e8af54ba2613ef3ae17206f0dfbd31a958d2595162dab71dd0d4cf5815d4dc3e023160c1edc1dbcaeea77d2cfd5f2e468af28051fe41b51dd77fae0f689
@@ -46,7 +46,7 @@ jobs:
46
46
  - name: Install dependencies
47
47
  run: bundle install
48
48
  - name: Download test cases
49
- run: git clone --depth 5 --branch v4.1.0 https://github.com/Unleash/client-specification.git client-specification
49
+ run: git clone --depth 5 --branch v4.2.2 https://github.com/Unleash/client-specification.git client-specification
50
50
  - name: Run tests
51
51
  run: bundle exec rake
52
52
  env:
data/.rspec CHANGED
@@ -1,2 +1,3 @@
1
1
  --format documentation
2
2
  --color
3
+ --require 'spec_helper'
data/.rubocop.yml CHANGED
@@ -9,7 +9,7 @@ Naming/PredicateName:
9
9
 
10
10
 
11
11
  Metrics/ClassLength:
12
- Max: 125
12
+ Max: 130
13
13
  Layout/LineLength:
14
14
  Max: 140
15
15
  Metrics/MethodLength:
@@ -34,6 +34,9 @@ Style/StringLiterals:
34
34
  Style/RedundantSelf:
35
35
  Enabled: false
36
36
 
37
+ Style/OptionalBooleanParameter:
38
+ Enabled: false
39
+
37
40
  Style/SymbolArray:
38
41
  EnforcedStyle: brackets
39
42
  Style/WordArray:
data/README.md CHANGED
@@ -97,6 +97,7 @@ Argument | Description | Required? | Type | Default Value|
97
97
  `logger` | Specify a custom `Logger` class to handle logs for the Unleash client. | N | Class | `Logger.new(STDOUT)` |
98
98
  `log_level` | Change the log level for the `Logger` class. Constant from `Logger::Severity`. | N | Constant | `Logger::WARN` |
99
99
  `bootstrap_config` | Bootstrap config on how to loaded data on start-up. This is useful for loading large states on startup without (or before) hitting the network. | N | Unleash::Bootstrap::Configuration | `nil` |
100
+ `strategies` | Strategies manager that holds all strategies and allows to add custom strategies | N | Unleash::Strategies | `Unleash::Strategies.new` |
100
101
 
101
102
  For a more in-depth look, please see `lib/unleash/configuration.rb`.
102
103
 
@@ -472,6 +473,27 @@ This client comes with the all the required strategies out of the box:
472
473
  * UnknownStrategy
473
474
  * UserWithIdStrategy
474
475
 
476
+ ## Custom Strategies
477
+
478
+ Client allows to add [custom activation strategies](https://docs.getunleash.io/advanced/custom_activation_strategy) using configuration.
479
+ In order for strategy to work correctly it should support two methods `name` and `is_enabled?`
480
+
481
+ ```ruby
482
+ class MyCustomStrategy
483
+ def name
484
+ 'muCustomStrategy'
485
+ end
486
+
487
+ def is_enabled?(params = {}, context = nil)
488
+ true
489
+ end
490
+ end
491
+
492
+ Unleash.configure do |config|
493
+ config.strategies.add(MyCustomStrategy.new)
494
+ end
495
+ ```
496
+
475
497
  ## Development
476
498
 
477
499
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -1,9 +1,10 @@
1
1
  module Unleash
2
2
  class ActivationStrategy
3
- attr_accessor :name, :params, :constraints
3
+ attr_accessor :name, :params, :constraints, :disabled
4
4
 
5
5
  def initialize(name, params, constraints = [])
6
6
  self.name = name
7
+ self.disabled = false
7
8
 
8
9
  if params.is_a?(Hash)
9
10
  self.params = params
@@ -18,6 +19,7 @@ module Unleash
18
19
  self.constraints = constraints
19
20
  else
20
21
  Unleash.logger.warn "Invalid constraints provided for ActivationStrategy (contraints: #{constraints})"
22
+ self.disabled = true
21
23
  self.constraints = []
22
24
  end
23
25
  end
@@ -12,7 +12,7 @@ module Unleash
12
12
  attr_accessor :fetcher_scheduled_executor, :metrics_scheduled_executor
13
13
 
14
14
  def initialize(*opts)
15
- Unleash.configuration ||= Unleash::Configuration.new(*opts)
15
+ Unleash.configuration = Unleash::Configuration.new(*opts) unless opts.empty?
16
16
  Unleash.configuration.validate!
17
17
 
18
18
  Unleash.logger = Unleash.configuration.logger.clone
@@ -45,7 +45,7 @@ module Unleash
45
45
  return default_value
46
46
  end
47
47
 
48
- toggle = Unleash::FeatureToggle.new(toggle_as_hash)
48
+ toggle = Unleash::FeatureToggle.new(toggle_as_hash, Unleash&.segment_cache)
49
49
 
50
50
  toggle.is_enabled?(context)
51
51
  end
@@ -110,7 +110,7 @@ module Unleash
110
110
  'appName': Unleash.configuration.app_name,
111
111
  'instanceId': Unleash.configuration.instance_id,
112
112
  'sdkVersion': "unleash-client-ruby:" + Unleash::VERSION,
113
- 'strategies': Unleash::STRATEGIES.keys,
113
+ 'strategies': Unleash.strategies.keys,
114
114
  'started': Time.now.iso8601(Unleash::TIME_RESOLUTION),
115
115
  'interval': Unleash.configuration.metrics_interval_in_millis
116
116
  }
@@ -20,7 +20,8 @@ module Unleash
20
20
  :backup_file,
21
21
  :logger,
22
22
  :log_level,
23
- :bootstrap_config
23
+ :bootstrap_config,
24
+ :strategies
24
25
 
25
26
  def initialize(opts = {})
26
27
  validate_custom_http_headers!(opts[:custom_http_headers]) if opts.has_key?(:custom_http_headers)
@@ -51,7 +52,8 @@ module Unleash
51
52
  def http_headers
52
53
  {
53
54
  'UNLEASH-INSTANCEID' => self.instance_id,
54
- 'UNLEASH-APPNAME' => self.app_name
55
+ 'UNLEASH-APPNAME' => self.app_name,
56
+ 'Unleash-Client-Spec' => '4.2.2'
55
57
  }.merge!(generate_custom_http_headers)
56
58
  end
57
59
 
@@ -94,12 +96,13 @@ module Unleash
94
96
  self.backup_file = nil
95
97
  self.log_level = Logger::WARN
96
98
  self.bootstrap_config = nil
99
+ self.strategies = Unleash::Strategies.new
97
100
 
98
101
  self.custom_http_headers = {}
99
102
  end
100
103
 
101
104
  def initialize_default_logger
102
- self.logger = Logger.new(STDOUT)
105
+ self.logger = Logger.new($stdout)
103
106
 
104
107
  # on default logger, use custom formatter that includes thread_name:
105
108
  self.logger.formatter = proc do |severity, datetime, _progname, msg|
@@ -1,12 +1,11 @@
1
1
  require 'date'
2
-
3
2
  module Unleash
4
3
  class Constraint
5
4
  attr_accessor :context_name, :operator, :value, :inverted, :case_insensitive
6
5
 
7
6
  OPERATORS = {
8
- IN: ->(context_v, constraint_v){ constraint_v.include? context_v },
9
- NOT_IN: ->(context_v, constraint_v){ !constraint_v.include? context_v },
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 },
10
9
  STR_STARTS_WITH: ->(context_v, constraint_v){ constraint_v.any?{ |v| context_v.start_with? v } },
11
10
  STR_ENDS_WITH: ->(context_v, constraint_v){ constraint_v.any?{ |v| context_v.end_with? v } },
12
11
  STR_CONTAINS: ->(context_v, constraint_v){ constraint_v.any?{ |v| context_v.include? v } },
@@ -19,16 +18,21 @@ module Unleash
19
18
  DATE_BEFORE: ->(context_v, constraint_v){ on_valid_date(constraint_v, context_v){ |x, y| (x > y) } },
20
19
  SEMVER_EQ: ->(context_v, constraint_v){ on_valid_version(constraint_v, context_v){ |x, y| (x == y) } },
21
20
  SEMVER_GT: ->(context_v, constraint_v){ on_valid_version(constraint_v, context_v){ |x, y| (x < y) } },
22
- SEMVER_LT: ->(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
23
  }.freeze
24
24
 
25
25
  LIST_OPERATORS = [:IN, :NOT_IN, :STR_STARTS_WITH, :STR_ENDS_WITH, :STR_CONTAINS].freeze
26
26
 
27
27
  def initialize(context_name, operator, value = [], inverted: false, case_insensitive: false)
28
28
  raise ArgumentError, "context_name is not a String" unless context_name.is_a?(String)
29
- raise ArgumentError, "operator does not hold a valid value:" + OPERATORS.keys unless OPERATORS.include? operator.to_sym
30
29
 
31
- self.validate_constraint_value_type(operator.to_sym, value)
30
+ unless OPERATORS.include? operator.to_sym
31
+ Unleash.logger.warn "Operator #{operator} is not a supported operator, " \
32
+ "falling back to FALLBACK_VALIDATOR which skips this constraint."
33
+ operator = "FALLBACK_VALIDATOR"
34
+ end
35
+ self.log_inconsistent_constraint_configuration(operator.to_sym, value)
32
36
 
33
37
  self.context_name = context_name
34
38
  self.operator = operator.to_sym
@@ -38,8 +42,8 @@ module Unleash
38
42
  end
39
43
 
40
44
  def matches_context?(context)
41
- Unleash.logger.debug "Unleash::Constraint matches_context? value: #{self.value} context.get_by_name(#{self.context_name})" \
42
- " #{context.get_by_name(self.context_name)} "
45
+ Unleash.logger.debug "Unleash::Constraint matches_context? value: #{self.value} context.get_by_name(#{self.context_name})"
46
+
43
47
  match = matches_constraint?(context)
44
48
  self.inverted ? !match : match
45
49
  rescue KeyError
@@ -79,19 +83,25 @@ module Unleash
79
83
  end
80
84
 
81
85
  # This should be a private method but for some reason this fails on Ruby 2.5
82
- def validate_constraint_value_type(operator, value)
83
- raise ArgumentError, "context_name is not an Array" if LIST_OPERATORS.include?(operator) && value.is_a?(String)
84
- raise ArgumentError, "context_name is not a String" if !LIST_OPERATORS.include?(operator) && value.is_a?(Array)
86
+ def log_inconsistent_constraint_configuration(operator, value)
87
+ Unleash.logger.warn "value is a String, operator is expecting an Array" if LIST_OPERATORS.include?(operator) && value.is_a?(String)
88
+ Unleash.logger.warn "value is an Array, operator is expecting a String" if !LIST_OPERATORS.include?(operator) && value.is_a?(Array)
85
89
  end
86
90
 
87
91
  private
88
92
 
89
93
  def matches_constraint?(context)
94
+ Unleash.logger.debug "Unleash::Constraint matches_constraint? value: #{self.value} operator: #{self.operator} " \
95
+ " context.get_by_name(#{self.context_name})"
96
+
90
97
  unless OPERATORS.include?(self.operator)
91
98
  Unleash.logger.warn "Invalid constraint operator: #{self.operator}, this should be unreachable. Always returning false."
92
99
  false
93
100
  end
94
101
 
102
+ # when the operator is NOT_IN and there is no data, return true. In all other cases the operator doesn't match.
103
+ return self.operator == :NOT_IN unless context.include?(self.context_name)
104
+
95
105
  v = self.value.dup
96
106
  context_value = context.get_by_name(self.context_name)
97
107
 
@@ -33,6 +33,13 @@ module Unleash
33
33
  end
34
34
  end
35
35
 
36
+ def include?(name)
37
+ normalized_name = underscore(name)
38
+ return self.instance_variable_defined? "@#{normalized_name}" if ATTRS.include? normalized_name.to_sym
39
+
40
+ self.properties.include?(normalized_name.to_sym) || self.properties.include?(name.to_sym)
41
+ end
42
+
36
43
  private
37
44
 
38
45
  # Method to fetch values from hash for two types of keys: string in camelCase and symbol in snake_case
@@ -9,13 +9,13 @@ module Unleash
9
9
  class FeatureToggle
10
10
  attr_accessor :name, :enabled, :strategies, :variant_definitions
11
11
 
12
- def initialize(params = {})
12
+ def initialize(params = {}, segment_map = {})
13
13
  params = {} if params.nil?
14
14
 
15
15
  self.name = params.fetch('name', nil)
16
16
  self.enabled = params.fetch('enabled', false)
17
17
 
18
- self.strategies = initialize_strategies(params)
18
+ self.strategies = initialize_strategies(params, segment_map)
19
19
  self.variant_definitions = initialize_variant_definitions(params)
20
20
  end
21
21
 
@@ -75,12 +75,14 @@ module Unleash
75
75
  end
76
76
 
77
77
  def strategy_enabled?(strategy, context)
78
- r = Unleash::STRATEGIES.fetch(strategy.name.to_sym, :unknown).is_enabled?(strategy.params, context)
78
+ r = Unleash.strategies.fetch(strategy.name).is_enabled?(strategy.params, context)
79
79
  Unleash.logger.debug "Unleash::FeatureToggle.strategy_enabled? Strategy #{strategy.name} returned #{r} with context: #{context}"
80
80
  r
81
81
  end
82
82
 
83
83
  def strategy_constraint_matches?(strategy, context)
84
+ return false if strategy.disabled
85
+
84
86
  strategy.constraints.empty? || strategy.constraints.all?{ |c| c.matches_context?(context) }
85
87
  end
86
88
 
@@ -128,26 +130,35 @@ module Unleash
128
130
  context
129
131
  end
130
132
 
131
- def initialize_strategies(params)
133
+ def initialize_strategies(params, segment_map)
132
134
  params.fetch('strategies', [])
133
- .select{ |s| s.has_key?('name') && Unleash::STRATEGIES.has_key?(s['name'].to_sym) }
135
+ .select{ |s| s.has_key?('name') && Unleash.strategies.includes?(s['name']) }
134
136
  .map do |s|
135
137
  ActivationStrategy.new(
136
138
  s['name'],
137
139
  s['parameters'],
138
- (s['constraints'] || []).map do |c|
139
- Constraint.new(
140
- c.fetch('contextName'),
141
- c.fetch('operator'),
142
- c.fetch('values', nil) || c.fetch('value', nil),
143
- inverted: c.fetch('inverted', false),
144
- case_insensitive: c.fetch('caseInsensitive', false)
145
- )
146
- end
140
+ resolve_constraints(s, segment_map)
147
141
  )
148
142
  end || []
149
143
  end
150
144
 
145
+ def resolve_constraints(strategy, segment_map)
146
+ segment_constraints = (strategy["segments"] || []).map do |segment_id|
147
+ segment_map[segment_id]&.fetch("constraints")
148
+ end
149
+ (strategy.fetch("constraints", []) + segment_constraints).flatten.map do |constraint|
150
+ return nil if constraint.nil?
151
+
152
+ Constraint.new(
153
+ constraint.fetch('contextName'),
154
+ constraint.fetch('operator'),
155
+ constraint.fetch('value', nil) || constraint.fetch('values', nil),
156
+ inverted: constraint.fetch('inverted', false),
157
+ case_insensitive: constraint.fetch('caseInsensitive', false)
158
+ )
159
+ end
160
+ end
161
+
151
162
  def initialize_variant_definitions(params)
152
163
  (params.fetch('variants', []) || [])
153
164
  .select{ |v| v.is_a?(Hash) && v.has_key?('name') }
@@ -0,0 +1,80 @@
1
+ require 'unleash/strategy/base'
2
+ Gem.find_files('unleash/strategy/**/*.rb').each{ |path| require path }
3
+
4
+ module Unleash
5
+ class Strategies
6
+ def initialize
7
+ @strategies = {}
8
+ register_strategies
9
+ end
10
+
11
+ def keys
12
+ @strategies.keys
13
+ end
14
+
15
+ def includes?(name)
16
+ @strategies.has_key?(name.to_s)
17
+ end
18
+
19
+ def fetch(name)
20
+ raise Unleash::Strategy::NotImplemented, "Strategy is not implemented" unless (strategy = @strategies[name.to_s])
21
+
22
+ strategy
23
+ end
24
+
25
+ def add(strategy)
26
+ @strategies[strategy.name] = strategy
27
+ end
28
+
29
+ def []=(key, strategy)
30
+ warn_deprecated_registration(strategy, 'modifying Unleash::STRATEGIES')
31
+ @strategies[key.to_s] = strategy
32
+ end
33
+
34
+ def [](key)
35
+ @strategies[key.to_s]
36
+ end
37
+
38
+ def register_strategies
39
+ register_base_strategies
40
+ register_custom_strategies
41
+ end
42
+
43
+ protected
44
+
45
+ # Deprecated: Use Unleash.configuration to add custom strategies
46
+ def register_custom_strategies
47
+ Unleash::Strategy.constants
48
+ .select{ |c| Unleash::Strategy.const_get(c).is_a? Class }
49
+ .reject{ |c| ['NotImplemented', 'Base'].include?(c.to_s) } # Reject abstract classes
50
+ .map{ |c| Object.const_get("Unleash::Strategy::#{c}") }
51
+ .reject{ |c| DEFAULT_STRATEGIES.include?(c) } # Reject base classes
52
+ .each do |c|
53
+ strategy = c.new
54
+ warn_deprecated_registration(strategy, 'adding custom class into Unleash::Strategy namespace')
55
+ self.add(strategy)
56
+ end
57
+ end
58
+
59
+ def register_base_strategies
60
+ DEFAULT_STRATEGIES.each{ |c| self.add(c.new) }
61
+ end
62
+
63
+ DEFAULT_STRATEGIES = [
64
+ Unleash::Strategy::ApplicationHostname,
65
+ Unleash::Strategy::Default,
66
+ Unleash::Strategy::FlexibleRollout,
67
+ Unleash::Strategy::GradualRolloutRandom,
68
+ Unleash::Strategy::GradualRolloutSessionId,
69
+ Unleash::Strategy::GradualRolloutUserId,
70
+ Unleash::Strategy::RemoteAddress,
71
+ Unleash::Strategy::UserWithId
72
+ ].freeze
73
+
74
+ def warn_deprecated_registration(strategy, method)
75
+ warn "[DEPRECATED] Registering custom Unleash strategy by #{method} is deprecated.
76
+ Please use Unleash configuration to register custom strategy: " \
77
+ "`Unleash.configure {|c| c.strategies.add(#{strategy.class.name}.new) }`"
78
+ end
79
+ end
80
+ end
@@ -10,7 +10,7 @@ module Unleash
10
10
  # need: params['percentage']
11
11
  def is_enabled?(params = {}, context = nil)
12
12
  return false unless params.is_a?(Hash)
13
- return false unless context.class.name == 'Unleash::Context'
13
+ return false unless context.instance_of?(Unleash::Context)
14
14
 
15
15
  stickiness = params.fetch('stickiness', 'default')
16
16
  stickiness_id = resolve_stickiness(stickiness, context)
@@ -10,7 +10,7 @@ module Unleash
10
10
  # need: params['percentage'], params['groupId'], context.user_id,
11
11
  def is_enabled?(params = {}, context = nil)
12
12
  return false unless params.is_a?(Hash) && params.has_key?('percentage')
13
- return false unless context.class.name == 'Unleash::Context'
13
+ return false unless context.instance_of?(Unleash::Context)
14
14
  return false if context.session_id.nil? || context.session_id.empty?
15
15
 
16
16
  percentage = Integer(params['percentage'] || 0)
@@ -10,7 +10,7 @@ module Unleash
10
10
  # need: params['percentage'], params['groupId'], context.user_id,
11
11
  def is_enabled?(params = {}, context = nil, _constraints = [])
12
12
  return false unless params.is_a?(Hash) && params.has_key?('percentage')
13
- return false unless context.class.name == 'Unleash::Context'
13
+ return false unless context.instance_of?(Unleash::Context)
14
14
  return false if context.user_id.nil? || context.user_id.empty?
15
15
 
16
16
  percentage = Integer(params['percentage'] || 0)
@@ -11,7 +11,7 @@ module Unleash
11
11
  def is_enabled?(params = {}, context = nil)
12
12
  return false unless params.is_a?(Hash) && params.has_key?(PARAM)
13
13
  return false unless params.fetch(PARAM, nil).is_a? String
14
- return false unless context.class.name == 'Unleash::Context'
14
+ return false unless context.instance_of?(Unleash::Context)
15
15
 
16
16
  remote_address = ipaddr_or_nil_from_str(context.remote_address)
17
17
 
@@ -11,7 +11,7 @@ module Unleash
11
11
  def is_enabled?(params = {}, context = nil)
12
12
  return false unless params.is_a?(Hash) && params.has_key?(PARAM)
13
13
  return false unless params.fetch(PARAM, nil).is_a? String
14
- return false unless context.class.name == 'Unleash::Context'
14
+ return false unless context.instance_of?(Unleash::Context)
15
15
 
16
16
  params[PARAM].split(",").map(&:strip).include?(context.user_id)
17
17
  end
@@ -5,11 +5,12 @@ require 'json'
5
5
 
6
6
  module Unleash
7
7
  class ToggleFetcher
8
- attr_accessor :toggle_cache, :toggle_lock, :toggle_resource, :etag, :retry_count
8
+ attr_accessor :toggle_cache, :toggle_lock, :toggle_resource, :etag, :retry_count, :segment_cache
9
9
 
10
10
  def initialize
11
11
  self.etag = nil
12
12
  self.toggle_cache = nil
13
+ self.segment_cache = nil
13
14
  self.toggle_lock = Mutex.new
14
15
  self.toggle_resource = ConditionVariable.new
15
16
  self.retry_count = 0
@@ -95,9 +96,10 @@ module Unleash
95
96
  end
96
97
 
97
98
  def update_running_client!
98
- if Unleash.toggles != self.toggles
99
+ if Unleash.toggles != self.toggles["features"] || Unleash.segment_cache != self.toggles["segments"]
99
100
  Unleash.logger.info "Updating toggles to main client, there has been a change in the server."
100
- Unleash.toggles = self.toggles
101
+ Unleash.toggles = self.toggles["features"]
102
+ Unleash.segment_cache = self.toggles["segments"]
101
103
  end
102
104
  end
103
105
 
@@ -126,10 +128,19 @@ module Unleash
126
128
  Unleash.configuration.bootstrap_config = nil
127
129
  end
128
130
 
131
+ def build_segment_map(segments_array)
132
+ return {} if segments_array.nil?
133
+
134
+ segments_array.map{ |segment| [segment["id"], segment] }.to_h
135
+ end
136
+
129
137
  # @param response_body [String]
130
138
  def get_features(response_body)
131
139
  response_hash = JSON.parse(response_body)
132
- return response_hash['features'] if response_hash['version'] >= 1
140
+
141
+ if response_hash['version'] >= 1
142
+ return { "features" => response_hash["features"], "segments" => build_segment_map(response_hash["segments"]) }
143
+ end
133
144
 
134
145
  raise NotImplemented, "Version of features provided by unleash server" \
135
146
  " is unsupported by this client."
@@ -4,7 +4,7 @@ module Unleash
4
4
  class VariantDefinition
5
5
  attr_accessor :name, :weight, :payload, :overrides, :stickiness
6
6
 
7
- def initialize(name, weight = 0, payload = nil, stickiness = nil, overrides = [])
7
+ def initialize(name, weight = 0, payload = nil, stickiness = nil, overrides = []) # rubocop:disable Metrics/ParameterLists
8
8
  self.name = name
9
9
  self.weight = weight
10
10
  self.payload = payload
@@ -14,7 +14,7 @@ module Unleash
14
14
  end
15
15
 
16
16
  def matches_context?(context)
17
- raise ArgumentError, 'context must be of class Unleash::Context' unless context.class.name == 'Unleash::Context'
17
+ raise ArgumentError, 'context must be of class Unleash::Context' unless context.instance_of?(Unleash::Context)
18
18
 
19
19
  context_value =
20
20
  case self.context_name
@@ -1,3 +1,3 @@
1
1
  module Unleash
2
- VERSION = "4.3.0".freeze
2
+ VERSION = "4.4.0".freeze
3
3
  end
data/lib/unleash.rb CHANGED
@@ -1,35 +1,31 @@
1
1
  require 'unleash/version'
2
2
  require 'unleash/configuration'
3
- require 'unleash/strategy/base'
3
+ require 'unleash/strategies'
4
4
  require 'unleash/context'
5
5
  require 'unleash/client'
6
6
  require 'logger'
7
7
 
8
- Gem.find_files('unleash/strategy/**/*.rb').each{ |path| require path unless path.end_with? '_spec.rb' }
9
-
10
8
  module Unleash
11
9
  TIME_RESOLUTION = 3
12
10
 
13
- STRATEGIES = Unleash::Strategy.constants
14
- .select{ |c| Unleash::Strategy.const_get(c).is_a? Class }
15
- .reject{ |c| ['NotImplemented', 'Base'].include?(c.to_s) }
16
- .map do |c|
17
- lowered_c = c.to_s
18
- lowered_c[0] = lowered_c[0].downcase
19
- [lowered_c.to_sym, Object.const_get("Unleash::Strategy::#{c}").new]
20
- end
21
- .to_h
22
-
23
11
  class << self
24
- attr_accessor :configuration, :toggle_fetcher, :toggles, :toggle_metrics, :reporter, :logger
12
+ attr_accessor :configuration, :toggle_fetcher, :toggles, :toggle_metrics, :reporter, :segment_cache, :logger
25
13
  end
26
14
 
15
+ self.configuration = Unleash::Configuration.new
16
+
17
+ # Deprecated: Use Unleash.configure to add custom strategies
18
+ STRATEGIES = self.configuration.strategies
19
+
27
20
  # Support for configuration via yield:
28
21
  def self.configure
29
- self.configuration ||= Unleash::Configuration.new
30
22
  yield(configuration)
31
23
 
32
24
  self.configuration.validate!
33
25
  self.configuration.refresh_backup_file!
34
26
  end
27
+
28
+ def self.strategies
29
+ self.configuration.strategies
30
+ end
35
31
  end
@@ -31,7 +31,7 @@ Gem::Specification.new do |spec|
31
31
  spec.add_development_dependency "rspec-json_expectations", "~> 2.2"
32
32
  spec.add_development_dependency "webmock", "~> 3.8"
33
33
 
34
- spec.add_development_dependency "rubocop", "< 1.0.0"
34
+ spec.add_development_dependency "rubocop", "~> 1.28.2"
35
35
  spec.add_development_dependency "simplecov", "~> 0.21.2"
36
36
  spec.add_development_dependency "simplecov-lcov", "~> 0.8.0"
37
37
  end
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: 4.3.0
4
+ version: 4.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Renato Arruda
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-14 00:00:00.000000000 Z
11
+ date: 2022-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: murmurhash3
@@ -98,16 +98,16 @@ dependencies:
98
98
  name: rubocop
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - "<"
101
+ - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 1.0.0
103
+ version: 1.28.2
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - "<"
108
+ - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: 1.0.0
110
+ version: 1.28.2
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: simplecov
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -176,6 +176,7 @@ files:
176
176
  - lib/unleash/metrics.rb
177
177
  - lib/unleash/metrics_reporter.rb
178
178
  - lib/unleash/scheduled_executor.rb
179
+ - lib/unleash/strategies.rb
179
180
  - lib/unleash/strategy/application_hostname.rb
180
181
  - lib/unleash/strategy/base.rb
181
182
  - lib/unleash/strategy/default.rb
@@ -212,7 +213,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
212
213
  - !ruby/object:Gem::Version
213
214
  version: '0'
214
215
  requirements: []
215
- rubygems_version: 3.3.6
216
+ rubygems_version: 3.3.5
216
217
  signing_key:
217
218
  specification_version: 4
218
219
  summary: Unleash feature toggle client.