diff --git a/lib/open_feature/sdk/configuration.rb b/lib/open_feature/sdk/configuration.rb index 10591459..b1df9d6d 100644 --- a/lib/open_feature/sdk/configuration.rb +++ b/lib/open_feature/sdk/configuration.rb @@ -235,8 +235,7 @@ def run_immediate_handler(event_type, handler, client) provider_state = @provider_state_registry.get_state(provider) if event_type == status_to_event[provider_state] - provider_name = extract_provider_name(provider) - event_details = {provider_name: provider_name} + event_details = build_event_details(provider) begin handler.call(event_details) @@ -253,8 +252,7 @@ def run_immediate_handler(event_type, handler, client) provider_state = @provider_state_registry.get_state(client_provider) if event_type == status_to_event[provider_state] - provider_name = extract_provider_name(client_provider) - event_details = {provider_name: provider_name} + event_details = build_event_details(client_provider) begin handler.call(event_details) @@ -264,6 +262,11 @@ def run_immediate_handler(event_type, handler, client) end end end + + def build_event_details(provider) + stored_details = @provider_state_registry.get_details(provider) + {provider_name: extract_provider_name(provider)}.merge(stored_details) + end end end end diff --git a/lib/open_feature/sdk/provider_state_registry.rb b/lib/open_feature/sdk/provider_state_registry.rb index c4b5940c..b825b2cf 100644 --- a/lib/open_feature/sdk/provider_state_registry.rb +++ b/lib/open_feature/sdk/provider_state_registry.rb @@ -17,7 +17,7 @@ def set_initial_state(provider, state = ProviderState::NOT_READY) return unless provider @mutex.synchronize do - @states[provider.object_id] = state + @states[provider.object_id] = {state: state, details: {}} end end @@ -29,7 +29,7 @@ def update_state_from_event(provider, event_type, event_details = nil) # Only update state if the event should cause a state change if new_state @mutex.synchronize do - @states[provider.object_id] = new_state + @states[provider.object_id] = {state: new_state, details: event_details || {}} end new_state else @@ -42,7 +42,17 @@ def get_state(provider) return ProviderState::NOT_READY unless provider @mutex.synchronize do - @states[provider.object_id] || ProviderState::NOT_READY + entry = @states[provider.object_id] + entry ? entry[:state] : ProviderState::NOT_READY + end + end + + def get_details(provider) + return {} unless provider + + @mutex.synchronize do + entry = @states[provider.object_id] + entry ? entry[:details] : {} end end diff --git a/spec/specification/events_spec.rb b/spec/specification/events_spec.rb index bce81f5e..588f2faa 100644 --- a/spec/specification/events_spec.rb +++ b/spec/specification/events_spec.rb @@ -98,6 +98,66 @@ def init(_evaluation_context) end end + context "Requirement 5.1.4" do + specify "PROVIDER_ERROR events SHOULD populate the error message field" do + event_details_received = nil + handler = ->(event_details) { event_details_received = event_details } + + OpenFeature::SDK.add_handler(OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR, handler) + + provider = OpenFeature::SDK::Provider::InMemoryProvider.new + allow(provider).to receive(:init).and_raise("Custom init failure") + + OpenFeature::SDK.set_provider(provider) + sleep(0.001) until event_details_received + + expect(event_details_received[:message]).to eq("Custom init failure") + + OpenFeature::SDK.remove_handler(OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR, handler) + end + end + + context "Requirement 5.1.5" do + specify "PROVIDER_ERROR events SHOULD populate the error code field" do + event_details_received = nil + handler = ->(event_details) { event_details_received = event_details } + + OpenFeature::SDK.add_handler(OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR, handler) + + provider = OpenFeature::SDK::Provider::InMemoryProvider.new + allow(provider).to receive(:init).and_raise("Init failed") + + OpenFeature::SDK.set_provider(provider) + sleep(0.001) until event_details_received + + expect(event_details_received[:error_code]).to eq(OpenFeature::SDK::Provider::ErrorCode::GENERAL) + + OpenFeature::SDK.remove_handler(OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR, handler) + end + + specify "error details are available in immediate handlers attached after the error" do + provider = OpenFeature::SDK::Provider::InMemoryProvider.new + allow(provider).to receive(:init).and_raise("Delayed failure") + + begin + OpenFeature::SDK.set_provider_and_wait(provider) + rescue OpenFeature::SDK::ProviderInitializationError + # Expected — provider init fails, putting provider in ERROR state + end + + event_details_received = nil + handler = ->(event_details) { event_details_received = event_details } + + OpenFeature::SDK.add_handler(OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR, handler) + + expect(event_details_received).not_to be_nil + expect(event_details_received[:error_code]).to eq(OpenFeature::SDK::Provider::ErrorCode::GENERAL) + expect(event_details_received[:message]).to eq("Delayed failure") + + OpenFeature::SDK.remove_handler(OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR, handler) + end + end + context "Requirement 5.2.1" do specify "The client MUST provide a function for associating handler functions with provider event types" do client = OpenFeature::SDK.build_client(domain: "test-domain")