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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc.sample +3 -0
  3. data/.github/workflows/ruby.yml +4 -0
  4. data/.gitmodules +3 -0
  5. data/Gemfile +12 -12
  6. data/Gemfile.lock +16 -14
  7. data/README.md +1 -1
  8. data/Rakefile +13 -14
  9. data/VERSION +1 -1
  10. data/lib/prefab/auth_interceptor.rb +2 -1
  11. data/lib/prefab/cancellable_interceptor.rb +8 -7
  12. data/lib/prefab/client.rb +33 -24
  13. data/lib/prefab/config_client.rb +55 -66
  14. data/lib/prefab/config_loader.rb +7 -114
  15. data/lib/prefab/config_resolver.rb +27 -57
  16. data/lib/prefab/config_value_unwrapper.rb +23 -0
  17. data/lib/prefab/criteria_evaluator.rb +96 -0
  18. data/lib/prefab/errors/invalid_api_key_error.rb +1 -1
  19. data/lib/prefab/feature_flag_client.rb +13 -145
  20. data/lib/prefab/internal_logger.rb +6 -5
  21. data/lib/prefab/local_config_parser.rb +110 -0
  22. data/lib/prefab/logger_client.rb +26 -31
  23. data/lib/prefab/murmer3.rb +3 -4
  24. data/lib/prefab/noop_cache.rb +5 -7
  25. data/lib/prefab/noop_stats.rb +2 -3
  26. data/lib/prefab/options.rb +11 -9
  27. data/lib/prefab/ratelimit_client.rb +11 -13
  28. data/lib/prefab/sse_logger.rb +3 -2
  29. data/lib/prefab/weighted_value_resolver.rb +42 -0
  30. data/lib/prefab/yaml_config_parser.rb +32 -0
  31. data/lib/prefab-cloud-ruby.rb +7 -2
  32. data/lib/prefab_pb.rb +49 -43
  33. data/lib/prefab_services_pb.rb +0 -1
  34. data/prefab-cloud-ruby.gemspec +28 -19
  35. data/test/.prefab.unit_tests.config.yaml +3 -2
  36. data/test/integration_test.rb +98 -0
  37. data/test/integration_test_helpers.rb +37 -0
  38. data/test/test_client.rb +32 -31
  39. data/test/test_config_client.rb +21 -20
  40. data/test/test_config_loader.rb +48 -37
  41. data/test/test_config_resolver.rb +312 -135
  42. data/test/test_config_value_unwrapper.rb +83 -0
  43. data/test/test_criteria_evaluator.rb +533 -0
  44. data/test/test_feature_flag_client.rb +35 -347
  45. data/test/test_helper.rb +18 -14
  46. data/test/test_integration.rb +33 -0
  47. data/test/test_local_config_parser.rb +78 -0
  48. data/test/test_logger.rb +47 -46
  49. data/test/test_weighted_value_resolver.rb +65 -0
  50. metadata +24 -27
  51. data/lib/prefab/config_helper.rb +0 -31
  52. data/run_test_harness_server.sh +0 -8
  53. 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
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Prefab
3
4
  class LoggerClient < Logger
4
-
5
- SEP = "."
6
- BASE_KEY = "log-level"
7
- UNKNOWN_PATH = "unknown."
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
- path = "#{INTERNAL_PREFIX}.#{path}"
38
- else
39
- path = INTERNAL_PREFIX
40
- end
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, &block)
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).inject([BASE_KEY]) do |memo, n|
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!(/(.*)?(?=\/lib)/im, "") # replace everything before first lib
149
+ path.gsub!(%r{(.*)?(?=/lib)}im, '') # replace everything before first lib
154
150
 
155
- path = path.gsub("/", SEP).gsub(/.rb.*/, "") + SEP + base_label
156
- path.slice! ".lib"
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(key, default = nil)
166
- ENV["PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL"] ? ENV["PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL"].upcase.to_sym : default
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
-
@@ -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
@@ -1,17 +1,15 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Prefab
3
4
  class NoopCache
4
- def fetch(name, opts, &method)
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
@@ -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
@@ -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}:#{" " if progname}#{progname} #{msg}\n"
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["PREFAB_API_URL"] || 'https://api.prefab.cloud',
47
- prefab_grpc_url: ENV["PREFAB_GRPC_URL"] || 'grpc.prefab.cloud:443',
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'] == "LOCAL_ONLY" ? DATASOURCES::LOCAL_ONLY : DATASOURCES::ALL,
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: ENV['PREFAB_ENVS'].nil? ? [] : ENV['PREFAB_ENVS'].split(",")
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(/\./, "-")}.global.ssl.fastly.net"
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
- return result.passed
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("prefab.ratelimit.limitcheck.expirycache.hit", tags: [])
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}, params: req
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("prefab.ratelimit.limitcheck", tags: ["policy_group:#{result.policy_group}", "pass:#{result.passed}"])
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("prefab.ratelimit.error", tags: ["type:limit"])
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
-
@@ -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("sse", logger)
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, &block)
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
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
- require "concurrent/atomics"
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/config_helper'
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'