sdk-reforge 1.9.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 (103) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc.sample +3 -0
  3. data/.github/CODEOWNERS +2 -0
  4. data/.github/pull_request_template.md +8 -0
  5. data/.github/workflows/ruby.yml +48 -0
  6. data/.gitmodules +3 -0
  7. data/.rubocop.yml +13 -0
  8. data/.tool-versions +1 -0
  9. data/CHANGELOG.md +257 -0
  10. data/CODEOWNERS +1 -0
  11. data/Gemfile +29 -0
  12. data/Gemfile.lock +182 -0
  13. data/LICENSE.txt +20 -0
  14. data/README.md +105 -0
  15. data/Rakefile +63 -0
  16. data/VERSION +1 -0
  17. data/compile_protos.sh +20 -0
  18. data/dev/allocation_stats +60 -0
  19. data/dev/benchmark +40 -0
  20. data/dev/console +12 -0
  21. data/dev/script_setup.rb +18 -0
  22. data/lib/prefab_pb.rb +77 -0
  23. data/lib/reforge/caching_http_connection.rb +95 -0
  24. data/lib/reforge/client.rb +133 -0
  25. data/lib/reforge/config_client.rb +275 -0
  26. data/lib/reforge/config_client_presenter.rb +18 -0
  27. data/lib/reforge/config_loader.rb +67 -0
  28. data/lib/reforge/config_resolver.rb +84 -0
  29. data/lib/reforge/config_value_unwrapper.rb +123 -0
  30. data/lib/reforge/config_value_wrapper.rb +18 -0
  31. data/lib/reforge/context.rb +241 -0
  32. data/lib/reforge/context_shape.rb +20 -0
  33. data/lib/reforge/context_shape_aggregator.rb +70 -0
  34. data/lib/reforge/criteria_evaluator.rb +345 -0
  35. data/lib/reforge/duration.rb +58 -0
  36. data/lib/reforge/encryption.rb +65 -0
  37. data/lib/reforge/error.rb +6 -0
  38. data/lib/reforge/errors/env_var_parse_error.rb +11 -0
  39. data/lib/reforge/errors/initialization_timeout_error.rb +12 -0
  40. data/lib/reforge/errors/invalid_sdk_key_error.rb +19 -0
  41. data/lib/reforge/errors/missing_default_error.rb +13 -0
  42. data/lib/reforge/errors/missing_env_var_error.rb +11 -0
  43. data/lib/reforge/errors/uninitialized_error.rb +13 -0
  44. data/lib/reforge/evaluation.rb +53 -0
  45. data/lib/reforge/evaluation_summary_aggregator.rb +86 -0
  46. data/lib/reforge/example_contexts_aggregator.rb +77 -0
  47. data/lib/reforge/exponential_backoff.rb +21 -0
  48. data/lib/reforge/feature_flag_client.rb +43 -0
  49. data/lib/reforge/fixed_size_hash.rb +14 -0
  50. data/lib/reforge/http_connection.rb +45 -0
  51. data/lib/reforge/internal_logger.rb +43 -0
  52. data/lib/reforge/javascript_stub.rb +99 -0
  53. data/lib/reforge/local_config_parser.rb +151 -0
  54. data/lib/reforge/murmer3.rb +50 -0
  55. data/lib/reforge/options.rb +191 -0
  56. data/lib/reforge/periodic_sync.rb +74 -0
  57. data/lib/reforge/prefab.rb +120 -0
  58. data/lib/reforge/rate_limit_cache.rb +41 -0
  59. data/lib/reforge/resolved_config_presenter.rb +86 -0
  60. data/lib/reforge/semver.rb +132 -0
  61. data/lib/reforge/sse_config_client.rb +112 -0
  62. data/lib/reforge/time_helpers.rb +7 -0
  63. data/lib/reforge/weighted_value_resolver.rb +42 -0
  64. data/lib/reforge/yaml_config_parser.rb +34 -0
  65. data/lib/reforge-sdk.rb +57 -0
  66. data/test/fixtures/datafile.json +87 -0
  67. data/test/integration_test.rb +171 -0
  68. data/test/integration_test_helpers.rb +114 -0
  69. data/test/support/common_helpers.rb +201 -0
  70. data/test/support/mock_base_client.rb +41 -0
  71. data/test/support/mock_config_client.rb +19 -0
  72. data/test/support/mock_config_loader.rb +1 -0
  73. data/test/test_caching_http_connection.rb +218 -0
  74. data/test/test_client.rb +351 -0
  75. data/test/test_config_client.rb +84 -0
  76. data/test/test_config_loader.rb +82 -0
  77. data/test/test_config_resolver.rb +502 -0
  78. data/test/test_config_value_unwrapper.rb +270 -0
  79. data/test/test_config_value_wrapper.rb +42 -0
  80. data/test/test_context.rb +271 -0
  81. data/test/test_context_shape.rb +50 -0
  82. data/test/test_context_shape_aggregator.rb +150 -0
  83. data/test/test_criteria_evaluator.rb +1180 -0
  84. data/test/test_duration.rb +37 -0
  85. data/test/test_encryption.rb +16 -0
  86. data/test/test_evaluation_summary_aggregator.rb +162 -0
  87. data/test/test_example_contexts_aggregator.rb +233 -0
  88. data/test/test_exponential_backoff.rb +18 -0
  89. data/test/test_feature_flag_client.rb +16 -0
  90. data/test/test_fixed_size_hash.rb +119 -0
  91. data/test/test_helper.rb +17 -0
  92. data/test/test_integration.rb +75 -0
  93. data/test/test_internal_logger.rb +25 -0
  94. data/test/test_javascript_stub.rb +176 -0
  95. data/test/test_local_config_parser.rb +147 -0
  96. data/test/test_logger_initialization.rb +12 -0
  97. data/test/test_options.rb +93 -0
  98. data/test/test_prefab.rb +16 -0
  99. data/test/test_rate_limit_cache.rb +44 -0
  100. data/test/test_semver.rb +108 -0
  101. data/test/test_sse_config_client.rb +211 -0
  102. data/test/test_weighted_value_resolver.rb +71 -0
  103. metadata +345 -0
