quonfig 0.0.11 → 0.0.13

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +94 -0
  4. data/lib/quonfig/caching_http_connection.rb +3 -3
  5. data/lib/quonfig/client.rb +22 -27
  6. data/lib/quonfig/config_loader.rb +5 -1
  7. data/lib/quonfig/config_store.rb +10 -6
  8. data/lib/quonfig/context.rb +5 -4
  9. data/lib/quonfig/datadir.rb +4 -10
  10. data/lib/quonfig/dev_context.rb +4 -2
  11. data/lib/quonfig/duration.rb +2 -2
  12. data/lib/quonfig/encryption.rb +12 -16
  13. data/lib/quonfig/errors/invalid_environment_error.rb +1 -3
  14. data/lib/quonfig/errors/invalid_sdk_key_error.rb +6 -7
  15. data/lib/quonfig/errors/missing_env_var_error.rb +0 -3
  16. data/lib/quonfig/errors/missing_environment_error.rb +1 -1
  17. data/lib/quonfig/errors/uninitialized_error.rb +1 -1
  18. data/lib/quonfig/evaluation.rb +11 -8
  19. data/lib/quonfig/evaluator.rb +47 -37
  20. data/lib/quonfig/fixed_size_hash.rb +1 -0
  21. data/lib/quonfig/http_connection.rb +2 -4
  22. data/lib/quonfig/internal_logger.rb +63 -27
  23. data/lib/quonfig/murmer3.rb +2 -2
  24. data/lib/quonfig/options.rb +62 -75
  25. data/lib/quonfig/periodic_sync.rb +1 -1
  26. data/lib/quonfig/quonfig.rb +3 -3
  27. data/lib/quonfig/reason.rb +2 -1
  28. data/lib/quonfig/resolver.rb +8 -9
  29. data/lib/quonfig/semantic_logger_filter.rb +4 -3
  30. data/lib/quonfig/semver.rb +6 -8
  31. data/lib/quonfig/sse_config_client.rb +13 -14
  32. data/lib/quonfig/stdlib_formatter.rb +3 -3
  33. data/lib/quonfig/telemetry/context_shape_aggregator.rb +2 -3
  34. data/lib/quonfig/telemetry/example_contexts_aggregator.rb +1 -1
  35. data/lib/quonfig/telemetry/telemetry_reporter.rb +1 -0
  36. data/lib/quonfig/time_helpers.rb +2 -0
  37. data/lib/quonfig/version.rb +1 -1
  38. data/quonfig.gemspec +6 -7
  39. metadata +2 -16
@@ -51,6 +51,8 @@ module Quonfig
51
51
  OP_PROP_SEMVER_GREATER_THAN = 'PROP_SEMVER_GREATER_THAN'
52
52
  OP_IN_SEG = 'IN_SEG'
53
53
  OP_NOT_IN_SEG = 'NOT_IN_SEG'
54
+ OP_IS_PRESENT = 'IS_PRESENT'
55
+ OP_IS_NOT_PRESENT = 'IS_NOT_PRESENT'
54
56
 
55
57
  MAGIC_CURRENT_TIME_PROPS = %w[quonfig.current-time prefab.current-time reforge.current-time].freeze
56
58
 
@@ -99,8 +101,10 @@ module Quonfig
99
101
 
100
102
  keys.each do |k|
101
103
  return hash[k] if hash.key?(k)
104
+
102
105
  sk = k.to_s
103
106
  return hash[sk] if hash.key?(sk)
107
+
104
108
  sym = k.to_sym
105
109
  return hash[sym] if hash.key?(sym)
106
110
  end
@@ -147,7 +151,7 @@ module Quonfig
147
151
  # Faithful port of sdk-node/src/operators.ts evaluateCriterion. Matches
148
152
  # context-exists / missing-context semantics (e.g. PROP_IS_NOT_ONE_OF is
149
153
  # true when context is missing).
150
- def evaluate_criterion(criterion, context, config)
154
+ def evaluate_criterion(criterion, context, _config)
151
155
  property_name = hget(criterion, :propertyName) || ''
152
156
  operator = hget(criterion, :operator)
153
157
  match_value = hget(criterion, :valueToMatch)
@@ -156,10 +160,10 @@ module Quonfig
156
160
 
157
161
  case operator
158
162
  when OP_NOT_SET, nil
159
- return false
163
+ false
160
164
 
