prefab-cloud-ruby 0.13.3 → 0.16.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: fc875095444727947594dfa96e7a4e59fbab20a14ec486fdd4b804b936aad374
4
- data.tar.gz: 7af3f2155fdb85063c674b4f4bf3365b089e507789adf9eaa0ca5b4e206b9e19
3
+ metadata.gz: 82381e4b656d675f4b98ee11601cde1920637e52eb5b039814247500207b9fa5
4
+ data.tar.gz: 4ff8914774523e745d512d506cc42f20e71a657fd8191d82e7e6ba1878bf04c9
5
5
  SHA512:
6
- metadata.gz: aff23956f0c34934a5702bd51517ac14111a7888ba99948537fb76fbcdf162abf6a032ae8caba5c902e06894b0f3bf5ffcf5a95efce9cf00210b4f333059e233
7
- data.tar.gz: 21717c0f85eecd1972baf9fc1d8fa8b57d225da60eb6cba006b3ac4f288f4fab77c18f5d6a34e49ca8beb8835e22a520b3bc92b058658309d41db5eba9a05c86
6
+ metadata.gz: aac3fc04c779900ca92a6a859430a2fe7776bee90afba632456ff45e3bacb428dda61314d3cbf5a5b04ce61672e4591afb7ae0248b77352b381f52dcbbfb6f24
7
+ data.tar.gz: 5e2008ec802e2f32154bffb34804673187feb618a60edf2aba19666851787e14111e3d6e0fbeeebe8e3192cac56a32f3010b4d46ef507c3d16359886973ee90c
data/Gemfile CHANGED
@@ -8,6 +8,7 @@ gem 'google-protobuf', :platforms => :ruby
8
8
  gem 'googleapis-common-protos-types', :platforms => :ruby
9
9
 
10
10
  group :development do
11
+ gem 'benchmark-ips'
11
12
  gem 'grpc-tools', :platforms => :ruby
12
13
  gem "rdoc", "~> 3.12"
13
14
  gem "bundler"
data/Gemfile.lock CHANGED
@@ -3,6 +3,7 @@ GEM
3
3
  specs:
4
4
  addressable (2.8.0)
5
5
  public_suffix (>= 2.0.2, < 5.0)
6
+ benchmark-ips (2.10.0)
6
7
  builder (3.2.4)
7
8
  concurrent-ruby (1.1.10)
8
9
  daemons (1.4.1)
@@ -109,6 +110,7 @@ PLATFORMS
109
110
  ruby
110
111
 
111
112
  DEPENDENCIES
113
+ benchmark-ips
112
114
  bundler
113
115
  concurrent-ruby (~> 1.0, >= 1.0.5)
114
116
  faraday
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.13.3
1
+ 0.16.0
data/lib/prefab/client.rb CHANGED
@@ -5,7 +5,7 @@ module Prefab
5
5
  BASE_SLEEP_SEC = 0.5
6
6
  NO_DEFAULT_PROVIDED = :no_default_provided
7
7
 
8
- attr_reader :project_id, :shared_cache, :stats, :namespace, :interceptor, :api_key, :prefab_api_url, :options
8
+ attr_reader :shared_cache, :stats, :namespace, :interceptor, :api_key, :prefab_api_url, :options
9
9
 
10
10
  def initialize(options = Prefab::Options.new)
11
11
  @options = options
@@ -15,12 +15,10 @@ module Prefab
15
15
  @stubs = {}
16
16
 
17
17
  if @options.local_only?
18
- @project_id = 0
19
18
  log_internal Logger::INFO, "Prefab Running in Local Mode"
20
19
  else
21
20
  @api_key = @options.api_key
22
- raise Prefab::Errors::InvalidApiKeyError.new(@api_key) if @api_key.nil? || @api_key.empty? || api_key.count("-") != 3
23
- @project_id = @api_key.split("-")[0].to_i # unvalidated, but that's ok. APIs only listen to the actual passwd
21
+ raise Prefab::Errors::InvalidApiKeyError.new(@api_key) if @api_key.nil? || @api_key.empty? || api_key.count("-") < 1
24
22
  @interceptor = Prefab::AuthInterceptor.new(@api_key)
25
23
  @prefab_api_url = @options.prefab_api_url
26
24
  @prefab_grpc_url = @options.prefab_grpc_url
@@ -82,10 +80,6 @@ module Prefab
82
80
  end
83
81
  end
84
82
 
85
- def cache_key(post_fix)
86
- "prefab:#{project_id}:#{post_fix}"
87
- end
88
-
89
83
  def reset!
90
84
  @stubs.clear
91
85
  @_channel = nil
