openfeature-sdk 0.6.6 → 0.7.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/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +31 -0
- data/Gemfile.lock +1 -1
- data/README.md +39 -10
- data/lib/open_feature/sdk/api.rb +1 -5
- data/lib/open_feature/sdk/client.rb +38 -29
- data/lib/open_feature/sdk/configuration.rb +205 -57
- data/lib/open_feature/sdk/event_dispatcher.rb +6 -2
- data/lib/open_feature/sdk/hooks/hook_executor.rb +1 -1
- data/lib/open_feature/sdk/provider/in_memory_provider.rb +0 -4
- data/lib/open_feature/sdk/version.rb +1 -1
- data/sig/open_feature/sdk/client.rbs +4 -3
- data/sig/open_feature/sdk/configuration.rbs +16 -2
- data/sig/open_feature/sdk/event_dispatcher.rbs +1 -0
- data/sig/open_feature/sdk/provider/in_memory_provider.rbs +0 -1
- data/sig/open_feature/sdk/provider.rbs +5 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1b8b6e8a5fd01205278fa44c809eb3c1ef578b56e5542aa94793a090e605458d
|
|
4
|
+
data.tar.gz: f85c7d0e8b804fb651e02cc3c59d14c21458f9fdb419d58df6d8fbb714a35b9b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a7fef199498fb02863ed74cf76a6fbff8acf65df23e2e5d4baad90b6ed3da589b6ca98da1540443a7367456a527819eb2380979aa4f1a1ef8780c33ccf425d32
|
|
7
|
+
data.tar.gz: 71db2db0d85592861ebd5f117d7599e9d54374deb9a6eae5060716044abddb27a4bd10db7ca07314540a36adeb7c366afa1a8c21fb4f19824e05e15fd59a1a27
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.0](https://github.com/open-feature/ruby-sdk/compare/v0.6.7...v0.7.0) (2026-10-23)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ⚠ BREAKING CHANGES
|
|
7
|
+
|
|
8
|
+
* resolve a client's provider by domain on every call ([#352](https://github.com/open-feature/ruby-sdk/issues/352))
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* let providers own their lifecycle events ([#353](https://github.com/open-feature/ruby-sdk/issues/353)) ([15faf98](https://github.com/open-feature/ruby-sdk/commit/15faf98d1c900118da362f6b8d76ccdec1b3b99e))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* catch NotImplementedError in track and the hook lifecycle ([#349](https://github.com/open-feature/ruby-sdk/issues/349)) ([62759f4](https://github.com/open-feature/ruby-sdk/commit/62759f443fa8cb2f7a1b67720397240063623da1))
|
|
18
|
+
* deliver provider events in order, outside handler locks ([#351](https://github.com/open-feature/ruby-sdk/issues/351)) ([16bf89c](https://github.com/open-feature/ruby-sdk/commit/16bf89cf72541c22de9565f0dcdbfcf9dfe097ad))
|
|
19
|
+
* release init waiters on replacement and same-thread re-entry ([#348](https://github.com/open-feature/ruby-sdk/issues/348)) ([9e82b15](https://github.com/open-feature/ruby-sdk/commit/9e82b15e9730f0c53f2186aeb39cc53275d5f929))
|
|
20
|
+
* resolve a client's provider by domain on every call ([#352](https://github.com/open-feature/ruby-sdk/issues/352)) ([fa0076f](https://github.com/open-feature/ruby-sdk/commit/fa0076fb8a159a180af408ee0a335e9f8d43afc9))
|
|
21
|
+
|
|
22
|
+
## [0.6.7](https://github.com/open-feature/ruby-sdk/compare/v0.6.6...v0.6.7) (2026-10-23)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Features
|
|
26
|
+
|
|
27
|
+
* pass the bound domain to provider init and support domain-scoped providers ([#345](https://github.com/open-feature/ruby-sdk/issues/345)) ([f3cf6b7](https://github.com/open-feature/ruby-sdk/commit/f3cf6b76c06297aa4c0a2a95ea1055f816269e3b))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Bug Fixes
|
|
31
|
+
|
|
32
|
+
* return defaults instead of raising from client calls ([#346](https://github.com/open-feature/ruby-sdk/issues/346)) ([481f21c](https://github.com/open-feature/ruby-sdk/commit/481f21cfe70365de92c6ece3eb92c40117a7e1c5))
|
|
33
|
+
|
|
3
34
|
## [0.6.6](https://github.com/open-feature/ruby-sdk/compare/v0.6.5...v0.6.6) (2026-10-22)
|
|
4
35
|
|
|
5
36
|
|
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.
|
|
21
|
-
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v0.
|
|
20
|
+
<a href="https://github.com/open-feature/ruby-sdk/releases/tag/v0.7.0">
|
|
21
|
+
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v0.7.0&color=blue&style=for-the-badge" />
|
|
22
22
|
</a>
|
|
23
23
|
|
|
24
24
|
<!-- x-release-please-end -->
|
|
@@ -287,6 +287,7 @@ Log output uses a structured key=value format:
|
|
|
287
287
|
|
|
288
288
|
Clients can be assigned to a domain. A domain is a logical identifier which can be used to associate clients with a particular provider.
|
|
289
289
|
If a domain has no associated provider, the default provider is used.
|
|
290
|
+
Clients look up their domain's provider on every call, so a client created before `set_provider` follows the new binding.
|
|
290
291
|
|
|
291
292
|
```ruby
|
|
292
293
|
OpenFeature::SDK.configure do |config|
|
|
@@ -300,28 +301,35 @@ legacy_flag_client = OpenFeature::SDK.build_client(domain: "legacy_flags")
|
|
|
300
301
|
### Eventing
|
|
301
302
|
|
|
302
303
|
Events allow you to react to state changes in the provider or underlying flag management system, such as flag definition changes, provider readiness, or error conditions.
|
|
303
|
-
|
|
304
|
-
|
|
304
|
+
Providers emit initialization events (`PROVIDER_READY` on success, `PROVIDER_ERROR` on failure) from `init`.
|
|
305
|
+
A provider without `init` is `READY` as soon as it's registered, and its `PROVIDER_READY` handlers run before `set_provider` returns.
|
|
306
|
+
For providers that define `init` but not `emits_lifecycle_events?` returning `true`, the SDK emits `PROVIDER_READY` after `init` returns and `PROVIDER_ERROR` if it raises.
|
|
307
|
+
That path is deprecated (spec Appendix E), and the SDK logs a deprecation warning once per provider class to its configured logger.
|
|
308
|
+
Handlers for events a provider emits from `init` run while `init` is still in progress, so they can't call `set_provider_and_wait` for that same provider.
|
|
309
|
+
Some providers emit additional events: `PROVIDER_CONFIGURATION_CHANGED`, `PROVIDER_STALE`, `PROVIDER_RECONCILING` and `PROVIDER_CONTEXT_CHANGED`.
|
|
310
|
+
Handlers receive a hash with the `provider_name` and any details the provider sent, such as `message` or `error_code`.
|
|
305
311
|
|
|
306
312
|
Please refer to the documentation of the provider you're using to see what events are supported.
|
|
307
313
|
|
|
308
314
|
```ruby
|
|
309
315
|
# Register event handlers at the API (global) level
|
|
310
316
|
ready_handler = ->(event_details) do
|
|
311
|
-
puts "Provider #{event_details[:
|
|
317
|
+
puts "Provider #{event_details[:provider_name]} is ready!"
|
|
312
318
|
end
|
|
313
319
|
|
|
314
320
|
OpenFeature::SDK.add_handler(OpenFeature::SDK::ProviderEvent::PROVIDER_READY, ready_handler)
|
|
315
321
|
|
|
316
|
-
#
|
|
317
|
-
#
|
|
322
|
+
# Providers emit events using the EventEmitter mixin: lifecycle events from init, and spontaneous
|
|
323
|
+
# events to signal internal state changes like configuration updates.
|
|
318
324
|
class MyEventAwareProvider
|
|
319
325
|
include OpenFeature::SDK::Provider::EventEmitter
|
|
320
326
|
|
|
327
|
+
def emits_lifecycle_events? = true
|
|
328
|
+
|
|
321
329
|
def init(evaluation_context)
|
|
322
330
|
# Start background process to monitor for configuration changes
|
|
323
|
-
# Note: SDK automatically emits PROVIDER_READY when init completes successfully
|
|
324
331
|
start_background_process
|
|
332
|
+
emit_event(OpenFeature::SDK::ProviderEvent::PROVIDER_READY)
|
|
325
333
|
end
|
|
326
334
|
|
|
327
335
|
def start_background_process
|
|
@@ -351,6 +359,9 @@ This should only be called when your application is in the process of shutting d
|
|
|
351
359
|
OpenFeature::SDK.shutdown
|
|
352
360
|
```
|
|
353
361
|
|
|
362
|
+
The SDK also shuts down a replaced provider once no domain uses it.
|
|
363
|
+
Events from shut-down providers are ignored.
|
|
364
|
+
|
|
354
365
|
Individual providers can implement a `shutdown` method to perform cleanup:
|
|
355
366
|
|
|
356
367
|
```ruby
|
|
@@ -442,11 +453,29 @@ You’ll then need to write the provider by implementing the `Provider` duck.
|
|
|
442
453
|
|
|
443
454
|
```ruby
|
|
444
455
|
class MyProvider
|
|
445
|
-
|
|
446
|
-
|
|
456
|
+
include OpenFeature::SDK::Provider::EventEmitter
|
|
457
|
+
|
|
458
|
+
# Tells the SDK that init emits PROVIDER_READY/PROVIDER_ERROR itself.
|
|
459
|
+
def emits_lifecycle_events? = true
|
|
460
|
+
|
|
461
|
+
# Optional: a provider without init is READY as soon as it's registered.
|
|
462
|
+
# Both arguments are optional; declare only the ones you need.
|
|
463
|
+
# `domain` is the domain the provider was bound to, or nil for the default provider.
|
|
464
|
+
def init(evaluation_context = nil, domain: nil)
|
|
465
|
+
# Perform any initialization steps with flag management system here, then emit PROVIDER_READY.
|
|
447
466
|
# Return value is ignored
|
|
448
467
|
# **Note** The OpenFeature spec defines a lifecycle method called `initialize` to be called when a new provider is set.
|
|
449
468
|
# To avoid conflicting with the Ruby `initialize` method, this method should be named `init` when creating a provider.
|
|
469
|
+
emit_event(OpenFeature::SDK::ProviderEvent::PROVIDER_READY)
|
|
470
|
+
rescue => e
|
|
471
|
+
emit_event(OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR, error_code: OpenFeature::SDK::Provider::ErrorCode::GENERAL, message: e.message)
|
|
472
|
+
raise
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
# Optional: return true if this provider keeps state for a single domain.
|
|
476
|
+
# The SDK then raises ArgumentError if the same instance is bound to a second domain.
|
|
477
|
+
def domain_scoped?
|
|
478
|
+
false
|
|
450
479
|
end
|
|
451
480
|
|
|
452
481
|
def shutdown
|
data/lib/open_feature/sdk/api.rb
CHANGED
|
@@ -48,11 +48,7 @@ module OpenFeature
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def build_client(domain: nil, evaluation_context: nil)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
Client.new(provider: active_provider, domain:, evaluation_context:)
|
|
54
|
-
rescue
|
|
55
|
-
Client.new(provider: Provider::NoOpProvider.new, evaluation_context:)
|
|
51
|
+
Client.new(domain:, evaluation_context:)
|
|
56
52
|
end
|
|
57
53
|
|
|
58
54
|
def provider_metadata(domain: nil)
|
|
@@ -17,11 +17,11 @@ module OpenFeature
|
|
|
17
17
|
SUFFIXES = %i[value details].freeze
|
|
18
18
|
EMPTY_HINTS = Hooks::Hints.new.freeze
|
|
19
19
|
|
|
20
|
-
attr_reader :metadata
|
|
20
|
+
attr_reader :metadata
|
|
21
21
|
|
|
22
|
-
attr_accessor :hooks
|
|
22
|
+
attr_accessor :hooks, :evaluation_context
|
|
23
23
|
|
|
24
|
-
def initialize(provider
|
|
24
|
+
def initialize(provider: nil, domain: nil, evaluation_context: nil)
|
|
25
25
|
@provider = provider
|
|
26
26
|
@metadata = ClientMetadata.new(domain:)
|
|
27
27
|
@evaluation_context = evaluation_context
|
|
@@ -33,7 +33,7 @@ module OpenFeature
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def provider_status
|
|
36
|
-
OpenFeature::SDK.configuration.provider_state(
|
|
36
|
+
OpenFeature::SDK.configuration.provider_state(resolve_provider)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def add_handler(event_type, handler = nil, &block)
|
|
@@ -51,17 +51,21 @@ module OpenFeature
|
|
|
51
51
|
# Records a tracking event. If the provider does not implement
|
|
52
52
|
# tracking, this is a no-op (spec 6.1.4).
|
|
53
53
|
def track(tracking_event_name, evaluation_context: nil, tracking_event_details: nil)
|
|
54
|
-
|
|
54
|
+
provider = resolve_provider
|
|
55
|
+
return unless provider.respond_to?(:track)
|
|
55
56
|
|
|
56
57
|
built_context = build_evaluation_context(evaluation_context)
|
|
57
58
|
|
|
58
|
-
|
|
59
|
+
provider.track(tracking_event_name, evaluation_context: built_context, tracking_event_details: tracking_event_details)
|
|
60
|
+
rescue StandardError, NotImplementedError
|
|
61
|
+
nil
|
|
59
62
|
end
|
|
60
63
|
|
|
61
64
|
RESULT_TYPE.each do |result_type|
|
|
62
65
|
SUFFIXES.each do |suffix|
|
|
63
66
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
64
67
|
def fetch_#{result_type}_#{suffix}(flag_key:, default_value:, evaluation_context: nil, hooks: [], hook_hints: nil)
|
|
68
|
+
validate_default_value_type(:#{result_type}, default_value)
|
|
65
69
|
evaluation_details = fetch_details(type: :#{result_type}, flag_key:, default_value:, evaluation_context:, invocation_hooks: hooks, hook_hints: hook_hints)
|
|
66
70
|
#{"evaluation_details.value" if suffix == :value}
|
|
67
71
|
end
|
|
@@ -72,18 +76,18 @@ module OpenFeature
|
|
|
72
76
|
private
|
|
73
77
|
|
|
74
78
|
def fetch_details(type:, flag_key:, default_value:, evaluation_context: nil, invocation_hooks: [], hook_hints: nil)
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
provider = resolve_provider
|
|
77
80
|
built_context = build_evaluation_context(evaluation_context)
|
|
78
81
|
|
|
79
82
|
# Assemble ordered hooks: API → Client → Invocation → Provider (spec 4.4.2)
|
|
80
|
-
provider_hooks =
|
|
83
|
+
provider_hooks = provider.respond_to?(:hooks) ? Array(provider.hooks) : []
|
|
81
84
|
ordered_hooks = [*OpenFeature::SDK.hooks, *@hooks, *invocation_hooks, *provider_hooks]
|
|
82
85
|
|
|
83
|
-
# Short-circuit NOT_READY/FATAL providers (required by evaluation_v2.feature)
|
|
86
|
+
# Short-circuit NOT_READY/FATAL providers (required by evaluation_v2.feature). A provider resolved from the
|
|
87
|
+
# configuration is tracked while bound, so one that isn't was replaced mid-call and reads NOT_READY.
|
|
84
88
|
short_circuit_code = nil
|
|
85
|
-
if OpenFeature::SDK.configuration.provider_tracked?(
|
|
86
|
-
short_circuit_code = short_circuit_error_code(
|
|
89
|
+
if @provider ? OpenFeature::SDK.configuration.provider_tracked?(provider) : !provider.equal?(@fallback_provider)
|
|
90
|
+
short_circuit_code = short_circuit_error_code(OpenFeature::SDK.configuration.provider_state(provider))
|
|
87
91
|
end
|
|
88
92
|
|
|
89
93
|
# Fast path: skip hook ceremony when no hooks are registered
|
|
@@ -97,17 +101,7 @@ module OpenFeature
|
|
|
97
101
|
return EvaluationDetails.new(flag_key: flag_key, resolution_details: resolution)
|
|
98
102
|
end
|
|
99
103
|
|
|
100
|
-
|
|
101
|
-
return evaluate_flag(type: type, flag_key: flag_key, default_value: default_value, evaluation_context: built_context)
|
|
102
|
-
rescue => e
|
|
103
|
-
resolution = Provider::ResolutionDetails.new(
|
|
104
|
-
value: default_value,
|
|
105
|
-
error_code: EvaluationError.code_for(e),
|
|
106
|
-
reason: Provider::Reason::ERROR,
|
|
107
|
-
error_message: e.message
|
|
108
|
-
)
|
|
109
|
-
return EvaluationDetails.new(flag_key: flag_key, resolution_details: resolution)
|
|
110
|
-
end
|
|
104
|
+
return evaluate_flag(provider: provider, type: type, flag_key: flag_key, default_value: default_value, evaluation_context: built_context)
|
|
111
105
|
end
|
|
112
106
|
|
|
113
107
|
hook_context = Hooks::HookContext.new(
|
|
@@ -116,7 +110,7 @@ module OpenFeature
|
|
|
116
110
|
default_value: default_value,
|
|
117
111
|
evaluation_context: built_context,
|
|
118
112
|
client_metadata: @metadata,
|
|
119
|
-
provider_metadata:
|
|
113
|
+
provider_metadata: provider.respond_to?(:metadata) ? provider.metadata : nil
|
|
120
114
|
)
|
|
121
115
|
|
|
122
116
|
hints = if hook_hints.is_a?(Hooks::Hints)
|
|
@@ -140,22 +134,31 @@ module OpenFeature
|
|
|
140
134
|
evaluation_details
|
|
141
135
|
else
|
|
142
136
|
executor.execute(ordered_hooks: ordered_hooks, hook_context: hook_context, hints: hints) do |hctx|
|
|
143
|
-
evaluate_flag(type: type, flag_key: flag_key, default_value: default_value, evaluation_context: hctx.evaluation_context)
|
|
137
|
+
evaluate_flag(provider: provider, type: type, flag_key: flag_key, default_value: default_value, evaluation_context: hctx.evaluation_context)
|
|
144
138
|
end
|
|
145
139
|
end
|
|
140
|
+
rescue StandardError, NotImplementedError => e
|
|
141
|
+
# Spec 1.4.10 and 1.4.11: report through the details, never raise or log. NotImplementedError is the abstract-method idiom.
|
|
142
|
+
resolution = Provider::ResolutionDetails.new(
|
|
143
|
+
value: default_value,
|
|
144
|
+
error_code: EvaluationError.code_for(e),
|
|
145
|
+
reason: Provider::Reason::ERROR,
|
|
146
|
+
error_message: e.message
|
|
147
|
+
)
|
|
148
|
+
EvaluationDetails.new(flag_key: flag_key, resolution_details: resolution)
|
|
146
149
|
end
|
|
147
150
|
|
|
148
|
-
def evaluate_flag(type:, flag_key:, default_value:, evaluation_context:)
|
|
149
|
-
resolution_details =
|
|
151
|
+
def evaluate_flag(provider:, type:, flag_key:, default_value:, evaluation_context:)
|
|
152
|
+
resolution_details = provider.send(
|
|
150
153
|
:"fetch_#{type}_value",
|
|
151
154
|
flag_key: flag_key,
|
|
152
155
|
default_value: default_value,
|
|
153
156
|
evaluation_context: evaluation_context
|
|
154
157
|
)
|
|
155
158
|
|
|
156
|
-
if TYPE_CLASS_MAP[type].none? { |klass| resolution_details.value.is_a?(klass) }
|
|
159
|
+
if resolution_details.error_code || TYPE_CLASS_MAP[type].none? { |klass| resolution_details.value.is_a?(klass) }
|
|
157
160
|
resolution_details.value = default_value
|
|
158
|
-
resolution_details.error_code
|
|
161
|
+
resolution_details.error_code ||= Provider::ErrorCode::TYPE_MISMATCH
|
|
159
162
|
resolution_details.reason = Provider::Reason::ERROR
|
|
160
163
|
resolution_details.variant = nil
|
|
161
164
|
end
|
|
@@ -163,6 +166,12 @@ module OpenFeature
|
|
|
163
166
|
EvaluationDetails.new(flag_key: flag_key, resolution_details: resolution_details)
|
|
164
167
|
end
|
|
165
168
|
|
|
169
|
+
# Callers resolve once per operation, so a rebinding mid-evaluation can't mix two providers.
|
|
170
|
+
# The fallback NoOp is never registered: untracked, so not short-circuited, and its status reads NOT_READY.
|
|
171
|
+
def resolve_provider
|
|
172
|
+
@provider || OpenFeature::SDK.configuration.provider(domain: @metadata.domain) || (@fallback_provider ||= Provider::NoOpProvider.new)
|
|
173
|
+
end
|
|
174
|
+
|
|
166
175
|
def short_circuit_error_code(state)
|
|
167
176
|
case state
|
|
168
177
|
when ProviderState::NOT_READY then Provider::ErrorCode::PROVIDER_NOT_READY
|
|
@@ -20,15 +20,22 @@ module OpenFeature
|
|
|
20
20
|
attr_accessor :evaluation_context, :hooks, :transaction_context_propagator
|
|
21
21
|
attr_reader :logger
|
|
22
22
|
|
|
23
|
+
# One per registration; +done+ is closed once init finishes, so waiters can pop on it
|
|
24
|
+
InitAttempt = Struct.new(:state, :error, :done, :thread)
|
|
25
|
+
private_constant :InitAttempt
|
|
26
|
+
|
|
23
27
|
def initialize
|
|
24
28
|
@hooks = []
|
|
25
29
|
@providers = {}
|
|
30
|
+
@init_attempts = {}.compare_by_identity
|
|
31
|
+
@legacy_warned = Set.new.compare_by_identity
|
|
26
32
|
@provider_mutex = Mutex.new
|
|
27
33
|
@logger = nil
|
|
28
34
|
@event_dispatcher = EventDispatcher.new(@logger)
|
|
29
35
|
@provider_state_registry = ProviderStateRegistry.new
|
|
30
36
|
@client_handlers = {}
|
|
31
37
|
@client_handlers_mutex = Mutex.new
|
|
38
|
+
@dispatch_key = :"openfeature_dispatch_#{object_id}"
|
|
32
39
|
end
|
|
33
40
|
|
|
34
41
|
def add_hooks(*new_hooks)
|
|
@@ -55,6 +62,8 @@ module OpenFeature
|
|
|
55
62
|
|
|
56
63
|
# @api private
|
|
57
64
|
def add_client_handler(client, event_type, handler)
|
|
65
|
+
@event_dispatcher.validate_handler!(event_type, handler)
|
|
66
|
+
|
|
58
67
|
@client_handlers_mutex.synchronize do
|
|
59
68
|
@client_handlers[client] ||= Hash.new { |h, k| h[k] = [] }
|
|
60
69
|
@client_handlers[client][event_type] << handler
|
|
@@ -118,6 +127,9 @@ module OpenFeature
|
|
|
118
127
|
@provider_state_registry.clear
|
|
119
128
|
@provider_mutex.synchronize do
|
|
120
129
|
@providers.clear
|
|
130
|
+
@init_attempts.each_value { |attempt| abandon_init_attempt(attempt) }
|
|
131
|
+
@init_attempts.clear
|
|
132
|
+
@legacy_warned.clear
|
|
121
133
|
end
|
|
122
134
|
@hooks.clear
|
|
123
135
|
@evaluation_context = nil
|
|
@@ -129,30 +141,42 @@ module OpenFeature
|
|
|
129
141
|
context_for_init = @evaluation_context
|
|
130
142
|
|
|
131
143
|
old_provider = nil
|
|
144
|
+
attempt = nil
|
|
132
145
|
needs_init = false
|
|
146
|
+
init_thread = nil
|
|
133
147
|
needs_shutdown = false
|
|
134
148
|
|
|
149
|
+
# Provider code runs before taking the lock: it may call back into the SDK
|
|
150
|
+
scoped = domain_scoped?(provider)
|
|
151
|
+
has_init = provider.respond_to?(:init)
|
|
152
|
+
legacy = has_init && !emits_lifecycle_events?(provider)
|
|
153
|
+
warn_legacy_provider(provider) if legacy
|
|
154
|
+
|
|
135
155
|
@provider_mutex.synchronize do
|
|
136
|
-
|
|
156
|
+
validate_domain_scoped_binding!(provider, domain, scoped)
|
|
137
157
|
|
|
138
|
-
|
|
139
|
-
new_providers[domain] = provider
|
|
140
|
-
@providers = new_providers
|
|
158
|
+
old_provider = @providers[domain]
|
|
141
159
|
|
|
142
|
-
# Spec 1.1.2.2:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
needs_init = !already_active
|
|
160
|
+
# Spec 1.1.2.2: initialize once per registration; a failed init is retried on the next set
|
|
161
|
+
attempt = @init_attempts[provider]
|
|
162
|
+
needs_init = attempt.nil? || attempt.state == :failed
|
|
146
163
|
|
|
147
164
|
if needs_init
|
|
165
|
+
attempt = @init_attempts[provider] = InitAttempt.new(:running, nil, Thread::Queue.new)
|
|
148
166
|
@provider_state_registry.set_initial_state(provider)
|
|
149
167
|
provider.send(:attach, self) if provider.is_a?(Provider::EventEmitter)
|
|
150
168
|
end
|
|
151
169
|
|
|
170
|
+
# Publish last: clients read @providers without the lock, and must not see an untracked provider
|
|
171
|
+
new_providers = @providers.dup
|
|
172
|
+
new_providers[domain] = provider
|
|
173
|
+
@providers = new_providers
|
|
174
|
+
|
|
152
175
|
# Spec 1.1.2.3: Only shutdown old provider if it's no longer bound to any domain
|
|
153
176
|
if old_provider && !old_provider.equal?(provider)
|
|
154
177
|
needs_shutdown = !provider_bound?(old_provider)
|
|
155
178
|
if needs_shutdown
|
|
179
|
+
abandon_init_attempt(@init_attempts.delete(old_provider))
|
|
156
180
|
@provider_state_registry.remove_provider(old_provider)
|
|
157
181
|
old_provider.send(:detach) if old_provider.is_a?(Provider::EventEmitter)
|
|
158
182
|
end
|
|
@@ -170,67 +194,139 @@ module OpenFeature
|
|
|
170
194
|
|
|
171
195
|
# Initialize provider outside the mutex to avoid blocking other operations
|
|
172
196
|
if needs_init
|
|
173
|
-
|
|
174
|
-
|
|
197
|
+
# Spec 2.8.5.1: a provider without init is READY, and its handlers have run, when set_provider returns
|
|
198
|
+
if wait_for_init || !has_init
|
|
199
|
+
init_provider(provider, context_for_init, domain:, attempt:, raise_on_error: wait_for_init)
|
|
200
|
+
# Replaced or shut down while init ran: fail like the waiters on this attempt did
|
|
201
|
+
raise incomplete_init_error(provider, "initialization did not complete") if wait_for_init && attempt.state != :succeeded
|
|
175
202
|
else
|
|
176
|
-
Thread.new do
|
|
177
|
-
init_provider(provider, context_for_init, raise_on_error: false)
|
|
203
|
+
init_thread = Thread.new do
|
|
204
|
+
init_provider(provider, context_for_init, domain:, attempt:, raise_on_error: false)
|
|
178
205
|
end
|
|
179
206
|
end
|
|
207
|
+
elsif !has_init
|
|
208
|
+
# Spec 2.8.5.1: each new binding of a provider without init runs its domain's READY handlers
|
|
209
|
+
dispatch_provider_event(provider, ProviderEvent::PROVIDER_READY, token: attempt) unless old_provider.equal?(provider)
|
|
180
210
|
elsif wait_for_init
|
|
181
|
-
#
|
|
182
|
-
|
|
211
|
+
# Already registered: wait for an init still in flight, then report its outcome.
|
|
212
|
+
# From inside that init (e.g. a handler of an event it emits) the wait could never end.
|
|
213
|
+
raise incomplete_init_error(provider, "is still initializing on this thread") if running_on_this_thread?(attempt)
|
|
214
|
+
|
|
215
|
+
attempt.done.pop
|
|
216
|
+
raise attempt.error || incomplete_init_error(provider, "initialization did not complete") unless attempt.state == :succeeded
|
|
217
|
+
|
|
218
|
+
dispatch_provider_event(provider, ProviderEvent::PROVIDER_READY, token: attempt) if legacy
|
|
183
219
|
end
|
|
220
|
+
ensure
|
|
221
|
+
# Fail an attempt no init took over (e.g. a Timeout hit first), or it would stay running forever
|
|
222
|
+
finish_init_attempt(attempt, :failed) if needs_init && !init_thread
|
|
184
223
|
end
|
|
185
224
|
|
|
186
|
-
def init_provider(provider, context, raise_on_error: false)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
provider.init
|
|
191
|
-
else
|
|
192
|
-
provider.init(context)
|
|
193
|
-
end
|
|
194
|
-
end
|
|
225
|
+
def init_provider(provider, context, domain:, attempt:, raise_on_error: false)
|
|
226
|
+
emits_lifecycle_events = emits_lifecycle_events?(provider)
|
|
227
|
+
attempt.thread = Thread.current
|
|
228
|
+
call_init(provider, context, domain)
|
|
195
229
|
|
|
196
|
-
|
|
230
|
+
# Settle the attempt before handlers run, so a handler can register this provider again.
|
|
231
|
+
# An abandoned attempt (provider replaced or shut down meanwhile) reports no READY.
|
|
232
|
+
# Spec 2.8.2/2.8.3: a provider that emits its own lifecycle events gets no synthetic READY or ERROR
|
|
233
|
+
dispatch_provider_event(provider, ProviderEvent::PROVIDER_READY, token: attempt) if finish_init_attempt(attempt, :succeeded) && !emits_lifecycle_events
|
|
197
234
|
rescue => e
|
|
198
235
|
# Spec 1.7.5: PROVIDER_FATAL from init makes the provider FATAL
|
|
199
236
|
error_code = EvaluationError.code_for(e)
|
|
200
|
-
|
|
201
|
-
|
|
237
|
+
error = ProviderInitializationError.new(
|
|
238
|
+
"Provider #{provider.class.name} initialization failed: #{e.message}",
|
|
239
|
+
provider:,
|
|
202
240
|
error_code: error_code,
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
error_code: error_code,
|
|
210
|
-
original_error: e
|
|
211
|
-
)
|
|
241
|
+
original_error: e
|
|
242
|
+
)
|
|
243
|
+
finish_init_attempt(attempt, :failed, error)
|
|
244
|
+
|
|
245
|
+
unless emits_lifecycle_events
|
|
246
|
+
dispatch_provider_event(provider, ProviderEvent::PROVIDER_ERROR,
|
|
247
|
+
{error_code: error_code, message: e.message}, token: attempt)
|
|
212
248
|
end
|
|
249
|
+
|
|
250
|
+
raise error if raise_on_error
|
|
251
|
+
ensure
|
|
252
|
+
# Reached unsettled only when init raised something other than a StandardError
|
|
253
|
+
finish_init_attempt(attempt, :failed)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Caller holds @provider_mutex: the provider was unbound or the API reset before its init settled
|
|
257
|
+
def abandon_init_attempt(attempt)
|
|
258
|
+
return unless attempt
|
|
259
|
+
|
|
260
|
+
attempt.state = :failed if attempt.state == :running
|
|
261
|
+
attempt.done.close
|
|
213
262
|
end
|
|
214
263
|
|
|
215
|
-
def
|
|
216
|
-
#
|
|
264
|
+
def incomplete_init_error(provider, reason)
|
|
265
|
+
ProviderInitializationError.new("Provider #{provider.class.name} #{reason}", provider:, error_code: Provider::ErrorCode::GENERAL)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def finish_init_attempt(attempt, state, error = nil)
|
|
217
269
|
@provider_mutex.synchronize do
|
|
218
|
-
return unless
|
|
270
|
+
return false unless attempt.state == :running
|
|
219
271
|
|
|
220
|
-
|
|
272
|
+
attempt.state = state
|
|
273
|
+
attempt.error = error
|
|
221
274
|
end
|
|
275
|
+
attempt.done.close
|
|
276
|
+
true
|
|
277
|
+
end
|
|
222
278
|
|
|
223
|
-
|
|
279
|
+
# An event for a provider already dispatching on this call stack is queued behind the current one
|
|
280
|
+
def dispatch_provider_event(provider, event_type, details = {}, token: nil)
|
|
281
|
+
# Lifecycle events (the SDK's own, or the provider's from its init thread) record status when sent, so
|
|
282
|
+
# set_provider_and_wait returns with it even from the provider's own handler (1.1.2.4); others record it when delivered (5.3.5)
|
|
283
|
+
token ||= running_init_attempt_on_this_thread(provider)
|
|
284
|
+
return if token && !accept_provider_event(provider, event_type, details, token)
|
|
285
|
+
|
|
286
|
+
queues = (Thread.current[@dispatch_key] ||= {}.compare_by_identity)
|
|
287
|
+
if (queue = queues[provider])
|
|
288
|
+
queue << [event_type, details.dup, token]
|
|
289
|
+
return
|
|
290
|
+
end
|
|
224
291
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
292
|
+
queue = queues[provider] = [[event_type, details, token]]
|
|
293
|
+
begin
|
|
294
|
+
deliver_provider_event(provider, *queue.shift)
|
|
295
|
+
until queue.empty?
|
|
296
|
+
begin
|
|
297
|
+
deliver_provider_event(provider, *queue.shift)
|
|
298
|
+
rescue => e
|
|
299
|
+
# Its own emitter already returned, so log rather than fail the event that was dispatching
|
|
300
|
+
@logger&.error("Queued provider event failed: #{e.message}")
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
ensure
|
|
304
|
+
queues.delete(provider)
|
|
305
|
+
Thread.current[@dispatch_key] = nil if queues.empty?
|
|
306
|
+
end
|
|
307
|
+
end
|
|
228
308
|
|
|
309
|
+
def deliver_provider_event(provider, event_type, details, token)
|
|
310
|
+
return unless accept_provider_event(provider, event_type, details, token, record_status: token.nil?)
|
|
311
|
+
|
|
312
|
+
event_details = {provider_name: extract_provider_name(provider)}.merge(details)
|
|
229
313
|
run_handlers_for_provider(provider, event_type, event_details)
|
|
230
314
|
end
|
|
231
315
|
|
|
232
316
|
private
|
|
233
317
|
|
|
318
|
+
# Under the replacement lock, so a late event can't re-track a replaced or shut-down provider
|
|
319
|
+
def accept_provider_event(provider, event_type, details, token, record_status: true)
|
|
320
|
+
@provider_mutex.synchronize do
|
|
321
|
+
return false unless provider_bound?(provider)
|
|
322
|
+
# An SDK READY/ERROR from an earlier registration of this provider is stale
|
|
323
|
+
return false unless token.nil? || @init_attempts[provider].equal?(token)
|
|
324
|
+
|
|
325
|
+
@provider_state_registry.update_state_from_event(provider, event_type, details) if record_status
|
|
326
|
+
true
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
234
330
|
def provider_bound?(provider)
|
|
235
331
|
@providers.each_value.any? { |p| p.equal?(provider) }
|
|
236
332
|
end
|
|
@@ -239,26 +335,78 @@ module OpenFeature
|
|
|
239
335
|
provider.respond_to?(:metadata) ? provider.metadata.name : provider.class.name
|
|
240
336
|
end
|
|
241
337
|
|
|
338
|
+
def domain_scoped?(provider)
|
|
339
|
+
provider.respond_to?(:domain_scoped?) && provider.domain_scoped?
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Spec 2.8.5.1: without init there's nothing to report, so the marker only counts alongside init
|
|
343
|
+
def emits_lifecycle_events?(provider)
|
|
344
|
+
provider.respond_to?(:init) && provider.respond_to?(:emits_lifecycle_events?) && provider.emits_lifecycle_events?
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def running_init_attempt_on_this_thread(provider)
|
|
348
|
+
attempt = @provider_mutex.synchronize { @init_attempts[provider] }
|
|
349
|
+
attempt if attempt && running_on_this_thread?(attempt)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def running_on_this_thread?(attempt)
|
|
353
|
+
attempt.state == :running && attempt.thread.equal?(Thread.current)
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def warn_legacy_provider(provider)
|
|
357
|
+
return if @logger.nil? || !@provider_mutex.synchronize { @legacy_warned.add?(provider.class) }
|
|
358
|
+
@logger.warn("Provider #{extract_provider_name(provider)} relies on SDK-emitted lifecycle events, which are deprecated; " \
|
|
359
|
+
"emit PROVIDER_READY/PROVIDER_ERROR from init and define emits_lifecycle_events? returning true (spec Appendix E)")
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def validate_domain_scoped_binding!(provider, domain, scoped)
|
|
363
|
+
return unless scoped && provider_bound?(provider) && !@providers[domain].equal?(provider)
|
|
364
|
+
|
|
365
|
+
raise ArgumentError, "Cannot bind domain-scoped provider to more than one domain"
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def call_init(provider, context, domain)
|
|
369
|
+
return unless provider.respond_to?(:init)
|
|
370
|
+
|
|
371
|
+
parameters = provider.method(:init).parameters
|
|
372
|
+
takes_domain = init_accepts_domain?(parameters)
|
|
373
|
+
if !takes_domain && domain_scoped?(provider)
|
|
374
|
+
@logger&.warn("Provider #{extract_provider_name(provider)} is domain-scoped but its init doesn't accept domain:")
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
args = init_accepts_evaluation_context?(parameters) ? [context] : []
|
|
378
|
+
provider.init(*args, **(takes_domain ? {domain:} : {}))
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# Anonymous `**` (from `...`) and delegators' bare `*` can't be trusted to accept `domain:`
|
|
382
|
+
def init_accepts_domain?(parameters)
|
|
383
|
+
parameters.any? do |kind, name|
|
|
384
|
+
((kind == :key || kind == :keyreq) && name == :domain) || (kind == :keyrest && name && name != :**)
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def init_accepts_evaluation_context?(parameters)
|
|
389
|
+
parameters.any? { |kind, _| kind == :req || kind == :opt || kind == :rest }
|
|
390
|
+
end
|
|
391
|
+
|
|
242
392
|
def run_handlers_for_provider(provider, event_type, event_details)
|
|
243
393
|
# Run global handlers (API-level, no domain filtering)
|
|
244
394
|
@event_dispatcher.trigger_event(event_type, event_details)
|
|
245
395
|
|
|
246
|
-
# Run client handlers (domain-scoped)
|
|
247
|
-
@client_handlers_mutex.synchronize do
|
|
248
|
-
@client_handlers.
|
|
396
|
+
# Run client handlers (domain-scoped) outside the lock, so they can add or remove handlers
|
|
397
|
+
handlers = @client_handlers_mutex.synchronize do
|
|
398
|
+
@client_handlers.flat_map do |client, handlers_by_event|
|
|
249
399
|
# Check if this client should receive events from this provider
|
|
250
400
|
client_provider = provider(domain: client.metadata.domain)
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
# Trigger handlers for this client
|
|
254
|
-
handlers = handlers_by_event[event_type]
|
|
255
|
-
handlers.each do |handler|
|
|
256
|
-
handler.call(event_details)
|
|
257
|
-
rescue => e
|
|
258
|
-
@logger&.error("Client event handler failed: #{e.message}")
|
|
259
|
-
end
|
|
401
|
+
client_provider&.equal?(provider) ? handlers_by_event[event_type] : []
|
|
260
402
|
end
|
|
261
403
|
end
|
|
404
|
+
|
|
405
|
+
handlers.each do |handler|
|
|
406
|
+
handler.call(event_details)
|
|
407
|
+
rescue => e
|
|
408
|
+
@logger&.error("Client event handler failed: #{e.message}")
|
|
409
|
+
end
|
|
262
410
|
end
|
|
263
411
|
|
|
264
412
|
def run_immediate_handler(event_type, handler, client)
|
|
@@ -270,8 +418,8 @@ module OpenFeature
|
|
|
270
418
|
}
|
|
271
419
|
|
|
272
420
|
if client.nil?
|
|
273
|
-
# API-level handler: check
|
|
274
|
-
@providers.each do |
|
|
421
|
+
# API-level handler: check each bound provider once, however many domains it's bound to
|
|
422
|
+
@providers.values.uniq(&:object_id).each do |provider|
|
|
275
423
|
next unless provider
|
|
276
424
|
|
|
277
425
|
provider_state = @provider_state_registry.get_state(provider)
|
|
@@ -16,14 +16,18 @@ module OpenFeature
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def add_handler(event_type, handler)
|
|
19
|
-
|
|
20
|
-
raise ArgumentError, "Handler must respond to call" unless handler.respond_to?(:call)
|
|
19
|
+
validate_handler!(event_type, handler)
|
|
21
20
|
|
|
22
21
|
@mutex.synchronize do
|
|
23
22
|
@handlers[event_type] << handler
|
|
24
23
|
end
|
|
25
24
|
end
|
|
26
25
|
|
|
26
|
+
def validate_handler!(event_type, handler)
|
|
27
|
+
raise ArgumentError, "Invalid event type: #{event_type}" unless valid_event?(event_type)
|
|
28
|
+
raise ArgumentError, "Handler must respond to call" unless handler.respond_to?(:call)
|
|
29
|
+
end
|
|
30
|
+
|
|
27
31
|
def remove_handler(event_type, handler)
|
|
28
32
|
return unless valid_event?(event_type)
|
|
29
33
|
|
|
@@ -54,7 +54,7 @@ module OpenFeature
|
|
|
54
54
|
else
|
|
55
55
|
run_after_hooks(ordered_hooks, hook_context, evaluation_details, hints)
|
|
56
56
|
end
|
|
57
|
-
rescue => e
|
|
57
|
+
rescue StandardError, NotImplementedError => e
|
|
58
58
|
run_error_hooks(ordered_hooks, hook_context, e, hints)
|
|
59
59
|
|
|
60
60
|
evaluation_details = EvaluationDetails.new(
|
|
@@ -7,10 +7,10 @@ module OpenFeature
|
|
|
7
7
|
EMPTY_HINTS: Hooks::Hints
|
|
8
8
|
|
|
9
9
|
attr_reader metadata: ClientMetadata
|
|
10
|
-
|
|
10
|
+
attr_accessor evaluation_context: EvaluationContext?
|
|
11
11
|
attr_accessor hooks: Array[untyped]
|
|
12
12
|
|
|
13
|
-
def initialize: (provider: untyped, ?domain: String?, ?evaluation_context: EvaluationContext?) -> void
|
|
13
|
+
def initialize: (?provider: untyped, ?domain: String?, ?evaluation_context: EvaluationContext?) -> void
|
|
14
14
|
|
|
15
15
|
def add_hooks: (*Array[untyped] new_hooks) -> void
|
|
16
16
|
|
|
@@ -40,7 +40,8 @@ module OpenFeature
|
|
|
40
40
|
private
|
|
41
41
|
|
|
42
42
|
def fetch_details: (type: Symbol, flag_key: String, default_value: untyped, ?evaluation_context: EvaluationContext?, ?invocation_hooks: Array[untyped], ?hook_hints: Hooks::Hints?) -> EvaluationDetails?
|
|
43
|
-
def evaluate_flag: (type: Symbol, flag_key: String, default_value: untyped, evaluation_context: EvaluationContext?) -> EvaluationDetails
|
|
43
|
+
def evaluate_flag: (provider: untyped, type: Symbol, flag_key: String, default_value: untyped, evaluation_context: EvaluationContext?) -> EvaluationDetails
|
|
44
|
+
def resolve_provider: () -> untyped
|
|
44
45
|
def short_circuit_error_code: (String state) -> String?
|
|
45
46
|
def build_evaluation_context: (EvaluationContext? invocation_context) -> EvaluationContext?
|
|
46
47
|
def validate_default_value_type: (Symbol type, untyped default_value) -> void
|
|
@@ -32,9 +32,23 @@ module OpenFeature
|
|
|
32
32
|
|
|
33
33
|
def reset: () -> void
|
|
34
34
|
def set_provider_internal: (untyped provider, domain: String?, wait_for_init: bool) -> void
|
|
35
|
-
def init_provider: (untyped provider, EvaluationContext? context, ?raise_on_error: bool) -> void
|
|
36
|
-
def
|
|
35
|
+
def init_provider: (untyped provider, EvaluationContext? context, domain: String?, attempt: untyped, ?raise_on_error: bool) -> void
|
|
36
|
+
def finish_init_attempt: (untyped attempt, Symbol state, ?ProviderInitializationError? error) -> bool
|
|
37
|
+
def abandon_init_attempt: (untyped attempt) -> void
|
|
38
|
+
def incomplete_init_error: (untyped provider, String reason) -> ProviderInitializationError
|
|
39
|
+
def dispatch_provider_event: (untyped provider, String event_type, ?Hash[Symbol, untyped] details, ?token: untyped) -> void
|
|
40
|
+
def deliver_provider_event: (untyped provider, String event_type, Hash[Symbol, untyped]? details, untyped token) -> void
|
|
41
|
+
def accept_provider_event: (untyped provider, String event_type, Hash[Symbol, untyped]? details, untyped token, ?record_status: bool) -> bool
|
|
37
42
|
def provider_bound?: (untyped provider) -> bool
|
|
43
|
+
def domain_scoped?: (untyped provider) -> bool
|
|
44
|
+
def emits_lifecycle_events?: (untyped provider) -> bool
|
|
45
|
+
def warn_legacy_provider: (untyped provider) -> void
|
|
46
|
+
def running_init_attempt_on_this_thread: (untyped provider) -> untyped
|
|
47
|
+
def running_on_this_thread?: (untyped attempt) -> bool
|
|
48
|
+
def validate_domain_scoped_binding!: (untyped provider, String? domain, bool scoped) -> void
|
|
49
|
+
def call_init: (untyped provider, EvaluationContext? context, String? domain) -> void
|
|
50
|
+
def init_accepts_domain?: (Array[[Symbol, Symbol?]] parameters) -> bool
|
|
51
|
+
def init_accepts_evaluation_context?: (Array[[Symbol, Symbol?]] parameters) -> bool
|
|
38
52
|
def extract_provider_name: (untyped provider) -> String
|
|
39
53
|
def run_handlers_for_provider: (untyped provider, String event_type, Hash[Symbol, untyped] event_details) -> void
|
|
40
54
|
def run_immediate_handler: (String event_type, _ToProc handler, Client? client) -> void
|
|
@@ -6,6 +6,7 @@ module OpenFeature
|
|
|
6
6
|
def initialize: (?untyped? logger) -> void
|
|
7
7
|
|
|
8
8
|
def add_handler: (String event_type, _ToProc handler) -> void
|
|
9
|
+
def validate_handler!: (String event_type, untyped handler) -> void
|
|
9
10
|
def remove_handler: (String event_type, _ToProc handler) -> void
|
|
10
11
|
def remove_all_handlers: (String event_type) -> void
|
|
11
12
|
def trigger_event: (String event_type, ?Hash[Symbol, untyped] event_details) -> void
|
|
@@ -11,11 +11,15 @@ module OpenFeature
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
interface _LifecycleProvider
|
|
14
|
-
def init: (?EvaluationContext? evaluation_context) -> void
|
|
14
|
+
def init: (?EvaluationContext? evaluation_context, ?domain: String?) -> void
|
|
15
15
|
def shutdown: () -> void
|
|
16
16
|
def metadata: () -> ProviderMetadata
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
interface _DomainScopedProvider
|
|
20
|
+
def domain_scoped?: () -> bool
|
|
21
|
+
end
|
|
22
|
+
|
|
19
23
|
interface _TrackableProvider
|
|
20
24
|
def track: (String tracking_event_name, ?evaluation_context: EvaluationContext?, ?tracking_event_details: TrackingEventDetails?) -> void
|
|
21
25
|
end
|