161
165
  when OP_ALWAYS_TRUE
162
- return true
166
+ true
163
167
 
164
168
  when OP_PROP_IS_ONE_OF, OP_PROP_IS_NOT_ONE_OF
165
169
  if context_exists && match_value
@@ -170,7 +174,7 @@ module Quonfig
170
174
  return match_found == (operator == OP_PROP_IS_ONE_OF)
171
175
  end
172
176
  end
173
- return operator == OP_PROP_IS_NOT_ONE_OF
177
+ operator == OP_PROP_IS_NOT_ONE_OF
174
178
 
175
179
  when OP_PROP_STARTS_WITH_ONE_OF, OP_PROP_DOES_NOT_START_WITH_ONE_OF
176
180
  if context_exists && match_value
@@ -181,7 +185,7 @@ module Quonfig
181
185
  return match_found == (operator == OP_PROP_STARTS_WITH_ONE_OF)
182
186
  end
183
187
  end
184
- return operator == OP_PROP_DOES_NOT_START_WITH_ONE_OF
188
+ operator == OP_PROP_DOES_NOT_START_WITH_ONE_OF
185
189
 
186
190
  when OP_PROP_ENDS_WITH_ONE_OF, OP_PROP_DOES_NOT_END_WITH_ONE_OF
187
191
  if context_exists && match_value
@@ -192,7 +196,7 @@ module Quonfig
192
196
  return match_found == (operator == OP_PROP_ENDS_WITH_ONE_OF)
193
197
  end
194
198
  end
195
- return operator == OP_PROP_DOES_NOT_END_WITH_ONE_OF
199
+ operator == OP_PROP_DOES_NOT_END_WITH_ONE_OF
196
200
 
197
201
  when OP_PROP_CONTAINS_ONE_OF, OP_PROP_DOES_NOT_CONTAIN_ONE_OF
198
202
  if context_exists && match_value
@@ -203,7 +207,7 @@ module Quonfig
203
207
  return match_found == (operator == OP_PROP_CONTAINS_ONE_OF)
204
208
  end
205
209
  end
206
- return operator == OP_PROP_DOES_NOT_CONTAIN_ONE_OF
210
+ operator == OP_PROP_DOES_NOT_CONTAIN_ONE_OF
207
211
 
208
212
  when OP_PROP_MATCHES, OP_PROP_DOES_NOT_MATCH
209
213
  mv = hget(match_value, :value)
@@ -216,7 +220,7 @@ module Quonfig
216
220
  return false
217
221
  end
218
222
  end
219
- return false
223
+ false
220
224
 
221
225
  when OP_HIERARCHICAL_MATCH
222
226
  if context_exists && match_value
@@ -224,7 +228,7 @@ module Quonfig
224
228
  mv = to_s_nil(hget(match_value, :value))
225
229
  return cv.start_with?(mv)
226
230
  end
227
- return false
231
+ false
228
232
 
229
233
  when OP_IN_INT_RANGE
230
234
  if context_exists && match_value
@@ -232,7 +236,7 @@ module Quonfig
232
236
  num_val = to_float(context_value)
233
237
  return num_val >= start_v && num_val < end_v unless num_val.nil?
234
238
  end
235
- return false
239
+ false
236
240
 
237
241
  when OP_PROP_GREATER_THAN, OP_PROP_GREATER_THAN_OR_EQUAL,
238
242
  OP_PROP_LESS_THAN, OP_PROP_LESS_THAN_OR_EQUAL
@@ -244,13 +248,13 @@ module Quonfig
244
248
  return false if cmp.nil?
245
249
 
246
250
  case operator
247
- when OP_PROP_GREATER_THAN then return cmp > 0
251
+ when OP_PROP_GREATER_THAN then return cmp.positive?
248
252
  when OP_PROP_GREATER_THAN_OR_EQUAL then return cmp >= 0
249
- when OP_PROP_LESS_THAN then return cmp < 0
253
+ when OP_PROP_LESS_THAN then return cmp.negative?
250
254
  when OP_PROP_LESS_THAN_OR_EQUAL then return cmp <= 0
251
255
  end
252
256
  end
253
- return false
257
+ false
254
258
 
255
259
  when OP_PROP_BEFORE, OP_PROP_AFTER
256
260
  if context_exists && match_value
@@ -260,7 +264,7 @@ module Quonfig
260
264
  return operator == OP_PROP_BEFORE ? context_millis < match_millis : context_millis > match_millis