@@ -5,8 +5,8 @@ module Prefab
5
5
 
6
6
  RECONNECT_WAIT = 5
7
7
  DEFAULT_CHECKPOINT_FREQ_SEC = 60
8
- DEFAULT_S3CF_BUCKET = 'http://d2j4ed6ti5snnd.cloudfront.net'
9
8
  SSE_READ_TIMEOUT = 300
9
+ AUTH_USER = "authuser"
10
10
 
11
11
  def initialize(base_client, timeout)
12
12
  @base_client = base_client
@@ -29,8 +29,6 @@ module Prefab
29
29
 
30
30
  @cancellable_interceptor = Prefab::CancellableInterceptor.new(@base_client)
31
31
 
32
- @s3_cloud_front = ENV["PREFAB_S3CF_BUCKET"] || DEFAULT_S3CF_BUCKET
33
-
34
32
  if @options.local_only?
35
33
  finish_init!(:local_only)
36
34
  else
@@ -119,7 +117,6 @@ module Prefab
119
117
  interceptors: [@base_client.interceptor, @cancellable_interceptor])
120
118
  end
121
119
 
122
- # try API first, if not, fallback to s3
123
120
  def load_checkpoint
124
121
  success = load_checkpoint_api_cdn
125
122
 
@@ -134,12 +131,6 @@ module Prefab
134
131
  if success
135
132
  return
136
133
  else
137
- @base_client.log_internal Logger::INFO, "LoadCheckpoint: Fallback to S3"
138
- end
139
-
140
- success = load_checkpoint_from_s3
141
-
142
- if !success
143
134
  @base_client.log_internal Logger::WARN, "No success loading checkpoints"
144
135
  end
145
136
  end
@@ -159,24 +150,19 @@ module Prefab
159
150
 
160
151
  def load_checkpoint_api_cdn
161
152
  key_hash = Murmur3.murmur3_32(@base_client.api_key)
162
- url = "#{@options.url_for_api_cdn}/api/v1/configs/#{@base_client.project_id}/#{key_hash}/0"
153
+ url = "#{@options.url_for_api_cdn}/api/v1/configs/0/#{key_hash}/0"
163
154
  conn = if Faraday::VERSION[0].to_i >= 2
164
155
  Faraday.new(url) do |conn|
165
- conn.request :authorization, :basic, @base_client.project_id, @base_client.api_key
156
+ conn.request :authorization, :basic, AUTH_USER, @base_client.api_key
166
157
  end
167
158
  else
168
159
  Faraday.new(url) do |conn|
169
- conn.request :basic_auth, @base_client.project_id, @base_client.api_key
160
+ conn.request :basic_auth, AUTH_USER, @base_client.api_key
170
161
  end
171
162
  end
172
163
  load_url(conn, :remote_cdn_api)
173
164
  end
174
165
 
175
- def load_checkpoint_from_s3
176
- url = "#{@s3_cloud_front}/#{@base_client.api_key.gsub("|", "/")}"
177
- load_url(Faraday.new(url), :remote_s3)
178
- end
179
-
180
166
  def load_url(conn, source)
181
167
  resp = conn.get('')
182
168
  if resp.status == 200
@@ -193,6 +179,7 @@ module Prefab
193
179
  end
194
180
 
195
181
  def load_configs(configs, source)
182
+ project_id = configs.config_service_pointer.project_id
196
183
  project_env_id = configs.config_service_pointer.project_env_id
197
184
  @config_resolver.project_env_id = project_env_id
198
185
  starting_highwater_mark = @config_loader.highwater_mark
@@ -201,7 +188,7 @@ module Prefab
201
188
  @config_loader.set(config, source)
202
189
  end
203
190
  if @config_loader.highwater_mark > starting_highwater_mark
204
- @base_client.log_internal Logger::INFO, "Found new checkpoint with highwater id #{@config_loader.highwater_mark} from #{source} in project #{@base_client.project_id} environment: #{project_env_id} and namespace: '#{@namespace}'"
191
+ @base_client.log_internal Logger::INFO, "Found new checkpoint with highwater id #{@config_loader.highwater_mark} from #{source} in project #{project_id} environment: #{project_env_id} and namespace: '#{@namespace}'"
205
192
  else
206
193
  @base_client.log_internal Logger::DEBUG, "Checkpoint with highwater id #{@config_loader.highwater_mark} from #{source}. No changes.", "prefab.config_client.load_configs"
207
194
  end
@@ -240,7 +227,7 @@ module Prefab
240
227
  end
241
228
 
242
229
  def start_sse_streaming_connection_thread(start_at_id)
