prefab-cloud-ruby 0.20.0 → 0.21.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/.envrc.sample +3 -0
- data/.github/workflows/ruby.yml +4 -0
- data/.gitmodules +3 -0
- data/Gemfile +12 -12
- data/Gemfile.lock +16 -14
- data/README.md +1 -1
- data/Rakefile +13 -14
- data/VERSION +1 -1
- data/lib/prefab/auth_interceptor.rb +2 -1
- data/lib/prefab/cancellable_interceptor.rb +8 -7
- data/lib/prefab/client.rb +33 -24
- data/lib/prefab/config_client.rb +55 -66
- data/lib/prefab/config_loader.rb +7 -114
- data/lib/prefab/config_resolver.rb +27 -57
- data/lib/prefab/config_value_unwrapper.rb +23 -0
- data/lib/prefab/criteria_evaluator.rb +96 -0
- data/lib/prefab/errors/invalid_api_key_error.rb +1 -1
- data/lib/prefab/feature_flag_client.rb +13 -145
- data/lib/prefab/internal_logger.rb +6 -5
- data/lib/prefab/local_config_parser.rb +110 -0
- data/lib/prefab/logger_client.rb +26 -31
- data/lib/prefab/murmer3.rb +3 -4
- data/lib/prefab/noop_cache.rb +5 -7
- data/lib/prefab/noop_stats.rb +2 -3
- data/lib/prefab/options.rb +11 -9
- data/lib/prefab/ratelimit_client.rb +11 -13
- data/lib/prefab/sse_logger.rb +3 -2
- data/lib/prefab/weighted_value_resolver.rb +42 -0
- data/lib/prefab/yaml_config_parser.rb +32 -0
- data/lib/prefab-cloud-ruby.rb +7 -2
- data/lib/prefab_pb.rb +49 -43
- data/lib/prefab_services_pb.rb +0 -1
- data/prefab-cloud-ruby.gemspec +28 -19
- data/test/.prefab.unit_tests.config.yaml +3 -2
- data/test/integration_test.rb +98 -0
- data/test/integration_test_helpers.rb +37 -0
- data/test/test_client.rb +32 -31
- data/test/test_config_client.rb +21 -20
- data/test/test_config_loader.rb +48 -37
- data/test/test_config_resolver.rb +312 -135
- data/test/test_config_value_unwrapper.rb +83 -0
- data/test/test_criteria_evaluator.rb +533 -0
- data/test/test_feature_flag_client.rb +35 -347
- data/test/test_helper.rb +18 -14
- data/test/test_integration.rb +33 -0
- data/test/test_local_config_parser.rb +78 -0
- data/test/test_logger.rb +47 -46
- data/test/test_weighted_value_resolver.rb +65 -0
- metadata +24 -27
- data/lib/prefab/config_helper.rb +0 -31
- data/run_test_harness_server.sh +0 -8
- data/test/harness_server.rb +0 -64
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prefab
|
4
|
+
class LocalConfigParser
|
5
|
+
class << self
|
6
|
+
def parse(key, value, config, file)
|
7
|
+
if value.instance_of?(Hash)
|
8
|
+
if value['feature_flag']
|
9
|
+
config[key] = feature_flag_config(file, key, value)
|
10
|
+
else
|
11
|
+
value.each do |nest_key, nest_value|
|
12
|
+
nested_key = "#{key}.#{nest_key}"
|
13
|
+
nested_key = key if nest_key == '_'
|
14
|
+
parse(nested_key, nest_value, config, file)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
else
|
18
|
+
config[key] = {
|
19
|
+
source: file,
|
20
|
+
match: 'default',
|
21
|
+
config: Prefab::Config.new(
|
22
|
+
config_type: :CONFIG,
|
23
|
+
key: key,
|
24
|
+
rows: [
|
25
|
+
Prefab::ConfigRow.new(values: [
|
26
|
+
Prefab::ConditionalValue.new(value: value_from(key, value))
|
27
|
+
])
|
28
|
+
]
|
29
|
+
)
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
config
|
34
|
+
end
|
35
|
+
|
36
|
+
def value_from(key, raw)
|
37
|
+
case raw
|
38
|
+
when String
|
39
|
+
if key.to_s.start_with? Prefab::LoggerClient::BASE_KEY
|
40
|
+
prefab_log_level_resolve = Prefab::LogLevel.resolve(raw.upcase.to_sym) || Prefab::LogLevel::NOT_SET_LOG_LEVEL
|
41
|
+
{ log_level: prefab_log_level_resolve }
|
42
|
+
else
|
43
|
+
{ string: raw }
|
44
|
+
end
|
45
|
+
when Integer
|
46
|
+
{ int: raw }
|
47
|
+
when TrueClass, FalseClass
|
48
|
+
{ bool: raw }
|
49
|
+
when Float
|
50
|
+
{ double: raw }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def feature_flag_config(file, key, value)
|
55
|
+
criterion = (parse_criterion(value['criterion']) if value['criterion'])
|
56
|
+
|
57
|
+
variant = Prefab::ConfigValue.new(value_from(key, value['value']))
|
58
|
+
|
59
|
+
row = Prefab::ConfigRow.new(
|
60
|
+
values: [
|
61
|
+
Prefab::ConditionalValue.new(
|
62
|
+
criteria: [criterion].compact,
|
63
|
+
value: Prefab::ConfigValue.new(
|
64
|
+
weighted_values: Prefab::WeightedValues.new(weighted_values: [
|
65
|
+
Prefab::WeightedValue.new(
|
66
|
+
weight: 1000,
|
67
|
+
value: variant
|
68
|
+
)
|
69
|
+
])
|
70
|
+
)
|
71
|
+
)
|
72
|
+
]
|
73
|
+
)
|
74
|
+
|
75
|
+
raise Prefab::Error, "Feature flag config `#{key}` #{file} must have a `value`" unless value.key?('value')
|
76
|
+
|
77
|
+
{
|
78
|
+
source: file,
|
79
|
+
match: key,
|
80
|
+
config: Prefab::Config.new(
|
81
|
+
config_type: :FEATURE_FLAG,
|
82
|
+
key: key,
|
83
|
+
allowable_values: [variant],
|
84
|
+
rows: [row]
|
85
|
+
)
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse_criterion(criterion)
|
90
|
+
Prefab::Criterion.new(operator: criterion['operator'],
|
91
|
+
property_name: parse_property(criterion),
|
92
|
+
value_to_match: parse_value_to_match(criterion['values']))
|
93
|
+
end
|
94
|
+
|
95
|
+
def parse_property(criterion)
|
96
|
+
if criterion['operator'] == 'LOOKUP_KEY_IN'
|
97
|
+
Prefab::CriteriaEvaluator::LOOKUP_KEY
|
98
|
+
else
|
99
|
+
criterion['property']
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def parse_value_to_match(values)
|
104
|
+
raise "Can't handle #{values}" unless values.instance_of?(Array)
|
105
|
+
|
106
|
+
Prefab::ConfigValue.new(string_list: Prefab::StringList.new(values: values))
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/lib/prefab/logger_client.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Prefab
|
3
4
|
class LoggerClient < Logger
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
INTERNAL_PREFIX = "cloud.prefab.client"
|
5
|
+
SEP = '.'
|
6
|
+
BASE_KEY = 'log-level'
|
7
|
+
UNKNOWN_PATH = 'unknown.'
|
8
|
+
INTERNAL_PREFIX = 'cloud.prefab.client'
|
9
9
|
|
10
10
|
LOG_LEVEL_LOOKUPS = {
|
11
11
|
Prefab::LogLevel::NOT_SET_LOG_LEVEL => Logger::DEBUG,
|
@@ -33,20 +33,18 @@ module Prefab
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def log_internal(message, path = nil, progname, severity, &block)
|
36
|
-
if path
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
36
|
+
path = if path
|
37
|
+
"#{INTERNAL_PREFIX}.#{path}"
|
38
|
+
else
|
39
|
+
INTERNAL_PREFIX
|
40
|
+
end
|
41
41
|
|
42
42
|
log(message, path, progname, severity, &block)
|
43
43
|
end
|
44
44
|
|
45
|
-
def log(message, path, progname, severity
|
45
|
+
def log(message, path, progname, severity)
|
46
46
|
severity ||= Logger::UNKNOWN
|
47
|
-
if @logdev.nil? || severity < level_of(path) || @silences[local_log_id]
|
48
|
-
return true
|
49
|
-
end
|
47
|
+
return true if @logdev.nil? || severity < level_of(path) || @silences[local_log_id]
|
50
48
|
|
51
49
|
progname = "#{path}: #{progname || @progname}"
|
52
50
|
|
@@ -60,7 +58,8 @@ module Prefab
|
|
60
58
|
end
|
61
59
|
|
62
60
|
@logdev.write(
|
63
|
-
format_message(format_severity(severity), Time.now, progname, message)
|
61
|
+
format_message(format_severity(severity), Time.now, progname, message)
|
62
|
+
)
|
64
63
|
true
|
65
64
|
end
|
66
65
|
|
@@ -85,23 +84,23 @@ module Prefab
|
|
85
84
|
end
|
86
85
|
|
87
86
|
def debug?
|
88
|
-
true
|
87
|
+
true
|
89
88
|
end
|
90
89
|
|
91
90
|
def info?
|
92
|
-
true
|
91
|
+
true
|
93
92
|
end
|
94
93
|
|
95
94
|
def warn?
|
96
|
-
true
|
95
|
+
true
|
97
96
|
end
|
98
97
|
|
99
98
|
def error?
|
100
|
-
true
|
99
|
+
true
|
101
100
|
end
|
102
101
|
|
103
102
|
def fatal?
|
104
|
-
true
|
103
|
+
true
|
105
104
|
end
|
106
105
|
|
107
106
|
def level
|
@@ -128,13 +127,10 @@ module Prefab
|
|
128
127
|
# Find the closest match to 'log_level.path' in config
|
129
128
|
def level_of(path)
|
130
129
|
closest_log_level_match = @config_client.get(BASE_KEY, :WARN)
|
131
|
-
path.split(SEP).
|
130
|
+
path.split(SEP).each_with_object([BASE_KEY]) do |n, memo|
|
132
131
|
memo << n
|
133
132
|
val = @config_client.get(memo.join(SEP), nil)
|
134
|
-
unless val.nil?
|
135
|
-
closest_log_level_match = val
|
136
|
-
end
|
137
|
-
memo
|
133
|
+
closest_log_level_match = val unless val.nil?
|
138
134
|
end
|
139
135
|
closest_log_level_match_int = Prefab::LogLevel.resolve(closest_log_level_match)
|
140
136
|
LOG_LEVEL_LOOKUPS[closest_log_level_match_int]
|
@@ -150,10 +146,10 @@ module Prefab
|
|
150
146
|
def get_path(absolute_path, base_label)
|
151
147
|
path = (absolute_path || UNKNOWN_PATH).dup
|
152
148
|
path.slice! Dir.pwd
|
153
|
-
path.gsub!(
|
149
|
+
path.gsub!(%r{(.*)?(?=/lib)}im, '') # replace everything before first lib
|
154
150
|
|
155
|
-
path = path.gsub(
|
156
|
-
path.slice!
|
151
|
+
path = path.gsub('/', SEP).gsub(/.rb.*/, '') + SEP + base_label
|
152
|
+
path.slice! '.lib'
|
157
153
|
path.slice! SEP
|
158
154
|
path
|
159
155
|
end
|
@@ -162,9 +158,8 @@ module Prefab
|
|
162
158
|
# StubConfigClient to be used while config client initializes
|
163
159
|
# since it may log
|
164
160
|
class BootstrappingConfigClient
|
165
|
-
def get(
|
166
|
-
ENV[
|
161
|
+
def get(_key, default = nil)
|
162
|
+
ENV['PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL'] ? ENV['PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL'].upcase.to_sym : default
|
167
163
|
end
|
168
164
|
end
|
169
165
|
end
|
170
|
-
|
data/lib/prefab/murmer3.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
class Murmur3
|
3
4
|
## MurmurHash3 was written by Austin Appleby, and is placed in the public
|
4
5
|
## domain. The author hereby disclaims copyright to this source code.
|
@@ -9,7 +10,6 @@ class Murmur3
|
|
9
10
|
((x << r) | (x >> (32 - r))) & MASK32
|
10
11
|
end
|
11
12
|
|
12
|
-
|
13
13
|
def self.murmur3_32_fmix(h)
|
14
14
|
h &= MASK32
|
15
15
|
h ^= h >> 16
|
@@ -25,7 +25,7 @@ class Murmur3
|
|
25
25
|
(k1 * 0x1b873593) & MASK32
|
26
26
|
end
|
27
27
|
|
28
|
-
def self.murmur3_32(str, seed=0)
|
28
|
+
def self.murmur3_32(str, seed = 0)
|
29
29
|
h1 = seed
|
30
30
|
numbers = str.unpack('V*C*')
|
31
31
|
tailn = str.length % 4
|
@@ -33,7 +33,7 @@ class Murmur3
|
|
33
33
|
for k1 in numbers
|
34
34
|
h1 ^= murmur3_32__mmix(k1)
|
35
35
|
h1 = murmur3_32_rotl(h1, 13)
|
36
|
-
h1 = (h1*5 + 0xe6546b64) & MASK32
|
36
|
+
h1 = (h1 * 5 + 0xe6546b64) & MASK32
|
37
37
|
end
|
38
38
|
|
39
39
|
unless tail.empty?
|
@@ -47,5 +47,4 @@ class Murmur3
|
|
47
47
|
h1 ^= str.length
|
48
48
|
murmur3_32_fmix(h1)
|
49
49
|
end
|
50
|
-
|
51
50
|
end
|
data/lib/prefab/noop_cache.rb
CHANGED
@@ -1,17 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Prefab
|
3
4
|
class NoopCache
|
4
|
-
def fetch(
|
5
|
+
def fetch(_name, _opts)
|
5
6
|
yield
|
6
7
|
end
|
7
8
|
|
8
|
-
def write(name, value, opts=nil)
|
9
|
-
end
|
9
|
+
def write(name, value, opts = nil); end
|
10
10
|
|
11
|
-
def read(name)
|
12
|
-
end
|
11
|
+
def read(name); end
|
13
12
|
|
14
|
-
def delete(name)
|
15
|
-
end
|
13
|
+
def delete(name); end
|
16
14
|
end
|
17
15
|
end
|
data/lib/prefab/noop_stats.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
module Prefab
|
3
2
|
|
3
|
+
module Prefab
|
4
4
|
class NoopStats
|
5
5
|
# receives increment("prefab.ratelimit.limitcheck", {:tags=>["policy_group:page_view", "pass:true"]})
|
6
|
-
def increment(name, opts={})
|
7
|
-
end
|
6
|
+
def increment(name, opts = {}); end
|
8
7
|
end
|
9
8
|
end
|
data/lib/prefab/options.rb
CHANGED
@@ -19,17 +19,19 @@ module Prefab
|
|
19
19
|
attr_reader :prefab_envs
|
20
20
|
|
21
21
|
DEFAULT_LOG_FORMATTER = proc { |severity, datetime, progname, msg|
|
22
|
-
"#{severity.ljust(5)} #{datetime}:#{
|
22
|
+
"#{severity.ljust(5)} #{datetime}:#{' ' if progname}#{progname} #{msg}\n"
|
23
23
|
}
|
24
24
|
|
25
25
|
module ON_INITIALIZATION_FAILURE
|
26
26
|
RAISE = 1
|
27
27
|
RETURN = 2
|
28
28
|
end
|
29
|
+
|
29
30
|
module ON_NO_DEFAULT
|
30
31
|
RAISE = 1
|
31
32
|
RETURN_NIL = 2
|
32
33
|
end
|
34
|
+
|
33
35
|
module DATASOURCES
|
34
36
|
ALL = 1
|
35
37
|
LOCAL_ONLY = 2
|
@@ -40,20 +42,20 @@ module Prefab
|
|
40
42
|
logdev: $stdout,
|
41
43
|
stats: NoopStats.new, # receives increment("prefab.limitcheck", {:tags=>["policy_group:page_view", "pass:true"]})
|
42
44
|
shared_cache: NoopCache.new, # Something that quacks like Rails.cache ideally memcached
|
43
|
-
namespace:
|
45
|
+
namespace: '',
|
44
46
|
log_formatter: DEFAULT_LOG_FORMATTER,
|
45
47
|
log_prefix: nil,
|
46
|
-
prefab_api_url: ENV[
|
47
|
-
prefab_grpc_url: ENV[
|
48
|
+
prefab_api_url: ENV['PREFAB_API_URL'] || 'https://api.prefab.cloud',
|
49
|
+
prefab_grpc_url: ENV['PREFAB_GRPC_URL'] || 'grpc.prefab.cloud:443',
|
48
50
|
on_no_default: ON_NO_DEFAULT::RAISE, # options :raise, :warn_and_return_nil,
|
49
51
|
initialization_timeout_sec: 10, # how long to wait before on_init_failure
|
50
|
-
on_init_failure: ON_INITIALIZATION_FAILURE::RAISE, #options :unlock_and_continue, :lock_and_keep_trying, :raise
|
52
|
+
on_init_failure: ON_INITIALIZATION_FAILURE::RAISE, # options :unlock_and_continue, :lock_and_keep_trying, :raise
|
51
53
|
# new_config_callback: nil, #callback method
|
52
54
|
# live_override_url: nil,
|
53
|
-
prefab_datasources: ENV['PREFAB_DATASOURCES'] ==
|
55
|
+
prefab_datasources: ENV['PREFAB_DATASOURCES'] == 'LOCAL_ONLY' ? DATASOURCES::LOCAL_ONLY : DATASOURCES::ALL,
|
54
56
|
prefab_config_override_dir: Dir.home,
|
55
|
-
prefab_config_classpath_dir:
|
56
|
-
prefab_envs:
|
57
|
+
prefab_config_classpath_dir: '.',
|
58
|
+
prefab_envs: ENV['PREFAB_ENVS'].nil? ? [] : ENV['PREFAB_ENVS'].split(',')
|
57
59
|
)
|
58
60
|
# debugger
|
59
61
|
@api_key = api_key
|
@@ -80,7 +82,7 @@ module Prefab
|
|
80
82
|
|
81
83
|
# https://api.prefab.cloud -> https://api-prefab-cloud.global.ssl.fastly.net
|
82
84
|
def url_for_api_cdn
|
83
|
-
ENV['PREFAB_CDN_URL'] || "#{@prefab_api_url.gsub(/\./,
|
85
|
+
ENV['PREFAB_CDN_URL'] || "#{@prefab_api_url.gsub(/\./, '-')}.global.ssl.fastly.net"
|
84
86
|
end
|
85
87
|
end
|
86
88
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Prefab
|
3
4
|
class RateLimitClient
|
4
|
-
|
5
5
|
def initialize(base_client, timeout)
|
6
6
|
@timeout = timeout
|
7
7
|
@base_client = base_client
|
@@ -9,14 +9,14 @@ module Prefab
|
|
9
9
|
|
10
10
|
def pass?(group)
|
11
11
|
result = acquire([group], 1)
|
12
|
-
|
12
|
+
result.passed
|
13
13
|
end
|
14
14
|
|
15
15
|
def acquire(groups, acquire_amount, allow_partial_response: false, on_error: :log_and_pass)
|
16
|
-
expiry_cache_key = "prefab.ratelimit.expiry:#{groups.join(
|
16
|
+
expiry_cache_key = "prefab.ratelimit.expiry:#{groups.join('.')}"
|
17
17
|
expiry = @base_client.shared_cache.read(expiry_cache_key)
|
18
18
|
if !expiry.nil? && Integer(expiry) > Time.now.utc.to_f * 1000
|
19
|
-
@base_client.stats.increment(
|
19
|
+
@base_client.stats.increment('prefab.ratelimit.limitcheck.expirycache.hit', tags: [])
|
20
20
|
return Prefab::LimitResponse.new(passed: false, amount: 0)
|
21
21
|
end
|
22
22
|
|
@@ -27,16 +27,17 @@ module Prefab
|
|
27
27
|
allow_partial_response: allow_partial_response
|
28
28
|
)
|
29
29
|
|
30
|
-
result = @base_client.request Prefab::RateLimitService, :limit_check, req_options: {timeout: @timeout},
|
30
|
+
result = @base_client.request Prefab::RateLimitService, :limit_check, req_options: { timeout: @timeout },
|
31
|
+
params: req
|
31
32
|
|
32
33
|
reset = result.limit_reset_at
|
33
34
|
@base_client.shared_cache.write(expiry_cache_key, reset) unless reset < 1 # protobuf default int to 0
|
34
35
|
|
35
|
-
@base_client.stats.increment(
|
36
|
+
@base_client.stats.increment('prefab.ratelimit.limitcheck',
|
37
|
+
tags: ["policy_group:#{result.policy_group}", "pass:#{result.passed}"])
|
36
38
|
|
37
39
|
result
|
38
|
-
|
39
|
-
rescue => e
|
40
|
+
rescue StandardError => e
|
40
41
|
handle_error(e, on_error, groups)
|
41
42
|
end
|
42
43
|
|
@@ -48,9 +49,7 @@ module Prefab
|
|
48
49
|
limit: limit,
|
49
50
|
burst: burst
|
50
51
|
)
|
51
|
-
unless safety_level.nil?
|
52
|
-
limit_definition.safety_level = safety_level
|
53
|
-
end
|
52
|
+
limit_definition.safety_level = safety_level unless safety_level.nil?
|
54
53
|
config_value = Prefab::ConfigValue.new(limit_definition: limit_definition)
|
55
54
|
config_delta = Prefab::ConfigClient.value_to_delta(key, config_value)
|
56
55
|
upsert_req = Prefab::UpsertRequest.new(config_delta: config_delta)
|
@@ -61,7 +60,7 @@ module Prefab
|
|
61
60
|
private
|
62
61
|
|
63
62
|
def handle_error(e, on_error, groups)
|
64
|
-
@base_client.stats.increment(
|
63
|
+
@base_client.stats.increment('prefab.ratelimit.error', tags: ['type:limit'])
|
65
64
|
|
66
65
|
message = "ratelimit for #{groups} error: #{e.message}"
|
67
66
|
case on_error
|
@@ -77,4 +76,3 @@ module Prefab
|
|
77
76
|
end
|
78
77
|
end
|
79
78
|
end
|
80
|
-
|
data/lib/prefab/sse_logger.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Prefab
|
3
4
|
class SseLogger < InternalLogger
|
4
5
|
def initialize(logger)
|
5
|
-
super(
|
6
|
+
super('sse', logger)
|
6
7
|
end
|
7
8
|
|
8
9
|
# The SSE::Client warns on a perfectly normal stream disconnect, recast to info
|
9
|
-
def warn(progname = nil
|
10
|
+
def warn(progname = nil)
|
10
11
|
@logger.log_internal yield, @path, progname, INFO
|
11
12
|
end
|
12
13
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prefab
|
4
|
+
class WeightedValueResolver
|
5
|
+
MAX_32_FLOAT = 4_294_967_294.0
|
6
|
+
|
7
|
+
def initialize(weights, config_key, lookup_key)
|
8
|
+
@weights = weights
|
9
|
+
@config_key = config_key
|
10
|
+
@lookup_key = lookup_key
|
11
|
+
end
|
12
|
+
|
13
|
+
def resolve
|
14
|
+
percent = @lookup_key ? user_percent : rand
|
15
|
+
|
16
|
+
index = variant_index(percent)
|
17
|
+
|
18
|
+
@weights[index]
|
19
|
+
end
|
20
|
+
|
21
|
+
def user_percent
|
22
|
+
to_hash = "#{@config_key}#{@lookup_key}"
|
23
|
+
int_value = Murmur3.murmur3_32(to_hash)
|
24
|
+
int_value / MAX_32_FLOAT
|
25
|
+
end
|
26
|
+
|
27
|
+
def variant_index(percent_through_distribution)
|
28
|
+
distribution_space = @weights.inject(0) { |sum, v| sum + v.weight }
|
29
|
+
bucket = distribution_space * percent_through_distribution
|
30
|
+
|
31
|
+
sum = 0
|
32
|
+
@weights.each_with_index do |variant_weight, index|
|
33
|
+
return index if bucket < sum + variant_weight.weight
|
34
|
+
|
35
|
+
sum += variant_weight.weight
|
36
|
+
end
|
37
|
+
|
38
|
+
# In the event that all weights are zero, return the last variant
|
39
|
+
@weights.size - 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Prefab
|
4
|
+
class YAMLConfigParser
|
5
|
+
def initialize(file, client)
|
6
|
+
@file = file
|
7
|
+
@client = client
|
8
|
+
end
|
9
|
+
|
10
|
+
def merge(config)
|
11
|
+
yaml = load
|
12
|
+
|
13
|
+
yaml.each do |k, v|
|
14
|
+
config = Prefab::LocalConfigParser.parse(k, v, config, @file)
|
15
|
+
end
|
16
|
+
|
17
|
+
config
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def load
|
23
|
+
if File.exist?(@file)
|
24
|
+
@client.log_internal Logger::INFO, "Load #{@file}"
|
25
|
+
YAML.load_file(@file)
|
26
|
+
else
|
27
|
+
@client.log_internal Logger::INFO, "No file #{@file}"
|
28
|
+
{}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/prefab-cloud-ruby.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
require 'concurrent/atomics'
|
3
4
|
require 'concurrent'
|
4
5
|
require 'faraday'
|
5
6
|
require 'openssl'
|
@@ -14,8 +15,12 @@ require 'prefab_services_pb'
|
|
14
15
|
require 'prefab/options'
|
15
16
|
require 'prefab/internal_logger'
|
16
17
|
require 'prefab/sse_logger'
|
17
|
-
require 'prefab/
|
18
|
+
require 'prefab/weighted_value_resolver'
|
19
|
+
require 'prefab/config_value_unwrapper'
|
20
|
+
require 'prefab/criteria_evaluator'
|
18
21
|
require 'prefab/config_loader'
|
22
|
+
require 'prefab/local_config_parser'
|
23
|
+
require 'prefab/yaml_config_parser'
|
19
24
|
require 'prefab/config_resolver'
|
20
25
|
require 'prefab/client'
|
21
26
|
require 'prefab/ratelimit_client'
|