261
265
  end
262
266
  end
263
- return false
267
+ false
264
268
 
265
269
  when OP_PROP_SEMVER_LESS_THAN, OP_PROP_SEMVER_EQUAL, OP_PROP_SEMVER_GREATER_THAN
266
270
  mv = hget(match_value, :value)
@@ -270,13 +274,24 @@ module Quonfig
270
274
  if sv_ctx && sv_mv
271
275
  cmp = (sv_ctx <=> sv_mv)
272
276
  case operator
273
- when OP_PROP_SEMVER_LESS_THAN then return cmp < 0
274
- when OP_PROP_SEMVER_EQUAL then return cmp == 0
275
- when OP_PROP_SEMVER_GREATER_THAN then return cmp > 0
277
+ when OP_PROP_SEMVER_LESS_THAN then return cmp.negative?
278
+ when OP_PROP_SEMVER_EQUAL then return cmp.zero?
279
+ when OP_PROP_SEMVER_GREATER_THAN then return cmp.positive?
276
280
  end
277
281
  end
278
282
  end
279
- return false
283
+ false
284
+
285
+ when OP_IS_PRESENT, OP_IS_NOT_PRESENT
286
+ # Presence is type-agnostic: empty string "", 0, and false are all
287
+ # PRESENT. Only nil / missing key (incl. nested) is NOT present.
288
+ # `lookup_context` already returns context_exists=true iff the
289
+ # resolved value is non-nil — exactly the semantic we need. Do not
290
+ # use ActiveSupport's #present? / #blank? here: they treat "" and
291
+ # false as blank, which is the wrong behaviour for this operator.
292
+ return context_exists if operator == OP_IS_PRESENT
293
+
294
+ !context_exists
280
295
 
281
296
  when OP_IN_SEG, OP_NOT_IN_SEG
282
297
  if match_value
@@ -286,21 +301,17 @@ module Quonfig
286
301
 
287
302
  return result == (operator == OP_IN_SEG)
288
303
  end
289
- return operator == OP_NOT_IN_SEG
304
+ operator == OP_NOT_IN_SEG
290
305
 
291
306
  else
292
- return false
307
+ false
293
308
  end
294
309
  end
295
310
 
296
311
  def lookup_context(context, property_name)
297
- if MAGIC_CURRENT_TIME_PROPS.include?(property_name)
298
- return [(Time.now.utc.to_f * 1000).to_i, true]
299
- end
312
+ return [(Time.now.utc.to_f * 1000).to_i, true] if MAGIC_CURRENT_TIME_PROPS.include?(property_name)
300
313
 
301
- if property_name.nil? || property_name.empty?
302
- return [nil, false]
303
- end
314
+ return [nil, false] if property_name.nil? || property_name.empty?
304
315
 
305
316
  value = context.get(property_name)
306
317
  [value, !value.nil?]
@@ -362,8 +373,7 @@ module Quonfig
362
373
  return v.to_f if v.is_a?(Numeric)
363
374
  return nil unless v.is_a?(String)
364
375
 
365
- f = Float(v, exception: false)
366
- f
376
+ Float(v, exception: false)
367
377
  end
368
378
 
369
379
  def compare_numbers(a, b)
@@ -375,7 +385,7 @@ module Quonfig
375
385
  end
376
386
 
377
387
  def extract_int_range(value_hash)
378
- min = -(2**53) + 1 # approx Number.MIN_SAFE_INTEGER
388
+ min = -(2**53) + 1 # approx Number.MIN_SAFE_INTEGER
379
389
  max = (2**53) - 1
380
390
  raw = hget(value_hash, :value)
381
391
  return [min, max] unless raw.is_a?(Hash)
@@ -397,10 +407,8 @@ module Quonfig
397
407
  rescue ArgumentError, TypeError
398
408
  # not a date; try integer
399
409
  end
400
- n = Integer(val, exception: false)
401
- n
402
- else
403
- nil
410
+ Integer(val, exception: false)
411
+
404
412
  end
405
413
  end
406
414
  end
@@ -437,7 +445,7 @@ module Quonfig
437
445
  # of a config with no targeting rules matched, otherwise TARGETING_MATCH.
438
446
  def wire_reason
439
447
  return REASON_SPLIT unless @weighted_value_index.nil?
