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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 518ef7e90608d00dc60292d93fa9c9b6a14037df9194597a1cc309a4c02ea166
4
- data.tar.gz: bc52ae8cda109bd7aa20dcc5e0a235716a503d497987d4000d45a97ae6541bba
3
+ metadata.gz: cd942b88517b56c8ff6d676684e0f1c27fbd5fd62c96c15964af1239d0ac2fe6
4
+ data.tar.gz: bff802046f4d1ec5c0422e93f7c13a28edeb98178f2703354511976dc4c3384e
5
5
  SHA512:
6
- metadata.gz: '080feecf4971e26beff1c05427d6ffa0d9c494c26696968502ae7fe8ea462e6eb770a1a57d6486bf8d39093133902b8672cebea3f3f4cd3fc4a92ed0355ba5ae'
7
- data.tar.gz: e783174efaa4e7c8577259bec7ebaa7ccc3eb1f900a844fed68e1449cabffeada4b2925fc5d650f15c51c52d27da35a79380bec8d716e6cf4259bd4f5d93ee6b
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** -- exposed only via the native `get_duration` (returns milliseconds).
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.1.0
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
- to_resolution(client.get_bool_details(flag_key, context: mapped_ctx), default_value)
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
- to_resolution(client.get_string_details(flag_key, context: mapped_ctx), default_value)
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
- to_resolution(client.get_int_details(flag_key, context: mapped_ctx), default_value)
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
- to_resolution(client.get_float_details(flag_key, context: mapped_ctx), default_value)
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
- def to_resolution(details, default_value)
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: 1.1.0
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-23 00:00:00.000000000 Z
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: 1.11.0
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: 1.11.0
40
+ version: 2.0.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: minitest
43
43
  requirement: !ruby/object:Gem::Requirement