diff --git a/src/main/java/dev/openfeature/sdk/EventDetails.java b/src/main/java/dev/openfeature/sdk/EventDetails.java index c75b046e0..fca5d4114 100644 --- a/src/main/java/dev/openfeature/sdk/EventDetails.java +++ b/src/main/java/dev/openfeature/sdk/EventDetails.java @@ -26,6 +26,7 @@ static EventDetails fromProviderEventDetails( .flagsChanged(providerEventDetails.getFlagsChanged()) .eventMetadata(providerEventDetails.getEventMetadata()) .message(providerEventDetails.getMessage()) + .errorCode(providerEventDetails.getErrorCode()) .build(); } } diff --git a/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java b/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java index 4f8d7d6e4..06cb366e7 100644 --- a/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java +++ b/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java @@ -317,7 +317,10 @@ private void emitError(FeatureProvider provider, OpenFeatureError exception) { runHandlersForProvider( provider, ProviderEvent.PROVIDER_ERROR, - ProviderEventDetails.builder().message(exception.getMessage()).build()); + ProviderEventDetails.builder() + .message(exception.getMessage()) + .errorCode(exception.getErrorCode()) + .build()); } private void emitErrorAndThrow(FeatureProvider provider, OpenFeatureError exception) throws OpenFeatureError { diff --git a/src/test/java/dev/openfeature/sdk/EventDetailsTest.java b/src/test/java/dev/openfeature/sdk/EventDetailsTest.java new file mode 100644 index 000000000..63a7f5dbf --- /dev/null +++ b/src/test/java/dev/openfeature/sdk/EventDetailsTest.java @@ -0,0 +1,53 @@ +package dev.openfeature.sdk; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class EventDetailsTest { + + @Test + @DisplayName("should carry the error code a provider set, so API-level handlers can see it") + void shouldCopyErrorCode() { + ProviderEventDetails providerEventDetails = ProviderEventDetails.builder() + .errorCode(ErrorCode.PROVIDER_NOT_READY) + .build(); + + EventDetails details = EventDetails.fromProviderEventDetails(providerEventDetails, "provider"); + + assertThat(details.getErrorCode()).isEqualTo(ErrorCode.PROVIDER_NOT_READY); + } + + @Test + @DisplayName("should leave the error code unset when the provider did not set one") + void shouldLeaveErrorCodeUnsetWhenAbsent() { + EventDetails details = EventDetails.fromProviderEventDetails( + ProviderEventDetails.builder().build(), "provider"); + + assertThat(details.getErrorCode()).isNull(); + } + + @Test + @DisplayName("should carry every field of the provider event details") + void shouldCopyEveryField() { + ImmutableMetadata metadata = + ImmutableMetadata.builder().addString("key", "value").build(); + ProviderEventDetails providerEventDetails = ProviderEventDetails.builder() + .flagsChanged(Arrays.asList("flag1", "flag2")) + .message("message") + .eventMetadata(metadata) + .errorCode(ErrorCode.GENERAL) + .build(); + + EventDetails details = EventDetails.fromProviderEventDetails(providerEventDetails, "provider", "domain"); + + assertThat(details.getFlagsChanged()).isEqualTo(providerEventDetails.getFlagsChanged()); + assertThat(details.getMessage()).isEqualTo(providerEventDetails.getMessage()); + assertThat(details.getEventMetadata()).isEqualTo(providerEventDetails.getEventMetadata()); + assertThat(details.getErrorCode()).isEqualTo(providerEventDetails.getErrorCode()); + assertThat(details.getProviderName()).isEqualTo("provider"); + assertThat(details.getDomain()).isEqualTo("domain"); + } +} diff --git a/src/test/java/dev/openfeature/sdk/EventsTest.java b/src/test/java/dev/openfeature/sdk/EventsTest.java index b3cd2a05d..e010007f2 100644 --- a/src/test/java/dev/openfeature/sdk/EventsTest.java +++ b/src/test/java/dev/openfeature/sdk/EventsTest.java @@ -538,6 +538,22 @@ void handlersRunIfOneThrows() { verify(lastHandler, timeout(TIMEOUT)).accept(any()); } + @Test + @DisplayName("errors the SDK raises itself must carry the error code") + @Specification( + number = "5.1.5", + text = "`PROVIDER_ERROR` events SHOULD populate the `provider event details`'s `error code` field.") + void errorsRaisedBySdkMustCarryErrorCode() { + final Consumer handler = mockHandler(); + api.onProviderError(handler); + + api.setProvider( + "errorsRaisedBySdkMustCarryErrorCode", TestProvider.builder().initsToFatal()); + + verify(handler, timeout(TIMEOUT)) + .accept(argThat((EventDetails details) -> ErrorCode.PROVIDER_FATAL.equals(details.getErrorCode()))); + } + @Test @DisplayName("should have all properties") @Specification(number = "5.2.4", text = "The handler function MUST accept a event details parameter.") @@ -561,10 +577,12 @@ void shouldHaveAllProperties() { ImmutableMetadata metadata = ImmutableMetadata.builder().addInteger("int", 1).build(); String message = "a message"; + ErrorCode errorCode = ErrorCode.GENERAL; ProviderEventDetails details = ProviderEventDetails.builder() .eventMetadata(metadata) .flagsChanged(flagsChanged) .message(message) + .errorCode(errorCode) .build(); provider.emit(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, details); @@ -574,12 +592,14 @@ void shouldHaveAllProperties() { return metadata.equals(eventDetails.getEventMetadata()) // TODO: issue for client name in events && flagsChanged.equals(eventDetails.getFlagsChanged()) - && message.equals(eventDetails.getMessage()); + && message.equals(eventDetails.getMessage()) + && errorCode.equals(eventDetails.getErrorCode()); })); verify(handler2, timeout(TIMEOUT)).accept(argThat((EventDetails eventDetails) -> { return metadata.equals(eventDetails.getEventMetadata()) && flagsChanged.equals(eventDetails.getFlagsChanged()) && message.equals(eventDetails.getMessage()) + && errorCode.equals(eventDetails.getErrorCode()) && name.equals(eventDetails.getDomain()); })); }