Class: Quonfig::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/quonfig/client.rb

Overview

Public Quonfig SDK client.

Wires the JSON stack: Quonfig::ConfigStore + Quonfig::Evaluator + Quonfig::Resolver. Three modes are supported:

  1. datadir: (offline) – load a workspace from the local filesystem.

  2. store: (test harness) – caller-supplied ConfigStore, no I/O.

  3. network mode (default) – HTTP fetch from api_urls populates the ConfigStore, then (if enabled) an SSE subscription keeps it live.

Network mode is the happy path for production SDK usage. The protobuf stack was retired in qfg-dk6.32; HTTP + SSE were wired back through Client in qfg-s7h.

Constant Summary collapse

LOG =
Quonfig::InternalLogger.new(self)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = nil, store: nil, **option_kwargs) ⇒ Client

Returns a new instance of Client.



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/quonfig/client.rb', line 26

def initialize(options = nil, store: nil, **option_kwargs)
  @options =
    if options.is_a?(Quonfig::Options)
      options
    elsif options.is_a?(Hash)
      Quonfig::Options.new(options.merge(option_kwargs))
    else
      Quonfig::Options.new(option_kwargs)
    end
  @global_context = normalize_context(@options.global_context)
  @instance_hash = SecureRandom.uuid
  @store = store || Quonfig::ConfigStore.new
  @evaluator = Quonfig::Evaluator.new(@store, env_id: @options.environment)
  @resolver = Quonfig::Resolver.new(@store, @evaluator)
  @semantic_logger_filters = {}
  @sse_client = nil
  @poll_thread = nil
  @stopped = false

  # If the caller injected a store, we're in test/bootstrap mode; skip I/O.
  return if store

  if @options.datadir
    load_datadir_into_store
  else
    initialize_network_mode
  end
end

Instance Attribute Details

#config_loaderObject (readonly)

Returns the value of attribute config_loader.



23
24
25
# File 'lib/quonfig/client.rb', line 23

def config_loader
  @config_loader
end

#evaluatorObject (readonly)

Returns the value of attribute evaluator.



23
24
25
# File 'lib/quonfig/client.rb', line 23

def evaluator
  @evaluator
end

#instance_hashObject (readonly)

Returns the value of attribute instance_hash.



23
24
25
# File 'lib/quonfig/client.rb', line 23

def instance_hash
  @instance_hash
end

#optionsObject (readonly)

Returns the value of attribute options.



23
24
25
# File 'lib/quonfig/client.rb', line 23

def options
  @options
end

#resolverObject (readonly)

Returns the value of attribute resolver.



23
24
25
# File 'lib/quonfig/client.rb', line 23

def resolver
  @resolver
end

#storeObject (readonly)

Returns the value of attribute store.



23
24
25
# File 'lib/quonfig/client.rb', line 23

def store
  @store
end

Instance Method Details

#defined?(key) ⇒ Boolean

Returns:

  • (Boolean)


98
99
100
# File 'lib/quonfig/client.rb', line 98

def defined?(key)
  !@store.get(key).nil?
end

#enabled?(feature_name, jit_context = NO_DEFAULT_PROVIDED) ⇒ Boolean

Returns:

  • (Boolean)


93
94
95
96
# File 'lib/quonfig/client.rb', line 93

def enabled?(feature_name, jit_context = NO_DEFAULT_PROVIDED)
  value = get(feature_name, false, jit_context)
  value == true || value == 'true'
end

#forkObject



205
206
207
# File 'lib/quonfig/client.rb', line 205

def fork
  self.class.new(@options.for_fork)
end

#get(key, default = NO_DEFAULT_PROVIDED, jit_context = NO_DEFAULT_PROVIDED) ⇒ Object

—- Lookup ——————————————————–



57
58
59
60
61
62
63
# File 'lib/quonfig/client.rb', line 57

def get(key, default = NO_DEFAULT_PROVIDED, jit_context = NO_DEFAULT_PROVIDED)
  ctx = build_context(jit_context)
  result = @resolver.get(key, ctx)
  return handle_missing(key, default) if result.nil?

  result.unwrapped_value
end

#get_bool(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) ⇒ Object



77
78
79
# File 'lib/quonfig/client.rb', line 77

def get_bool(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED)
  typed_get(key, :bool, default: default, context: context)
end

#get_duration(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) ⇒ Object



85
86
87
# File 'lib/quonfig/client.rb', line 85

def get_duration(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED)
  typed_get(key, :duration, default: default, context: context)
end

#get_float(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) ⇒ Object



73
74
75
# File 'lib/quonfig/client.rb', line 73

def get_float(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED)
  typed_get(key, Float, default: default, context: context)
end

#get_int(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) ⇒ Object



69
70
71
# File 'lib/quonfig/client.rb', line 69

def get_int(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED)
  typed_get(key, Integer, default: default, context: context)
end

#get_json(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) ⇒ Object



89
90
91
# File 'lib/quonfig/client.rb', line 89

