prefab-cloud-ruby 0.12.0 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +39 -0
- data/Gemfile +4 -1
- data/Gemfile.lock +5 -20
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/lib/prefab/auth_interceptor.rb +1 -0
- data/lib/prefab/cancellable_interceptor.rb +1 -0
- data/lib/prefab/client.rb +17 -3
- data/lib/prefab/config_client.rb +16 -4
- data/lib/prefab/config_helper.rb +1 -0
- data/lib/prefab/config_loader.rb +73 -11
- data/lib/prefab/config_resolver.rb +3 -2
- data/lib/prefab/error.rb +6 -0
- data/lib/prefab/errors/initialization_timeout_error.rb +13 -0
- data/lib/prefab/errors/invalid_api_key_error.rb +19 -0
- data/lib/prefab/errors/missing_default_error.rb +13 -0
- data/lib/prefab/feature_flag_client.rb +32 -12
- data/lib/prefab/internal_logger.rb +1 -0
- data/lib/prefab/logger_client.rb +10 -8
- data/lib/prefab/murmer3.rb +1 -0
- data/lib/prefab/noop_cache.rb +1 -0
- data/lib/prefab/noop_stats.rb +1 -0
- data/lib/prefab/options.rb +2 -1
- data/lib/prefab/ratelimit_client.rb +1 -0
- data/lib/prefab-cloud-ruby.rb +5 -0
- data/lib/prefab_pb.rb +1 -0
- data/lib/prefab_services_pb.rb +1 -0
- data/prefab-cloud-ruby.gemspec +9 -6
- data/run_test_harness_server.sh +8 -2
- data/test/.prefab.test.config.yaml +22 -0
- data/test/harness_server.rb +10 -1
- data/test/test_client.rb +91 -0
- data/test/test_config_client.rb +38 -1
- data/test/test_config_loader.rb +1 -1
- data/test/test_config_resolver.rb +1 -1
- data/test/test_feature_flag_client.rb +25 -19
- data/test/test_helper.rb +22 -2
- data/test/test_logger.rb +23 -19
- metadata +8 -17
- data/lib/prefab/prefab.rb +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 70fdeb9c1e8651a918fb7700a921e23843374ee9d5f3904a09d076e4be3b3669
|
4
|
+
data.tar.gz: bd1cd6750f66a4e1f1940010fdaef01596de605f84ec80034747c906203aa8fc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 47cd4f946a8ed79956d5b7f9822eb6b4d666db234bab4767990aa9fd79402acfbe569f4708d2ce08c57b65dedeafa374d6c9f97460ded8c6307d71293ee812fa
|
7
|
+
data.tar.gz: 5078a253a71a7166a00f45a325d3f5b1f3435d99f2011ad197ebeb0af8be38699caecdba18fb36d019b13b92992f6c7cef82ae14ef672cda4ae8a3ca199c7236
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# This workflow uses actions that are not certified by GitHub.
|
2
|
+
# They are provided by a third-party and are governed by
|
3
|
+
# separate terms of service, privacy policy, and support
|
4
|
+
# documentation.
|
5
|
+
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
|
6
|
+
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
|
7
|
+
|
8
|
+
name: Ruby
|
9
|
+
|
10
|
+
on:
|
11
|
+
push:
|
12
|
+
branches: [ "main" ]
|
13
|
+
pull_request:
|
14
|
+
branches: [ "main" ]
|
15
|
+
|
16
|
+
permissions:
|
17
|
+
contents: read
|
18
|
+
|
19
|
+
jobs:
|
20
|
+
test:
|
21
|
+
|
22
|
+
runs-on: ubuntu-latest
|
23
|
+
strategy:
|
24
|
+
matrix:
|
25
|
+
ruby-version: ['2.6', '2.7', '3.0']
|
26
|
+
|
27
|
+
steps:
|
28
|
+
- uses: actions/checkout@v3
|
29
|
+
- name: Set up Ruby
|
30
|
+
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
31
|
+
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
32
|
+
# uses: ruby/setup-ruby@v1
|
33
|
+
# uses: ruby/setup-ruby@2b019609e2b0f1ea1a2bc8ca11cb82ab46ada124
|
34
|
+
uses: ruby/setup-ruby@v1
|
35
|
+
with:
|
36
|
+
ruby-version: ${{ matrix.ruby-version }}
|
37
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
38
|
+
- name: Run tests
|
39
|
+
run: bundle exec rake
|
data/Gemfile
CHANGED
@@ -9,10 +9,13 @@ gem 'googleapis-common-protos-types', :platforms => :ruby
|
|
9
9
|
|
10
10
|
group :development do
|
11
11
|
gem 'grpc-tools', :platforms => :ruby
|
12
|
-
gem "shoulda", ">= 0"
|
13
12
|
gem "rdoc", "~> 3.12"
|
14
13
|
gem "bundler"
|
15
14
|
gem "juwelier", "~> 2.4.9"
|
16
15
|
gem "simplecov", ">= 0"
|
17
16
|
gem 'thin'
|
18
17
|
end
|
18
|
+
|
19
|
+
group :test do
|
20
|
+
gem "minitest"
|
21
|
+
end
|
data/Gemfile.lock
CHANGED
@@ -1,15 +1,10 @@
|
|
1
1
|
GEM
|
2
2
|
remote: https://rubygems.org/
|
3
3
|
specs:
|
4
|
-
activesupport (5.2.4.5)
|
5
|
-
concurrent-ruby (~> 1.0, >= 1.0.2)
|
6
|
-
i18n (>= 0.7, < 2)
|
7
|
-
minitest (~> 5.1)
|
8
|
-
tzinfo (~> 1.1)
|
9
4
|
addressable (2.8.0)
|
10
5
|
public_suffix (>= 2.0.2, < 5.0)
|
11
6
|
builder (3.2.4)
|
12
|
-
concurrent-ruby (1.1.
|
7
|
+
concurrent-ruby (1.1.10)
|
13
8
|
daemons (1.4.1)
|
14
9
|
descendants_tracker (0.0.4)
|
15
10
|
thread_safe (~> 0.3, >= 0.3.1)
|
@@ -34,7 +29,7 @@ GEM
|
|
34
29
|
faraday (>= 0.8, < 2)
|
35
30
|
hashie (~> 3.5, >= 3.5.2)
|
36
31
|
oauth2 (~> 1.0)
|
37
|
-
google-protobuf (3.
|
32
|
+
google-protobuf (3.21.4)
|
38
33
|
googleapis-common-protos-types (1.3.0)
|
39
34
|
google-protobuf (~> 3.14)
|
40
35
|
grpc (1.43.1)
|
@@ -51,8 +46,6 @@ GEM
|
|
51
46
|
http-cookie (1.0.4)
|
52
47
|
domain_name (~> 0.5)
|
53
48
|
http-form_data (2.3.0)
|
54
|
-
i18n (1.8.9)
|
55
|
-
concurrent-ruby (~> 1.0)
|
56
49
|
json (1.8.6)
|
57
50
|
juwelier (2.4.9)
|
58
51
|
builder
|
@@ -76,7 +69,7 @@ GEM
|
|
76
69
|
ffi-compiler (~> 1.0)
|
77
70
|
rake (~> 13.0)
|
78
71
|
mini_portile2 (2.8.0)
|
79
|
-
minitest (5.
|
72
|
+
minitest (5.16.2)
|
80
73
|
multi_json (1.15.0)
|
81
74
|
multi_xml (0.6.0)
|
82
75
|
multipart-post (2.1.1)
|
@@ -92,19 +85,13 @@ GEM
|
|
92
85
|
psych (3.3.1)
|
93
86
|
public_suffix (4.0.6)
|
94
87
|
racc (1.6.0)
|
95
|
-
rack (2.2.
|
88
|
+
rack (2.2.4)
|
96
89
|
rake (13.0.3)
|
97
90
|
rchardet (1.8.0)
|
98
91
|
rdoc (3.12.2)
|
99
92
|
json (~> 1.4)
|
100
93
|
ruby2_keywords (0.0.4)
|
101
94
|
semver2 (3.4.2)
|
102
|
-
shoulda (4.0.0)
|
103
|
-
shoulda-context (~> 2.0)
|
104
|
-
shoulda-matchers (~> 4.0)
|
105
|
-
shoulda-context (2.0.0)
|
106
|
-
shoulda-matchers (4.5.1)
|
107
|
-
activesupport (>= 4.2.0)
|
108
95
|
simplecov (0.18.5)
|
109
96
|
docile (~> 1.1)
|
110
97
|
simplecov-html (~> 0.11)
|
@@ -114,8 +101,6 @@ GEM
|
|
114
101
|
eventmachine (~> 1.0, >= 1.0.4)
|
115
102
|
rack (>= 1, < 3)
|
116
103
|
thread_safe (0.3.6)
|
117
|
-
tzinfo (1.2.9)
|
118
|
-
thread_safe (~> 0.1)
|
119
104
|
unf (0.1.4)
|
120
105
|
unf_ext
|
121
106
|
unf_ext (0.0.8)
|
@@ -133,8 +118,8 @@ DEPENDENCIES
|
|
133
118
|
grpc-tools
|
134
119
|
juwelier (~> 2.4.9)
|
135
120
|
ld-eventsource
|
121
|
+
minitest
|
136
122
|
rdoc (~> 3.12)
|
137
|
-
shoulda
|
138
123
|
simplecov
|
139
124
|
thin
|
140
125
|
|
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.13.0
|
data/lib/prefab/client.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Prefab
|
2
|
-
|
3
3
|
class Client
|
4
4
|
MAX_SLEEP_SEC = 10
|
5
5
|
BASE_SLEEP_SEC = 0.5
|
6
|
+
NO_DEFAULT_PROVIDED = :no_default_provided
|
6
7
|
|
7
8
|
attr_reader :project_id, :shared_cache, :stats, :namespace, :interceptor, :api_key, :prefab_api_url, :options
|
8
9
|
|
@@ -18,8 +19,7 @@ module Prefab
|
|
18
19
|
log_internal Logger::INFO, "Prefab Running in Local Mode"
|
19
20
|
else
|
20
21
|
@api_key = @options.api_key
|
21
|
-
raise
|
22
|
-
raise "PREFAB_API_KEY format invalid. Expecting 123-development-yourapikey-SDK" unless @api_key.count("-") == 3
|
22
|
+
raise Prefab::Errors::InvalidApiKeyError.new(@api_key) if @api_key.nil? || @api_key.empty? || api_key.count("-") != 3
|
23
23
|
@project_id = @api_key.split("-")[0].to_i # unvalidated, but that's ok. APIs only listen to the actual passwd
|
24
24
|
@interceptor = Prefab::AuthInterceptor.new(@api_key)
|
25
25
|
@prefab_api_url = @options.prefab_api_url
|
@@ -91,6 +91,20 @@ module Prefab
|
|
91
91
|
@_channel = nil
|
92
92
|
end
|
93
93
|
|
94
|
+
def enabled?(feature_name, lookup_key=nil, attributes={})
|
95
|
+
feature_flag_client.feature_is_on_for?(feature_name, lookup_key, attributes: attributes)
|
96
|
+
end
|
97
|
+
|
98
|
+
def get(key, default_or_lookup_key=NO_DEFAULT_PROVIDED, attributes={}, ff_default=NO_DEFAULT_PROVIDED)
|
99
|
+
result = config_client.get(key, default_or_lookup_key)
|
100
|
+
|
101
|
+
if result.is_a?(Prefab::FeatureFlag)
|
102
|
+
feature_flag_client.get(key, default_or_lookup_key, attributes, default: ff_default)
|
103
|
+
else
|
104
|
+
result
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
94
108
|
private
|
95
109
|
|
96
110
|
def http_secure?
|
data/lib/prefab/config_client.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Prefab
|
2
3
|
class ConfigClient
|
3
4
|
include Prefab::ConfigHelper
|
@@ -73,9 +74,9 @@ module Prefab
|
|
73
74
|
rows: [Prefab::ConfigRow.new(value: config_value)])
|
74
75
|
end
|
75
76
|
|
76
|
-
def get(key)
|
77
|
+
def get(key, default=Prefab::Client::NO_DEFAULT_PROVIDED)
|
77
78
|
config = _get(key)
|
78
|
-
config ? value_of(config[:value]) :
|
79
|
+
config ? value_of(config[:value]) : handle_default(key, default)
|
79
80
|
end
|
80
81
|
|
81
82
|
def get_config_obj(key)
|
@@ -85,6 +86,18 @@ module Prefab
|
|
85
86
|
|
86
87
|
private
|
87
88
|
|
89
|
+
def handle_default(key, default)
|
90
|
+
if default != Prefab::Client::NO_DEFAULT_PROVIDED
|
91
|
+
return default
|
92
|
+
end
|
93
|
+
|
94
|
+
if @options.on_no_default == Prefab::Options::ON_NO_DEFAULT::RAISE
|
95
|
+
raise Prefab::Errors::MissingDefaultError.new(key)
|
96
|
+
end
|
97
|
+
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
|
88
101
|
def _get(key)
|
89
102
|
# wait timeout sec for the initalization to be complete
|
90
103
|
@initialized_future.value(@options.initialization_timeout_sec)
|
@@ -93,7 +106,7 @@ module Prefab
|
|
93
106
|
@base_client.log_internal Logger::WARN, "Couldn't Initialize In #{@options.initialization_timeout_sec}. Key #{key}. Returning what we have"
|
94
107
|
@initialization_lock.release_write_lock
|
95
108
|
else
|
96
|
-
raise
|
109
|
+
raise Prefab::Errors::InitializationTimeoutError.new(@options.initialization_timeout_sec, key)
|
97
110
|
end
|
98
111
|
end
|
99
112
|
@config_resolver._get(key)
|
@@ -140,7 +153,6 @@ module Prefab
|
|
140
153
|
rescue GRPC::Unauthenticated
|
141
154
|
@base_client.log_internal Logger::WARN, "Unauthenticated"
|
142
155
|
rescue => e
|
143
|
-
puts e.class
|
144
156
|
@base_client.log_internal Logger::WARN, "Unexpected grpc_api problem loading checkpoint #{e}"
|
145
157
|
false
|
146
158
|
end
|
data/lib/prefab/config_helper.rb
CHANGED
data/lib/prefab/config_loader.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'yaml'
|
2
4
|
module Prefab
|
3
5
|
class ConfigLoader
|
@@ -70,22 +72,35 @@ module Prefab
|
|
70
72
|
if v.class == Hash
|
71
73
|
v.each do |env_k, env_v|
|
72
74
|
if k == @prefab_options.defaults_env
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
78
89
|
else
|
79
90
|
next
|
80
91
|
end
|
81
92
|
end
|
82
93
|
else
|
83
|
-
rtn[k] = {
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
+
}
|
89
104
|
end
|
90
105
|
end
|
91
106
|
end
|
@@ -114,5 +129,52 @@ module Prefab
|
|
114
129
|
{ double: raw }
|
115
130
|
end
|
116
131
|
end
|
132
|
+
|
133
|
+
def feature_flag_config(file, k, env_k, env_v)
|
134
|
+
criteria = Prefab::Criteria.new(operator: 'ALWAYS_TRUE')
|
135
|
+
|
136
|
+
if env_v['criteria']
|
137
|
+
criteria = Prefab::Criteria.new(criteria_values(env_v['criteria']))
|
138
|
+
end
|
139
|
+
|
140
|
+
row = Prefab::ConfigRow.new(
|
141
|
+
value: Prefab::ConfigValue.new(
|
142
|
+
feature_flag: Prefab::FeatureFlag.new(
|
143
|
+
active: true,
|
144
|
+
inactive_variant_idx: -1, # not supported
|
145
|
+
rules: [
|
146
|
+
Prefab::Rule.new(
|
147
|
+
variant_weights: [
|
148
|
+
Prefab::VariantWeight.new(variant_idx: 0, weight: 1000)
|
149
|
+
],
|
150
|
+
criteria: criteria
|
151
|
+
)
|
152
|
+
]
|
153
|
+
)
|
154
|
+
)
|
155
|
+
)
|
156
|
+
|
157
|
+
unless env_v.has_key?('value')
|
158
|
+
raise Prefab::Error, "Feature flag config `#{env_k}` #{file} must have a `value`"
|
159
|
+
end
|
160
|
+
|
161
|
+
{
|
162
|
+
source: file,
|
163
|
+
match: k,
|
164
|
+
config: Prefab::Config.new(
|
165
|
+
key: env_k,
|
166
|
+
variants: [Prefab::FeatureFlagVariant.new(value_from(env_v['value']))],
|
167
|
+
rows: [row]
|
168
|
+
)
|
169
|
+
}
|
170
|
+
end
|
171
|
+
|
172
|
+
def criteria_values(criteria_hash)
|
173
|
+
if RUBY_VERSION < '2.7'
|
174
|
+
criteria_hash.transform_keys(&:to_sym)
|
175
|
+
else
|
176
|
+
criteria_hash
|
177
|
+
end
|
178
|
+
end
|
117
179
|
end
|
118
180
|
end
|
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Prefab
|
2
3
|
class ConfigResolver
|
3
4
|
include Prefab::ConfigHelper
|
4
|
-
NAMESPACE_DELIMITER = "."
|
5
|
+
NAMESPACE_DELIMITER = "."
|
5
6
|
|
6
7
|
attr_accessor :project_env_id # this will be set by the config_client when it gets an API response
|
7
8
|
|
@@ -28,7 +29,7 @@ module Prefab
|
|
28
29
|
elements << "Match: #{v[:match]}".slice(0..29).ljust(30)
|
29
30
|
elements << "Source: #{v[:source]}"
|
30
31
|
end
|
31
|
-
str
|
32
|
+
str += elements.join(" | ") << "\n"
|
32
33
|
end
|
33
34
|
end
|
34
35
|
str
|
data/lib/prefab/error.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prefab
|
4
|
+
module Errors
|
5
|
+
class InitializationTimeoutError < Prefab::Error
|
6
|
+
def initialize(timeout_sec, key)
|
7
|
+
message = "Prefab couldn't initialize in #{timeout_sec} second timeout. Trying to fetch key `#{key}`."
|
8
|
+
|
9
|
+
super(message)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prefab
|
4
|
+
module Errors
|
5
|
+
class InvalidApiKeyError < Prefab::Error
|
6
|
+
def initialize(key)
|
7
|
+
if key.nil? || key.empty?
|
8
|
+
message = "No API key. Set PREFAB_API_KEY env var or use PREFAB_DATASOURCES=LOCAL_ONLY"
|
9
|
+
|
10
|
+
super(message)
|
11
|
+
else
|
12
|
+
message = "Your API key format is invalid. Expecting something like 123-development-yourapikey-SDK. You provided `#{key}`"
|
13
|
+
|
14
|
+
super(message)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prefab
|
4
|
+
module Errors
|
5
|
+
class MissingDefaultError < Prefab::Error
|
6
|
+
def initialize(key)
|
7
|
+
message = "No value found for key '#{key}' and no default was provided.\n\nIf you'd prefer returning `nil` rather than raising when this occurs, modify the `on_no_default` value you provide in your Prefab::Options."
|
8
|
+
|
9
|
+
super(message)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Prefab
|
2
3
|
class FeatureFlagClient
|
3
4
|
include Prefab::ConfigHelper
|
@@ -18,27 +19,48 @@ module Prefab
|
|
18
19
|
def feature_is_on_for?(feature_name, lookup_key, attributes: {})
|
19
20
|
@base_client.stats.increment("prefab.featureflag.on", tags: ["feature:#{feature_name}"])
|
20
21
|
|
21
|
-
return is_on?(
|
22
|
+
return is_on?(_get(feature_name, lookup_key, attributes, default: false))
|
22
23
|
end
|
23
24
|
|
24
|
-
def get(feature_name, lookup_key=nil, attributes={})
|
25
|
-
|
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)
|
30
|
-
end
|
25
|
+
def get(feature_name, lookup_key=nil, attributes={}, default: false)
|
26
|
+
variant = _get(feature_name, lookup_key, attributes, default: default)
|
31
27
|
|
32
|
-
|
33
|
-
value_of_variant(get_variant(feature_name, lookup_key, attributes, feature_obj, variants))
|
28
|
+
value_of_variant_or_nil(variant, default)
|
34
29
|
end
|
35
30
|
|
36
31
|
private
|
37
32
|
|
33
|
+
def value_of_variant_or_nil(variant_maybe, default)
|
34
|
+
if variant_maybe.nil?
|
35
|
+
default != Prefab::Client::NO_DEFAULT_PROVIDED ? default : nil
|
36
|
+
else
|
37
|
+
value_of_variant(variant_maybe)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def _get(feature_name, lookup_key=nil, attributes={}, default:)
|
42
|
+
feature_obj = @base_client.config_client.get(feature_name, default)
|
43
|
+
config_obj = @base_client.config_client.get_config_obj(feature_name)
|
44
|
+
|
45
|
+
return nil if feature_obj.nil? || config_obj.nil?
|
46
|
+
|
47
|
+
if feature_obj == !!feature_obj
|
48
|
+
return feature_obj
|
49
|
+
end
|
50
|
+
|
51
|
+
variants = config_obj.variants
|
52
|
+
get_variant(feature_name, lookup_key, attributes, feature_obj, variants)
|
53
|
+
end
|
54
|
+
|
38
55
|
def is_on?(variant)
|
39
56
|
if variant.nil?
|
40
57
|
return false
|
41
58
|
end
|
59
|
+
|
60
|
+
if variant == !!variant
|
61
|
+
return variant
|
62
|
+
end
|
63
|
+
|
42
64
|
variant.bool
|
43
65
|
rescue
|
44
66
|
@base_client.log.info("is_on? methods only work for boolean feature flags variants. This feature flags variant is '#{variant}'. Returning false")
|
@@ -61,7 +83,6 @@ module Prefab
|
|
61
83
|
end
|
62
84
|
end
|
63
85
|
|
64
|
-
|
65
86
|
percent_through_distribution = rand()
|
66
87
|
if lookup_key
|
67
88
|
percent_through_distribution = get_user_pct(feature_name, lookup_key)
|
@@ -104,7 +125,6 @@ module Prefab
|
|
104
125
|
#
|
105
126
|
# end
|
106
127
|
def criteria_match?(criteria, lookup_key, attributes)
|
107
|
-
|
108
128
|
if criteria.operator == :ALWAYS_TRUE
|
109
129
|
return true
|
110
130
|
elsif criteria.operator == :LOOKUP_KEY_IN
|
data/lib/prefab/logger_client.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Prefab
|
2
3
|
class LoggerClient < Logger
|
3
4
|
|
4
|
-
SEP = "."
|
5
|
-
BASE = "log_level"
|
5
|
+
SEP = "."
|
6
|
+
BASE = "log_level"
|
7
|
+
UNKNOWN = "unknown"
|
6
8
|
|
7
9
|
def initialize(logdev, formatter: nil)
|
8
10
|
super(logdev)
|
@@ -107,10 +109,10 @@ module Prefab
|
|
107
109
|
|
108
110
|
# Find the closest match to 'log_level.path' in config
|
109
111
|
def level_of(path)
|
110
|
-
closest_log_level_match = @config_client.get(BASE
|
112
|
+
closest_log_level_match = @config_client.get(BASE, :warn)
|
111
113
|
path.split(SEP).inject([BASE]) do |memo, n|
|
112
114
|
memo << n
|
113
|
-
val = @config_client.get(memo.join(SEP))
|
115
|
+
val = @config_client.get(memo.join(SEP), nil)
|
114
116
|
unless val.nil?
|
115
117
|
closest_log_level_match = val
|
116
118
|
end
|
@@ -122,12 +124,12 @@ module Prefab
|
|
122
124
|
# sanitize & clean the path of the caller so the key
|
123
125
|
# looks like log_level.app.models.user
|
124
126
|
def get_path(absolute_path, base_label)
|
125
|
-
path = (absolute_path ||
|
127
|
+
path = (absolute_path || UNKNOWN).dup
|
126
128
|
path.slice! Dir.pwd
|
127
129
|
|
128
130
|
path.gsub!(/.*?(?=\/lib\/)/im, "")
|
129
131
|
|
130
|
-
path =
|
132
|
+
path = path.gsub("/", SEP).gsub(".rb", "") + SEP + base_label
|
131
133
|
path.slice! ".lib"
|
132
134
|
path.slice! SEP
|
133
135
|
path
|
@@ -141,8 +143,8 @@ module Prefab
|
|
141
143
|
# StubConfigClient to be used while config client initializes
|
142
144
|
# since it may log
|
143
145
|
class BootstrappingConfigClient
|
144
|
-
def get(key)
|
145
|
-
ENV["PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL"] ||
|
146
|
+
def get(key, default=nil)
|
147
|
+
ENV["PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL"] || default
|
146
148
|
end
|
147
149
|
end
|
148
150
|
end
|
data/lib/prefab/murmer3.rb
CHANGED
data/lib/prefab/noop_cache.rb
CHANGED
data/lib/prefab/noop_stats.rb
CHANGED
data/lib/prefab/options.rb
CHANGED
data/lib/prefab-cloud-ruby.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "concurrent/atomics"
|
2
3
|
require 'concurrent'
|
3
4
|
require 'faraday'
|
@@ -5,6 +6,10 @@ require 'openssl'
|
|
5
6
|
require 'openssl'
|
6
7
|
require 'ld-eventsource'
|
7
8
|
require 'prefab_pb'
|
9
|
+
require 'prefab/error'
|
10
|
+
require 'prefab/errors/initialization_timeout_error'
|
11
|
+
require 'prefab/errors/invalid_api_key_error'
|
12
|
+
require 'prefab/errors/missing_default_error'
|
8
13
|
require 'prefab_services_pb'
|
9
14
|
require 'prefab/options'
|
10
15
|
require 'prefab/internal_logger'
|
data/lib/prefab_pb.rb
CHANGED
data/lib/prefab_services_pb.rb
CHANGED
data/prefab-cloud-ruby.gemspec
CHANGED
@@ -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.
|
5
|
+
# stub: prefab-cloud-ruby 0.13.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "prefab-cloud-ruby".freeze
|
9
|
-
s.version = "0.
|
9
|
+
s.version = "0.13.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-
|
14
|
+
s.date = "2022-08-10"
|
15
15
|
s.description = "RateLimits & Config as a service".freeze
|
16
16
|
s.email = "jdwyer@prefab.cloud".freeze
|
17
17
|
s.extra_rdoc_files = [
|
@@ -20,6 +20,7 @@ Gem::Specification.new do |s|
|
|
20
20
|
]
|
21
21
|
s.files = [
|
22
22
|
".envrc",
|
23
|
+
".github/workflows/ruby.yml",
|
23
24
|
".tool-versions",
|
24
25
|
"CODEOWNERS",
|
25
26
|
"Gemfile",
|
@@ -37,6 +38,10 @@ Gem::Specification.new do |s|
|
|
37
38
|
"lib/prefab/config_helper.rb",
|
38
39
|
"lib/prefab/config_loader.rb",
|
39
40
|
"lib/prefab/config_resolver.rb",
|
41
|
+
"lib/prefab/error.rb",
|
42
|
+
"lib/prefab/errors/initialization_timeout_error.rb",
|
43
|
+
"lib/prefab/errors/invalid_api_key_error.rb",
|
44
|
+
"lib/prefab/errors/missing_default_error.rb",
|
40
45
|
"lib/prefab/feature_flag_client.rb",
|
41
46
|
"lib/prefab/internal_logger.rb",
|
42
47
|
"lib/prefab/logger_client.rb",
|
@@ -44,7 +49,6 @@ Gem::Specification.new do |s|
|
|
44
49
|
"lib/prefab/noop_cache.rb",
|
45
50
|
"lib/prefab/noop_stats.rb",
|
46
51
|
"lib/prefab/options.rb",
|
47
|
-
"lib/prefab/prefab.rb",
|
48
52
|
"lib/prefab/ratelimit_client.rb",
|
49
53
|
"lib/prefab_pb.rb",
|
50
54
|
"lib/prefab_services_pb.rb",
|
@@ -52,6 +56,7 @@ Gem::Specification.new do |s|
|
|
52
56
|
"run_test_harness_server.sh",
|
53
57
|
"test/.prefab.test.config.yaml",
|
54
58
|
"test/harness_server.rb",
|
59
|
+
"test/test_client.rb",
|
55
60
|
"test/test_config_client.rb",
|
56
61
|
"test/test_config_loader.rb",
|
57
62
|
"test/test_config_resolver.rb",
|
@@ -76,7 +81,6 @@ Gem::Specification.new do |s|
|
|
76
81
|
s.add_runtime_dependency(%q<google-protobuf>.freeze, [">= 0"])
|
77
82
|
s.add_runtime_dependency(%q<googleapis-common-protos-types>.freeze, [">= 0"])
|
78
83
|
s.add_development_dependency(%q<grpc-tools>.freeze, [">= 0"])
|
79
|
-
s.add_development_dependency(%q<shoulda>.freeze, [">= 0"])
|
80
84
|
s.add_development_dependency(%q<rdoc>.freeze, ["~> 3.12"])
|
81
85
|
s.add_development_dependency(%q<bundler>.freeze, [">= 0"])
|
82
86
|
s.add_development_dependency(%q<juwelier>.freeze, ["~> 2.4.9"])
|
@@ -90,7 +94,6 @@ Gem::Specification.new do |s|
|
|
90
94
|
s.add_dependency(%q<google-protobuf>.freeze, [">= 0"])
|
91
95
|
s.add_dependency(%q<googleapis-common-protos-types>.freeze, [">= 0"])
|
92
96
|
s.add_dependency(%q<grpc-tools>.freeze, [">= 0"])
|
93
|
-
s.add_dependency(%q<shoulda>.freeze, [">= 0"])
|
94
97
|
s.add_dependency(%q<rdoc>.freeze, ["~> 3.12"])
|
95
98
|
s.add_dependency(%q<bundler>.freeze, [">= 0"])
|
96
99
|
s.add_dependency(%q<juwelier>.freeze, ["~> 2.4.9"])
|
data/run_test_harness_server.sh
CHANGED
@@ -1,2 +1,8 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
#! /usr/bin/env bash
|
2
|
+
|
3
|
+
PREFAB_CDN_URL="https://api-prefab-cloud.global.ssl.fastly.net" \
|
4
|
+
PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL=debug \
|
5
|
+
PREFAB_CLOUD_HTTP=true \
|
6
|
+
PREFAB_API_KEY="1|local_development_api_key" \
|
7
|
+
PREFAB_GRPC_URL="localhost:50051" \
|
8
|
+
ruby -Ilib test/harness_server.rb
|
@@ -2,9 +2,31 @@ sample: default sample value
|
|
2
2
|
sample_int: 123
|
3
3
|
sample_double: 12.12
|
4
4
|
sample_bool: true
|
5
|
+
false_value: false
|
6
|
+
zero_value: 0
|
5
7
|
sample_to_override: Foo
|
6
8
|
prefab.log_level: debug
|
7
9
|
unit_tests:
|
8
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"]
|
9
31
|
ignored_env:
|
10
32
|
sample: ignored value
|
data/test/harness_server.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'prefab-cloud-ruby'
|
2
3
|
require 'rack'
|
3
4
|
require 'base64'
|
@@ -20,11 +21,19 @@ class RackApp
|
|
20
21
|
is_feature_flag = !props["feature_flag"].nil?
|
21
22
|
attributes = props["attributes"]
|
22
23
|
puts props
|
23
|
-
|
24
|
+
|
25
|
+
options = Prefab::Options.new(
|
24
26
|
api_key: api_key,
|
25
27
|
namespace: namespace,
|
28
|
+
initialization_timeout_sec: 1,
|
29
|
+
# We want to `return` rather than raise so we'll use the initial payload if we can't connect to the SSE server
|
30
|
+
on_init_failure: Prefab::Options::ON_INITIALIZATION_FAILURE::RETURN,
|
31
|
+
# Want to return `nil` rather than raise so we can verify empty values
|
32
|
+
on_no_default: Prefab::Options::ON_NO_DEFAULT::RETURN_NIL
|
26
33
|
)
|
27
34
|
|
35
|
+
client = Prefab::Client.new(options)
|
36
|
+
|
28
37
|
puts "Key #{key}"
|
29
38
|
puts "User #{user_key}"
|
30
39
|
puts "api_key #{api_key}"
|
data/test/test_client.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
class TestClient < Minitest::Test
|
5
|
+
def setup
|
6
|
+
@client = new_client
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_get
|
10
|
+
assert_equal "test sample value", @client.get("sample")
|
11
|
+
assert_equal 123, @client.get("sample_int")
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_get_with_default
|
15
|
+
# A `false` value is not replaced with the default
|
16
|
+
assert_equal false, @client.get("false_value", "red")
|
17
|
+
|
18
|
+
# A falsy value is not replaced with the default
|
19
|
+
assert_equal 0, @client.get("zero_value", "red")
|
20
|
+
|
21
|
+
# A missing value returns the default
|
22
|
+
assert_equal "buckets", @client.get("missing_value", "buckets")
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_get_with_missing_default
|
26
|
+
# it raises by default
|
27
|
+
err = assert_raises(Prefab::Errors::MissingDefaultError) do
|
28
|
+
assert_nil @client.get("missing_value")
|
29
|
+
end
|
30
|
+
|
31
|
+
assert_match(/No value found for key/, err.message)
|
32
|
+
assert_match(/on_no_default/, err.message)
|
33
|
+
|
34
|
+
# you can opt-in to return `nil` instead
|
35
|
+
client = new_client(on_no_default: Prefab::Options::ON_NO_DEFAULT::RETURN_NIL)
|
36
|
+
assert_nil client.get("missing_value")
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_enabled
|
40
|
+
assert_equal false, @client.enabled?("does_not_exist")
|
41
|
+
assert_equal true, @client.enabled?("enabled_flag")
|
42
|
+
assert_equal false, @client.enabled?("disabled_flag")
|
43
|
+
assert_equal false, @client.enabled?("flag_with_a_value")
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_ff_enabled_with_lookup_key
|
47
|
+
assert_equal false, @client.enabled?("in_lookup_key", "jimmy")
|
48
|
+
assert_equal true, @client.enabled?("in_lookup_key", "abc123")
|
49
|
+
assert_equal true, @client.enabled?("in_lookup_key", "xyz987")
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_ff_get_with_lookup_key
|
53
|
+
assert_nil @client.get("in_lookup_key", "jimmy")
|
54
|
+
assert_equal "DEFAULT", @client.get("in_lookup_key", "jimmy", {}, "DEFAULT")
|
55
|
+
|
56
|
+
assert_equal true, @client.get("in_lookup_key", "abc123")
|
57
|
+
assert_equal true, @client.get("in_lookup_key", "xyz987")
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_ff_enabled_with_attributes
|
61
|
+
assert_equal false, @client.enabled?("just_my_domain", "abc123", { domain: "gmail.com" })
|
62
|
+
assert_equal false, @client.enabled?("just_my_domain", "abc123", { domain: "prefab.cloud" })
|
63
|
+
assert_equal false, @client.enabled?("just_my_domain", "abc123", { domain: "example.com" })
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_ff_get_with_attributes
|
67
|
+
assert_nil @client.get("just_my_domain", "abc123", { domain: "gmail.com" })
|
68
|
+
assert_equal "DEFAULT", @client.get("just_my_domain", "abc123", { domain: "gmail.com" }, "DEFAULT")
|
69
|
+
|
70
|
+
assert_equal "new-version", @client.get("just_my_domain", "abc123", { domain: "prefab.cloud" })
|
71
|
+
assert_equal "new-version", @client.get("just_my_domain", "abc123", { domain: "example.com" })
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_getting_feature_flag_value
|
75
|
+
assert_equal false, @client.enabled?("flag_with_a_value")
|
76
|
+
assert_equal "all-features", @client.get("flag_with_a_value")
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def new_client(overrides = {})
|
82
|
+
options = Prefab::Options.new(**{
|
83
|
+
prefab_config_override_dir: "none",
|
84
|
+
prefab_config_classpath_dir: "test",
|
85
|
+
defaults_env: "unit_tests",
|
86
|
+
prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
|
87
|
+
}.merge(overrides))
|
88
|
+
|
89
|
+
Prefab::Client.new(options)
|
90
|
+
end
|
91
|
+
end
|
data/test/test_config_client.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'test_helper'
|
2
3
|
|
3
4
|
class TestConfigClient < Minitest::Test
|
@@ -6,8 +7,9 @@ class TestConfigClient < Minitest::Test
|
|
6
7
|
prefab_config_override_dir: "none",
|
7
8
|
prefab_config_classpath_dir: "test",
|
8
9
|
defaults_env: "unit_tests",
|
9
|
-
|
10
|
+
prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
|
10
11
|
)
|
12
|
+
|
11
13
|
@config_client = Prefab::ConfigClient.new(MockBaseClient.new(options), 10)
|
12
14
|
end
|
13
15
|
|
@@ -16,4 +18,39 @@ class TestConfigClient < Minitest::Test
|
|
16
18
|
assert_equal 123, @config_client.get("sample_int")
|
17
19
|
end
|
18
20
|
|
21
|
+
def test_initialization_timeout_error
|
22
|
+
options = Prefab::Options.new(
|
23
|
+
api_key: "123-ENV-KEY-SDK",
|
24
|
+
initialization_timeout_sec: 0.01,
|
25
|
+
logdev: StringIO.new
|
26
|
+
)
|
27
|
+
|
28
|
+
err = assert_raises(Prefab::Errors::InitializationTimeoutError) do
|
29
|
+
Prefab::Client.new(options).config_client.get("anything")
|
30
|
+
end
|
31
|
+
|
32
|
+
assert_match(/couldn't initialize in 0.01 second timeout/, err.message)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_invalid_api_key_error
|
36
|
+
options = Prefab::Options.new(
|
37
|
+
api_key: "",
|
38
|
+
)
|
39
|
+
|
40
|
+
err = assert_raises(Prefab::Errors::InvalidApiKeyError) do
|
41
|
+
Prefab::Client.new(options).config_client.get("anything")
|
42
|
+
end
|
43
|
+
|
44
|
+
assert_match(/No API key/, err.message)
|
45
|
+
|
46
|
+
options = Prefab::Options.new(
|
47
|
+
api_key: "invalid",
|
48
|
+
)
|
49
|
+
|
50
|
+
err = assert_raises(Prefab::Errors::InvalidApiKeyError) do
|
51
|
+
Prefab::Client.new(options).config_client.get("anything")
|
52
|
+
end
|
53
|
+
|
54
|
+
assert_match(/format is invalid/, err.message)
|
55
|
+
end
|
19
56
|
end
|
data/test/test_config_loader.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'test_helper'
|
2
3
|
|
3
4
|
class TestConfigLoader < Minitest::Test
|
@@ -89,5 +90,4 @@ class TestConfigLoader < Minitest::Test
|
|
89
90
|
assert_equal type, @loader.calc_config[key][:config].rows[0].value.type
|
90
91
|
assert_equal value, @loader.calc_config[key][:config].rows[0].value.send(type)
|
91
92
|
end
|
92
|
-
|
93
93
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'test_helper'
|
2
3
|
|
3
4
|
class TestConfigResolver < Minitest::Test
|
@@ -174,7 +175,6 @@ class TestConfigResolver < Minitest::Test
|
|
174
175
|
assert_equal "value", @resolverKeyWith.get("Key:With:Colons")
|
175
176
|
|
176
177
|
@resolverKeyWithExtra = resolver_for_namespace("Key:With:Extra", @loader)
|
177
|
-
puts @resolverKeyWithExtra.to_s
|
178
178
|
assert_nil @resolverKeyWithExtra.get("Colons")
|
179
179
|
assert_nil @resolverKeyWithExtra.get("With:Colons")
|
180
180
|
assert_equal "value", @resolverKeyWithExtra.get("Key:With:Colons")
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'test_helper'
|
2
3
|
|
3
4
|
class TestFeatureFlagClient < Minitest::Test
|
@@ -8,6 +9,7 @@ class TestFeatureFlagClient < Minitest::Test
|
|
8
9
|
@client = Prefab::FeatureFlagClient.new(@mock_base_client)
|
9
10
|
Prefab::FeatureFlagClient.send(:public, :is_on?) #publicize for testing
|
10
11
|
Prefab::FeatureFlagClient.send(:public, :segment_match?) #publicize for testing
|
12
|
+
Prefab::FeatureFlagClient.send(:public, :get_variant) #publicize for testing
|
11
13
|
end
|
12
14
|
|
13
15
|
def test_pct
|
@@ -37,10 +39,10 @@ class TestFeatureFlagClient < Minitest::Test
|
|
37
39
|
|
38
40
|
# "1FlagNamehashes high" hashes to 86.322% through dist
|
39
41
|
assert_equal false,
|
40
|
-
|
42
|
+
evaluate(feature, "hashes high", [], flag, variants)
|
41
43
|
# "1FlagNamehashes low" hashes to 44.547% through dist
|
42
44
|
assert_equal true,
|
43
|
-
|
45
|
+
evaluate(feature, "hashes low", [], flag, variants)
|
44
46
|
|
45
47
|
end
|
46
48
|
|
@@ -56,9 +58,9 @@ class TestFeatureFlagClient < Minitest::Test
|
|
56
58
|
rules: default_ff_rule(2)
|
57
59
|
)
|
58
60
|
assert_equal true,
|
59
|
-
|
61
|
+
evaluate(feature, "hashes high", [], flag, variants)
|
60
62
|
assert_equal true,
|
61
|
-
|
63
|
+
evaluate(feature, "hashes low", [], flag, variants)
|
62
64
|
|
63
65
|
variants = [
|
64
66
|
Prefab::FeatureFlagVariant.new(bool: false),
|
@@ -70,9 +72,9 @@ class TestFeatureFlagClient < Minitest::Test
|
|
70
72
|
rules: default_ff_rule(2)
|
71
73
|
)
|
72
74
|
assert_equal false,
|
73
|
-
|
75
|
+
evaluate(feature, "hashes high", [], flag, variants)
|
74
76
|
assert_equal false,
|
75
|
-
|
77
|
+
evaluate(feature, "hashes low", [], flag, variants)
|
76
78
|
end
|
77
79
|
|
78
80
|
def test_inclusion_rule
|
@@ -108,9 +110,9 @@ class TestFeatureFlagClient < Minitest::Test
|
|
108
110
|
)
|
109
111
|
|
110
112
|
assert_equal "rule target",
|
111
|
-
|
113
|
+
evaluate(feature, "user:1", [], flag, variants)
|
112
114
|
assert_equal "default",
|
113
|
-
|
115
|
+
evaluate(feature, "user:2", [], flag, variants)
|
114
116
|
|
115
117
|
end
|
116
118
|
|
@@ -148,13 +150,13 @@ class TestFeatureFlagClient < Minitest::Test
|
|
148
150
|
)
|
149
151
|
|
150
152
|
assert_equal "default",
|
151
|
-
|
153
|
+
evaluate(feature, "user:1", { email: "not@example.com" }, flag, variants)
|
152
154
|
assert_equal "default",
|
153
|
-
|
155
|
+
evaluate(feature, "user:2", {}, flag, variants)
|
154
156
|
assert_equal "rule target",
|
155
|
-
|
157
|
+
evaluate(feature, "user:2", { email: "b@example.com" }, flag, variants)
|
156
158
|
assert_equal "rule target",
|
157
|
-
|
159
|
+
evaluate(feature, "user:2", { "email" => "b@example.com" }, flag, variants)
|
158
160
|
|
159
161
|
end
|
160
162
|
|
@@ -223,9 +225,9 @@ class TestFeatureFlagClient < Minitest::Test
|
|
223
225
|
)
|
224
226
|
|
225
227
|
assert_equal "rule target",
|
226
|
-
|
228
|
+
evaluate(feature, "user:1", [], flag, variants)
|
227
229
|
assert_equal "default",
|
228
|
-
|
230
|
+
evaluate(feature, "user:2", [], flag, variants)
|
229
231
|
|
230
232
|
end
|
231
233
|
|
@@ -285,16 +287,20 @@ class TestFeatureFlagClient < Minitest::Test
|
|
285
287
|
)
|
286
288
|
|
287
289
|
assert_equal "rule target",
|
288
|
-
|
290
|
+
evaluate(feature, "user:1", [], flag, variants)
|
289
291
|
assert_equal "rule target",
|
290
|
-
|
292
|
+
evaluate(feature, "user:2", [], flag, variants), "matches segment 1"
|
291
293
|
assert_equal "rule target",
|
292
|
-
|
294
|
+
evaluate(feature, "user:3", [], flag, variants)
|
293
295
|
assert_equal "rule target",
|
294
|
-
|
296
|
+
evaluate(feature, "user:4", [], flag, variants)
|
295
297
|
assert_equal "default",
|
296
|
-
|
298
|
+
evaluate(feature, "user:5", [], flag, variants)
|
297
299
|
|
298
300
|
end
|
299
301
|
|
302
|
+
def evaluate(feature_name, lookup_key, attributes, flag, variants)
|
303
|
+
variant = @client.get_variant(feature_name, lookup_key, attributes, flag, variants)
|
304
|
+
@client.value_of_variant(variant)
|
305
|
+
end
|
300
306
|
end
|
data/test/test_helper.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'minitest/autorun'
|
2
3
|
require 'prefab-cloud-ruby'
|
3
4
|
|
@@ -35,8 +36,8 @@ class MockConfigClient
|
|
35
36
|
def initialize(config_values = {})
|
36
37
|
@config_values = config_values
|
37
38
|
end
|
38
|
-
def get(key)
|
39
|
-
@config_values
|
39
|
+
def get(key, default=nil)
|
40
|
+
@config_values.fetch(key, default)
|
40
41
|
end
|
41
42
|
|
42
43
|
def get_config(key)
|
@@ -67,3 +68,22 @@ def default_ff_rule(variant_idx)
|
|
67
68
|
)
|
68
69
|
]
|
69
70
|
end
|
71
|
+
|
72
|
+
def with_env(key, value)
|
73
|
+
old_value = ENV[key]
|
74
|
+
|
75
|
+
ENV[key] = value
|
76
|
+
ensure
|
77
|
+
ENV[key] = old_value
|
78
|
+
end
|
79
|
+
|
80
|
+
def new_client(overrides = {})
|
81
|
+
options = Prefab::Options.new(**{
|
82
|
+
prefab_config_override_dir: "none",
|
83
|
+
prefab_config_classpath_dir: "test",
|
84
|
+
defaults_env: "unit_tests",
|
85
|
+
prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
|
86
|
+
}.merge(overrides))
|
87
|
+
|
88
|
+
Prefab::Client.new(options)
|
89
|
+
end
|
data/test/test_logger.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'test_helper'
|
2
3
|
|
3
4
|
class TestCLogger < Minitest::Test
|
@@ -18,24 +19,27 @@ class TestCLogger < Minitest::Test
|
|
18
19
|
end
|
19
20
|
|
20
21
|
def test_level_of
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
22
|
+
with_env("PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL", "info") do
|
23
|
+
# env var overrides the default level
|
24
|
+
assert_equal Logger::INFO,
|
25
|
+
@logger.level_of("app.models.user"), "PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL is info"
|
26
|
+
|
27
|
+
@logger.set_config_client(MockConfigClient.new({}))
|
28
|
+
assert_equal Logger::WARN,
|
29
|
+
@logger.level_of("app.models.user"), "default is warn"
|
30
|
+
|
31
|
+
@logger.set_config_client(MockConfigClient.new("log_level.app" => "info"))
|
32
|
+
assert_equal Logger::INFO,
|
33
|
+
@logger.level_of("app.models.user")
|
34
|
+
|
35
|
+
@logger.set_config_client(MockConfigClient.new("log_level.app" => "debug"))
|
36
|
+
assert_equal Logger::DEBUG,
|
37
|
+
@logger.level_of("app.models.user")
|
38
|
+
|
39
|
+
@logger.set_config_client(MockConfigClient.new("log_level.app" => "debug",
|
40
|
+
"log_level.app.models" => "warn"))
|
41
|
+
assert_equal Logger::WARN,
|
42
|
+
@logger.level_of("app.models.user")
|
43
|
+
end
|
40
44
|
end
|
41
45
|
end
|
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.
|
4
|
+
version: 0.13.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-
|
11
|
+
date: 2022-08-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -114,20 +114,6 @@ dependencies:
|
|
114
114
|
- - ">="
|
115
115
|
- !ruby/object:Gem::Version
|
116
116
|
version: '0'
|
117
|
-
- !ruby/object:Gem::Dependency
|
118
|
-
name: shoulda
|
119
|
-
requirement: !ruby/object:Gem::Requirement
|
120
|
-
requirements:
|
121
|
-
- - ">="
|
122
|
-
- !ruby/object:Gem::Version
|
123
|
-
version: '0'
|
124
|
-
type: :development
|
125
|
-
prerelease: false
|
126
|
-
version_requirements: !ruby/object:Gem::Requirement
|
127
|
-
requirements:
|
128
|
-
- - ">="
|
129
|
-
- !ruby/object:Gem::Version
|
130
|
-
version: '0'
|
131
117
|
- !ruby/object:Gem::Dependency
|
132
118
|
name: rdoc
|
133
119
|
requirement: !ruby/object:Gem::Requirement
|
@@ -207,6 +193,7 @@ extra_rdoc_files:
|
|
207
193
|
- README.md
|
208
194
|
files:
|
209
195
|
- ".envrc"
|
196
|
+
- ".github/workflows/ruby.yml"
|
210
197
|
- ".tool-versions"
|
211
198
|
- CODEOWNERS
|
212
199
|
- Gemfile
|
@@ -224,6 +211,10 @@ files:
|
|
224
211
|
- lib/prefab/config_helper.rb
|
225
212
|
- lib/prefab/config_loader.rb
|
226
213
|
- lib/prefab/config_resolver.rb
|
214
|
+
- lib/prefab/error.rb
|
215
|
+
- lib/prefab/errors/initialization_timeout_error.rb
|
216
|
+
- lib/prefab/errors/invalid_api_key_error.rb
|
217
|
+
- lib/prefab/errors/missing_default_error.rb
|
227
218
|
- lib/prefab/feature_flag_client.rb
|
228
219
|
- lib/prefab/internal_logger.rb
|
229
220
|
- lib/prefab/logger_client.rb
|
@@ -231,7 +222,6 @@ files:
|
|
231
222
|
- lib/prefab/noop_cache.rb
|
232
223
|
- lib/prefab/noop_stats.rb
|
233
224
|
- lib/prefab/options.rb
|
234
|
-
- lib/prefab/prefab.rb
|
235
225
|
- lib/prefab/ratelimit_client.rb
|
236
226
|
- lib/prefab_pb.rb
|
237
227
|
- lib/prefab_services_pb.rb
|
@@ -239,6 +229,7 @@ files:
|
|
239
229
|
- run_test_harness_server.sh
|
240
230
|
- test/.prefab.test.config.yaml
|
241
231
|
- test/harness_server.rb
|
232
|
+
- test/test_client.rb
|
242
233
|
- test/test_config_client.rb
|
243
234
|
- test/test_config_loader.rb
|
244
235
|
- test/test_config_resolver.rb
|
data/lib/prefab/prefab.rb
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
module Prefab
|
2
|
-
|
3
|
-
@@singleton__mutex__ = Mutex.new
|
4
|
-
|
5
|
-
public
|
6
|
-
|
7
|
-
def self.initialize(options = nil)
|
8
|
-
@@singleton__mutex__.synchronize do
|
9
|
-
unless @singleton.nil?
|
10
|
-
puts 'Prefab Double Initialization'
|
11
|
-
return @singleton
|
12
|
-
end
|
13
|
-
@singleton = Prefab::Client.new(options)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.get(key)
|
18
|
-
require_initialization
|
19
|
-
@singleton.config_client.get(key)
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def self.require_initialization
|
25
|
-
if @singleton.nil?
|
26
|
-
puts "Prefab initializing for you. You probably want to call Prefab.initialize(Prefab::Options.new())"
|
27
|
-
self.initialize
|
28
|
-
end
|
29
|
-
end
|
30
|
-
module_function initialize
|
31
|
-
module_function get
|
32
|
-
end
|