quonfig-openfeature 1.1.0 → 2.1.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 +64 -0
- data/README.md +20 -1
- data/VERSION +1 -1
- data/lib/quonfig/openfeature/provider.rb +66 -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: cd942b88517b56c8ff6d676684e0f1c27fbd5fd62c96c15964af1239d0ac2fe6
|
|
4
|
+
data.tar.gz: bff802046f4d1ec5c0422e93f7c13a28edeb98178f2703354511976dc4c3384e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 57db850f4f8384be2cc8b47e690ff363876d9ab51a5c7e80468880b3cf9b4607b7263f9081b965feef1dc6cc3e3c0594180153cb67cf9a5317fb7025562e70a9
|
|
7
|
+
data.tar.gz: 56bb8b4420465725dd15303de581c254abae3d5f27a98205463dbed394f9cc9848ae47c61d97f7ac89962d707fa35ae8ed6d8c05fb66c2f9e0a50050c434123f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.1.0 - 2026-10-28
|
|
4
|
+
|
|
5
|
+
A malformed value now resolves with error code `PARSE_ERROR` instead of `TYPE_MISMATCH`
|
|
6
|
+
(qfg-9xsl, mirrors integration-test-data `tests/openfeature/openfeature.yaml` Case 6). You still
|
|
7
|
+
get your default with reason `ERROR`. This covers a value of a type the `fetch_*` method reads that
|
|
8
|
+
does not parse:
|
|
9
|
+
|
|
10
|
+
- a stored int `"twelve"` or double `"abc"`;
|
|
11
|
+
- an int or double provided by an ENV_VAR set to an unparseable string, such as `"twelve"`;
|
|
12
|
+
- a malformed stored duration such as `"30s"`, read with `fetch_string_value`.
|
|
13
|
+
|
|
14
|
+
A read with the method for another type (an int config read with `fetch_boolean_value`) stays
|
|
15
|
+
`TYPE_MISMATCH`, as does a number that parses but does not convert exactly (`fetch_integer_value`
|
|
16
|
+
on `3.9`). sdk-ruby's `*_details` report both cases as `TYPE_MISMATCH`, so the provider tells them
|
|
17
|
+
apart: on a `TYPE_MISMATCH` it checks the config's stored `valueType` and, if the method can read
|
|
18
|
+
that type, reads it with the native getter for that type. If that read fails too, the value is
|
|
19
|
+
malformed. This extra read happens only on the error path.
|
|
20
|
+
|
|
21
|
+
If you alert or branch on `TYPE_MISMATCH` for bad config data, match `PARSE_ERROR` as well.
|
|
22
|
+
|
|
23
|
+
## 2.0.0 - 2026-10-27
|
|
24
|
+
|
|
25
|
+
**BREAKING / UPGRADE (major, qfg-zvyy.6).** This release tracks sdk-ruby 2.0.0, the cross-SDK
|
|
26
|
+
typed-getter contract. Require `quonfig` (sdk-ruby) `>= 2.0.0` (floor raised from `>= 1.11.0`). No
|
|
27
|
+
change to this provider's code: it passes through the SDK's `*_details` results, so provider users
|
|
28
|
+
inherit the contract below.
|
|
29
|
+
|
|
30
|
+
### UPGRADE to 2.0
|
|
31
|
+
|
|
32
|
+
A value a `fetch_*` method cannot read as its type returns your default with reason `ERROR` and
|
|
33
|
+
error code `TYPE_MISMATCH`. Strings are never coerced, and the only number conversions are exact
|
|
34
|
+
ones. What changes for provider users:
|
|
35
|
+
|
|
36
|
+
- `fetch_float_value` on an int config now returns the value (`30` -> `30.0`). 1.x returned your
|
|
37
|
+
default with `TYPE_MISMATCH`.
|
|
38
|
+
- `fetch_integer_value` on a double config returns the value when it is whole and within
|
|
39
|
+
+/-(2^53-1) (`3.0` -> `3`). 1.x returned your default for every double. `3.9`, `2^53` and `1e300`
|
|
40
|
+
still return your default with `TYPE_MISMATCH`, never truncated.
|
|
41
|
+
- `fetch_number_value` on a `duration` config returns your default with `TYPE_MISMATCH`. 1.x
|
|
42
|
+
returned the duration in milliseconds (`30000`, reason `STATIC`). Durations are native-only: use
|
|
43
|
+
`provider.client.get_duration` (integer milliseconds). `fetch_float_value` on a duration already
|
|
44
|
+
returned your default.
|
|
45
|
+
- A malformed stored int or double (for example `"twelve"`) or a malformed ENV_VAR value returns
|
|
46
|
+
your default with error code `TYPE_MISMATCH`, as in sdk-go. 1.x reported `GENERAL`. An unset
|
|
47
|
+
ENV_VAR still returns your default with `GENERAL`.
|
|
48
|
+
- sdk-ruby logs one WARN per key per client when a read returns your default because of a type
|
|
49
|
+
mismatch, a malformed value, an unset ENV_VAR or a decryption failure. The line names the key and
|
|
50
|
+
types, never the value. A wrong decryption key now degrades to your defaults with these WARNs.
|
|
51
|
+
- If you call the native client via `provider.client`: typed getters with a `default:` return the
|
|
52
|
+
default instead of raising, `enabled?` is true only for the bool `true` and never raises, and
|
|
53
|
+
`get_bool` on a stored string `"true"` is a type mismatch. See the sdk-ruby CHANGELOG
|
|
54
|
+
"UPGRADE to 2.0".
|
|
55
|
+
|
|
56
|
+
These already held under 1.11.0 through the provider, and the new tests now pin them: a stored
|
|
57
|
+
string `"true"` / `"false"` read as a boolean, a stored `"3"` read as a number, an int or bool read as
|
|
58
|
+
a string, an int read as a boolean, and a string or bool read as an object all return your default
|
|
59
|
+
with `TYPE_MISMATCH`.
|
|
60
|
+
|
|
61
|
+
### Tests
|
|
62
|
+
|
|
63
|
+
- New `test/integration/test_typed_getter_contract.rb` (31 tests) drives the real provider through
|
|
64
|
+
the OpenFeature client against the integration-test-data `getter.contract.*` fixtures. 7 of the 31
|
|
65
|
+
fail on sdk-ruby 1.11.0; all pass on 2.0.0.
|
|
66
|
+
|
|
3
67
|
## 1.1.0 - 2026-10-23
|
|
4
68
|
|
|
5
69
|
- **UPGRADE: raise the `quonfig` floor from `>= 1.10.0` to `>= 1.11.0`. Requires sdk-ruby 1.11.0:
|
data/README.md
CHANGED
|
@@ -134,13 +134,32 @@ 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** --
|
|
137
|
+
3. **`duration` configs** -- native-only: read them with `provider.client.get_duration` (integer
|
|
138
|
+
milliseconds). `fetch_float_value` and `fetch_number_value` on a duration config return your
|
|
139
|
+
default with `TYPE_MISMATCH`.
|
|
138
140
|
4. **`bytes` configs** -- not accessible via OpenFeature (no binary type in OF).
|
|
139
141
|
5. **`keys` / raw config inspection** -- native-only via `provider.client`.
|
|
140
142
|
6. **Context keys use dot-notation** -- `"user.email"`, not nested hashes.
|
|
141
143
|
7. **`targeting_key` maps to `user.id` by default** -- configure
|
|
142
144
|
`targeting_key_mapping` if you target a different namespace.
|
|
143
145
|
|
|
146
|
+
## Typed reads
|
|
147
|
+
|
|
148
|
+
Each `fetch_*` method reads only values of its type (the cross-SDK typed-getter contract,
|
|
149
|
+
`quonfig` >= 2.0.0). A value of another type returns your default with reason `ERROR` and error
|
|
150
|
+
code `TYPE_MISMATCH`:
|
|
151
|
+
|
|
152
|
+
- A stored string is never coerced: `"false"` is not a boolean and `"3"` is not a number.
|
|
153
|
+
- Numbers convert only when exact: `fetch_float_value` on an int config returns `30.0`, and
|
|
154
|
+
`fetch_integer_value` on a double config returns the value only when it is whole and within
|
|
155
|
+
+/-(2^53-1) (`3.0` -> `3`; `3.9` is a mismatch, never truncated).
|
|
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`.
|
|
160
|
+
|
|
161
|
+
sdk-ruby logs one WARN per key when a read returns your default for one of these reasons.
|
|
162
|
+
|
|
144
163
|
## Offline / test mode
|
|
145
164
|
|
|
146
165
|
Pass `datadir:` to evaluate against a Quonfig workspace on disk -- the same
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
2.1.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,15 @@ 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
|
|
|
124
151
|
def fetch_string_value(flag_key:, default_value:, evaluation_context: nil)
|
|
125
152
|
evaluate(flag_key, default_value, evaluation_context) do |client, mapped_ctx|
|
|
126
|
-
|
|
153
|
+
details = client.get_string_details(flag_key, context: mapped_ctx)
|
|
154
|
+
to_resolution(details, default_value, parse_error: malformed?(client, flag_key, mapped_ctx, :string, details))
|
|
127
155
|
end
|
|
128
156
|
end
|
|
129
157
|
|
|
@@ -134,19 +162,21 @@ module Quonfig
|
|
|
134
162
|
evaluate(flag_key, default_value, evaluation_context) do |client, mapped_ctx|
|
|
135
163
|
details = client.get_int_details(flag_key, context: mapped_ctx)
|
|
136
164
|
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)
|
|
165
|
+
to_resolution(details, default_value, parse_error: malformed?(client, flag_key, mapped_ctx, :number, details))
|
|
138
166
|
end
|
|
139
167
|
end
|
|
140
168
|
|
|
141
169
|
def fetch_integer_value(flag_key:, default_value:, evaluation_context: nil)
|
|
142
170
|
evaluate(flag_key, default_value, evaluation_context) do |client, mapped_ctx|
|
|
143
|
-
|
|
171
|
+
details = client.get_int_details(flag_key, context: mapped_ctx)
|
|
172
|
+
to_resolution(details, default_value, parse_error: malformed?(client, flag_key, mapped_ctx, :integer, details))
|
|
144
173
|
end
|
|
145
174
|
end
|
|
146
175
|
|
|
147
176
|
def fetch_float_value(flag_key:, default_value:, evaluation_context: nil)
|
|
148
177
|
evaluate(flag_key, default_value, evaluation_context) do |client, mapped_ctx|
|
|
149
|
-
|
|
178
|
+
details = client.get_float_details(flag_key, context: mapped_ctx)
|
|
179
|
+
to_resolution(details, default_value, parse_error: malformed?(client, flag_key, mapped_ctx, :float, details))
|
|
150
180
|
end
|
|
151
181
|
end
|
|
152
182
|
|
|
@@ -157,7 +187,7 @@ module Quonfig
|
|
|
157
187
|
evaluate(flag_key, default_value, evaluation_context) do |client, mapped_ctx|
|
|
158
188
|
details = client.get_string_list_details(flag_key, context: mapped_ctx)
|
|
159
189
|
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)
|
|
190
|
+
to_resolution(details, default_value, parse_error: malformed?(client, flag_key, mapped_ctx, :object, details))
|
|
161
191
|
end
|
|
162
192
|
end
|
|
163
193
|
|
|
@@ -198,10 +228,37 @@ module Quonfig
|
|
|
198
228
|
end
|
|
199
229
|
end
|
|
200
230
|
|
|
231
|
+
# True when +details+ is a TYPE_MISMATCH for a config whose value has a
|
|
232
|
+
# type +kind+ can read but did not parse: a stored int "twelve", an int
|
|
233
|
+
# ENV_VAR set to "twelve", a duration "30s" (qfg-9xsl). sdk-ruby's
|
|
234
|
+
# *_details report a malformed value and a wrong-type read alike as
|
|
235
|
+
# TYPE_MISMATCH, so this checks the stored +valueType+ and reads the
|
|
236
|
+
# config with the getter for that type: if that read fails too, the
|
|
237
|
+
# value is malformed. A config of a type +kind+ cannot read stays
|
|
238
|
+
# TYPE_MISMATCH, and so does a value that parsed but does not convert
|
|
239
|
+
# exactly (fetch_integer_value on a 3.9 double).
|
|
240
|
+
def malformed?(client, flag_key, mapped_ctx, kind, details)
|
|
241
|
+
return false unless details.error_code == ::Quonfig::EvaluationDetails::ERROR_TYPE_MISMATCH
|
|
242
|
+
|
|
243
|
+
value_type = stored_value_type(client, flag_key)
|
|
244
|
+
return false unless READABLE_VALUE_TYPES.fetch(kind).include?(value_type)
|
|
245
|
+
|
|
246
|
+
native = client.public_send(NATIVE_DETAILS_GETTER.fetch(value_type), flag_key, context: mapped_ctx)
|
|
247
|
+
native.error_code == ::Quonfig::EvaluationDetails::ERROR_TYPE_MISMATCH
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def stored_value_type(client, flag_key)
|
|
251
|
+
config = client.store.get(flag_key)
|
|
252
|
+
return nil unless config.is_a?(Hash)
|
|
253
|
+
|
|
254
|
+
(config['valueType'] || config[:valueType] || config['value_type'] || config[:value_type])&.to_s
|
|
255
|
+
end
|
|
256
|
+
|
|
201
257
|
# Map a Quonfig::EvaluationDetails to the OpenFeature ResolutionDetails
|
|
202
258
|
# the SDK consumes. The Quonfig SDK's *_details methods don't raise, so
|
|
203
|
-
# this is a pure mapping layer.
|
|
204
|
-
|
|
259
|
+
# this is a pure mapping layer. +parse_error+ reports an ERROR as
|
|
260
|
+
# PARSE_ERROR (see #malformed?).
|
|
261
|
+
def to_resolution(details, default_value, parse_error: false)
|
|
205
262
|
variant = details.variant
|
|
206
263
|
flag_metadata = details.flag_metadata || {}
|
|
207
264
|
case details.reason
|
|
@@ -221,7 +278,7 @@ module Quonfig
|
|
|
221
278
|
ResolutionDetails.new(
|
|
222
279
|
value: default_value,
|
|
223
280
|
reason: Reason::ERROR,
|
|
224
|
-
error_code: map_error_code(details.error_code),
|
|
281
|
+
error_code: parse_error ? ErrorCode::PARSE_ERROR : map_error_code(details.error_code),
|
|
225
282
|
error_message: details.error_message,
|
|
226
283
|
variant: variant,
|
|
227
284
|
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:
|
|
4
|
+
version: 2.1.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-28 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:
|
|
33
|
+
version: 2.0.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:
|
|
40
|
+
version: 2.0.0
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: minitest
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|