prefab-cloud-ruby 0.8.0 → 0.11.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: 171f3c104141000ec55b8c331f9f0439d56e991936c69a032f7c78394560ddea
4
- data.tar.gz: bb2fa93f2bc589c7e58bda95f7ab9519d780d80de212b62e1a29b1e187ab55a1
3
+ metadata.gz: 22d564bcc76424b3308623b4d2e27a4acaf4217c285bcca02542291d8cc15827
4
+ data.tar.gz: c097169d379d9f477a66d20fc378f693173de044baecad08059263fd239257e5
5
5
  SHA512:
6
- metadata.gz: c883ea3ba1f608c5fd88e0371302dca440968ad34fae8c279b735f180a73986498c9a87b95d2d51571daa2be723944568824bf8444425a8ff463c210bb9c7429
7
- data.tar.gz: 278cccca1c2e834e79971394310fdba30c03e806b31708094d90d62ed1623c7c5d2e19cdd204165437a75a8711c979f915f0b1ecd86220991fa22697ddad3a49
6
+ metadata.gz: cdf6ca3b1b28bf83caa629193b21e2d26e4d22d64ee4cd30b7a88fdce623adc7f3feb3605df7107cdca00e7823709f9eeac2a68176db1d10b6616196d2bcb580
7
+ data.tar.gz: c61b171a2be217c016fad1cc0a1ce9faed159c6f894065db4202ec200ec1f8ac22caabb880fae0f358fcced0930ce49d4e273f800a0fb72c39c6d6f19cc59d07
data/Gemfile CHANGED
@@ -2,6 +2,7 @@ source "https://rubygems.org"
2
2
 
3
3
  gem 'concurrent-ruby', '~> 1.0', '>= 1.0.5'
4
4
  gem 'faraday'
5
+ gem 'ld-eventsource'
5
6
  gem 'grpc', :platforms => :ruby
6
7
  gem 'google-protobuf', :platforms => :ruby
7
8
  gem 'googleapis-common-protos-types', :platforms => :ruby
data/Gemfile.lock CHANGED
@@ -14,12 +14,18 @@ GEM
14
14
  descendants_tracker (0.0.4)
15
15
  thread_safe (~> 0.3, >= 0.3.1)
16
16
  docile (1.3.5)
17
+ domain_name (0.5.20190701)
18
+ unf (>= 0.0.5, < 1.0.0)
17
19
  eventmachine (1.2.7)
18
20
  faraday (1.3.0)
19
21
  faraday-net_http (~> 1.0)
20
22
  multipart-post (>= 1.2, < 3)
21
23
  ruby2_keywords
22
24
  faraday-net_http (1.0.1)
25
+ ffi (1.15.5)
26
+ ffi-compiler (1.0.1)
27
+ ffi (>= 1.0.0)
28
+ rake
23
29
  git (1.8.1)
24
30
  rchardet (~> 1.8)
25
31
  github_api (0.19.0)
@@ -37,6 +43,14 @@ GEM
37
43
  grpc-tools (1.43.1)
38
44
  hashie (3.6.0)
39
45
  highline (2.0.3)
46
+ http (5.0.1)
47
+ addressable (~> 2.3)
48
+ http-cookie (~> 1.0)
49
+ http-form_data (~> 2.2)
50
+ llhttp-ffi (~> 0.3.0)
51
+ http-cookie (1.0.4)
52
+ domain_name (~> 0.5)
53
+ http-form_data (2.3.0)
40
54
  i18n (1.8.9)
41
55
  concurrent-ruby (~> 1.0)
42
56
  json (1.8.6)
@@ -55,6 +69,12 @@ GEM
55
69
  jwt (2.2.2)
56
70
  kamelcase (0.0.2)
57
71
  semver2 (~> 3)
