prefab-cloud-ruby 0.12.0 → 0.13.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 +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
|