quonfig-openfeature
OpenFeature provider for Quonfig -- Ruby server-side SDK.
This gem wraps the quonfig native Ruby SDK and implements the
OpenFeature OpenFeature::SDK::Provider contract.
Install
gem install quonfig-openfeature openfeature-sdk quonfigOr with Bundler:
gem 'quonfig-openfeature'
gem 'openfeature-sdk'
gem 'quonfig'Usage
require 'quonfig/openfeature'
require 'open_feature/sdk'
provider = Quonfig::OpenFeature::Provider.new(
sdk_key: 'qf_sk_production_...'
# targeting_key_mapping: 'user.id', # default
)
OpenFeature::SDK.set_provider_and_wait(provider)
client = OpenFeature::SDK.build_client
# Boolean flag
enabled = client.fetch_boolean_value(flag_key: 'my-feature', default_value: false)
# String config
welcome = client.fetch_string_value(flag_key: 'welcome-message', default_value: 'Hello!')
# Number config (Integer or Float)
timeout = client.fetch_number_value(flag_key: 'request-timeout-ms', default_value: 5000)
# Object config (JSON or string_list)
allowed_plans = client.fetch_object_value(flag_key: 'allowed-plans', default_value: [])
# With evaluation context (per-request)
ctx = OpenFeature::SDK::EvaluationContext.new(
targeting_key: 'user-123', # maps to user.id by default
'user.plan' => 'pro',
'org.tier' => 'enterprise'
)
is_pro = client.fetch_boolean_value(flag_key: 'pro-feature', default_value: false,
evaluation_context: ctx)Startup failure and provider status
The provider forwards on_init_failure: and init_timeout_ms: to Quonfig::Client.new, and its
OpenFeature status follows the mode:
on_init_failure |
init when no config loads |
Provider status | Later |
|---|---|---|---|
:fail (default) |
raises Quonfig::Errors::InitializationError after init_timeout_ms (at once if the SDK key is rejected) |
ERROR | No client exists, so nothing retries; restart the process |
:wait |
blocks and retries with no upper bound | NOT_READY while waiting | READY when config loads |
:continue |
returns; emits PROVIDER_ERROR
|
ERROR; evaluations return your default with PROVIDER_NOT_READY
|
emits PROVIDER_READY when the background retry loads config |
provider = Quonfig::OpenFeature::Provider.new(
sdk_key: 'qf_sk_production_...',
on_init_failure: :continue, # :fail (default), :wait or :continue
init_timeout_ms: 10_000
)
OpenFeature::SDK.set_provider_and_wait(provider)Context mapping
OpenFeature context is flat; Quonfig context is nested by namespace. This provider maps between them using dot-notation:
| OpenFeature context key | Quonfig namespace | Quonfig property |
|---|---|---|
targeting_key |
user |
id (configurable via targeting_key_mapping) |
"user.email" |
user |
email |
"org.tier" |
org |
tier |
"country" (no dot) |
"" (default) |
country |
"user.ip.address" |
user |
ip.address (first dot only) |
Customising targeting_key mapping
provider = Quonfig::OpenFeature::Provider.new(
sdk_key: 'qf_sk_...',
targeting_key_mapping: 'account.id' # maps targeting_key to { account: { id: ... } }
)Accessing native SDK features
The client reader returns the underlying Quonfig::Client for features not
available through the OpenFeature API. Extra keyword arguments to Provider.new
are forwarded to Quonfig::Client.new, so pass logger_key: there to use
should_log?(logger_path:) (without it, should_log? raises Quonfig::Error):
provider = Quonfig::OpenFeature::Provider.new(
sdk_key: 'qf_sk_production_...',
logger_key: 'log-level.my-app' # the log-level config should_log? evaluates
)
OpenFeature::SDK.set_provider_and_wait(provider)
native = provider.client
# Log level integration (requires logger_key above)
native.should_log?(logger_path: 'auth', desired_level: :debug,
contexts: { 'user' => { 'id' => 'user-123' } })
# List all config keys
keys = native.keys
# Per-request bound client
native.with_context('user' => { 'plan' => 'pro' }) do |bound|
bound.get_bool('pro-feature')
endWhat you lose vs. the native SDK
OpenFeature is designed for feature flags, not general configuration. Some
Quonfig features require the native quonfig SDK:
-
Log levels --
should_log?and thesemantic_logger_filterare native-only. -
string_listconfigs -- accessed viafetch_object_valueand used asArray<String>. -
durationconfigs --fetch_string_valuereturns the stored ISO 8601 text unchanged ("PT90S"). For integer milliseconds useprovider.client.get_duration.fetch_float_valueandfetch_number_valueon a duration config return your default withTYPE_MISMATCH. -
bytesconfigs -- not accessible via OpenFeature (no binary type in OF). -
keys/ raw config inspection -- native-only viaprovider.client. -
Context keys use dot-notation --
"user.email", not nested hashes. -
targeting_keymaps touser.idby default -- configuretargeting_key_mappingif you target a different namespace.
Typed reads
Each fetch_* method reads only values of its type (the cross-SDK typed-getter contract,
quonfig >= 2.0.0). A value of another type returns your default with reason ERROR and error
code TYPE_MISMATCH:
- A stored string is never coerced:
"false"is not a boolean and"3"is not a number. - Numbers convert only when exact:
fetch_float_valueon an int config returns30.0, andfetch_integer_valueon a double config returns the value only when it is whole and within +/-(2^53-1) (3.0->3;3.9is a mismatch, never truncated). - A malformed value of a type the method reads returns your default with
PARSE_ERROR: a stored int"twelve", an int or double ENV_VAR set to"twelve", or a stored duration"30s"read withfetch_string_value. A read with the method for another type staysTYPE_MISMATCH. An unset ENV_VAR returns your default withGENERAL.
sdk-ruby logs one WARN per key when a read returns your default for one of these reasons.
Offline / test mode
Pass datadir: to evaluate against a Quonfig workspace on disk -- the same
layout the integration test suite uses. No network or SDK key is required.
provider = Quonfig::OpenFeature::Provider.new(
datadir: '/path/to/workspace',
environment: 'Production',
enable_sse: false
)Development
bundle install
bundle exec rake testThe development Gemfile references the sibling ../sdk-ruby/ checkout via a
path: reference; downstream consumers depend on the quonfig gem from
RubyGems.