@@ -0,0 +1,275 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reforge
4
+ class ConfigClient
5
+ LOG = Reforge::InternalLogger.new(self)
6
+ DEFAULT_CHECKPOINT_FREQ_SEC = 60
7
+ STALE_CACHE_WARN_HOURS = 5
8
+
9
+ def initialize(base_client, timeout)
10
+ @base_client = base_client
11
+ @options = base_client.options
12
+ LOG.debug 'Initialize ConfigClient'
13
+ @timeout = timeout
14
+
15
+ @checkpoint_freq_secs = DEFAULT_CHECKPOINT_FREQ_SEC
16
+
17
+ @config_loader = Reforge::ConfigLoader.new(@base_client)
18
+ @config_resolver = Reforge::ConfigResolver.new(@base_client, @config_loader)
19
+
20
+ @initialization_lock = Concurrent::CountDownLatch.new(1)
21
+
22
+ if @options.local_only?
23
+ finish_init!(:local_only, nil)
24
+ elsif @options.datafile?
25
+ load_json_file(@options.datafile)
26
+ else
27
+ load_checkpoint
28
+ start_checkpointing_thread
29
+ start_streaming
30
+ end
31
+ end
32
+
33
+ def stop
34
+ @sse_config_client&.close
35
+ end
36
+
37
+ def start_streaming
38
+ Thread.new do
39
+ # wait for the config loader to have a highwater mark before we connect the SSE
40
+ loop do
41
+ break if @config_loader.highwater_mark > 0
42
+
43
+ sleep 0.1
44
+ end
45
+
46
+ stream_lock = Concurrent::ReadWriteLock.new
47
+ @sse_config_client = Reforge::SSEConfigClient.new(@options, @config_loader)
48
+
49
+ @sse_config_client.start do |configs, _event, source|
50
+ stream_lock.with_write_lock do
51
+ load_configs(configs, source)
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ def to_s
58
+ @config_resolver.to_s
59
+ end
60
+
61
+ def resolver
62
+ @config_resolver
63
+ end
64
+
65
+ def self.value_to_delta(key, config_value, namespace = nil)
66
+ PrefabProto::Config.new(key: [namespace, key].compact.join(':'),
67
+ rows: [PrefabProto::ConfigRow.new(value: config_value)])
68
+ end
69
+
70
+ def get(key, default = NO_DEFAULT_PROVIDED, properties = NO_DEFAULT_PROVIDED)
71
+ context = @config_resolver.make_context(properties)
72
+
73
+ if !context.blank? && @base_client.example_contexts_aggregator
74
+ @base_client.example_contexts_aggregator.record(context)
75
+ end
76
+
77
+ evaluation = _get(key, properties)
78
+
79
+ @base_client.context_shape_aggregator&.push(context)
80
+
81
+ if evaluation
82
+ evaluation.report_and_return(@base_client.evaluation_summary_aggregator)
83
+ else
84
+ handle_default(key, default)
85
+ end
86
+ end
87
+
88
+ def initialized?
89
+ @initialization_lock.count <= 0
90
+ end
91
+
92
+ private
93
+
94
+ def raw(key)
95
+ @config_resolver.raw(key)
96
+ end
97
+
98
+ def handle_default(key, default)
99
+ return default if default != NO_DEFAULT_PROVIDED
100
+
101
+ raise Reforge::Errors::MissingDefaultError, key if @options.on_no_default == Reforge::Options::ON_NO_DEFAULT::RAISE
102
+
103
+ nil
104
+ end
105
+
106
+ def _get(key, properties)
107
+ # wait timeout sec for the initialization to be complete
108
+ success = @initialization_lock.wait(@options.initialization_timeout_sec)
109
+ if !success
110
+ unless @options.on_init_failure == Reforge::Options::ON_INITIALIZATION_FAILURE::RETURN
111
+ raise Reforge::Errors::InitializationTimeoutError.new(@options.initialization_timeout_sec, key)
112
+ end
113
+
114
+ LOG.warn("Couldn't Initialize In #{@options.initialization_timeout_sec}. Key #{key}. Returning what we have")
115
+ end
116
+
117
+ @config_resolver.get key, properties
118
+ end
119
+
120
+ def load_checkpoint
121
+ success = load_source_checkpoint(:start_at_id => @config_loader.highwater_mark)
122
+ return if success
123
+
124
+ success = load_cache
125
+ return if success
126
+
127
+ LOG.warn 'No success loading checkpoints'
128
+ end
129
+
130
+ def load_source_checkpoint(start_at_id: 0)
131
+ @options.config_sources.each do |source|
132
+ conn = Reforge::CachingHttpConnection.new("#{source}/api/v2/configs/#{start_at_id}", @base_client.sdk_key)
133
+ result = load_url(conn, :remote_api)
134
+ return true if result
135
+ end
136
+
137
+ return false
138
+ end
139
+
140
+ def load_url(conn, source)
141
+ resp = conn.get('')
142
+ if resp.status == 200
143
+ configs = PrefabProto::Configs.decode(resp.body)
144
+ load_configs(configs, source)
145
+ cache_configs(configs)
146
+ true
147
+ else
148
+ LOG.info "Checkpoint #{source} [#{conn.uri}] failed to load. Response #{resp.status}"
149
+ false
150
+ end
151
+ rescue Faraday::ConnectionFailed => e
152
+ if !initialized?
153
+ LOG.warn "Connection Fail loading #{source} [#{conn.uri}] checkpoint."
154
+ else
155
+ LOG.debug "Connection Fail loading #{source} [#{conn.uri}] checkpoint."
156
+ end
157
+ false
158
+ rescue StandardError => e
159
+ LOG.warn "Unexpected #{source} [#{conn.uri}] problem loading checkpoint #{e} #{conn}"
160
+ LOG.debug e.backtrace
161
+ false
162
+ end
163
+
164
+ def load_configs(configs, source)
165
+ project_id = configs.config_service_pointer.project_id
166
+ project_env_id = configs.config_service_pointer.project_env_id
167
+ @config_resolver.project_env_id = project_env_id
168
+ starting_highwater_mark = @config_loader.highwater_mark
169
+
170
+ default_contexts = configs.default_context&.contexts&.map do |context|
171
+ [
172
+ context.type,
173
+ context.values.keys.map do |k|
174
+ [k, Reforge::ConfigValueUnwrapper.new(context.values[k], @config_resolver).unwrap]
175
+ end.to_h
176
+ ]
177
+ end.to_h
178
+
179
+ Reforge::Context.default_context = default_contexts || {}
180
+
181
+ configs.configs.each do |config|
182
+ @config_loader.set(config, source)
183
+ end
184
+ if @config_loader.highwater_mark > starting_highwater_mark
185
+ LOG.debug("Found new checkpoint with highwater id #{@config_loader.highwater_mark} from #{source} in project #{project_id} environment: #{project_env_id} and namespace: '#{@namespace}'")
186
+ else
187
+ LOG.debug("Checkpoint with highwater id #{@config_loader.highwater_mark} from #{source}. No changes.")
188
+ end
189
+ @config_resolver.update
190
+ finish_init!(source, project_id)
191
+ end
192
+
193
+ def cache_path
194
+ return @cache_path unless @cache_path.nil?
195
+ @cache_path ||= calc_cache_path
196
+ FileUtils.mkdir_p(File.dirname(@cache_path))
197
+ @cache_path
198
+ end
199
+
200
+ def calc_cache_path
201
+ file_name = "prefab.cache.#{@base_client.options.sdk_key_id}.json"
202
+ dir = ENV.fetch('XDG_CACHE_HOME', File.join(Dir.home, '.cache'))
203
+ File.join(dir, file_name)
204
+ end
205
+
206
+ def cache_configs(configs)
207
+ return unless @options.use_local_cache && !@options.is_fork
208
+ File.open(cache_path, "w") do |f|
209
+ f.flock(File::LOCK_EX)
210
+ f.write(PrefabProto::Configs.encode_json(configs))
211
+ end
212
+ LOG.debug "Cached configs to #{cache_path}"
213
+ rescue => e
214
+ LOG.debug "Failed to cache configs to #{cache_path} #{e}"
215
+ end
216
+
217
+ def load_cache
218
+ return false unless @options.use_local_cache
219
+ File.open(cache_path) do |f|
220
+ f.flock(File::LOCK_SH)
221
+ configs = PrefabProto::Configs.decode_json(f.read)
222
+ load_configs(configs, :cache)
223
+
224
+ hours_old = ((Time.now - File.mtime(f)) / 60 / 60).round(2)
225
+ if hours_old > STALE_CACHE_WARN_HOURS
226
+ LOG.info "Stale Cache Load: #{hours_old} hours old"
227
+ end
228
+ true
229
+ end
230
+ rescue => e
231
+ LOG.debug "Failed to read cached configs at #{cache_path}. #{e}"
232
+ false
233
+ end
234
+
235
+ def load_json_file(file)
236
+ File.open(file) do |f|
237
+ f.flock(File::LOCK_SH)
238
+ configs = PrefabProto::Configs.decode_json(f.read)
239
+ load_configs(configs, :datafile)
240
+ end
241
+ end
242
+
243
+ # A thread that checks for a checkpoint
244
+ def start_checkpointing_thread
245
+ Thread.new do
246
+ loop do
247
+ started_at = Time.now
248
+ delta = @checkpoint_freq_secs - (Time.now - started_at)
249
+ sleep(delta) if delta > 0
250
+
251
+ load_checkpoint
252
+ rescue StandardError => e
253
+ LOG.debug "Issue Checkpointing #{e.message}"
254
+ end
255
+ end
256
+ end
257
+
258
+ def finish_init!(source, project_id)
259
+ return if initialized?
260
+
261
+ LOG.debug "Unlocked Config via #{source}"
262
+ @initialization_lock.count_down
263
+
264
+ presenter = Reforge::ConfigClientPresenter.new(
265
+ size: @config_resolver.local_store.size,
266
+ source: source,
267
+ project_id: project_id,
268
+ project_env_id: @config_resolver.project_env_id,
269
+ sdk_key_id: @base_client.options.sdk_key_id
270
+ )
271
+ LOG.info presenter.to_s
272
+ LOG.debug to_s
273
+ end
274
+ end
275
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reforge
4
+ class ConfigClientPresenter
5
+ def initialize(size:, source:, project_id:, project_env_id:, sdk_key_id:)
6
+ @size = size
7
+ @source = source
8
+ @project_id = project_id
9
+ @project_env_id = project_env_id
10
+ @sdk_key_id = sdk_key_id
11
+ end
12
+
13
+ def to_s
14
+ "Configuration Loaded count=#{@size} source=#{@source} project=#{@project_id} project-env=#{@project_env_id} prefab.sdk-key-id=#{@sdk_key_id}"
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reforge
4
+ class ConfigLoader
5
+ LOG = Reforge::InternalLogger.new(self)
6
+
7
+ attr_reader :highwater_mark
8
+
9
+ def initialize(base_client)
10
+ @base_client = base_client
11
+ @prefab_options = base_client.options
12
+ @highwater_mark = 0
13
+ @classpath_config = load_classpath_config
14
+ @local_overrides = load_local_overrides
15
+ @api_config = Concurrent::Map.new
16
+ end
17
+
18
+ def calc_config
19
+ rtn = @classpath_config.clone
20
+ @api_config.each_key do |k|
21
+ rtn[k] = @api_config[k]
22
+ end
23
+ rtn.merge(@local_overrides)
24
+ end
25
+
26
+ def set(config, source)
27
+ # don't overwrite newer values
28
+ return if @api_config[config.key] && @api_config[config.key][:config].id >= config.id
29
+
30
+ if config.rows.empty?
31
+ @api_config.delete(config.key)
32
+ else
33
+ if @api_config[config.key]
34
+ LOG.debug(
35
+ "Replace #{config.key} with value from #{source} #{@api_config[config.key][:config].id} -> #{config.id}")
36
+ end
37
+ @api_config[config.key] = { source: source, config: config }
38
+ end
39
+ @highwater_mark = [config.id, @highwater_mark].max
40
+ end
41
+
42
+ def rm(key)
43
+ @api_config.delete key
44
+ end
45
+
46
+ def get_api_deltas
47
+ configs = PrefabProto::Configs.new
48
+ @api_config.each_value do |config_value|
49
+ configs.configs << config_value[:config]
50
+ end
51
+ configs
52
+ end
53
+
54
+ private
55
+
56
+ def load_classpath_config
57
+ return {} if @prefab_options.datafile?
58
+ {}
59
+ end
60
+
61
+ def load_local_overrides
62
+ return {} if @prefab_options.datafile?
63
+ {}
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reforge
4
+ class ConfigResolver
5
+ attr_accessor :project_env_id # this will be set by the config_client when it gets an API response
6
+ attr_reader :local_store
7
+
8
+ def initialize(base_client, config_loader)
9
+ @lock = Concurrent::ReadWriteLock.new
10
+ @local_store = {}
11
+ @config_loader = config_loader
12
+ @project_env_id = 0 # we don't know this yet, it is set from the API results
13
+ @base_client = base_client
14
+ @on_update = nil
15
+ make_local
16
+ end
17
+
18
+ def to_s
19
+ presenter.to_s
20
+ end
21
+
22
+ def presenter
23
+ Reforge::ResolvedConfigPresenter.new(self, @lock, @local_store)
24
+ end
25
+
26
+ def keys
27
+ @local_store.keys
28
+ end
29
+
30
+ def raw(key)
31
+ @local_store.dig(key, :config)
32
+ end
33
+
34
+ def get(key, properties = NO_DEFAULT_PROVIDED)
35
+ @lock.with_read_lock do
36
+ raw_config = raw(key)
37
+
38
+ return nil unless raw_config
39
+
40
+ evaluate(raw_config, properties)
41
+ end
42
+ end
43
+
44
+ def evaluate(config, properties = NO_DEFAULT_PROVIDED)
45
+ Reforge::CriteriaEvaluator.new(config,
46
+ project_env_id: @project_env_id,
47
+ resolver: self,
48
+ namespace: @base_client.options.namespace,
49
+ base_client: @base_client).evaluate(make_context(properties))
50
+ end
51
+
52
+ def update
53
+ make_local
54
+
55
+ @on_update ? @on_update.call : nil
56
+ end
57
+
58
+ def on_update(&block)
59
+ @on_update = block
60
+ end
61
+
62
+ def make_context(properties)
63
+ if properties.is_a?(Context)
64
+ properties
65
+ elsif properties == NO_DEFAULT_PROVIDED || properties.nil?
66
+ Context.current
67
+ else
68
+ Context.join(parent: Context.current, hash: properties, id: :jit)
69
+ end
70
+ end
71
+
72
+ def symbolize_json_names?
73
+ @base_client.options.symbolize_json_names
74
+ end
75
+
76
+ private
77
+
78
+ def make_local
79
+ @lock.with_write_lock do
80
+ @local_store = @config_loader.calc_config
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reforge
4
+ class ConfigValueUnwrapper
5
+ LOG = Reforge::InternalLogger.new(self)
6
+ CONFIDENTIAL_PREFIX = "*****"
7
+ attr_reader :weighted_value_index
8
+
9
+ def initialize(config_value, resolver, weighted_value_index = nil)
10
+ @config_value = config_value
11
+ @resolver = resolver
12
+ @weighted_value_index = weighted_value_index
13
+ end
14
+
15
+ def reportable_wrapped_value
16
+ if @config_value.confidential || @config_value.decrypt_with&.length&.positive?
17
+ # Unique hash for differentiation
18
+ Reforge::ConfigValueWrapper.wrap("#{CONFIDENTIAL_PREFIX}#{Digest::MD5.hexdigest(unwrap)[0, 5]}")
19
+ else
20
+ @config_value
21
+ end
22
+ end
23
+
24
+ def reportable_value
25
+ Reforge::ConfigValueUnwrapper.new(reportable_wrapped_value, @resolver, @weighted_value_index).unwrap
26
+ end
27
+
28
+ def raw_config_value
29
+ @config_value
30
+ end
31
+
32
+ # this will return the actual value of confidential, use reportable_value unless you need it
33
+ def unwrap(raw_json: false)
34
+ raw = case @config_value.type
35
+ when :int, :string, :double, :bool, :log_level
36
+ @config_value.public_send(@config_value.type)
37
+ when :string_list
38
+ @config_value.string_list.values
39
+ when :duration
40
+ Reforge::Duration.new(@config_value.duration.definition)
41
+ when :json
42
+ if raw_json
43
+ @config_value.json.json
44
+ else
45
+ JSON.parse(@config_value.json.json, symbolize_names: @resolver.symbolize_json_names?)
46
+ end
47
+ else
48
+ LOG.error "Unknown type: #{@config_value.type}"
49
+ raise "Unknown type: #{@config_value.type}"
50
+ end
51
+ if @config_value.has_decrypt_with?
52
+ decryption_key = @resolver.get(@config_value.decrypt_with)&.unwrapped_value
53
+ if decryption_key.nil?
54
+ LOG.warn "No value for decryption key #{@config_value.decrypt_with} found."
55
+ return ""
56
+ else
57
+ unencrypted = Reforge::Encryption.new(decryption_key).decrypt(raw)
58
+ return unencrypted
59
+ end
60
+ end
61
+
62
+ raw
63
+ end
64
+
65
+ def self.deepest_value(config_value, config, context, resolver)
66
+ if config_value&.type == :weighted_values
67
+ value, index = Reforge::WeightedValueResolver.new(
68
+ config_value.weighted_values.weighted_values,
69
+ config.key,
70
+ context.get(config_value.weighted_values.hash_by_property_name)
71
+ ).resolve
72
+
73
+ new(deepest_value(value.value, config, context, resolver).raw_config_value, resolver, index)
74
+
75
+ elsif config_value&.type == :provided
76
+ if :ENV_VAR == config_value.provided.source
77
+ raw = ENV[config_value.provided.lookup]
78
+ if raw.nil?
79
+ raise Reforge::Errors::MissingEnvVarError.new("Missing environment variable #{config_value.provided.lookup}")
80
+ else
81
+ coerced = coerce_into_type(raw, config, config_value.provided.lookup)
82
+ new(Reforge::ConfigValueWrapper.wrap(coerced, confidential: config_value.confidential), resolver)
83
+ end
84
+ else
85
+ raise "Unknown Provided Source #{config_value.provided.source}"
86
+ end
87
+ else
88
+ new(config_value, resolver)
89
+ end
90
+ end
91
+
92
+ # Don't allow env vars to resolve to a value_type other than the config's value_type
93
+ def self.coerce_into_type(value_string, config, env_var_name)
94
+ case config.value_type
95
+ when :INT then Integer(value_string)
96
+ when :DOUBLE then Float(value_string)
97
+ when :STRING then String(value_string)
98
+ when :STRING_LIST then
99
+ maybe_string_list = YAML.load(value_string)
100
+ case maybe_string_list
101
+ when Array
102
+ maybe_string_list
103
+ else
104
+ raise raise Reforge::Errors::EnvVarParseError.new(value_string, config, env_var_name)
105
+ end
106
+ when :BOOL then
107
+ maybe_bool = YAML.load(value_string)
108
+ case maybe_bool
109
+ when TrueClass, FalseClass
110
+ maybe_bool
111
+ else
112
+ raise Reforge::Errors::EnvVarParseError.new(value_string, config, env_var_name)
113
+ end
114
+ when :NOT_SET_VALUE_TYPE
115
+ YAML.load(value_string)
116
+ else
117
+ raise Reforge::Errors::EnvVarParseError.new(value_string, config, env_var_name)
118
+ end
119
+ rescue ArgumentError
120
+ raise Reforge::Errors::EnvVarParseError.new(value_string, config, env_var_name)
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,18 @@
1
+ module Reforge
2
+ class ConfigValueWrapper
3
+ def self.wrap(value, confidential: nil)
4
+ case value
5
+ when Integer
6
+ PrefabProto::ConfigValue.new(int: value, confidential: confidential)
7
+ when Float
8
+ PrefabProto::ConfigValue.new(double: value, confidential: confidential)
9
+ when TrueClass, FalseClass
10
+ PrefabProto::ConfigValue.new(bool: value, confidential: confidential)
11
+ when Array
12
+ PrefabProto::ConfigValue.new(string_list: PrefabProto::StringList.new(values: value.map(&:to_s)), confidential: confidential)
13
+ else
14
+ PrefabProto::ConfigValue.new(string: value.to_s, confidential: confidential)
15
+ end
16
+ end
17
+ end
18
+ end