440
- return REASON_STATIC if @rule_index == 0 && !EvalResult.send(:targeting_rules?, @config)
448
+ return REASON_STATIC if @rule_index.zero? && !EvalResult.send(:targeting_rules?, @config)
441
449
 
442
450
  REASON_TARGETING_MATCH
443
451
  end
@@ -494,13 +502,15 @@ module Quonfig
494
502
  def unwrapped_value
495
503
  raw = raw_value
496
504
  case type
497
- when 'bool' then !!raw
505
+ when 'bool' then !!raw
498
506
  when 'int'
499
507
  return raw if raw.is_a?(Integer)
500
508
  return raw.to_i if raw.is_a?(Numeric)
509
+
501
510
  Integer(raw.to_s, 10)
502
511
  when 'double'
503
512
  return raw.to_f if raw.is_a?(Numeric)
513
+
504
514
  Float(raw.to_s)
505
515
  when 'string' then raw.to_s
506
516
  when 'string_list' then raw.is_a?(Array) ? raw.map(&:to_s) : []
@@ -535,8 +545,8 @@ module Quonfig
535
545
  (seconds * 1000).round
536
546
  when Hash
537
547
  secs = (raw['seconds'] || raw[:seconds] || 0).to_f
538
- nanos = (raw['nanos'] || raw[:nanos] || 0).to_f
539
- (secs * 1000 + nanos / 1_000_000.0).round
548
+ nanos = (raw['nanos'] || raw[:nanos] || 0).to_f
549
+ ((secs * 1000) + (nanos / 1_000_000.0)).round
540
550
  else
541
551
  raw
542
552
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Quonfig
3
4
  class FixedSizeHash < Hash
4
5
  def initialize(max_size)
@@ -18,9 +18,7 @@ module Quonfig
18
18
  @sdk_key = sdk_key
19
19
  end
20
20
 
21
- def uri
22
- @uri
23
- end
21
+ attr_reader :uri
24
22
 
25
23
  def get(path, headers = {})
26
24
  connection(headers).get(path)
@@ -40,7 +38,7 @@ module Quonfig
40
38
  private
41
39
 
42
40
  def auth_header
43
- 'Basic ' + Base64.strict_encode64("1:#{@sdk_key}")
41
+ "Basic #{Base64.strict_encode64("1:#{@sdk_key}")}"
44
42
  end
45
43
  end
46
44
  end
@@ -4,11 +4,24 @@ module Quonfig
4
4
  # Internal logger for the Quonfig SDK
5
5
  # Uses SemanticLogger if available, falls back to stdlib Logger
6
6
  class InternalLogger
7
- def initialize(klass)
7
+ # Optional, host-app-supplied logger. When set (typically via
8
+ # Quonfig::Client.new(logger:)), all InternalLogger instances route
9
+ # writes to it instead of their default backend. Must duck-type as a
10
+ # stdlib Logger (responds to debug/info/warn/error). Missing levels
11
+ # are silently dropped.
12
+ class << self
13
+ attr_accessor :user_logger
14
+ end
15
+
16
+ def initialize(klass, logger: nil)
8
17
  @klass = klass
9
18
  @level_sym = nil # Track the symbol level for consistency
19
+ @injected_logger = logger
10
20
 
11
- if defined?(SemanticLogger)
21
+ if @injected_logger
22
+ @logger = @injected_logger
23
+ @using_semantic = false
24
+ elsif defined?(SemanticLogger)
12
25
  @logger = create_semantic_logger
13
26
  @using_semantic = true
14
27
  else
@@ -51,13 +64,13 @@ module Quonfig
51
64
  else
52
65
  # Return the symbol level we tracked, or map from Logger constant
53
66
  @level_sym || case @logger.level
54
- when Logger::DEBUG then :debug
55
- when Logger::INFO then :info
56
- when Logger::WARN then :warn
57
- when Logger::ERROR then :error
58
- when Logger::FATAL then :fatal
59
- else :warn
60
- end
67
+ when Logger::DEBUG then :debug
68
+ when Logger::INFO then :info
69
+ when Logger::WARN then :warn
70
+ when Logger::ERROR then :error
71
+ when Logger::FATAL then :fatal
72
+ else :warn
73
+ end
61
74
  end
62
75
  end
63
76
 
@@ -69,14 +82,16 @@ module Quonfig
69
82
  @level_sym = new_level
70
83
 
71
84
  # Map symbol to Logger constant