243
- auth = "#{@base_client.project_id}:#{@base_client.api_key}"
230
+ auth = "#{AUTH_USER}:#{@base_client.api_key}"
244
231
  auth_string = Base64.strict_encode64(auth)
245
232
  headers = {
246
233
  "x-prefab-start-at-id": start_at_id,
@@ -56,57 +56,59 @@ module Prefab
56
56
 
57
57
  def load_classpath_config
58
58
  classpath_dir = @prefab_options.prefab_config_classpath_dir
59
- load_glob(File.join(classpath_dir, ".prefab*config.yaml"))
59
+ rtn = load_glob(File.join(classpath_dir, ".prefab.default.config.yaml"))
60
+ @prefab_options.prefab_envs.each do |env|
61
+ rtn = rtn.merge load_glob(File.join(classpath_dir, ".prefab.#{env}.config.yaml"))
62
+ end
63
+ rtn
60
64
  end
61
65
 
62
66
  def load_local_overrides
63
67
  override_dir = @prefab_options.prefab_config_override_dir
64
- load_glob(File.join(override_dir, ".prefab*config.yaml"))
68
+ rtn = load_glob(File.join(override_dir, ".prefab.overrides.config.yaml"))
69
+ @prefab_options.prefab_envs.each do |env|
70
+ rtn = rtn.merge load_glob(File.join(override_dir, ".prefab.#{env}.config.yaml"))
71
+ end
72
+ rtn
65
73
  end
66
74
 
67
75
  def load_glob(glob)
68
76
  rtn = {}
69
77
  Dir.glob(glob).each do |file|
78
+ @base_client.log_internal Logger::INFO, "Load #{file}"
70
79
  yaml = load(file)
71
80
  yaml.each do |k, v|
72
- if v.class == Hash
73
- v.each do |env_k, env_v|
74
- if k == @prefab_options.defaults_env
75
- if env_v.class == Hash && env_v['feature_flag']
76
- rtn[env_k] = feature_flag_config(file, k, env_k, env_v)
77
- else
78
- rtn[env_k] = {
79
- source: file,
80
- match: k,
81
- config: Prefab::Config.new(
82
- key: env_k,
83
- rows: [
84
- Prefab::ConfigRow.new(value: Prefab::ConfigValue.new(value_from(env_v)))
85
- ]
86
- )
87
- }
88
- end
89
- else
90
- next
91
- end
92
- end
93
- else
94
- rtn[k] = {
95
- source: file,
96
- match: "default",
97
- config: Prefab::Config.new(
98
- key: k,
99
- rows: [
100
- Prefab::ConfigRow.new(value: Prefab::ConfigValue.new(value_from(v)))
101
- ]
102
- )
103
- }
104
- end
81
+ load_kv(k, v, rtn, file)
105
82
  end
106
83
  end
107
84
  rtn
108
85
  end
109
86
 
87
+ def load_kv(k, v, rtn, file)
88
+ if v.class == Hash
89
+ if v['feature_flag']
90
+ rtn[k] = feature_flag_config(file, k, v)
91
+ else
92
+ v.each do |nest_k, nest_v|
93
+ nested_key = "#{k}.#{nest_k}"
94
+ nested_key = k if nest_k == "_"
95
+ load_kv(nested_key, nest_v, rtn, file)
96
+ end
97
+ end
98
+ else
99
+ rtn[k] = {
100
+ source: file,
101
+ match: "default",
102
+ config: Prefab::Config.new(
103
+ key: k,
104
+ rows: [
105
+ Prefab::ConfigRow.new(value: Prefab::ConfigValue.new(value_from(v)))
106
+ ]
107
+ )
108
+ }
109
+ end
110
+ end
111
+
110
112
  def load(filename)
111
113
  if File.exist? filename
112
114
  @base_client.log_internal Logger::INFO, "Load #{filename}"
@@ -130,11 +132,11 @@ module Prefab
130
132
  end
131
133
  end
132
134
 
133
- def feature_flag_config(file, k, env_k, env_v)
135
+ def feature_flag_config(file, key, value)
134
136
  criteria = Prefab::Criteria.new(operator: 'ALWAYS_TRUE')
135
137
 
136
- if env_v['criteria']
137
- criteria = Prefab::Criteria.new(criteria_values(env_v['criteria']))
138
+ if value['criteria']
139
+ criteria = Prefab::Criteria.new(criteria_values(value['criteria']))
138
140
  end
139
141
 
140
142
  row = Prefab::ConfigRow.new(
@@ -154,16 +156,16 @@ module Prefab
154
156
  )
155
157
  )
156
158
 
