Class: Quonfig::OpenFeature::Provider

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

Overview

OpenFeature provider that wraps the quonfig Ruby SDK and implements the OpenFeature server-side provider contract:

* +metadata+
* +init(evaluation_context = nil)+
* +shutdown+
* +fetch_boolean_value(flag_key:, default_value:, evaluation_context:)+
* +fetch_string_value(flag_key:, default_value:, evaluation_context:)+
* +fetch_number_value(flag_key:, default_value:, evaluation_context:)+
* +fetch_integer_value(flag_key:, default_value:, evaluation_context:)+
* +fetch_float_value(flag_key:, default_value:, evaluation_context:)+
* +fetch_object_value(flag_key:, default_value:, evaluation_context:)+

Usage:

require 'quonfig/openfeature'
require 'open_feature/sdk'

provider = Quonfig::OpenFeature::Provider.new(sdk_key: 'qf_sk_...')
OpenFeature::SDK.set_provider_and_wait(provider)

client = OpenFeature::SDK.build_client
client.fetch_boolean_value(flag_key: 'my-flag', default_value: false)

Constant Summary collapse

NAME =
'quonfig'
ResolutionDetails =
::OpenFeature::SDK::Provider::ResolutionDetails
Reason =
::OpenFeature::SDK::Provider::Reason
ErrorCode =
::OpenFeature::SDK::Provider::ErrorCode
ProviderMetadata =
::OpenFeature::SDK::Provider::ProviderMetadata

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(sdk_key: nil, datadir: nil, environment: nil, targeting_key_mapping: Context::DEFAULT_TARGETING_KEY_MAPPING, client: nil, **quonfig_options) ⇒ Provider

Returns a new instance of Provider.

Parameters:

  • sdk_key (String, nil) (defaults to: nil)

    SDK key for the live delivery service.

  • datadir (String, nil) (defaults to: nil)

    path to a Quonfig workspace for offline mode.

  • environment (String, nil) (defaults to: nil)

    which environment to evaluate.

  • targeting_key_mapping (String) (defaults to: Context::DEFAULT_TARGETING_KEY_MAPPING)

    dot-notation path the OpenFeature targeting_key is rewritten to (default “user.id”).

  • client (Quonfig::Client, nil) (defaults to: nil)

    inject a pre-built Quonfig client (primarily for tests). When supplied, the other sdk_key/datadir/etc. options are ignored.

  • quonfig_options (Hash)

    any other keyword arguments are forwarded verbatim to Quonfig::Client.new.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/quonfig/openfeature/provider.rb', line 54

def initialize(sdk_key: nil, datadir: nil, environment: nil,
               targeting_key_mapping: Context::DEFAULT_TARGETING_KEY_MAPPING,
               client: nil, **quonfig_options)
  @metadata = ProviderMetadata.new(name: NAME).freeze
  @targeting_key_mapping = targeting_key_mapping
  @client = client
  @quonfig_options = build_quonfig_options(
    sdk_key: sdk_key,
    datadir: datadir,
    environment: environment,
    extra: quonfig_options
  )
  @initialized = !@client.nil?
end

Instance Attribute Details

#metadataObject (readonly)

Returns the value of attribute metadata.



42
43
44
# File 'lib/quonfig/openfeature/provider.rb', line 42

def 
  @metadata
end

#targeting_key_mappingObject (readonly)

Returns the value of attribute targeting_key_mapping.



42
43
44
# File 'lib/quonfig/openfeature/provider.rb', line 42

def targeting_key_mapping
  @targeting_key_mapping
end

Instance Method Details

#clientObject

Escape hatch: returns the underlying Quonfig::Client for native-only features (keys, raw config, durations, log levels). Returns nil until init has run.



92
93
94
# File 'lib/quonfig/openfeature/provider.rb', line 92

def client
  @client
end

#fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil) ⇒ Object

—- fetch_*_value —————————————————–



98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/quonfig/openfeature/provider.rb', line 98

def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
  evaluate(flag_key, default_value, evaluation_context) do |client, mapped_ctx|
    value = client.get_bool(flag_key, default: nil, context: mapped_ctx)
    if value.nil?
      ResolutionDetails.new(value: default_value, reason: Reason::ERROR,
                            error_code: ErrorCode::FLAG_NOT_FOUND,
                            error_message: "flag not found: #{flag_key}")
    else
      ResolutionDetails.new(value: value, reason: Reason::TARGETING_MATCH)
    end
  end
end

#fetch_float_value(flag_key:, default_value:, evaluation_context: nil) ⇒ Object



159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/quonfig/openfeature/provider.rb', line 159

