prefab-cloud-ruby 0.8.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
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'