157
- unless env_v.has_key?('value')
158
- raise Prefab::Error, "Feature flag config `#{env_k}` #{file} must have a `value`"
159
+ unless value.has_key?('value')
160
+ raise Prefab::Error, "Feature flag config `#{key}` #{file} must have a `value`"
159
161
  end
160
162
 
161
163
  {
162
164
  source: file,
163
- match: k,
165
+ match: key,
164
166
  config: Prefab::Config.new(
165
- key: env_k,
166
- variants: [Prefab::FeatureFlagVariant.new(value_from(env_v['value']))],
167
+ key: key,
168
+ variants: [Prefab::FeatureFlagVariant.new(value_from(value['value']))],
167
169
  rows: [row]
168
170
  )
169
171
  }
@@ -116,38 +116,45 @@ module Prefab
116
116
  end
117
117
 
118
118
  def get_user_pct(feature, lookup_key)
119
- to_hash = "#{@base_client.project_id}#{feature}#{lookup_key}"
119
+ to_hash = "#{feature}#{lookup_key}"
120
120
  int_value = Murmur3.murmur3_32(to_hash)
121
121
  int_value / MAX_32_FLOAT
122
122
  end
123
123
 
124
- # def criteria_match?(rule, lookup_key, attributes)
125
- #
126
- # end
127
124
  def criteria_match?(criteria, lookup_key, attributes)
128
- if criteria.operator == :ALWAYS_TRUE
129
- return true
130
- elsif criteria.operator == :LOOKUP_KEY_IN
131
- return criteria.values.include?(lookup_key)
132
- elsif criteria.operator == :LOOKUP_KEY_NOT_IN
133
- return !criteria.values.include?(lookup_key)
134
- elsif criteria.operator == :IN_SEG
135
- return segment_matches(criteria.values, lookup_key, attributes).any?
136
- elsif criteria.operator == :NOT_IN_SEG
137
- return segment_matches(criteria.values, lookup_key, attributes).none?
138
- elsif criteria.operator == :PROP_IS_ONE_OF
139
- return criteria.values.include?(attributes[criteria.property]) || criteria.values.include?(attributes[criteria.property.to_sym])
140
- elsif criteria.operator == :PROP_IS_NOT_ONE_OF
141
- return !(criteria.values.include?(attributes[criteria.property]) || criteria.values.include?(attributes[criteria.property.to_sym]))
125
+ case criteria.operator
126
+ when :ALWAYS_TRUE
127
+ true
128
+ when :LOOKUP_KEY_IN
129
+ criteria.values.include?(lookup_key)
130
+ when :LOOKUP_KEY_NOT_IN
131
+ !criteria.values.include?(lookup_key)
132
+ when :IN_SEG
133
+ segment_matches?(criteria.values, lookup_key, attributes)
134
+ when :NOT_IN_SEG
135
+ !segment_matches?(criteria.values, lookup_key, attributes)
136
+ when :PROP_IS_ONE_OF
137
+ criteria.values.include?(attribute_value(attributes, criteria.property))
138
+ when :PROP_IS_NOT_ONE_OF
139
+ !criteria.values.include?(attribute_value(attributes, criteria.property))
140
+ when :PROP_ENDS_WITH_ONE_OF
141
+ criteria.values.any? { |value| attribute_value(attributes, criteria.property)&.end_with?(value) }
142
+ when :PROP_DOES_NOT_END_WITH_ONE_OF
143
+ criteria.values.none? { |value| attribute_value(attributes, criteria.property)&.end_with?(value) }
144
+ else
145
+ @base_client.log.info("Unknown Operator: #{criteria.operator}")
146
+ false
142
147
  end
143
- @base_client.log.info("Unknown Operator")
144
- false
145
148
  end
146
149
 
147
- # evaluate each segment key and return whether each one matches
150
+ def attribute_value(attributes, property)
151
+ attributes[property] || attributes[property.to_sym]
152
+ end
153
+
154
+ # evaluate each segment key and return whether any match
148
155
  # there should be an associated segment available as a standard config obj
149
- def segment_matches(segment_keys, lookup_key, attributes)
150
- segment_keys.map do |segment_key|
156
+ def segment_matches?(segment_keys, lookup_key, attributes)
157
+ segment_keys.any? do |segment_key|
151
158
  segment = @base_client.config_client.get(segment_key)
152
159
  if segment.nil?
153
160
  @base_client.log.info("Missing Segment")
@@ -160,9 +167,9 @@ module Prefab
160
167
 
161
168
  # does a given segment match?
162
169
  def segment_match?(segment, lookup_key, attributes)
163
- segment.criterion.map do |criteria|
170
+ segment.criterion.any? do |criteria|
164
171
  criteria_match?(criteria, lookup_key, attributes)
165
- end.any?
172
+ end
166
173
  end
