quonfig-openfeature 2.0.0 → 2.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -0
- data/README.md +7 -5
- data/VERSION +1 -1
- data/lib/quonfig/openfeature/provider.rb +102 -9
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d4a4af7330ac53ebeb637ed61d4c7cd0530bcfe62db92b845e5233d105496a3b
|
|
4
|
+
data.tar.gz: 7761829a3596aace1dad718f622b1156656079fd9ffe15123fcf0114c06e9837
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1de71990148d10403dcc3559f5a2d07d16a73a3c6c920e1fc38bdd282a8ba2fbd2d6e9e10524c1b8f04195c337918757f51d94365ec8368824aaccc52da17e8e
|
|
7
|
+
data.tar.gz: 80802c8ddb10fada30d464bbbaf55a9b45e45d05a819980dd555d601568c01003b2d8de9aa5c3f970727e39a4e0d5bcfe5c370d29b82bd11db4b33de987d5ae0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.0 - 2026-10-29
|
|
4
|
+
|
|
5
|
+
`fetch_string_value` on a `duration` config returns its stored ISO 8601 text again (reason
|
|
6
|
+
`STATIC`), as in openfeature-go and the shared OpenFeature cases (qfg-177f). The text comes back
|
|
7
|
+
unchanged: `"PT90S"` stays `"PT90S"`, not `"PT1M30S"`. 2.x returned your default with
|
|
8
|
+
`TYPE_MISMATCH`. Number reads of a duration still return your default. A malformed stored duration
|
|
9
|
+
such as `"30s"` still returns your default with `PARSE_ERROR`, and so does a duration ENV_VAR set
|
|
10
|
+
to a malformed value.
|
|
11
|
+
|
|
12
|
+
Requires quonfig (sdk-ruby) 2.1.0: the text is read with its new public `evaluate_details`, in one
|
|
13
|
+
evaluation, and checked against the shared duration grammar with `Quonfig::Duration.valid?`.
|
|
14
|
+
|
|
15
|
+
## 2.1.0 - 2026-10-28
|
|
16
|
+
|
|
17
|
+
A malformed value now resolves with error code `PARSE_ERROR` instead of `TYPE_MISMATCH`
|
|
18
|
+
(qfg-9xsl, mirrors integration-test-data `tests/openfeature/openfeature.yaml` Case 6). You still
|
|
19
|
+
get your default with reason `ERROR`. This covers a value of a type the `fetch_*` method reads that
|
|
20
|
+
does not parse:
|
|
21
|
+
|
|
22
|
+
- a stored int `"twelve"` or double `"abc"`;
|
|
23
|
+
- an int or double provided by an ENV_VAR set to an unparseable string, such as `"twelve"`;
|
|
24
|
+
- a malformed stored duration such as `"30s"`, read with `fetch_string_value`.
|
|
25
|
+
|
|
26
|
+
A read with the method for another type (an int config read with `fetch_boolean_value`) stays
|
|
27
|
+
`TYPE_MISMATCH`, as does a number that parses but does not convert exactly (`fetch_integer_value`
|
|
28
|
+
on `3.9`). sdk-ruby's `*_details` report both cases as `TYPE_MISMATCH`, so the provider tells them
|
|
29
|
+
apart: on a `TYPE_MISMATCH` it checks the config's stored `valueType` and, if the method can read
|
|
30
|
+
that type, reads it with the native getter for that type. If that read fails too, the value is
|
|
31
|
+
malformed. This extra read happens only on the error path.
|
|
32
|
+
|
|
33
|
+
If you alert or branch on `TYPE_MISMATCH` for bad config data, match `PARSE_ERROR` as well.
|
|
34
|
+
|
|
3
35
|
## 2.0.0 - 2026-10-27
|
|
4
36
|
|
|
5
37
|
**BREAKING / UPGRADE (major, qfg-zvyy.6).** This release tracks sdk-ruby 2.0.0, the cross-SDK
|
data/README.md
CHANGED
|
@@ -134,9 +134,9 @@ Quonfig features require the native `quonfig` SDK:
|
|
|
134
134
|
|
|
135
135
|
1. **Log levels** -- `should_log?` and the `semantic_logger_filter` are native-only.
|
|
136
136
|
2. **`string_list` configs** -- accessed via `fetch_object_value` and used as `Array<String>`.
|
|
137
|
-
3. **`duration` configs** --
|
|
138
|
-
|
|
139
|
-
default with `TYPE_MISMATCH`.
|
|
137
|
+
3. **`duration` configs** -- `fetch_string_value` returns the stored ISO 8601 text unchanged
|
|
138
|
+
(`"PT90S"`). For integer milliseconds use `provider.client.get_duration`. `fetch_float_value`
|
|
139
|
+
and `fetch_number_value` on a duration config return your default with `TYPE_MISMATCH`.
|
|
140
140
|
4. **`bytes` configs** -- not accessible via OpenFeature (no binary type in OF).
|
|
141
141
|
5. **`keys` / raw config inspection** -- native-only via `provider.client`.
|
|
142
142
|
6. **Context keys use dot-notation** -- `"user.email"`, not nested hashes.
|
|
@@ -153,8 +153,10 @@ code `TYPE_MISMATCH`:
|
|
|
153
153
|
- Numbers convert only when exact: `fetch_float_value` on an int config returns `30.0`, and
|
|
154
154
|
`fetch_integer_value` on a double config returns the value only when it is whole and within
|
|
155
155
|
+/-(2^53-1) (`3.0` -> `3`; `3.9` is a mismatch, never truncated).
|
|
156
|
-
- A malformed
|
|
157
|
-
`
|
|
156
|
+
- A malformed value of a type the method reads returns your default with `PARSE_ERROR`: a stored
|
|
157
|
+
int `"twelve"`, an int or double ENV_VAR set to `"twelve"`, or a stored duration `"30s"` read
|
|
158
|
+
with `fetch_string_value`. A read with the method for another type stays `TYPE_MISMATCH`. An
|
|
159
|
+
unset ENV_VAR returns your default with `GENERAL`.
|
|
158
160
|
|
|
159
161
|
sdk-ruby logs one WARN per key when a read returns your default for one of these reasons.
|
|
160
162
|
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.2.0
|
|
@@ -56,6 +56,32 @@ module Quonfig
|
|
|
56
56
|
ErrorCode = ::OpenFeature::SDK::Provider::ErrorCode
|
|
57
57
|
ProviderMetadata = ::OpenFeature::SDK::Provider::ProviderMetadata
|
|
58
58
|
|
|
59
|
+
# The stored +valueType+s each fetch method can read: its own type plus
|
|
60
|
+
# the ones it converts exactly (int <-> double, duration -> ISO string).
|
|
61
|
+
# A TYPE_MISMATCH on a config of one of these types means the value
|
|
62
|
+
# did not parse, so it is reported as PARSE_ERROR (qfg-9xsl).
|
|
63
|
+
READABLE_VALUE_TYPES = {
|
|
64
|
+
boolean: %w[bool],
|
|
65
|
+
string: %w[string log_level duration],
|
|
66
|
+
integer: %w[int double],
|
|
67
|
+
float: %w[double int],
|
|
68
|
+
number: %w[int double],
|
|
69
|
+
object: %w[string_list json]
|
|
70
|
+
}.freeze
|
|
71
|
+
|
|
72
|
+
# The native details getter that reads each stored +valueType+ as itself.
|
|
73
|
+
NATIVE_DETAILS_GETTER = {
|
|
74
|
+
'bool' => :get_bool_details,
|
|
75
|
+
'string' => :get_string_details,
|
|
76
|
+
'log_level' => :get_string_details,
|
|
77
|
+
'duration' => :get_duration_details,
|
|
78
|
+
'int' => :get_int_details,
|
|
79
|
+
'double' => :get_float_details,
|
|
80
|
+
'string_list' => :get_string_list_details,
|
|
81
|
+
'json' => :get_json_details
|
|
82
|
+
}.freeze
|
|
83
|
+
private_constant :READABLE_VALUE_TYPES, :NATIVE_DETAILS_GETTER
|
|
84
|
+
|
|
59
85
|
attr_reader :metadata, :targeting_key_mapping
|
|
60
86
|
|
|
61
87
|
# @param sdk_key [String, nil] SDK key for the live delivery service.
|
|
@@ -117,13 +143,19 @@ module Quonfig
|
|
|
117
143
|
|
|
118
144
|
def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
|
|
119
145
|
evaluate(flag_key, default_value, evaluation_context) do |client, mapped_ctx|
|
|
120
|
-
|
|
146
|
+
details = client.get_bool_details(flag_key, context: mapped_ctx)
|
|
147
|
+
to_resolution(details, default_value, parse_error: malformed?(client, flag_key, mapped_ctx, :boolean, details))
|
|
121
148
|
end
|
|
122
149
|
end
|
|
123
150
|
|
|
151
|
+
# A +duration+ config reads as its stored ISO 8601 text, unchanged
|
|
152
|
+
# ("PT90S" stays "PT90S"): OpenFeature has no duration type (qfg-177f).
|
|
124
153
|
def fetch_string_value(flag_key:, default_value:, evaluation_context: nil)
|
|
125
154
|
evaluate(flag_key, default_value, evaluation_context) do |client, mapped_ctx|
|
|
126
|
-
|
|
155
|
+
next duration_iso_resolution(client, flag_key, mapped_ctx, default_value) if stored_value_type(client, flag_key) == 'duration'
|
|
156
|
+
|
|
157
|
+
details = client.get_string_details(flag_key, context: mapped_ctx)
|
|
158
|
+
to_resolution(details, default_value, parse_error: malformed?(client, flag_key, mapped_ctx, :string, details))
|
|
127
159
|
end
|
|
128
160
|
end
|
|
129
161
|
|
|
@@ -134,19 +166,21 @@ module Quonfig
|
|
|
134
166
|
evaluate(flag_key, default_value, evaluation_context) do |client, mapped_ctx|
|
|
135
167
|
details = client.get_int_details(flag_key, context: mapped_ctx)
|
|
136
168
|
details = client.get_float_details(flag_key, context: mapped_ctx) if details.error_code == ::Quonfig::EvaluationDetails::ERROR_TYPE_MISMATCH
|
|
137
|
-
to_resolution(details, default_value)
|
|
169
|
+
to_resolution(details, default_value, parse_error: malformed?(client, flag_key, mapped_ctx, :number, details))
|
|
138
170
|
end
|
|
139
171
|
end
|
|
140
172
|
|
|
141
173
|
def fetch_integer_value(flag_key:, default_value:, evaluation_context: nil)
|
|
142
174
|
evaluate(flag_key, default_value, evaluation_context) do |client, mapped_ctx|
|
|
143
|
-
|
|
175
|
+
details = client.get_int_details(flag_key, context: mapped_ctx)
|
|
176
|
+
to_resolution(details, default_value, parse_error: malformed?(client, flag_key, mapped_ctx, :integer, details))
|
|
144
177
|
end
|
|
145
178
|
end
|
|
146
179
|
|
|
147
180
|
def fetch_float_value(flag_key:, default_value:, evaluation_context: nil)
|
|
148
181
|
evaluate(flag_key, default_value, evaluation_context) do |client, mapped_ctx|
|
|
149
|
-
|
|
182
|
+
details = client.get_float_details(flag_key, context: mapped_ctx)
|
|
183
|
+
to_resolution(details, default_value, parse_error: malformed?(client, flag_key, mapped_ctx, :float, details))
|
|
150
184
|
end
|
|
151
185
|
end
|
|
152
186
|
|
|
@@ -157,7 +191,7 @@ module Quonfig
|
|
|
157
191
|
evaluate(flag_key, default_value, evaluation_context) do |client, mapped_ctx|
|
|
158
192
|
details = client.get_string_list_details(flag_key, context: mapped_ctx)
|
|
159
193
|
details = client.get_json_details(flag_key, context: mapped_ctx) if details.error_code == ::Quonfig::EvaluationDetails::ERROR_TYPE_MISMATCH
|
|
160
|
-
to_resolution(details, default_value)
|
|
194
|
+
to_resolution(details, default_value, parse_error: malformed?(client, flag_key, mapped_ctx, :object, details))
|
|
161
195
|
end
|
|
162
196
|
end
|
|
163
197
|
|
|
@@ -198,10 +232,69 @@ module Quonfig
|
|
|
198
232
|
end
|
|
199
233
|
end
|
|
200
234
|
|
|
235
|
+
# True when +details+ is a TYPE_MISMATCH for a config whose value has a
|
|
236
|
+
# type +kind+ can read but did not parse: a stored int "twelve", an int
|
|
237
|
+
# ENV_VAR set to "twelve", a duration "30s" (qfg-9xsl). sdk-ruby's
|
|
238
|
+
# *_details report a malformed value and a wrong-type read alike as
|
|
239
|
+
# TYPE_MISMATCH, so this checks the stored +valueType+ and reads the
|
|
240
|
+
# config with the getter for that type: if that read fails too, the
|
|
241
|
+
# value is malformed. A config of a type +kind+ cannot read stays
|
|
242
|
+
# TYPE_MISMATCH, and so does a value that parsed but does not convert
|
|
243
|
+
# exactly (fetch_integer_value on a 3.9 double).
|
|
244
|
+
def malformed?(client, flag_key, mapped_ctx, kind, details)
|
|
245
|
+
return false unless details.error_code == ::Quonfig::EvaluationDetails::ERROR_TYPE_MISMATCH
|
|
246
|
+
|
|
247
|
+
value_type = stored_value_type(client, flag_key)
|
|
248
|
+
return false unless READABLE_VALUE_TYPES.fetch(kind).include?(value_type)
|
|
249
|
+
|
|
250
|
+
native = client.public_send(NATIVE_DETAILS_GETTER.fetch(value_type), flag_key, context: mapped_ctx)
|
|
251
|
+
native.error_code == ::Quonfig::EvaluationDetails::ERROR_TYPE_MISMATCH
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Read a duration config as its ISO 8601 text with one evaluation:
|
|
255
|
+
# sdk-ruby's evaluate_details (quonfig >= 2.1.0) returns the resolved
|
|
256
|
+
# value in stored form, so the text is the stored text, or an ENV_VAR's
|
|
257
|
+
# text for a provided duration. evaluate_details does no type check, so
|
|
258
|
+
# the text is validated here with Quonfig::Duration.valid?, the grammar
|
|
259
|
+
# get_duration uses:
|
|
260
|
+
#
|
|
261
|
+
# * valid text: the text, with the evaluation's reason, variant and
|
|
262
|
+
# flag_metadata.
|
|
263
|
+
# * malformed stored text ("30s"): the default with PARSE_ERROR.
|
|
264
|
+
# * malformed ENV_VAR: evaluate_details reports ERROR / TYPE_MISMATCH,
|
|
265
|
+
# which on a duration config can only mean a value that did not
|
|
266
|
+
# parse, so it is PARSE_ERROR too.
|
|
267
|
+
# * the structured { seconds, nanos } shape has no ISO text: the
|
|
268
|
+
# default with TYPE_MISMATCH.
|
|
269
|
+
def duration_iso_resolution(client, flag_key, mapped_ctx, default_value)
|
|
270
|
+
details = client.evaluate_details(flag_key, context: mapped_ctx)
|
|
271
|
+
if details.reason == ::Quonfig::EvaluationDetails::REASON_ERROR
|
|
272
|
+
parse_error = details.error_code == ::Quonfig::EvaluationDetails::ERROR_TYPE_MISMATCH
|
|
273
|
+
return to_resolution(details, default_value, parse_error: parse_error)
|
|
274
|
+
end
|
|
275
|
+
return to_resolution(details, default_value) if details.reason == ::Quonfig::EvaluationDetails::REASON_DEFAULT
|
|
276
|
+
|
|
277
|
+
value = details.value
|
|
278
|
+
return to_resolution(details, default_value) if value.is_a?(String) && ::Quonfig::Duration.valid?(value)
|
|
279
|
+
|
|
280
|
+
error_code = value.is_a?(String) ? ErrorCode::PARSE_ERROR : ErrorCode::TYPE_MISMATCH
|
|
281
|
+
ResolutionDetails.new(value: default_value, reason: Reason::ERROR, error_code: error_code,
|
|
282
|
+
error_message: "duration #{flag_key.inspect} has no valid ISO 8601 text",
|
|
283
|
+
variant: details.variant, flag_metadata: details.flag_metadata || {})
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def stored_value_type(client, flag_key)
|
|
287
|
+
config = client.store.get(flag_key)
|
|
288
|
+
return nil unless config.is_a?(Hash)
|
|
289
|
+
|
|
290
|
+
(config['valueType'] || config[:valueType] || config['value_type'] || config[:value_type])&.to_s
|
|
291
|
+
end
|
|
292
|
+
|
|
201
293
|
# Map a Quonfig::EvaluationDetails to the OpenFeature ResolutionDetails
|
|
202
294
|
# the SDK consumes. The Quonfig SDK's *_details methods don't raise, so
|
|
203
|
-
# this is a pure mapping layer.
|
|
204
|
-
|
|
295
|
+
# this is a pure mapping layer. +parse_error+ reports an ERROR as
|
|
296
|
+
# PARSE_ERROR (see #malformed?).
|
|
297
|
+
def to_resolution(details, default_value, parse_error: false)
|
|
205
298
|
variant = details.variant
|
|
206
299
|
flag_metadata = details.flag_metadata || {}
|
|
207
300
|
case details.reason
|
|
@@ -221,7 +314,7 @@ module Quonfig
|
|
|
221
314
|
ResolutionDetails.new(
|
|
222
315
|
value: default_value,
|
|
223
316
|
reason: Reason::ERROR,
|
|
224
|
-
error_code: map_error_code(details.error_code),
|
|
317
|
+
error_code: parse_error ? ErrorCode::PARSE_ERROR : map_error_code(details.error_code),
|
|
225
318
|
error_message: details.error_message,
|
|
226
319
|
variant: variant,
|
|
227
320
|
flag_metadata: flag_metadata
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: quonfig-openfeature
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jeff Dwyer
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: openfeature-sdk
|
|
@@ -30,14 +30,14 @@ dependencies:
|
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: 2.
|
|
33
|
+
version: 2.1.0
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: 2.
|
|
40
|
+
version: 2.1.0
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: minitest
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|