72
+ ld-eventsource (2.2.0)
73
+ concurrent-ruby (~> 1.0)
74
+ http (>= 4.4.1, < 6.0.0)
75
+ llhttp-ffi (0.3.1)
76
+ ffi-compiler (~> 1.0)
77
+ rake (~> 13.0)
58
78
  mini_portile2 (2.7.1)
59
79
  minitest (5.14.4)
60
80
  multi_json (1.15.0)
@@ -96,6 +116,9 @@ GEM
96
116
  thread_safe (0.3.6)
97
117
  tzinfo (1.2.9)
98
118
  thread_safe (~> 0.1)
119
+ unf (0.1.4)
120
+ unf_ext
121
+ unf_ext (0.0.8)
99
122
 
100
123
  PLATFORMS
101
124
  ruby
@@ -109,6 +132,7 @@ DEPENDENCIES
109
132
  grpc
110
133
  grpc-tools
111
134
  juwelier (~> 2.4.9)
135
+ ld-eventsource
112
136
  rdoc (~> 3.12)
113
137
  shoulda
114
138
  simplecov
data/README.md CHANGED
@@ -56,6 +56,9 @@ end
56
56
  ## Release
57
57
 
58
58
  ```shell
59
+ update VERSION
60
+ bundle exec rake gemspec:generate
61
+ git commit & push
59
62
  REMOTE_BRANCH=main LOCAL_BRANCH=main bundle exec rake release
60
63
  ```
61
64
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.8.0
1
+ 0.11.0
data/lib/prefab/client.rb CHANGED
@@ -8,7 +8,7 @@ module Prefab
8
8
  }
9
9
 
10
10
 
11
- attr_reader :project_id, :shared_cache, :stats, :namespace, :interceptor, :api_key, :environment
11
+ attr_reader :project_id, :shared_cache, :stats, :namespace, :interceptor, :api_key, :prefab_api_url
12
12
 