def get_json(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED)
  typed_get(key, :json, default: default, context: context)
end

#get_string(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) ⇒ Object



65
66
67
# File 'lib/quonfig/client.rb', line 65

def get_string(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED)
  typed_get(key, String, default: default, context: context)
end

#get_string_list(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) ⇒ Object



81
82
83
# File 'lib/quonfig/client.rb', line 81

def get_string_list(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED)
  typed_get(key, :string_list, default: default, context: context)
end

#in_context(properties) ⇒ Object

—- Context binding ———————————————-



108
109
110
111
# File 'lib/quonfig/client.rb', line 108

def in_context(properties)
  bound = Quonfig::BoundClient.new(self, properties)
  block_given? ? yield(bound) : bound
end

#inspectObject



209
210
211
# File 'lib/quonfig/client.rb', line 209

def inspect
  "#<Quonfig::Client:#{object_id} environment=#{@options.environment.inspect}>"
end

#keysObject



102
103
104
# File 'lib/quonfig/client.rb', line 102

def keys
  @store.keys
end

#logger_keyObject

The configured logger_key from Options — the Quonfig config key the higher-level should_log? helper evaluates per-logger. nil if the client was not configured for dynamic log levels.



131
132
133
# File 'lib/quonfig/client.rb', line 131

def logger_key
  @options.logger_key
end

#on_update(&block) ⇒ Object



187
188
189
# File 'lib/quonfig/client.rb', line 187

def on_update(&block)
  @on_update = block
end

#semantic_logger_filter(config_key:) ⇒ Object

—- Filters & helpers ——————————————–



123
124
125
126
# File 'lib/quonfig/client.rb', line 123

def semantic_logger_filter(config_key:)
  @semantic_logger_filters[config_key] ||=
    Quonfig::SemanticLoggerFilter.new(self, config_key: config_key)
end

#should_log?(logger_path:, desired_level:, contexts: {}) ⇒ Boolean

Higher-level log-level check — a convenience on top of the primitive get. Evaluates the client’s logger_key config and returns whether a message at desired_level should be emitted for logger_path.

The SDK injects logger_path under the quonfig-sdk-logging named context with property key so a single log-level config can drive per-logger overrides via the normal rule engine (e.g. PROP_STARTS_WITH_ONE_OF “MyApp::Services::”).

logger_path is passed through verbatim — the SDK does not normalize it. Callers may pass any identifier shape their host language prefers (dotted, colon, slash, etc.) and author matching rules in the config against that exact shape.

Parallels sdk-node’s shouldLog({loggerPath}) and sdk-go’s ShouldLogPath.

Raises Quonfig::Error if logger_key was not set on the client —use semantic_logger_filter(config_key:) directly if you want to evaluate a specific key without declaring it at init time.

Parameters:

  • logger_path (String)

    native logger name (typically a class name).

  • desired_level (Symbol, String)

    the level the caller wants to emit at (:trace, :debug, :info, :warn, :error, :fatal).

  • contexts (Hash) (defaults to: {})

    optional extra context to merge with the injected logger context.

Returns:

  • (Boolean)

    true if the message should be emitted.



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/quonfig/client.rb', line 162

def should_log?(logger_path:, desired_level:, contexts: {})
  unless logger_key
    raise Quonfig::Error,
          'logger_key must be set at init to use should_log?(logger_path:, ...). ' \
          'Pass `logger_key:` to Quonfig::Options.new, or call ' \
          'semantic_logger_filter(config_key:) / get(config_key) directly.'
  end

  logger_context = {
    Quonfig::SemanticLoggerFilter::LOGGER_CONTEXT_NAME => {
      Quonfig::SemanticLoggerFilter::LOGGER_CONTEXT_KEY_PROP => logger_path
    }
  }
  merged = merge_contexts(normalize_context(contexts), logger_context)

  configured = get(logger_key, nil, merged)
  return true if configured.nil?

  desired_severity = Quonfig::SemanticLoggerFilter::LEVELS[normalize_log_level(desired_level)] ||
                     Quonfig::SemanticLoggerFilter::LEVELS[:debug]
  min_severity     = Quonfig::SemanticLoggerFilter::LEVELS[normalize_log_level(configured)] ||
                     Quonfig::SemanticLoggerFilter::LEVELS[:debug]
  desired_severity >= min_severity
end

#stopObject



191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/quonfig/client.rb', line 191

def stop
  @stopped = true
  begin
    @sse_client&.close
  rescue StandardError => e
    LOG.debug "Error closing SSE client: #{e.message}"
  end
  @sse_client = nil

  thread = @poll_thread
  @poll_thread = nil
  thread&.kill
end

#with_context(properties, &block) ⇒ Object



113
114
115
116
117
118
119
# File 'lib/quonfig/client.rb', line 113

def with_context(properties, &block)
  if block_given?
    in_context(properties, &block)
  else
    Quonfig::BoundClient.new(self, properties)
  end
end