72
- @logger.level = case new_level
73
- when :trace, :debug then Logger::DEBUG
74
- when :info then Logger::INFO
75
- when :warn then Logger::WARN
76
- when :error then Logger::ERROR
77
- when :fatal then Logger::FATAL
78
- else Logger::WARN
79
- end
85
+ next_level = case new_level
86
+ when :trace, :debug then Logger::DEBUG
87
+ when :info then Logger::INFO
88
+ when :warn then Logger::WARN
89
+ when :error then Logger::ERROR
90
+ when :fatal then Logger::FATAL
91
+ else Logger::WARN
92
+ end
93
+
94
+ @logger.level = next_level if @logger.respond_to?(:level=)
80
95
  end
81
96
  end
82
97
 
@@ -99,9 +114,10 @@ module Quonfig
99
114
  class << logger
100
115
  def log(log, message = nil, progname = nil, &block)
101
116
  return if recurse_check[local_log_id]
117
+
102
118
  recurse_check[local_log_id] = true
103
119
  begin
104
- super(log, message, progname, &block)
120
+ super
105
121
  ensure
106
122
  recurse_check[local_log_id] = false
107
123
  end
@@ -132,19 +148,19 @@ module Quonfig
132
148
  @level_sym = env_log_level || default_level_sym
133
149
 
134
150
  logger.level = case @level_sym
135
- when :trace, :debug then Logger::DEBUG
136
- when :info then Logger::INFO
137
- when :warn then Logger::WARN
138
- when :error then Logger::ERROR
139
- when :fatal then Logger::FATAL
140
- else Logger::WARN
141
- end
151
+ when :trace, :debug then Logger::DEBUG
152
+ when :info then Logger::INFO
153
+ when :warn then Logger::WARN
154
+ when :error then Logger::ERROR
155
+ when :fatal then Logger::FATAL
156
+ else Logger::WARN
157
+ end
142
158
  logger.progname = @klass.to_s
143
159
 
144
160
  # Use a custom formatter that mimics SemanticLogger format
145
161
  # SemanticLogger format: "ClassName -- Message"
146
162
  # This helps tests that expect SemanticLogger-style output
147
- logger.formatter = proc do |severity, datetime, progname, msg|
163
+ logger.formatter = proc do |_severity, _datetime, progname, msg|
148
164
  "#{progname} -- #{msg}\n"
149
165
  end
150
166
 
@@ -152,20 +168,40 @@ module Quonfig
152
168
  end
153
169
 
154
170
  def env_log_level
155
- level_str = ENV['QUONFIG_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL']
171
+ level_str = ENV.fetch('QUONFIG_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL', nil)
156
172
  level_str&.downcase&.to_sym
157
173
  end
158
174
 
159
175
  def log_message(level, message, &block)
176
+ override = Quonfig::InternalLogger.user_logger
177
+ if override
178
+ write_to_user_logger(override, level, message, &block)
179
+ return
180
+ end
181
+
160
182
  if @using_semantic
161
183
  @logger.send(level, message, &block)
162
184
  else
163
185
  # stdlib Logger doesn't have trace
164
186
  level = :debug if level == :trace
187
+ return unless @logger.respond_to?(level)
188
+
165
189
  @logger.send(level, message || block&.call)
166
190
  end
167
191
  end
168
192
 
193
+ # Route a message to a host-app-supplied logger that duck-types as a
194
+ # stdlib Logger. Missing levels degrade gracefully (trace -> debug;
195
+ # otherwise a no-op). The class name is prepended to keep parity with
196
+ # the SemanticLogger / stdlib formatter output.
197
+ def write_to_user_logger(target, level, message, &block)
198
+ level = :debug if level == :trace && !target.respond_to?(:trace)
199
+ return unless target.respond_to?(level)
200
+
201
+ msg = message || block&.call
202
+ target.public_send(level, "#{@klass} -- #{msg}")
203
+ end
204
+
169
205
  def instances
170
206
  @@instances ||= []
171
207
  end
@@ -30,10 +30,10 @@ class Murmur3
30
30
  numbers = str.unpack('V*C*')
31
31
  tailn = str.length % 4
32
32
  tail = numbers.slice!(numbers.size - tailn, tailn)
33
- for k1 in numbers
33
+ numbers.each do |k1|
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?
@@ -5,23 +5,8 @@ require 'uri'
5
5
  module Quonfig
