prefab-cloud-ruby 0.20.0 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
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'