167
174
  end
168
175
  end
@@ -19,7 +19,7 @@ module Prefab
19
19
  end
20
20
 
21
21
  def add_internal(severity, message = nil, progname = nil, loc, &block)
22
- path = get_path(loc.absolute_path, loc.base_label)
22
+ path = get_loc_path(loc)
23
23
  log_internal(message, path, progname, severity, &block)
24
24
  end
25
25
 
@@ -121,15 +121,19 @@ module Prefab
121
121
  val(closest_log_level_match)
122
122
  end
123
123
 
124
+ def get_loc_path(loc)
125
+ loc_path = loc.absolute_path || loc.to_s
126
+ get_path(loc_path, loc.base_label)
127
+ end
128
+
124
129
  # sanitize & clean the path of the caller so the key
125
130
  # looks like log_level.app.models.user
126
131
  def get_path(absolute_path, base_label)
127
132
  path = (absolute_path || UNKNOWN).dup
128
133
  path.slice! Dir.pwd
134
+ path.gsub!(/(.*)?(?=\/lib)/im, "") # replace everything before first lib
129
135
 
130
- path.gsub!(/.*?(?=\/lib\/)/im, "")
131
-
132
- path = path.gsub("/", SEP).gsub(".rb", "") + SEP + base_label
136
+ path = path.gsub("/", SEP).gsub(/.rb.*/, "") + SEP + base_label
133
137
  path.slice! ".lib"
134
138
  path.slice! SEP
135
139
  path
@@ -14,7 +14,7 @@ module Prefab
14
14
  attr_reader :on_init_failure
15
15
  attr_reader :prefab_config_override_dir
16
16
  attr_reader :prefab_config_classpath_dir
17
- attr_reader :defaults_env
17
+ attr_reader :prefab_envs
18
18
 
19
19
  DEFAULT_LOG_FORMATTER = proc { |severity, datetime, progname, msg|
20
20
  "#{severity.ljust(5)} #{datetime}: #{progname} #{msg}\n"
@@ -50,7 +50,7 @@ module Prefab
50
50
  prefab_datasources: ENV['PREFAB_DATASOURCES'] == "LOCAL_ONLY" ? DATASOURCES::LOCAL_ONLY : DATASOURCES::ALL,
51
51
  prefab_config_override_dir: Dir.home,
52
52
  prefab_config_classpath_dir: ".",
53
- defaults_env: ""
53
+ prefab_envs: ENV['PREFAB_ENVS'].nil? ? [] : ENV['PREFAB_ENVS'].split(",")
54
54
  )
55
55
  # debugger
56
56
  @api_key = api_key
@@ -67,7 +67,7 @@ module Prefab
67
67
  @prefab_datasources = prefab_datasources
68
68
  @prefab_config_classpath_dir = prefab_config_classpath_dir
69
69
  @prefab_config_override_dir = prefab_config_override_dir
70
- @defaults_env = defaults_env
70
+ @prefab_envs = Array(prefab_envs)
71
71
  end
72
72
 
73
73
  def local_only?
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: prefab-cloud-ruby 0.13.3 ruby lib
5
+ # stub: prefab-cloud-ruby 0.16.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "prefab-cloud-ruby".freeze
9
- s.version = "0.13.3"
9
+ s.version = "0.16.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["Jeff Dwyer".freeze]
14
- s.date = "2022-08-12"
14
+ s.date = "2022-09-01"
15
15
  s.description = "RateLimits & Config as a service".freeze
16
16
  s.email = "jdwyer@prefab.cloud".freeze