6
6
  # Options passed to Quonfig::Client at construction time.
7
7
  class Options
8
- attr_reader :sdk_key
9
- attr_reader :environment
10
- attr_reader :api_urls
11
- attr_reader :sse_api_urls
12
- attr_reader :telemetry_destination
13
- attr_reader :config_api_urls
14
- attr_reader :on_no_default
15
- attr_reader :initialization_timeout_sec
16
- attr_reader :on_init_failure
17
- attr_reader :collect_sync_interval
18
- attr_reader :datadir
19
- attr_reader :enable_sse
20
- attr_reader :enable_polling
21
- attr_reader :poll_interval
22
- attr_reader :global_context
23
- attr_reader :logger_key
24
- attr_reader :enable_quonfig_user_context
8
+ attr_reader :sdk_key, :environment, :api_urls, :sse_api_urls, :telemetry_destination, :config_api_urls,
9
+ :on_no_default, :initialization_timeout_sec, :on_init_failure, :collect_sync_interval, :datadir, :enable_sse, :enable_polling, :poll_interval, :global_context, :logger_key, :logger, :enable_quonfig_user_context
25
10
  attr_accessor :is_fork
26
11
 
27
12
  module ON_INITIALIZATION_FAILURE
@@ -46,13 +31,13 @@ module Quonfig
46
31
  # and no explicit api_urls are provided). Mirrors derive_api_urls(DEFAULT_DOMAIN).
47
32
  DEFAULT_API_URLS = [
48
33
  'https://primary.quonfig.com',
49
- 'https://secondary.quonfig.com',
34
+ 'https://secondary.quonfig.com'
50
35
  ].freeze
51
36
 
52
37
  # Resolve the active domain. Reads QUONFIG_DOMAIN; falls back to
53
38
  # DEFAULT_DOMAIN. Mirrors `cli/src/util/domain-urls.ts#getDomain`.
54
39
  def self.domain
55
- env = ENV['QUONFIG_DOMAIN']
40
+ env = ENV.fetch('QUONFIG_DOMAIN', nil)
56
41
  env && !env.empty? ? env : DEFAULT_DOMAIN
57
42
  end
58
43
 
@@ -62,7 +47,7 @@ module Quonfig
62
47
  def self.derive_api_urls(domain)
63
48
  [
64
49
  "https://primary.#{domain}",
65
- "https://secondary.#{domain}",
50
+ "https://secondary.#{domain}"
66
51
  ]
67
52
  end
68
53
 
@@ -84,12 +69,62 @@ module Quonfig
84
69
  uri.to_s
85
70
  end
86
71
 