13
13
  def initialize(api_key: ENV['PREFAB_API_KEY'],
14
14
  logdev: nil,
@@ -26,22 +26,22 @@ module Prefab
26
26
  @stats = (stats || NoopStats.new)
27
27
  @shared_cache = (shared_cache || NoopCache.new)
28
28
  @api_key = api_key
29
- @project_id = api_key.split("-")[0].to_i
30
- @environment = api_key.split("-")[1]
29
+ @project_id = api_key.split("-")[0].to_i # unvalidated, but that's ok. APIs only listen to the actual passwd
31
30
  @namespace = namespace
32
31
  @interceptor = Prefab::AuthInterceptor.new(api_key)
33
32
  @stubs = {}
34
-
33
+ @prefab_api_url = ENV["PREFAB_API_URL"] || 'https://api.prefab.cloud'
34
+ @prefab_grpc_url = ENV["PREFAB_GRPC_URL"] || 'grpc.prefab.cloud:443'
35
+ log_internal Logger::INFO, "Prefab Connecting to: #{@prefab_api_url} and #{@prefab_grpc_url} Secure: #{http_secure?}"
35
36
  at_exit do
36
37
  channel.destroy
37
38
  end
38
39
  end
39
40
 
40
41
  def channel
41
- credentials = ENV["PREFAB_CLOUD_HTTP"] == "true" ? :this_channel_is_insecure : creds
42
- url = ENV["PREFAB_API_URL"] || 'grpc.prefab.cloud:443'
43
- log_internal Logger::DEBUG, "GRPC Channel #{url} #{credentials}"
44
- @_channel ||= GRPC::Core::Channel.new(url, nil, credentials)
42
+ credentials = http_secure? ? creds : :this_channel_is_insecure
43
+ log_internal Logger::DEBUG, "GRPC Channel #{@prefab_grpc_url} #{credentials}"
44
+ @_channel ||= GRPC::Core::Channel.new(@prefab_grpc_url, nil, credentials)
45
45
  end
46
46
 
47
47
  def config_client(timeout: 5.0)
@@ -101,6 +101,10 @@ module Prefab
101
101
 
102
102
  private
103
103
 
104
+ def http_secure?
105
+ ENV["PREFAB_CLOUD_HTTP"] != "true"
106
+ end
107
+
104
108
  def stub_for(service, timeout)
105
109
  @stubs["#{service}_#{timeout}"] ||= service::Stub.new(nil,
106
110
  nil,
@@ -29,7 +29,8 @@ module Prefab
29
29
 
30
30
  def start_streaming
31
31
  @streaming = true
32
- start_api_connection_thread(@config_loader.highwater_mark)
32
+ # start_grpc_streaming_connection_thread(@config_loader.highwater_mark)
33
+ start_sse_streaming_connection_thread(@config_loader.highwater_mark)
33
34
  end
34
35
 
35
36
  def get(key)
@@ -62,8 +63,14 @@ module Prefab
62
63
  end
63
64
 
64
65
  def self.value_to_delta(key, config_value, namespace = nil)
65
- Prefab::ConfigDelta.new(key: [namespace, key].compact.join(":"),
66
- value: config_value)
66
+ Prefab::Config.new(key: [namespace, key].compact.join(":"),
67
+ rows: [Prefab::ConfigRow.new(value: config_value)])
68
+ end
69
+
70
+ def get_config_obj(key)
71
+ @initialization_lock.with_read_lock do
72
+ @config_resolver.get_config(key)
73
+ end
67
74
  end
68
75
 
69
76
  private
@@ -92,14 +99,11 @@ module Prefab
92
99
  def load_checkpoint_from_config
93
100
  @base_client.log_internal Logger::DEBUG, "Load Checkpoint From Config"
94
101
 
95
- config_req = Prefab::ConfigServicePointer.new(project_id: @base_client.project_id,
96
- start_at_id: @config_loader.highwater_mark)
102
+ config_req = Prefab::ConfigServicePointer.new(start_at_id: @config_loader.highwater_mark)
103
+
97
104
  resp = stub.get_all_config(config_req)
98
105
  @base_client.log_internal Logger::DEBUG, "Got Response #{resp}"
99
- load_deltas(resp, :api)
100
- resp.deltas.each do |delta|
101
- @config_loader.set(delta)
102
- end
106
+ load_configs(resp, :api)
103
107
  @config_resolver.update
104
108
  finish_init!(:api)
105
109
  true
@@ -112,16 +116,20 @@ module Prefab
112
116
  url = "#{@s3_cloud_front}/#{@base_client.api_key.gsub("|", "/")}"
113
117
  resp = Faraday.get url
114
118
  if resp.status == 200
115
- deltas = Prefab::ConfigDeltas.decode(resp.body)
116
- load_deltas(deltas, :s3)
119
+ configs = Prefab::Configs.decode(resp.body)
120
+ load_configs(configs, :s3)
117
121
  else
118
122
  @base_client.log_internal Logger::INFO, "No S3 checkpoint. Response #{resp.status} Plan may not support this."
119
123
  end
120
124
  end
121
125
 
122
- def load_deltas(deltas, source)
123
- deltas.deltas.each do |delta|
124
- @config_loader.set(delta)
126
+ def load_configs(configs, source)
127
+ project_env_id = configs.config_service_pointer.project_env_id
128
+ @config_resolver.project_env_id = project_env_id
129
+
130
+ @base_client.log_internal Logger::INFO, "Prefab Initializing in project: #{@base_client.project_id} environment: #{project_env_id} and namespace: '#{@namespace}'"
131
+ configs.configs.each do |config|
132
+ @config_loader.set(config)
125
133
  end
126
134
  @base_client.log_internal Logger::INFO, "Found checkpoint with highwater id #{@config_loader.highwater_mark} from #{source}"
127
135
  @base_client.stats.increment("prefab.config.checkpoint.load")
@@ -156,11 +164,35 @@ module Prefab
156
164
  end
157
165
  end
158
166
 
167
+
168
+ def start_sse_streaming_connection_thread(start_at_id)
169
+ auth = "#{@base_client.project_id}:#{@base_client.api_key}"
170
+
171
+ auth_string = Base64.strict_encode64(auth)
172
+ headers = {
173
+ "x-prefab-start-at-id": start_at_id,
174
+ "Authorization": "Basic #{auth_string}",
175
+ }
176
+ url = "#{@base_client.prefab_api_url}/api/v1/sse/config"
177
+ @base_client.log_internal Logger::INFO, "SSE Streaming Connect to #{url}"
178
+ SSE::Client.new(url, headers: headers) do |client|
179
+ client.on_event do |event|
180
+ configs = Prefab::Configs.decode(Base64.decode64(event.data))
181
+ @base_client.log_internal Logger::INFO, "SSE received configs."
182
+ @base_client.log_internal Logger::DEBUG, "SSE received configs: #{configs}"
183
+ configs.configs.each do |config|
184
+ @config_loader.set(config)
185
+ end
186
+ @config_resolver.update
187
+ finish_init!(:streaming)
188
+ end
189
+ end
190
+ end
191
+
159
192
  # Setup a streaming connection to the API
160
193
  # Save new config values into the loader
161
- def start_api_connection_thread(start_at_id)
162
- config_req = Prefab::ConfigServicePointer.new(project_id: @base_client.project_id,
163
- start_at_id: start_at_id)
194
+ def start_grpc_streaming_connection_thread(start_at_id)
195
+ config_req = Prefab::ConfigServicePointer.new(start_at_id: start_at_id)
164
196
  @base_client.log_internal Logger::DEBUG, "start api connection thread #{start_at_id}"
165
197
  @base_client.stats.increment("prefab.config.api.start")
166
198
 
@@ -174,8 +206,8 @@ module Prefab
174
206
  begin
175
207
  resp = stub.get_config(config_req)
176
208
  resp.each do |r|
177
- r.deltas.each do |delta|
178
- @config_loader.set(delta)
209
+ r.configs.each do |config|
210
+ @config_loader.set(config)
179
211
  end
180
212
  @config_resolver.update
181
213
  finish_init!(:streaming)
@@ -20,18 +20,18 @@ module Prefab
20
20
  rtn
21
21
  end
22
22
 
23
- def set(delta)
23
+ def set(config)
24
24
  # don't overwrite newer values
25
- if @api_config[delta.key] && @api_config[delta.key].id > delta.id
25
+ if @api_config[config.key] && @api_config[config.key].id > config.id
26
26
  return
27
27
  end
28
28
 
29
- if delta.default.nil?
30
- @api_config.delete(delta.key)
29
+ if config.rows.empty?
30
+ @api_config.delete(config.key)
31
31
  else
32
- @api_config[delta.key] = delta
32
+ @api_config[config.key] = config
33
33
  end
34
- @highwater_mark = [delta.id, @highwater_mark].max
34
+ @highwater_mark = [config.id, @highwater_mark].max
35
35
  end
36
36
 
37
37
  def rm(key)
@@ -39,11 +39,11 @@ module Prefab
39
39
  end
40
40
 
41
41
  def get_api_deltas
42
- deltas = Prefab::ConfigDeltas.new
42
+ configs = Prefab::Configs.new
43
43
  @api_config.each_value do |config_value|
44
- deltas.deltas << config_value
44
+ configs.configs << config_value
45
45
  end
46
- deltas
46
+ configs
47
47
  end
48
48
 
49
49
  private
@@ -63,7 +63,9 @@ module Prefab
63
63
  Dir.glob(glob).each do |file|
64
64
  yaml = load(file)
65
65
  yaml.each do |k, v|
66
- rtn[k] = Prefab::ConfigDelta.new(key: k, default: Prefab::ConfigValue.new(value_from(v)))
66
+ rtn[k] = Prefab::Config.new(key: k, rows: [
67
+ Prefab::ConfigRow.new(value: Prefab::ConfigValue.new(value_from(v)))
68
+ ])
67
69
  end
68
70
  end
69
71
  rtn
@@ -3,12 +3,14 @@ module Prefab
3
3
  include Prefab::ConfigHelper
4
4
  NAMESPACE_DELIMITER = ".".freeze
5
5
 
6
+ attr_accessor :project_env_id # this will be set by the config_client when it gets an API response
7
+
6
8
  def initialize(base_client, config_loader)
7
9
  @lock = Concurrent::ReadWriteLock.new
8
10
  @local_store = {}
9
- @environment = base_client.environment
10
11
  @namespace = base_client.namespace
11
12
  @config_loader = config_loader
13
+ @project_env_id = 0
12
14
  make_local
13
15
  end
14
16
 
@@ -16,20 +18,27 @@ module Prefab
16
18
  str = ""
17
19
  @lock.with_read_lock do
18
20
  @local_store.each do |k, v|
19
- value = v[:value]
20
- str << "|#{k}| from #{v[:match]} |#{value_of(value)}|#{value_of(value).class}\n"
21
+ if v.nil?
22
+ str<< "|#{k}| tombstone\n"
23
+ else
24
+ value = v[:value]
25
+ str << "|#{k}| from #{v[:match]} |#{value_of(value)}|#{value_of(value).class}\n"
26
+ end
21
27
  end
22
28
  end
23
29
  str
24
30
  end
25
31
 
26
32
  def get(property)
27
- config = @lock.with_read_lock do
28
- @local_store[property]
29
- end
33
+ config = _get(property)
30
34
  config ? value_of(config[:value]) : nil
31
35
  end
32
36
 
37
+ def get_config(property)
38
+ config = _get(property)
39
+ config ? config[:config] : nil
40
+ end
41
+
33
42
  def update
34
43
  make_local
35
44
  end
@@ -55,46 +64,35 @@ module Prefab
55
64
 
56
65
  def make_local
57
66
  store = {}
58
- @config_loader.calc_config.each do |key, delta|
59
- # start with the top level default
60
- to_store = { match: "default", value: delta.default }
61
- if delta.envs.any?
62
- env_values = delta.envs.select { |e| e.environment == @environment }
63
-
64
- # do we have and env_values that match our env?
65
- if env_values.any?
66
- env_value = env_values.first
67
-
68
- # override the top level default with env default
69
- to_store = { match: "env_default", env: env_value.environment, value: env_value.default }
70
-
71
- if env_value.namespace_values.any?
72
- # check all namespace_values for match
73
- env_value.namespace_values.each do |namespace_value|
74
- (starts_with, count) = starts_with_ns?(namespace_value.namespace, @namespace)
75
- if starts_with
76
- # is this match the best match?
77
- if count > (to_store[:match_depth_count] || 0)
78
- to_store = { match: namespace_value.namespace, count: count, value: namespace_value.config_value }
79
- end
80
- end
67
+ @config_loader.calc_config.each do |key, config|
68
+ sortable = config.rows.map do |row|
69
+ if row.project_env_id != 0
70
+ if row.project_env_id == @project_env_id
71
+ if !row.namespace.empty?
72
+ (starts_with, count) = starts_with_ns?(row.namespace, @namespace)
73
+ # rubocop:disable BlockNesting
74
+ { sortable: 2 + count, match: row.namespace, value: row.value, config: config} if starts_with
75
+ else
76
+ { sortable: 1, match: row.project_env_id, value: row.value, config: config}
81
77
  end
82
78
  end
79
+ else
80
+ { sortable: 0, match: "default", value: row.value, config: config}
83
81
  end
84
- end
85
-
86
- # feature flags are a funny case
87
- # we only define the variants in the default in order to be DRY
88
- # but we want to access them in environments, clone them over
89
- if to_store[:value].type == :feature_flag
90
- to_store[:value].feature_flag.variants = delta.default.feature_flag.variants
91
- end
92
-
82
+ end.compact
83
+ to_store = sortable.sort_by { |h| h[:sortable] }.last
93
84
  store[key] = to_store
94
85
  end
86
+
95
87
  @lock.with_write_lock do
96
88
  @local_store = store
97
89
  end
98
90
  end
91
+
92
+ def _get(property)
93
+ @lock.with_read_lock do
94
+ @local_store[property]
95
+ end
96
+ end
99
97
  end
100
98
  end
@@ -2,7 +2,6 @@ module Prefab
2
2
  class FeatureFlagClient
3
3
  include Prefab::ConfigHelper
4
4
  MAX_32_FLOAT = 4294967294.0
5
- DISTRIBUTION_SPACE = 1000
6
5
 
7
6
  def initialize(base_client)
8
7
  @base_client = base_client
@@ -22,13 +21,16 @@ module Prefab
22
21
  return is_on?(get(feature_name, lookup_key, attributes))
23
22
  end
24
23
 
25
- def get(feature_name, lookup_key, attributes)
24
+ def get(feature_name, lookup_key=nil, attributes={})
26
25
  feature_obj = @base_client.config_client.get(feature_name)
27
- evaluate(feature_name, lookup_key, attributes, feature_obj)
26
+ config_obj = @base_client.config_client.get_config_obj(feature_name)
27
+ return nil if feature_obj.nil? || config_obj.nil?
28
+ variants = config_obj.variants
29
+ evaluate(feature_name, lookup_key, attributes, feature_obj, variants)
28
30
  end
29
31
 
30
- def evaluate(feature_name, lookup_key, attributes, feature_obj)
31
- value_of(get_variant(feature_name, lookup_key, attributes, feature_obj))
32
+ def evaluate(feature_name, lookup_key, attributes, feature_obj, variants)
33
+ value_of(get_variant(feature_name, lookup_key, attributes, feature_obj, variants))
32
34
  end
33
35
 
34
36
  private
@@ -38,50 +40,54 @@ module Prefab
38
40
  return false
39
41
  end
40
42
  variant.bool
43
+ rescue
44
+ @base_client.log.info("is_on? methods only work for boolean feature flags variants. This feature flags variant is '#{variant}'. Returning false")
45
+ false
41
46
  end
42
47
 
43
- def get_variant(feature_name, lookup_key, attributes, feature_obj)
48
+ def get_variant(feature_name, lookup_key, attributes, feature_obj, variants)
44
49
  if !feature_obj.active
45
- return get_variant_obj(feature_obj, feature_obj.inactive_variant_idx)
50
+ return get_variant_obj(variants, feature_obj.inactive_variant_idx)
46
51
  end
47
52
 
48
- variant_distribution = feature_obj.default
49
-
50
53
  # if user_targets.match
51
54
  feature_obj.user_targets.each do |target|
52
55
  if (target.identifiers.include? lookup_key)
53
- return get_variant_obj(feature_obj, target.variant_idx)
56
+ return get_variant_obj(variants, target.variant_idx)
54
57
  end
55
58
  end
56
59
 
60
+ #default to inactive
61
+ variant_weights = [Prefab::VariantWeight.new(variant_idx: feature_obj.inactive_variant_idx, weight: 1)]
62
+
57
63
  # if rules.match
58
64
  feature_obj.rules.each do |rule|
59
65
  if criteria_match?(rule, lookup_key, attributes)
60
- variant_distribution = rule.distribution
66
+ variant_weights = rule.variant_weights
67
+ break
61
68
  end
62
69
  end
63
70
 
64
- if variant_distribution.type == :variant_idx
65
- variant_idx = variant_distribution.variant_idx
66
- else
67
- percent_through_distribution = rand()
68
- if lookup_key
69
- percent_through_distribution = get_user_pct(feature_name, lookup_key)
70
- end
71
- distribution_bucket = DISTRIBUTION_SPACE * percent_through_distribution
72
71
 
73
- variant_idx = get_variant_idx_from_weights(variant_distribution.variant_weights.weights, distribution_bucket, feature_name)
72
+ percent_through_distribution = rand()
73
+ if lookup_key
74
+ percent_through_distribution = get_user_pct(feature_name, lookup_key)
74
75
  end
75
76
 
76
- return get_variant_obj(feature_obj, variant_idx)
77
+ variant_idx = get_variant_idx_from_weights(variant_weights, percent_through_distribution, feature_name)
78
+
79
+ return get_variant_obj(variants, variant_idx)
77
80
  end
78
81
 
79
- def get_variant_obj(feature_obj, idx)
80
- return feature_obj.variants[idx] if feature_obj.variants.length >= idx
82
+ def get_variant_obj(variants, idx)
83
+ # our array is 0 based, but the idx are 1 based so the protos are clearly set
84
+ return variants[idx - 1] if variants.length >= idx
81
85
  nil
82
86
  end
83
87
 
84
- def get_variant_idx_from_weights(variant_weights, bucket, feature_name)
88
+ def get_variant_idx_from_weights(variant_weights, percent_through_distribution, feature_name)
89
+ distrubution_space = variant_weights.inject(0) { |sum, v| sum + v.weight }
90
+ bucket = distrubution_space * percent_through_distribution
85
91
  sum = 0
86
92
  variant_weights.each do |variant_weight|
87
93
  if bucket < sum + variant_weight.weight
@@ -92,7 +98,7 @@ module Prefab
92
98
  end
93
99
  # variants didn't add up to 100%
94
100
  @base_client.log.info("Variants of #{feature_name} did not add to 100%")
95
- return variant_weights.last.variant
101
+ return variant_weights.last.variant_idx
96
102
  end
97
103
 
98
104
  def get_user_pct(feature, lookup_key)
@@ -102,14 +108,21 @@ module Prefab
102
108
  end
103
109
 
104
110
  def criteria_match?(rule, lookup_key, attributes)
105
- if rule.criteria.operator == :IN
111
+
112
+ if rule.criteria.operator == :ALWAYS_TRUE
113
+ return true
114
+ elsif rule.criteria.operator == :LOOKUP_KEY_IN
106
115
  return rule.criteria.values.include?(lookup_key)
107
- elsif rule.criteria.operator == :NOT_IN
116
+ elsif rule.criteria.operator == :LOOKUP_KEY_NOT_IN
108
117
  return !rule.criteria.values.include?(lookup_key)
109
118
  elsif rule.criteria.operator == :IN_SEG
110
119
  return segment_matches(rule.criteria.values, lookup_key, attributes).any?
111
120
  elsif rule.criteria.operator == :NOT_IN_SEG
112
121
  return segment_matches(rule.criteria.values, lookup_key, attributes).none?
122
+ elsif rule.criteria.operator == :PROP_IS_ONE_OF
123
+ return rule.criteria.values.include?(attributes[rule.criteria.property]) || rule.criteria.values.include?(attributes[rule.criteria.property.to_sym])
124
+ elsif rule.criteria.operator == :PROP_IS_NOT_ONE_OF
125
+ return !(rule.criteria.values.include?(attributes[rule.criteria.property]) || rule.criteria.values.include?(attributes[rule.criteria.property.to_sym]))
113
126
  end
114
127
  @base_client.log.info("Unknown Operator")
115
128
  false
@@ -2,6 +2,8 @@ require "concurrent/atomics"
2
2
  require 'concurrent'
3
3
  require 'faraday'
4
4
  require 'openssl'
5
+ require 'openssl'
6
+ require 'ld-eventsource'
5
7
  require 'prefab_pb'
6
8
  require 'prefab_services_pb'
7
9
  require 'prefab/config_helper'