17
17
  s.extra_rdoc_files = [
@@ -54,7 +54,8 @@ Gem::Specification.new do |s|
54
54
  "lib/prefab_services_pb.rb",
55
55
  "prefab-cloud-ruby.gemspec",
56
56
  "run_test_harness_server.sh",
57
- "test/.prefab.test.config.yaml",
57
+ "test/.prefab.default.config.yaml",
58
+ "test/.prefab.unit_tests.config.yaml",
58
59
  "test/harness_server.rb",
59
60
  "test/test_client.rb",
60
61
  "test/test_config_client.rb",
@@ -80,6 +81,7 @@ Gem::Specification.new do |s|
80
81
  s.add_runtime_dependency(%q<grpc>.freeze, [">= 0"])
81
82
  s.add_runtime_dependency(%q<google-protobuf>.freeze, [">= 0"])
82
83
  s.add_runtime_dependency(%q<googleapis-common-protos-types>.freeze, [">= 0"])
84
+ s.add_development_dependency(%q<benchmark-ips>.freeze, [">= 0"])
83
85
  s.add_development_dependency(%q<grpc-tools>.freeze, [">= 0"])
84
86
  s.add_development_dependency(%q<rdoc>.freeze, ["~> 3.12"])
85
87
  s.add_development_dependency(%q<bundler>.freeze, [">= 0"])
@@ -93,6 +95,7 @@ Gem::Specification.new do |s|
93
95
  s.add_dependency(%q<grpc>.freeze, [">= 0"])
94
96
  s.add_dependency(%q<google-protobuf>.freeze, [">= 0"])
95
97
  s.add_dependency(%q<googleapis-common-protos-types>.freeze, [">= 0"])
98
+ s.add_dependency(%q<benchmark-ips>.freeze, [">= 0"])
96
99
  s.add_dependency(%q<grpc-tools>.freeze, [">= 0"])
97
100
  s.add_dependency(%q<rdoc>.freeze, ["~> 3.12"])
98
101
  s.add_dependency(%q<bundler>.freeze, [">= 0"])
@@ -0,0 +1,2 @@
1
+ sample: default sample value
2
+ sample_bool: true
@@ -0,0 +1,25 @@
1
+ sample_int: 123
2
+ sample_double: 12.12
3
+ sample_bool: true
4
+ false_value: false
5
+ zero_value: 0
6
+ sample_to_override: Foo
7
+ prefab.log_level: debug
8
+ sample: test sample value
9
+ enabled_flag: true
10
+ disabled_flag: false
11
+ flag_with_a_value: { "feature_flag": "true", value: "all-features" }
12
+ in_lookup_key: { "feature_flag": "true", value: true, criteria: { operator: LOOKUP_KEY_IN, values: [ "abc123", "xyz987" ] } }
13
+ just_my_domain: { "feature_flag": "true", value: "new-version", criteria: { operator: PROP_IS_ONE_OF, property: "domain", values: [ "prefab.cloud", "example.com" ] } }
14
+ nested:
15
+ values:
16
+ _: top level
17
+ string: nested value
18
+
19
+ logging:
20
+ app:
21
+ _: error
22
+ controller:
23
+ hello:
24
+ _: warn
25
+ index: info
data/test/test_client.rb CHANGED
@@ -89,7 +89,7 @@ class TestClient < Minitest::Test
89
89
  options = Prefab::Options.new(**{
90
90
  prefab_config_override_dir: "none",
91
91
  prefab_config_classpath_dir: "test",
92
- defaults_env: "unit_tests",
92
+ prefab_envs: ["unit_tests"],
93
93
  prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
94
94
  }.merge(overrides))
95
95
 
@@ -6,7 +6,7 @@ class TestConfigClient < Minitest::Test
6
6
  options = Prefab::Options.new(
7
7
  prefab_config_override_dir: "none",
8
8
  prefab_config_classpath_dir: "test",
9
- defaults_env: "unit_tests",
9
+ prefab_envs: "unit_tests",
10
10
  prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
11
11
  )
12
12
 
@@ -32,6 +32,21 @@ class TestConfigClient < Minitest::Test
32
32
  assert_match(/couldn't initialize in 0.01 second timeout/, err.message)
33
33
  end
34
34
 
35
+ def test_prefab_envs_is_forgiving
36
+ assert_equal ["my_env"], Prefab::Options.new(
37
+ prefab_envs: "my_env",
38
+ ).prefab_envs
39
+
40
+ assert_equal ["my_env", "a_second_env"], Prefab::Options.new(
41
+ prefab_envs: ["my_env", "a_second_env"],
42
+ ).prefab_envs
43
+ end
44
+
45
+ def test_prefab_envs_env_var
46
+ ENV["PREFAB_ENVS"] = "one,two"
47
+ assert_equal ["one", "two"], Prefab::Options.new().prefab_envs
48
+ end
49
+
35
50
  def test_invalid_api_key_error
36
51
  options = Prefab::Options.new(
37
52
  api_key: "",
@@ -6,7 +6,7 @@ class TestConfigLoader < Minitest::Test
6
6
  options = Prefab::Options.new(
7
7
  prefab_config_override_dir: "none",
8
8
  prefab_config_classpath_dir: "test",
9
- defaults_env: "unit_tests"
9
+ prefab_envs: "unit_tests"
10
10
  )
11
11
  @loader = Prefab::ConfigLoader.new(MockBaseClient.new(options))
12
12
  end
@@ -18,11 +18,19 @@ class TestConfigLoader < Minitest::Test
18
18
  should_be :double, 12.12, "sample_double"
19
19
  end
20
20
 
21
- def test_load_in_no_default_env
21
+ def test_nested
22
+ should_be :string, "nested value", "nested.values.string"
23
+ should_be :string, "top level", "nested.values"
24
+ should_be :string, "error", "logging.app"
25
+ should_be :string, "warn", "logging.app.controller.hello"
26
+ should_be :string, "info", "logging.app.controller.hello.index"
27
+ end
28
+
29
+ def test_load_without_unit_test_env
22
30
  options = Prefab::Options.new(
23
31
  prefab_config_override_dir: "none",
24
32
  prefab_config_classpath_dir: "test",
25
- # no defaults_env
33
+ # no prefab_envs
26
34
  )
27
35
  @loader = Prefab::ConfigLoader.new(MockBaseClient.new(options))
28
36
  should_be :string, "default sample value", "sample"
@@ -37,10 +37,10 @@ class TestFeatureFlagClient < Minitest::Test
37
37
  # weights above chosen to be 86% in variant_idx 2. and 14% in variant_idx 1.
38
38
  # since hashes high is 86.32 > 86 it just falls outside the 86% range and gets false
39
39
 
40
- # "1FlagNamehashes high" hashes to 86.322% through dist
40
+ # "FlagNamevery high hash" hashes to 88.8602812% through dist
41
41
  assert_equal false,
42
- evaluate(feature, "hashes high", [], flag, variants)
43
- # "1FlagNamehashes low" hashes to 44.547% through dist
42
+ evaluate(feature, "very high hash", [], flag, variants)
43
+ # "FlagNamehashes low" hashes to 42.7934% through dist
44
44
  assert_equal true,
45
45
  evaluate(feature, "hashes low", [], flag, variants)
46
46
 
@@ -299,6 +299,66 @@ class TestFeatureFlagClient < Minitest::Test
299
299
 
300
300
  end
301
301
 
302
+ def test_prop_ends_with_one_of
303
+ feature = "FlagName"
304
+
305
+ variants = [
306
+ Prefab::FeatureFlagVariant.new(bool: false),
307
+ Prefab::FeatureFlagVariant.new(bool: true)
308
+ ]
309
+ flag = Prefab::FeatureFlag.new(
310
+ active: true,
311
+ inactive_variant_idx: 1,
312
+ rules: [
313
+ Prefab::Rule.new(
314
+ criteria: Prefab::Criteria.new(operator: Prefab::Criteria::CriteriaOperator::PROP_ENDS_WITH_ONE_OF,
315
+ property: "email",
316
+ values: ["@example.com"]),
317
+ variant_weights: [
318
+ Prefab::VariantWeight.new(weight: 100, variant_idx: 2)
319
+ ]
320
+ )
321
+ ]
322
+ )
323
+
324
+ assert_equal false, evaluate(feature, "user:0", {}, flag, variants)
325
+ assert_equal true, evaluate(feature, "user:0", {email: "test@example.com"}, flag, variants)
326
+ assert_equal true, evaluate(feature, "user:0", {"email" => "test@example.com"}, flag, variants)
327
+ end
328
+
329
+ def test_prop_does_not_end_with_one_of
330
+ feature = "FlagName"
331
+
332
+ variants = [
333
+ Prefab::FeatureFlagVariant.new(bool: false),
334
+ Prefab::FeatureFlagVariant.new(bool: true)
335
+ ]
336
+ flag = Prefab::FeatureFlag.new(
337
+ active: true,
338
+ inactive_variant_idx: 1,
339
+ rules: [
340
+ Prefab::Rule.new(
341
+ criteria: Prefab::Criteria.new(operator: Prefab::Criteria::CriteriaOperator::PROP_DOES_NOT_END_WITH_ONE_OF,
342
+ property: "email",
343
+ values: ["@example.com"]),
344
+ variant_weights: [
345
+ Prefab::VariantWeight.new(weight: 100, variant_idx: 2)
346
+ ]
347
+ ),
348
+ Prefab::Rule.new(
349
+ criteria: Prefab::Criteria.new(operator: Prefab::Criteria::CriteriaOperator::ALWAYS_TRUE),
350
+ variant_weights: [
351
+ Prefab::VariantWeight.new(weight: 100, variant_idx: 1)
352
+ ]
353
+ )
354
+ ],
355
+ )
356
+
357
+ assert_equal true, evaluate(feature, "user:0", {}, flag, variants)
358
+ assert_equal false, evaluate(feature, "user:0", {email: "test@example.com"}, flag, variants)
359
+ assert_equal false, evaluate(feature, "user:0", {"email" => "test@example.com"}, flag, variants)
360
+ end
361
+
302
362
  def evaluate(feature_name, lookup_key, attributes, flag, variants)
303
363
  variant = @client.get_variant(feature_name, lookup_key, attributes, flag, variants)
304
364
  @client.value_of_variant(variant)
data/test/test_helper.rb CHANGED
@@ -81,7 +81,7 @@ def new_client(overrides = {})
81
81
  options = Prefab::Options.new(**{
82
82
  prefab_config_override_dir: "none",
83
83
  prefab_config_classpath_dir: "test",
84
- defaults_env: "unit_tests",
84
+ prefab_envs: ["unit_tests"],
85
85
  prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
86
86
  }.merge(overrides))
87
87
 
data/test/test_logger.rb CHANGED
@@ -4,6 +4,7 @@ require 'test_helper'
4
4
  class TestCLogger < Minitest::Test
5
5
  def setup
6
6
  Prefab::LoggerClient.send(:public, :get_path)
7
+ Prefab::LoggerClient.send(:public, :get_loc_path)
7
8
  Prefab::LoggerClient.send(:public, :level_of)
8
9
  @logger = Prefab::LoggerClient.new($stdout)
9
10
  end
@@ -16,6 +17,27 @@ class TestCLogger < Minitest::Test
16
17
  assert_equal "active_support.log_subscriber.info",
17
18
  @logger.get_path("/Users/jdwyah/.rvm/gems/ruby-2.3.3@forcerank/gems/activesupport-4.1.16/lib/active_support/log_subscriber.rb",
18
19
  "info")
20
+ assert_equal "active_support.log_subscriber.info",
21
+ @logger.get_path("/Users/jeffdwyer/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/activesupport-7.0.2.4/lib/active_support/log_subscriber.rb:130:in `info'",
22
+ "info")
23
+ end
24
+
25
+ def test_loc_resolution
26
+ backtrace_location = Struct.new(:absolute_path, :base_label, :string) do
27
+ def to_s
28
+ string
29
+ end
30
+ end # https://ruby-doc.org/core-3.0.0/Thread/Backtrace/Location.html
31
+
32
+ # verify that even if the Thread::Backtrace::Location does not have an absolute_location, we do our best
33
+ assert_equal "active_support.log_subscriber.info",
34
+ @logger.get_loc_path(backtrace_location.new(nil,
35
+ "info",
36
+ "/Users/jeffdwyer/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/activesupport-7.0.2.4/lib/active_support/log_subscriber.rb:130:in `info'"))
37
+ assert_equal "test_l.info",
38
+ @logger.get_loc_path(backtrace_location.new("/Users/jdwyah/Documents/workspace/RateLimitInc/prefab-cloud-ruby/lib/test_l.rb",
39
+ "info",
40
+ "/Users/jeffdwyer/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/activesupport-7.0.2.4/lib/active_support/log_subscriber.rb:130:in `info'"))
19
41
  end
20
42
 
21
43
  def test_level_of
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prefab-cloud-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.3
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Dwyer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-12 00:00:00.000000000 Z
11
+ date: 2022-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -100,6 +100,20 @@ dependencies:
100
100
  - - ">="
101
101
  - !ruby/object:Gem::Version
102
102
  version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: benchmark-ips
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
103
117
  - !ruby/object:Gem::Dependency
104
118
  name: grpc-tools
105
119
  requirement: !ruby/object:Gem::Requirement
@@ -227,7 +241,8 @@ files:
227
241
  - lib/prefab_services_pb.rb
228
242
  - prefab-cloud-ruby.gemspec
229
243
  - run_test_harness_server.sh
230
- - test/.prefab.test.config.yaml
244
+ - test/.prefab.default.config.yaml
245
+ - test/.prefab.unit_tests.config.yaml
231
246
  - test/harness_server.rb
232
247
  - test/test_client.rb
233
248
  - test/test_config_client.rb
@@ -1,32 +0,0 @@
1
- sample: default sample value
2
- sample_int: 123
3
- sample_double: 12.12
4
- sample_bool: true
5
- false_value: false
6
- zero_value: 0
7
- sample_to_override: Foo
8
- prefab.log_level: debug
9
- unit_tests:
10
- sample: test sample value
11
- enabled_flag: true
12
- disabled_flag: false
13
- flag_with_a_value:
14
- feature_flag: true
15
- value: "all-features"
16
- in_lookup_key:
17
- feature_flag: true
18
- value: true
19
- criteria:
20
- operator: LOOKUP_KEY_IN
21
- values:
22
- - abc123
23
- - xyz987
24
- just_my_domain:
25
- feature_flag: true
26
- value: new-version
27
- criteria:
28
- operator: PROP_IS_ONE_OF
29
- property: domain
30
- values: ["prefab.cloud", "example.com"]
31
- ignored_env:
32
- sample: ignored value