def fetch_float_value(flag_key:, default_value:, evaluation_context: nil)
  evaluate(flag_key, default_value, evaluation_context) do |client, mapped_ctx|
    value = client.get_float(flag_key, default: nil, context: mapped_ctx)
    if value.nil?
      ResolutionDetails.new(value: default_value, reason: Reason::ERROR,
                            error_code: ErrorCode::FLAG_NOT_FOUND,
                            error_message: "flag not found: #{flag_key}")
    else
      ResolutionDetails.new(value: value, reason: Reason::TARGETING_MATCH)
    end
  end
end

#fetch_integer_value(flag_key:, default_value:, evaluation_context: nil) ⇒ Object



146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/quonfig/openfeature/provider.rb', line 146

def fetch_integer_value(flag_key:, default_value:, evaluation_context: nil)
  evaluate(flag_key, default_value, evaluation_context) do |client, mapped_ctx|
    value = client.get_int(flag_key, default: nil, context: mapped_ctx)
    if value.nil?
      ResolutionDetails.new(value: default_value, reason: Reason::ERROR,
                            error_code: ErrorCode::FLAG_NOT_FOUND,
                            error_message: "flag not found: #{flag_key}")
    else
      ResolutionDetails.new(value: value, reason: Reason::TARGETING_MATCH)
    end
  end
end

#fetch_number_value(flag_key:, default_value:, evaluation_context: nil) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/quonfig/openfeature/provider.rb', line 124

def fetch_number_value(flag_key:, default_value:, evaluation_context: nil)
  # OpenFeature's "number" is Ruby Numeric (Integer or Float). Try integer
  # first, fall back to float so we transparently handle both Quonfig
  # int and double configs.
  evaluate(flag_key, default_value, evaluation_context) do |client, mapped_ctx|
    value = nil
    begin
      value = client.get_int(flag_key, default: nil, context: mapped_ctx)
    rescue ::Quonfig::Errors::TypeMismatchError
      value = client.get_float(flag_key, default: nil, context: mapped_ctx)
    end

    if value.nil?
      ResolutionDetails.new(value: default_value, reason: Reason::ERROR,
                            error_code: ErrorCode::FLAG_NOT_FOUND,
                            error_message: "flag not found: #{flag_key}")
    else
      ResolutionDetails.new(value: value, reason: Reason::TARGETING_MATCH)
    end
  end
end

#fetch_object_value(flag_key:, default_value:, evaluation_context: nil) ⇒ Object

Object resolution tries get_string_list first (so Quonfig string_list configs surface as native arrays), then falls back to get_json for any other JSON-shaped config.



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/quonfig/openfeature/provider.rb', line 175

def fetch_object_value(flag_key:, default_value:, evaluation_context: nil)
  evaluate(flag_key, default_value, evaluation_context) do |client, mapped_ctx|
    value = nil
    begin
      value = client.get_string_list(flag_key, default: nil, context: mapped_ctx)
    rescue ::Quonfig::Errors::TypeMismatchError
      value = nil
    end
    if value.nil?
      value = client.get_json(flag_key, default: nil, context: mapped_ctx)
    end

    if value.nil?
      ResolutionDetails.new(value: default_value, reason: Reason::ERROR,
                            error_code: ErrorCode::FLAG_NOT_FOUND,
                            error_message: "flag not found: #{flag_key}")
    else
      ResolutionDetails.new(value: value, reason: Reason::TARGETING_MATCH)
    end
  end
end

#fetch_string_value(flag_key:, default_value:, evaluation_context: nil) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/quonfig/openfeature/provider.rb', line 111

def fetch_string_value(flag_key:, default_value:, evaluation_context: nil)
  evaluate(flag_key, default_value, evaluation_context) do |client, mapped_ctx|
    value = client.get_string(flag_key, default: nil, context: mapped_ctx)
    if value.nil?
      ResolutionDetails.new(value: default_value, reason: Reason::ERROR,
                            error_code: ErrorCode::FLAG_NOT_FOUND,
                            error_message: "flag not found: #{flag_key}")
    else
      ResolutionDetails.new(value: value, reason: Reason::TARGETING_MATCH)
    end
  end
end

#init(_evaluation_context = nil) ⇒ Object

Initialize the underlying Quonfig client. Called by OpenFeature::SDK.set_provider_and_wait.



71
72
73
74
75
76
77
# File 'lib/quonfig/openfeature/provider.rb', line 71

def init(_evaluation_context = nil)
  return if @initialized

  @client = ::Quonfig::Client.new(@quonfig_options)
  @initialized = true
  nil
end

#shutdownObject

Shut the provider down. Mirrors the OpenFeature InMemoryProvider contract — silently no-ops if the client was never built.



81
82
83
84
85
86
87
# File 'lib/quonfig/openfeature/provider.rb', line 81

def shutdown
  client = @client
  @client = nil
  @initialized = false
  client&.stop
  nil
end