87
- private def init(
72
+ def initialize(options = {})
73
+ init(**options)
74
+ end
75
+
76
+ # In datadir mode the SDK evaluates config from a local workspace and does
77
+ # not connect to the delivery service.
78
+ def local_only?
79
+ !@datadir.nil?
80
+ end
81
+
82
+ def datadir?
83
+ !@datadir.nil?
84
+ end
85
+
86
+ def collect_max_paths
87
+ return 0 unless telemetry_allowed?(true)
88
+
89
+ @collect_max_paths
90
+ end
91
+
92
+ def collect_max_shapes
93
+ return 0 unless telemetry_allowed?(@collect_shapes)
94
+
95
+ @collect_max_shapes
96
+ end
97
+
98
+ def collect_max_example_contexts
99
+ return 0 unless telemetry_allowed?(@collect_example_contexts)
100
+
101
+ @collect_max_example_contexts
102
+ end
103
+
104
+ def collect_max_evaluation_summaries
105
+ return 0 unless telemetry_allowed?(@collect_evaluation_summaries)
106
+
107
+ @collect_max_evaluation_summaries
108
+ end
109
+
110
+ def sdk_key_id
111
+ @sdk_key&.split('-')&.first
112
+ end
113
+
114
+ def for_fork
115
+ clone = self.clone
116
+ clone.is_fork = true
117
+ clone
118
+ end
119
+
120
+ private
121
+
122
+ def init(
88
123
  api_urls: nil,
89
124
  telemetry_url: nil,
90
- sdk_key: ENV['QUONFIG_BACKEND_SDK_KEY'],
91
- environment: ENV['QUONFIG_ENVIRONMENT'],
92
- datadir: ENV['QUONFIG_DIR'],
125
+ sdk_key: ENV.fetch('QUONFIG_BACKEND_SDK_KEY', nil),
126
+ environment: ENV.fetch('QUONFIG_ENVIRONMENT', nil),
127
+ datadir: ENV.fetch('QUONFIG_DIR', nil),
93
128
  enable_sse: true,
94
129
  enable_polling: true,
95
130
  poll_interval: 60,
@@ -105,6 +140,7 @@ module Quonfig
105
140
  allow_telemetry_in_local_mode: false,
106
141
  global_context: {},
107
142
  logger_key: nil,
143
+ logger: nil,
108
144
  enable_quonfig_user_context: false
109
145
  )
110
146
  @sdk_key = sdk_key
@@ -125,6 +161,7 @@ module Quonfig
125
161
  @is_fork = false
126
162
  @global_context = global_context
127
163
  @logger_key = logger_key
164
+ @logger = logger
128
165
  @enable_quonfig_user_context = enable_quonfig_user_context
129
166
 
130
167
  # defaults that may be overridden by context_upload_mode
@@ -140,7 +177,7 @@ module Quonfig
140
177
  domain = Quonfig::Options.domain
141
178
 
142
179
  @api_urls = Array(api_urls || Quonfig::Options.derive_api_urls(domain))
143
- .map { |url| remove_trailing_slash(url) }
180
+ .map { |url| remove_trailing_slash(url) }
144
181
 
145
182
  @sse_api_urls = @api_urls.map { |url| Quonfig::Options.derive_stream_url(url) }
146
183
  @config_api_urls = @api_urls
@@ -163,56 +200,6 @@ module Quonfig
163
200
  end
164
201
  end
165
202
 
166
- def initialize(options = {})
167
- init(**options)
168
- end
169
-
170
- # In datadir mode the SDK evaluates config from a local workspace and does
171
- # not connect to the delivery service.
172
- def local_only?
173
- !@datadir.nil?
174
- end
175
-
176
- def datadir?
177
- !@datadir.nil?
178
- end
179
-
180
- def collect_max_paths
181
- return 0 unless telemetry_allowed?(true)
182
-
183
- @collect_max_paths
184
- end
185
-
186
- def collect_max_shapes
187
- return 0 unless telemetry_allowed?(@collect_shapes)
188
-
189
- @collect_max_shapes
190
- end
191
-
192
- def collect_max_example_contexts
193
- return 0 unless telemetry_allowed?(@collect_example_contexts)
194
-
195
- @collect_max_example_contexts
196
- end
197
-
198
- def collect_max_evaluation_summaries
199
- return 0 unless telemetry_allowed?(@collect_evaluation_summaries)
200
-
201
- @collect_max_evaluation_summaries
202
- end
203
-
204
- def sdk_key_id
205
- @sdk_key&.split('-')&.first
206
- end
207
-
208
- def for_fork
209
- clone = self.clone
210
- clone.is_fork = true
211
- clone
212
- end
213
-
214
- private
215
-
216
203
  def telemetry_allowed?(option)
217
204
  option && (!local_only? || @allow_telemetry_in_local_mode)
218
205
  end
@@ -5,7 +5,7 @@ module Quonfig
5
5
  LOG = Quonfig::InternalLogger.new(self)
6
6
 
7
7
  def sync
8
- return if @data.size.zero?
8
+ return if @data.empty?
9
9
 
10
10
  LOG.debug "Syncing #{@data.size} items"
11
11
 
@@ -51,8 +51,8 @@ module Quonfig
51
51
  end
52
52
 
53
53
  def self.ensure_initialized(key = nil)
54
- if !defined?(@singleton) || @singleton.nil?
55
- raise Quonfig::Errors::UninitializedError.new(key)
56
- end
54
+ return unless !defined?(@singleton) || @singleton.nil?
55
+
56
+ raise Quonfig::Errors::UninitializedError, key
57
57
  end
58
58
  end
@@ -20,9 +20,10 @@ module Quonfig
20
20
  module_function
21
21
 
22
22
  def compute(config:, conditional_value:, weighted_value_index: nil)
23
- return SPLIT if weighted_value_index && weighted_value_index.positive?
23
+ return SPLIT if weighted_value_index&.positive?
24
24
  return RULE_MATCH if targeting_rules?(config)
25
25
  return RULE_MATCH if non_always_true_criteria?(conditional_value)
26
+
26
27
  DEFAULT
27
28
  end
28
29