Skip to content

Commit a185003

Browse files
josecolellaclaude
andauthored
feat: populate event details payload with error_code and message (#225)
Signed-off-by: Jose Colella <jose.colella@gusto.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0a148f6 commit a185003

3 files changed

Lines changed: 80 additions & 7 deletions

File tree

lib/open_feature/sdk/configuration.rb

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,7 @@ def run_immediate_handler(event_type, handler, client)
235235
provider_state = @provider_state_registry.get_state(provider)
236236

237237
if event_type == status_to_event[provider_state]
238-
provider_name = extract_provider_name(provider)
239-
event_details = {provider_name: provider_name}
238+
event_details = build_event_details(provider)
240239

241240
begin
242241
handler.call(event_details)
@@ -253,8 +252,7 @@ def run_immediate_handler(event_type, handler, client)
253252
provider_state = @provider_state_registry.get_state(client_provider)
254253

255254
if event_type == status_to_event[provider_state]
256-
provider_name = extract_provider_name(client_provider)
257-
event_details = {provider_name: provider_name}
255+
event_details = build_event_details(client_provider)
258256

259257
begin
260258
handler.call(event_details)
@@ -264,6 +262,11 @@ def run_immediate_handler(event_type, handler, client)
264262
end
265263
end
266264
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
267270
end
268271
end
269272
end

lib/open_feature/sdk/provider_state_registry.rb

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def set_initial_state(provider, state = ProviderState::NOT_READY)
1717
return unless provider
1818

1919
@mutex.synchronize do
20-
@states[provider.object_id] = state
20+
@states[provider.object_id] = {state: state, details: {}}
2121
end
2222
end
2323

@@ -29,7 +29,7 @@ def update_state_from_event(provider, event_type, event_details = nil)
2929
# Only update state if the event should cause a state change
3030
if new_state
3131
@mutex.synchronize do
32-
@states[provider.object_id] = new_state
32+
@states[provider.object_id] = {state: new_state, details: event_details || {}}
3333
end
3434
new_state
3535
else
@@ -42,7 +42,17 @@ def get_state(provider)
4242
return ProviderState::NOT_READY unless provider
4343

4444
@mutex.synchronize do
45-
@states[provider.object_id] || ProviderState::NOT_READY
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] : {}
4656
end
4757
end
4858

spec/specification/events_spec.rb

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,66 @@ def init(_evaluation_context)
9898
end
9999
end
100100

101+
context "Requirement 5.1.4" do
102+
specify "PROVIDER_ERROR events SHOULD populate the error message field" do
103+
event_details_received = nil
104+
handler = ->(event_details) { event_details_received = event_details }
105+
106+
OpenFeature::SDK.add_handler(OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR, handler)
107+
108+
provider = OpenFeature::SDK::Provider::InMemoryProvider.new
109+
allow(provider).to receive(:init).and_raise("Custom init failure")
110+
111+
OpenFeature::SDK.set_provider(provider)
112+
sleep(0.001) until event_details_received
113+
114+
expect(event_details_received[:message]).to eq("Custom init failure")
115+
116+
OpenFeature::SDK.remove_handler(OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR, handler)
117+
end
118+
end
119+
120+
context "Requirement 5.1.5" do
121+
specify "PROVIDER_ERROR events SHOULD populate the error code field" do
122+
event_details_received = nil
123+
handler = ->(event_details) { event_details_received = event_details }
124+
125+
OpenFeature::SDK.add_handler(OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR, handler)
126+
127+
provider = OpenFeature::SDK::Provider::InMemoryProvider.new
128+
allow(provider).to receive(:init).and_raise("Init failed")
129+
130+
OpenFeature::SDK.set_provider(provider)
131+
sleep(0.001) until event_details_received
132+
133+
expect(event_details_received[:error_code]).to eq(OpenFeature::SDK::Provider::ErrorCode::GENERAL)
134+
135+
OpenFeature::SDK.remove_handler(OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR, handler)
136+
end
137+
138+
specify "error details are available in immediate handlers attached after the error" do
139+
provider = OpenFeature::SDK::Provider::InMemoryProvider.new
140+
allow(provider).to receive(:init).and_raise("Delayed failure")
141+
142+
begin
143+
OpenFeature::SDK.set_provider_and_wait(provider)
144+
rescue OpenFeature::SDK::ProviderInitializationError
145+
# Expected — provider init fails, putting provider in ERROR state
146+
end
147+
148+
event_details_received = nil
149+
handler = ->(event_details) { event_details_received = event_details }
150+
151+
OpenFeature::SDK.add_handler(OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR, handler)
152+
153+
expect(event_details_received).not_to be_nil
154+
expect(event_details_received[:error_code]).to eq(OpenFeature::SDK::Provider::ErrorCode::GENERAL)
155+
expect(event_details_received[:message]).to eq("Delayed failure")
156+
157+
OpenFeature::SDK.remove_handler(OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR, handler)
158+
end
159+
end
160+
101161
context "Requirement 5.2.1" do
102162
specify "The client MUST provide a function for associating handler functions with provider event types" do
103163
client = OpenFeature::SDK.build_client(domain: "test-domain")

0 commit comments

Comments
 (0)