openfeature-sdk 0.6.0 → 0.6.1
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/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +12 -0
- data/Gemfile.lock +1 -1
- data/README.md +110 -23
- data/lib/open_feature/sdk/api.rb +5 -0
- data/lib/open_feature/sdk/client.rb +39 -0
- data/lib/open_feature/sdk/configuration.rb +27 -8
- data/lib/open_feature/sdk/hooks/hook_context.rb +8 -0
- data/lib/open_feature/sdk/provider/in_memory_provider.rb +22 -5
- data/lib/open_feature/sdk/provider/resolution_details.rb +14 -1
- data/lib/open_feature/sdk/provider_state_registry.rb +21 -3
- data/lib/open_feature/sdk/tracking_event_details.rb +23 -0
- data/lib/open_feature/sdk/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ac3b2793c1b6453e2525dcc04d7646fd4e5d61a87e6ade8b257754cf9b9c991c
|
|
4
|
+
data.tar.gz: c5aeedfe23851ef99e26024bf1db5f4ba38d7f669b9ea6b3e1e5f63d0693c1a6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9aa41845c24248cf6eec381f4531fbfc301196ea748f3b3189333c4879c64a2dc1eda8a935e3514291dcbb5100c1668260e1b87bec837952e1eb1777ae083501
|
|
7
|
+
data.tar.gz: c162a21b0a5b4ee91f132e676440b84cf7862afccd689416f97e9b41f9284bf8b86de5050a1c32a3c98096ec39074cbb0ae1dcbe0d85d4db905d447f3baccd3d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.1](https://github.com/open-feature/ruby-sdk/compare/v0.6.0...v0.6.1) (2026-03-05)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add flag metadata defaulting and immutability ([#221](https://github.com/open-feature/ruby-sdk/issues/221)) ([a300fc5](https://github.com/open-feature/ruby-sdk/commit/a300fc559293169f22eb1ce26f738cdee664cd26))
|
|
9
|
+
* add hook data per-hook mutable state ([#222](https://github.com/open-feature/ruby-sdk/issues/222)) ([28518a0](https://github.com/open-feature/ruby-sdk/commit/28518a0e08143d167b9d34c86e57a583fe5ee0de))
|
|
10
|
+
* add InMemoryProvider context callbacks and event emission ([#224](https://github.com/open-feature/ruby-sdk/issues/224)) ([0a148f6](https://github.com/open-feature/ruby-sdk/commit/0a148f66abc815fc2ec9fd70027075125dbd504a))
|
|
11
|
+
* add shutdown API, provider status, and status short-circuit ([#223](https://github.com/open-feature/ruby-sdk/issues/223)) ([f9c32ad](https://github.com/open-feature/ruby-sdk/commit/f9c32ad1b467af25697423a542bc568597f39743))
|
|
12
|
+
* implement Tracking API (spec section 6) ([#227](https://github.com/open-feature/ruby-sdk/issues/227)) ([5576fce](https://github.com/open-feature/ruby-sdk/commit/5576fce1c3bcf6e7510d8957c7e40e85c4b83b6f))
|
|
13
|
+
* populate event details payload with error_code and message ([#225](https://github.com/open-feature/ruby-sdk/issues/225)) ([a185003](https://github.com/open-feature/ruby-sdk/commit/a185003dc09a69b2dda1fe569d1f82c45979cdad))
|
|
14
|
+
|
|
3
15
|
## [0.6.0](https://github.com/open-feature/ruby-sdk/compare/v0.5.1...v0.6.0) (2026-03-05)
|
|
4
16
|
|
|
5
17
|
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
</a>
|
|
18
18
|
<!-- x-release-please-start-version -->
|
|
19
19
|
|
|
20
|
-
<a href="https://github.com/open-feature/ruby-sdk/releases/tag/v0.6.
|
|
21
|
-
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v0.6.
|
|
20
|
+
<a href="https://github.com/open-feature/ruby-sdk/releases/tag/v0.6.1">
|
|
21
|
+
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v0.6.1&color=blue&style=for-the-badge" />
|
|
22
22
|
</a>
|
|
23
23
|
|
|
24
24
|
<!-- x-release-please-end -->
|
|
@@ -100,13 +100,14 @@ object = client.fetch_object_value(flag_key: 'object_value', default_value: { na
|
|
|
100
100
|
| ------ | --------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
101
101
|
| ✅ | [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. |
|
|
102
102
|
| ✅ | [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context). |
|
|
103
|
-
|
|
|
103
|
+
| ✅ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. |
|
|
104
104
|
| ❌ | [Logging](#logging) | Integrate with popular logging packages. |
|
|
105
105
|
| ✅ | [Domains](#domains) | Logically bind clients with providers. |
|
|
106
106
|
| ✅ | [Eventing](#eventing) | React to state changes in the provider or flag management system. |
|
|
107
|
-
|
|
|
107
|
+
| ✅ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. |
|
|
108
|
+
| ✅ | [Tracking](#tracking) | Associate user actions with feature flag evaluations for experimentation. |
|
|
108
109
|
| ❌ | [Transaction Context Propagation](#transaction-context-propagation) | Set a specific [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context) for a transaction (e.g. an HTTP request or a thread) |
|
|
109
|
-
|
|
|
110
|
+
| ✅ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. |
|
|
110
111
|
|
|
111
112
|
<sub>Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌</sub>
|
|
112
113
|
|
|
@@ -200,15 +201,49 @@ bool_value = client.fetch_boolean_value(
|
|
|
200
201
|
|
|
201
202
|
### Hooks
|
|
202
203
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
<!-- [Hooks](https://openfeature.dev/docs/reference/concepts/hooks) allow for custom logic to be added at well-defined points of the flag evaluation life-cycle.
|
|
204
|
+
[Hooks](https://openfeature.dev/docs/reference/concepts/hooks) allow for custom logic to be added at well-defined points of the flag evaluation life-cycle.
|
|
206
205
|
Look [here](https://openfeature.dev/ecosystem/?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Hook&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=Ruby) for a complete list of available hooks.
|
|
207
206
|
If the hook you're looking for hasn't been created yet, see the [develop a hook](#develop-a-hook) section to learn how to build it yourself.
|
|
208
207
|
|
|
209
|
-
|
|
208
|
+
Hooks can be registered at the global, client, or flag invocation level.
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
# Define a hook
|
|
212
|
+
class MyHook
|
|
213
|
+
include OpenFeature::SDK::Hooks::Hook
|
|
214
|
+
|
|
215
|
+
def before(hook_context:, hints:)
|
|
216
|
+
puts "Evaluating flag: #{hook_context.flag_key}"
|
|
217
|
+
nil
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def after(hook_context:, evaluation_details:, hints:)
|
|
221
|
+
puts "Flag #{hook_context.flag_key} evaluated to: #{evaluation_details.value}"
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def error(hook_context:, exception:, hints:)
|
|
225
|
+
puts "Error evaluating #{hook_context.flag_key}: #{exception.message}"
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def finally(hook_context:, evaluation_details:, hints:)
|
|
229
|
+
puts "Evaluation complete for #{hook_context.flag_key}"
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Register at the API (global) level
|
|
234
|
+
OpenFeature::SDK.hooks << MyHook.new
|
|
235
|
+
|
|
236
|
+
# Register at the client level
|
|
237
|
+
client = OpenFeature::SDK.build_client
|
|
238
|
+
client.hooks << MyHook.new
|
|
210
239
|
|
|
211
|
-
|
|
240
|
+
# Register at the invocation level
|
|
241
|
+
client.fetch_boolean_value(
|
|
242
|
+
flag_key: "my-flag",
|
|
243
|
+
default_value: false,
|
|
244
|
+
hooks: [MyHook.new]
|
|
245
|
+
)
|
|
246
|
+
```
|
|
212
247
|
|
|
213
248
|
### Logging
|
|
214
249
|
|
|
@@ -276,19 +311,53 @@ OpenFeature::SDK.remove_handler(OpenFeature::SDK::ProviderEvent::PROVIDER_READY,
|
|
|
276
311
|
|
|
277
312
|
### Shutdown
|
|
278
313
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
<!-- TODO The OpenFeature API provides a close function to perform a cleanup of all registered providers.
|
|
314
|
+
The OpenFeature API provides a `shutdown` method to perform cleanup of all registered providers.
|
|
282
315
|
This should only be called when your application is in the process of shutting down.
|
|
283
316
|
|
|
317
|
+
```ruby
|
|
318
|
+
# Shut down all registered providers and clear state
|
|
319
|
+
OpenFeature::SDK.shutdown
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Individual providers can implement a `shutdown` method to perform cleanup:
|
|
323
|
+
|
|
284
324
|
```ruby
|
|
285
325
|
class MyProvider
|
|
286
326
|
def shutdown
|
|
287
327
|
# Perform any shutdown/reclamation steps with flag management system here
|
|
288
|
-
# Return value is ignored
|
|
289
328
|
end
|
|
290
329
|
end
|
|
291
|
-
```
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Tracking
|
|
333
|
+
|
|
334
|
+
The tracking API allows you to use OpenFeature abstractions and objects to associate user actions with feature flag evaluations.
|
|
335
|
+
This is essential for robust experimentation powered by feature flags.
|
|
336
|
+
For example, a flag enhancing the appearance of a UI component might drive user engagement to a new feature; to test this hypothesis, telemetry collected by a [hook](#hooks) or [provider](#providers) can be associated with telemetry reported in the client's `track` function.
|
|
337
|
+
|
|
338
|
+
```ruby
|
|
339
|
+
client = OpenFeature::SDK.build_client
|
|
340
|
+
|
|
341
|
+
# Simple tracking event
|
|
342
|
+
client.track("checkout_completed")
|
|
343
|
+
|
|
344
|
+
# With evaluation context
|
|
345
|
+
client.track(
|
|
346
|
+
"purchase",
|
|
347
|
+
evaluation_context: OpenFeature::SDK::EvaluationContext.new(targeting_key: "user-123")
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
# With tracking event details (optional numeric value + custom fields)
|
|
351
|
+
details = OpenFeature::SDK::TrackingEventDetails.new(
|
|
352
|
+
value: 99.99,
|
|
353
|
+
plan: "premium",
|
|
354
|
+
currency: "USD"
|
|
355
|
+
)
|
|
356
|
+
client.track("subscription", tracking_event_details: details)
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Note that some providers may not support tracking; if the provider does not implement a `track` method, the call is a no-op.
|
|
360
|
+
Check the documentation for your [provider](#providers) for more information.
|
|
292
361
|
|
|
293
362
|
### Transaction Context Propagation
|
|
294
363
|
|
|
@@ -344,6 +413,12 @@ class MyProvider
|
|
|
344
413
|
def fetch_object_value(flag_key:, default_value:, evaluation_context: nil)
|
|
345
414
|
# Retrieve a hash value from provider source
|
|
346
415
|
end
|
|
416
|
+
|
|
417
|
+
# Optional: implement tracking support (spec 6.1.4)
|
|
418
|
+
# If not defined, Client#track is a no-op
|
|
419
|
+
def track(tracking_event_name, evaluation_context:, tracking_event_details:)
|
|
420
|
+
# Record a tracking event with your flag management system
|
|
421
|
+
end
|
|
347
422
|
end
|
|
348
423
|
```
|
|
349
424
|
|
|
@@ -351,17 +426,29 @@ end
|
|
|
351
426
|
|
|
352
427
|
### Develop a hook
|
|
353
428
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
<!-- To develop a hook, you need to create a new project and include the OpenFeature SDK as a dependency.
|
|
429
|
+
To develop a hook, you need to create a new project and include the OpenFeature SDK as a dependency.
|
|
357
430
|
This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/ruby-sdk-contrib) available under the OpenFeature organization.
|
|
358
|
-
Implement your own hook by
|
|
359
|
-
|
|
360
|
-
|
|
431
|
+
Implement your own hook by including the `OpenFeature::SDK::Hooks::Hook` module.
|
|
432
|
+
You only need to define the stages you care about — unimplemented stages are no-ops by default.
|
|
433
|
+
|
|
434
|
+
```ruby
|
|
435
|
+
class MyLoggingHook
|
|
436
|
+
include OpenFeature::SDK::Hooks::Hook
|
|
361
437
|
|
|
362
|
-
|
|
438
|
+
def before(hook_context:, hints:)
|
|
439
|
+
puts "Evaluating #{hook_context.flag_key}"
|
|
440
|
+
nil # Return nil or an EvaluationContext to merge
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
def after(hook_context:, evaluation_details:, hints:)
|
|
444
|
+
puts "Result: #{evaluation_details.value}"
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
# error and finally are optional — only define what you need
|
|
448
|
+
end
|
|
449
|
+
```
|
|
363
450
|
|
|
364
|
-
|
|
451
|
+
> Built a new hook? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=&labels=hook&projects=&template=document-hook.yaml&title=%5BHook%5D%3A+) so we can add it to the docs!
|
|
365
452
|
|
|
366
453
|
<!-- x-hide-in-docs-start -->
|
|
367
454
|
## ⭐️ Support the project
|
data/lib/open_feature/sdk/api.rb
CHANGED
|
@@ -10,6 +10,7 @@ require_relative "evaluation_details"
|
|
|
10
10
|
require_relative "client_metadata"
|
|
11
11
|
require_relative "hooks"
|
|
12
12
|
require_relative "client"
|
|
13
|
+
require_relative "tracking_event_details"
|
|
13
14
|
require_relative "provider"
|
|
14
15
|
|
|
15
16
|
module OpenFeature
|
|
@@ -68,6 +69,10 @@ module OpenFeature
|
|
|
68
69
|
def logger=(new_logger)
|
|
69
70
|
configuration.logger = new_logger
|
|
70
71
|
end
|
|
72
|
+
|
|
73
|
+
def shutdown
|
|
74
|
+
configuration.shutdown
|
|
75
|
+
end
|
|
71
76
|
end
|
|
72
77
|
end
|
|
73
78
|
end
|
|
@@ -28,6 +28,10 @@ module OpenFeature
|
|
|
28
28
|
@hooks = []
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
def provider_status
|
|
32
|
+
OpenFeature::SDK.configuration.provider_state(@provider)
|
|
33
|
+
end
|
|
34
|
+
|
|
31
35
|
def add_handler(event_type, handler = nil, &block)
|
|
32
36
|
actual_handler = handler || block
|
|
33
37
|
OpenFeature::SDK.configuration.add_client_handler(self, event_type, actual_handler)
|
|
@@ -38,6 +42,22 @@ module OpenFeature
|
|
|
38
42
|
OpenFeature::SDK.configuration.remove_client_handler(self, event_type, actual_handler)
|
|
39
43
|
end
|
|
40
44
|
|
|
45
|
+
# Tracking API (spec 6.1.1.1) — dynamic-context paradigm
|
|
46
|
+
#
|
|
47
|
+
# Records a tracking event. If the provider does not implement
|
|
48
|
+
# tracking, this is a no-op (spec 6.1.4).
|
|
49
|
+
def track(tracking_event_name, evaluation_context: nil, tracking_event_details: nil)
|
|
50
|
+
return unless @provider.respond_to?(:track)
|
|
51
|
+
|
|
52
|
+
built_context = EvaluationContextBuilder.new.call(
|
|
53
|
+
api_context: OpenFeature::SDK.evaluation_context,
|
|
54
|
+
client_context: self.evaluation_context,
|
|
55
|
+
invocation_context: evaluation_context
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@provider.track(tracking_event_name, evaluation_context: built_context, tracking_event_details: tracking_event_details)
|
|
59
|
+
end
|
|
60
|
+
|
|
41
61
|
RESULT_TYPE.each do |result_type|
|
|
42
62
|
SUFFIXES.each do |suffix|
|
|
43
63
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
@@ -54,6 +74,18 @@ module OpenFeature
|
|
|
54
74
|
def fetch_details(type:, flag_key:, default_value:, evaluation_context: nil, invocation_hooks: [], hook_hints: nil)
|
|
55
75
|
validate_default_value_type(type, default_value)
|
|
56
76
|
|
|
77
|
+
if OpenFeature::SDK.configuration.provider_tracked?(@provider)
|
|
78
|
+
error_code = short_circuit_error_code(provider_status)
|
|
79
|
+
if error_code
|
|
80
|
+
resolution = Provider::ResolutionDetails.new(
|
|
81
|
+
value: default_value,
|
|
82
|
+
error_code: error_code,
|
|
83
|
+
reason: Provider::Reason::ERROR
|
|
84
|
+
)
|
|
85
|
+
return EvaluationDetails.new(flag_key: flag_key, resolution_details: resolution)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
57
89
|
built_context = EvaluationContextBuilder.new.call(
|
|
58
90
|
api_context: OpenFeature::SDK.evaluation_context,
|
|
59
91
|
client_context: self.evaluation_context,
|
|
@@ -109,6 +141,13 @@ module OpenFeature
|
|
|
109
141
|
EvaluationDetails.new(flag_key: flag_key, resolution_details: resolution_details)
|
|
110
142
|
end
|
|
111
143
|
|
|
144
|
+
def short_circuit_error_code(state)
|
|
145
|
+
case state
|
|
146
|
+
when ProviderState::NOT_READY then Provider::ErrorCode::PROVIDER_NOT_READY
|
|
147
|
+
when ProviderState::FATAL then Provider::ErrorCode::PROVIDER_FATAL
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
112
151
|
def validate_default_value_type(type, default_value)
|
|
113
152
|
expected_classes = TYPE_CLASS_MAP[type]
|
|
114
153
|
unless expected_classes.any? { |klass| default_value.is_a?(klass) }
|
|
@@ -76,6 +76,26 @@ module OpenFeature
|
|
|
76
76
|
set_provider_internal(provider, domain: domain, wait_for_init: true)
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
+
def provider_state(provider)
|
|
80
|
+
@provider_state_registry.get_state(provider)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def provider_tracked?(provider)
|
|
84
|
+
@provider_state_registry.tracked?(provider)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def shutdown
|
|
88
|
+
providers_to_shutdown = @provider_mutex.synchronize { @providers.values.uniq }
|
|
89
|
+
|
|
90
|
+
providers_to_shutdown.each do |prov|
|
|
91
|
+
prov.shutdown if prov.respond_to?(:shutdown)
|
|
92
|
+
rescue => e
|
|
93
|
+
@logger&.warn("Error shutting down provider #{prov&.class&.name || "unknown"}: #{e.message}")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
reset
|
|
97
|
+
end
|
|
98
|
+
|
|
79
99
|
private
|
|
80
100
|
|
|
81
101
|
def reset
|
|
@@ -171,10 +191,6 @@ module OpenFeature
|
|
|
171
191
|
run_handlers_for_provider(provider, event_type, event_details)
|
|
172
192
|
end
|
|
173
193
|
|
|
174
|
-
def provider_state(provider)
|
|
175
|
-
@provider_state_registry.get_state(provider)
|
|
176
|
-
end
|
|
177
|
-
|
|
178
194
|
private
|
|
179
195
|
|
|
180
196
|
def extract_provider_name(provider)
|
|
@@ -219,8 +235,7 @@ module OpenFeature
|
|
|
219
235
|
provider_state = @provider_state_registry.get_state(provider)
|
|
220
236
|
|
|
221
237
|
if event_type == status_to_event[provider_state]
|
|
222
|
-
|
|
223
|
-
event_details = {provider_name: provider_name}
|
|
238
|
+
event_details = build_event_details(provider)
|
|
224
239
|
|
|
225
240
|
begin
|
|
226
241
|
handler.call(event_details)
|
|
@@ -237,8 +252,7 @@ module OpenFeature
|
|
|
237
252
|
provider_state = @provider_state_registry.get_state(client_provider)
|
|
238
253
|
|
|
239
254
|
if event_type == status_to_event[provider_state]
|
|
240
|
-
|
|
241
|
-
event_details = {provider_name: provider_name}
|
|
255
|
+
event_details = build_event_details(client_provider)
|
|
242
256
|
|
|
243
257
|
begin
|
|
244
258
|
handler.call(event_details)
|
|
@@ -248,6 +262,11 @@ module OpenFeature
|
|
|
248
262
|
end
|
|
249
263
|
end
|
|
250
264
|
end
|
|
265
|
+
|
|
266
|
+
def build_event_details(provider)
|
|
267
|
+
stored_details = @provider_state_registry.get_details(provider)
|
|
268
|
+
{provider_name: extract_provider_name(provider)}.merge(stored_details)
|
|
269
|
+
end
|
|
251
270
|
end
|
|
252
271
|
end
|
|
253
272
|
end
|
|
@@ -22,6 +22,14 @@ module OpenFeature
|
|
|
22
22
|
@evaluation_context = evaluation_context
|
|
23
23
|
@client_metadata = client_metadata
|
|
24
24
|
@provider_metadata = provider_metadata
|
|
25
|
+
@hook_data = {}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns a mutable hash scoped to the given hook instance.
|
|
29
|
+
# The same hash is returned across all hook stages (before, after, error, finally),
|
|
30
|
+
# allowing hooks to share state across their lifecycle (spec 4.1.5, 4.6.1).
|
|
31
|
+
def hook_data_for(hook)
|
|
32
|
+
@hook_data[hook.object_id] ||= {}
|
|
25
33
|
end
|
|
26
34
|
end
|
|
27
35
|
end
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
module OpenFeature
|
|
4
4
|
module SDK
|
|
5
5
|
module Provider
|
|
6
|
-
# TODO: Add evaluation context support
|
|
7
6
|
class InMemoryProvider
|
|
7
|
+
include Provider::EventEmitter
|
|
8
|
+
|
|
8
9
|
NAME = "In-memory Provider"
|
|
9
10
|
|
|
10
11
|
attr_reader :metadata
|
|
@@ -24,7 +25,12 @@ module OpenFeature
|
|
|
24
25
|
|
|
25
26
|
def add_flag(flag_key:, value:)
|
|
26
27
|
flags[flag_key] = value
|
|
27
|
-
|
|
28
|
+
emit_provider_changed([flag_key])
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def update_flags(new_flags)
|
|
32
|
+
@flags = new_flags.dup
|
|
33
|
+
emit_provider_changed(new_flags.keys)
|
|
28
34
|
end
|
|
29
35
|
|
|
30
36
|
def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
|
|
@@ -56,13 +62,24 @@ module OpenFeature
|
|
|
56
62
|
attr_reader :flags
|
|
57
63
|
|
|
58
64
|
def fetch_value(flag_key:, default_value:, evaluation_context:)
|
|
59
|
-
|
|
65
|
+
raw_value = flags[flag_key]
|
|
60
66
|
|
|
61
|
-
if
|
|
67
|
+
if raw_value.nil?
|
|
62
68
|
return ResolutionDetails.new(value: default_value, error_code: ErrorCode::FLAG_NOT_FOUND, reason: Reason::ERROR)
|
|
63
69
|
end
|
|
64
70
|
|
|
65
|
-
|
|
71
|
+
if raw_value.respond_to?(:call)
|
|
72
|
+
value = raw_value.call(evaluation_context)
|
|
73
|
+
ResolutionDetails.new(value: value, reason: Reason::TARGETING_MATCH)
|
|
74
|
+
else
|
|
75
|
+
ResolutionDetails.new(value: raw_value, reason: Reason::STATIC)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def emit_provider_changed(flag_keys)
|
|
80
|
+
return unless configuration_attached?
|
|
81
|
+
|
|
82
|
+
emit_event(ProviderEvent::PROVIDER_CONFIGURATION_CHANGED, flags_changed: flag_keys)
|
|
66
83
|
end
|
|
67
84
|
end
|
|
68
85
|
end
|
|
@@ -3,7 +3,20 @@
|
|
|
3
3
|
module OpenFeature
|
|
4
4
|
module SDK
|
|
5
5
|
module Provider
|
|
6
|
-
|
|
6
|
+
EMPTY_FLAG_METADATA = {}.freeze
|
|
7
|
+
|
|
8
|
+
ResolutionDetails = Struct.new(:value, :reason, :variant, :error_code, :error_message, :flag_metadata, keyword_init: true) do
|
|
9
|
+
def flag_metadata
|
|
10
|
+
raw = self[:flag_metadata]
|
|
11
|
+
if raw.nil?
|
|
12
|
+
EMPTY_FLAG_METADATA
|
|
13
|
+
elsif raw.frozen?
|
|
14
|
+
raw
|
|
15
|
+
else
|
|
16
|
+
raw.dup.freeze
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
7
20
|
end
|
|
8
21
|
end
|
|
9
22
|
end
|
|
@@ -17,7 +17,7 @@ module OpenFeature
|
|
|
17
17
|
return unless provider
|
|
18
18
|
|
|
19
19
|
@mutex.synchronize do
|
|
20
|
-
@states[provider.object_id] = state
|
|
20
|
+
@states[provider.object_id] = {state: state, details: {}}
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
|
|
@@ -29,7 +29,7 @@ module OpenFeature
|
|
|
29
29
|
# Only update state if the event should cause a state change
|
|
30
30
|
if new_state
|
|
31
31
|
@mutex.synchronize do
|
|
32
|
-
@states[provider.object_id] = new_state
|
|
32
|
+
@states[provider.object_id] = {state: new_state, details: event_details || {}}
|
|
33
33
|
end
|
|
34
34
|
new_state
|
|
35
35
|
else
|
|
@@ -42,7 +42,17 @@ module OpenFeature
|
|
|
42
42
|
return ProviderState::NOT_READY unless provider
|
|
43
43
|
|
|
44
44
|
@mutex.synchronize do
|
|
45
|
-
@states[provider.object_id]
|
|
45
|
+
entry = @states[provider.object_id]
|
|
46
|
+
entry ? entry[:state] : ProviderState::NOT_READY
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def get_details(provider)
|
|
51
|
+
return {} unless provider
|
|
52
|
+
|
|
53
|
+
@mutex.synchronize do
|
|
54
|
+
entry = @states[provider.object_id]
|
|
55
|
+
entry ? entry[:details] : {}
|
|
46
56
|
end
|
|
47
57
|
end
|
|
48
58
|
|
|
@@ -54,6 +64,14 @@ module OpenFeature
|
|
|
54
64
|
end
|
|
55
65
|
end
|
|
56
66
|
|
|
67
|
+
def tracked?(provider)
|
|
68
|
+
return false unless provider
|
|
69
|
+
|
|
70
|
+
@mutex.synchronize do
|
|
71
|
+
@states.key?(provider.object_id)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
57
75
|
def ready?(provider)
|
|
58
76
|
get_state(provider) == ProviderState::READY
|
|
59
77
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenFeature
|
|
4
|
+
module SDK
|
|
5
|
+
# Represents tracking event details per spec section 6.2.
|
|
6
|
+
#
|
|
7
|
+
# Requirement 6.2.1: MUST define an optional numeric value.
|
|
8
|
+
# Requirement 6.2.2: MUST support custom fields (string keys,
|
|
9
|
+
# boolean/string/number/structure values).
|
|
10
|
+
class TrackingEventDetails
|
|
11
|
+
attr_reader :value, :fields
|
|
12
|
+
|
|
13
|
+
def initialize(value: nil, **fields)
|
|
14
|
+
if !value.nil? && !value.is_a?(Numeric)
|
|
15
|
+
raise ArgumentError, "Tracking event value must be Numeric, got #{value.class}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
@value = value
|
|
19
|
+
@fields = fields.transform_keys(&:to_s)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: openfeature-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- OpenFeature Authors
|
|
@@ -185,6 +185,7 @@ files:
|
|
|
185
185
|
- lib/open_feature/sdk/provider_initialization_error.rb
|
|
186
186
|
- lib/open_feature/sdk/provider_state.rb
|
|
187
187
|
- lib/open_feature/sdk/provider_state_registry.rb
|
|
188
|
+
- lib/open_feature/sdk/tracking_event_details.rb
|
|
188
189
|
- lib/open_feature/sdk/version.rb
|
|
189
190
|
- release-please-config.json
|
|
190
191
|
- renovate.json
|