Skip to content

Commit 422997a

Browse files
committed
fixup: further improve immutability and add release please
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com> diff --git c/.github/workflows/release.yml i/.github/workflows/release.yml index f130b89..4cf2d50 100644 --- c/.github/workflows/release.yml +++ i/.github/workflows/release.yml @@ -24,6 +24,8 @@ jobs: id: release with: token: ${{secrets.RELEASE_PLEASE_ACTION_TOKEN}} + prerelease: ${{ github.ref == 'refs/heads/beta' }} + prerelease-type: "beta" outputs: release_created: ${{ fromJSON(steps.release.outputs.paths_released)[0] != null }} # if we have a single release path, do the release diff --git c/.release-please-manifest.json i/.release-please-manifest.json index b0c1905..f6ebfaa 100644 --- c/.release-please-manifest.json +++ i/.release-please-manifest.json @@ -1 +1,4 @@ -{".":"1.18.0"} +{ + "./sdk": "2.0.0-beta", + "./api": "0.0.0-beta" +} diff --git c/openfeature-api/src/main/java/dev/openfeature/api/BaseEvaluation.java i/openfeature-api/src/main/java/dev/openfeature/api/BaseEvaluation.java index e9df678..443e5d1 100644 --- c/openfeature-api/src/main/java/dev/openfeature/api/BaseEvaluation.java +++ i/openfeature-api/src/main/java/dev/openfeature/api/BaseEvaluation.java @@ -41,4 +41,6 @@ public interface BaseEvaluation<T> { * @return {String} */ String getErrorMessage(); + + Metadata getFlagMetadata(); } diff --git c/openfeature-api/src/main/java/dev/openfeature/api/DefaultEvaluationEvent.java i/openfeature-api/src/main/java/dev/openfeature/api/DefaultEvaluationEvent.java new file mode 100644 index 0000000..a1f7726 --- /dev/null +++ i/openfeature-api/src/main/java/dev/openfeature/api/DefaultEvaluationEvent.java @@ -0,0 +1,96 @@ +package dev.openfeature.api; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Represents an evaluation event. + * This class is immutable and thread-safe. + */ +class DefaultEvaluationEvent implements EvaluationEvent { + + private final String name; + private final Map<String, Object> attributes; + + /** + * Private constructor - use builder() to create instances. + */ + private DefaultEvaluationEvent(String name, Map<String, Object> attributes) { + this.name = name; + this.attributes = attributes != null ? new HashMap<>(attributes) : new HashMap<>(); + } + + /** + * Gets the name of the evaluation event. + * + * @return the event name + */ + @Override + public String getName() { + return name; + } + + /** + * Gets a copy of the event attributes. + * + * @return a new map containing the event attributes + */ + @Override + public Map<String, Object> getAttributes() { + return new HashMap<>(attributes); + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + DefaultEvaluationEvent that = (DefaultEvaluationEvent) obj; + return Objects.equals(name, that.name) && Objects.equals(attributes, that.attributes); + } + + @Override + public int hashCode() { + return Objects.hash(name, attributes); + } + + @Override + public String toString() { + return "EvaluationEvent{" + "name='" + name + '\'' + ", attributes=" + attributes + '}'; + } + + /** + * Builder class for creating instances of EvaluationEvent. + */ + public static class Builder { + private String name; + private Map<String, Object> attributes = new HashMap<>(); + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder attributes(Map<String, Object> attributes) { + this.attributes = attributes != null ? new HashMap<>(attributes) : new HashMap<>(); + return this; + } + + public Builder attribute(String key, Object value) { + this.attributes.put(key, value); + return this; + } + + public EvaluationEvent build() { + return new DefaultEvaluationEvent(name, attributes); + } + } +} diff --git c/openfeature-api/src/main/java/dev/openfeature/api/DefaultFlagEvaluationDetails.java i/openfeature-api/src/main/java/dev/openfeature/api/DefaultFlagEvaluationDetails.java new file mode 100644 index 0000000..19a4e22 --- /dev/null +++ i/openfeature-api/src/main/java/dev/openfeature/api/DefaultFlagEvaluationDetails.java @@ -0,0 +1,118 @@ +package dev.openfeature.api; + +import java.util.Objects; + +/** + * Contains information about how the provider resolved a flag, including the + * resolved value. + * + * @param <T> the type of the flag being evaluated. + */ +class DefaultFlagEvaluationDetails<T> implements FlagEvaluationDetails<T> { + + private final String flagKey; + private final T value; + private final String variant; + private final String reason; + private final ErrorCode errorCode; + private final String errorMessage; + private final Metadata flagMetadata; + + /** + * Private constructor for builder pattern only. + */ + DefaultFlagEvaluationDetails() { + this(null, null, null, null, null, null, null); + } + + /** + * Private constructor for immutable FlagEvaluationDetails. + * + * @param flagKey the flag key + * @param value the resolved value + * @param variant the variant identifier + * @param reason the reason for the evaluation result + * @param errorCode the error code if applicable + * @param errorMessage the error message if applicable + * @param flagMetadata metadata associated with the flag + */ + DefaultFlagEvaluationDetails( + String flagKey, + T value, + String variant, + String reason, + ErrorCode errorCode, + String errorMessage, + Metadata flagMetadata) { + this.flagKey = flagKey; + this.value = value; + this.variant = variant; + this.reason = reason; + this.errorCode = errorCode; + this.errorMessage = errorMessage; + this.flagMetadata = flagMetadata != null ? flagMetadata : Metadata.EMPTY; + } + + public String getFlagKey() { + return flagKey; + } + + public T getValue() { + return value; + } + + public String getVariant() { + return variant; + } + + public String getReason() { + return reason; + } + + public ErrorCode getErrorCode() { + return errorCode; + } + + public String getErrorMessage() { + return errorMessage; + } + + public Metadata getFlagMetadata() { + return flagMetadata; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + FlagEvaluationDetails<?> that = (FlagEvaluationDetails<?>) obj; + return Objects.equals(flagKey, that.getFlagKey()) + && Objects.equals(value, that.getValue()) + && Objects.equals(variant, that.getVariant()) + && Objects.equals(reason, that.getReason()) + && errorCode == that.getErrorCode() + && Objects.equals(errorMessage, that.getErrorMessage()) + && Objects.equals(flagMetadata, that.getFlagMetadata()); + } + + @Override + public int hashCode() { + return Objects.hash(flagKey, value, variant, reason, errorCode, errorMessage, flagMetadata); + } + + @Override + public String toString() { + return "FlagEvaluationDetails{" + "flagKey='" + + flagKey + '\'' + ", value=" + + value + ", variant='" + + variant + '\'' + ", reason='" + + reason + '\'' + ", errorCode=" + + errorCode + ", errorMessage='" + + errorMessage + '\'' + ", flagMetadata=" + + flagMetadata + '}'; + } +} diff --git c/openfeature-api/src/main/java/dev/openfeature/api/DefaultProviderEvaluation.java i/openfeature-api/src/main/java/dev/openfeature/api/DefaultProviderEvaluation.java new file mode 100644 index 0000000..93e6169 --- /dev/null +++ i/openfeature-api/src/main/java/dev/openfeature/api/DefaultProviderEvaluation.java @@ -0,0 +1,101 @@ +package dev.openfeature.api; + +import java.util.Objects; + +/** + * Contains information about how the a flag was evaluated, including the resolved value. + * + * @param <T> the type of the flag being evaluated. + */ +class DefaultProviderEvaluation<T> implements ProviderEvaluation<T> { + private final T value; + private final String variant; + private final String reason; + private final ErrorCode errorCode; + private final String errorMessage; + private final Metadata flagMetadata; + + /** + * Private constructor for builder pattern only. + */ + DefaultProviderEvaluation() { + this(null, null, null, null, null, null); + } + + /** + * Private constructor for immutable ProviderEvaluation. + * + * @param value the resolved value + * @param variant the variant identifier + * @param reason the reason for the evaluation result + * @param errorCode the error code if applicable + * @param errorMessage the error message if applicable + * @param flagMetadata metadata associated with the flag + */ + DefaultProviderEvaluation( + T value, String variant, String reason, ErrorCode errorCode, String errorMessage, Metadata flagMetadata) { + this.value = value; + this.variant = variant; + this.reason = reason; + this.errorCode = errorCode; + this.errorMessage = errorMessage; + this.flagMetadata = flagMetadata != null ? flagMetadata : Metadata.EMPTY; + } + + public T getValue() { + return value; + } + + public String getVariant() { + return variant; + } + + public String getReason() { + return reason; + } + + public ErrorCode getErrorCode() { + return errorCode; + } + + public String getErrorMessage() { + return errorMessage; + } + + public Metadata getFlagMetadata() { + return flagMetadata; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + ProviderEvaluation<?> that = (ProviderEvaluation<?>) obj; + return Objects.equals(value, that.getValue()) + && Objects.equals(variant, that.getVariant()) + && Objects.equals(reason, that.getReason()) + && errorCode == that.getErrorCode() + && Objects.equals(errorMessage, that.getErrorMessage()) + && Objects.equals(flagMetadata, that.getFlagMetadata()); + } + + @Override + public int hashCode() { + return Objects.hash(value, variant, reason, errorCode, errorMessage, flagMetadata); + } + + @Override + public String toString() { + return "ProviderEvaluation{" + "value=" + + value + ", variant='" + + variant + '\'' + ", reason='" + + reason + '\'' + ", errorCode=" + + errorCode + ", errorMessage='" + + errorMessage + '\'' + ", flagMetadata=" + + flagMetadata + '}'; + } +} diff --git c/openfeature-api/src/main/java/dev/openfeature/api/EvaluationContext.java i/openfeature-api/src/main/java/dev/openfeature/api/EvaluationContext.java index 39ca965..86c1570 100644 --- c/openfeature-api/src/main/java/dev/openfeature/api/EvaluationContext.java +++ i/openfeature-api/src/main/java/dev/openfeature/api/EvaluationContext.java @@ -18,6 +18,22 @@ public interface EvaluationContext extends Structure { */ EvaluationContext EMPTY = new ImmutableContext(); + static EvaluationContext immutableOf(Map<String, Value> attributes) { + return new ImmutableContext(attributes); + } + + static EvaluationContext immutableOf(String targetingKey, Map<String, Value> attributes) { + return new ImmutableContext(targetingKey, attributes); + } + + static ImmutableContextBuilder immutableBuilder() { + return new ImmutableContext.Builder(); + } + + static ImmutableContextBuilder immutableBuilder(EvaluationContext original) { + return new ImmutableContext.Builder().attributes(original.asMap()).targetingKey(original.getTargetingKey()); + } + String getTargetingKey(); /** diff --git c/openfeature-api/src/main/java/dev/openfeature/api/EvaluationEvent.java i/openfeature-api/src/main/java/dev/openfeature/api/EvaluationEvent.java index f915a59..f8d90f9 100644 --- c/openfeature-api/src/main/java/dev/openfeature/api/EvaluationEvent.java +++ i/openfeature-api/src/main/java/dev/openfeature/api/EvaluationEvent.java @@ -1,94 +1,13 @@ package dev.openfeature.api; -import java.util.HashMap; import java.util.Map; -import java.util.Objects; /** * Represents an evaluation event. * This class is immutable and thread-safe. */ -public class EvaluationEvent { +public interface EvaluationEvent { + String getName(); - private final String name; - private final Map<String, Object> attributes; - - /** - * Private constructor - use builder() to create instances. - */ - private EvaluationEvent(String name, Map<String, Object> attributes) { - this.name = name; - this.attributes = attributes != null ? new HashMap<>(attributes) : new HashMap<>(); - } - - /** - * Gets the name of the evaluation event. - * - * @return the event name - */ - public String getName() { - return name; - } - - /** - * Gets a copy of the event attributes. - * - * @return a new map containing the event attributes - */ - public Map<String, Object> getAttributes() { - return new HashMap<>(attributes); - } - - public static Builder builder() { - return new Builder(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - EvaluationEvent that = (EvaluationEvent) obj; - return Objects.equals(name, that.name) && Objects.equals(attributes, that.attributes); - } - - @Override - public int hashCode() { - return Objects.hash(name, attributes); - } - - @Override - public String toString() { - return "EvaluationEvent{" + "name='" + name + '\'' + ", attributes=" + attributes + '}'; - } - - /** - * Builder class for creating instances of EvaluationEvent. - */ - public static class Builder { - private String name; - private Map<String, Object> attributes = new HashMap<>(); - - public Builder name(String name) { - this.name = name; - return this; - } - - public Builder attributes(Map<String, Object> attributes) { - this.attributes = attributes != null ? new HashMap<>(attributes) : new HashMap<>(); - return this; - } - - public Builder attribute(String key, Object value) { - this.attributes.put(key, value); - return this; - } - - public EvaluationEvent build() { - return new EvaluationEvent(name, attributes); - } - } + Map<String, Object> getAttributes(); } diff --git c/openfeature-api/src/main/java/dev/openfeature/api/EventDetails.java i/openfeature-api/src/main/java/dev/openfeature/api/EventDetails.java index d40a480..4263d95 100644 --- c/openfeature-api/src/main/java/dev/openfeature/api/EventDetails.java +++ i/openfeature-api/src/main/java/dev/openfeature/api/EventDetails.java @@ -62,7 +62,7 @@ public class EventDetails implements EventDetailsInterface { } @Override - public ImmutableMetadata getEventMetadata() { + public Metadata getEventMetadata() { return providerEventDetails.getEventMetadata(); } @@ -180,7 +180,7 @@ public class EventDetails implements EventDetailsInterface { * @param eventMetadata metadata associated with the event * @return this builder */ - public Builder eventMetadata(ImmutableMetadata eventMetadata) { + public Builder eventMetadata(Metadata eventMetadata) { ensureProviderEventDetailsBuilder(); this.providerEventDetails = ProviderEventDetails.builder() .flagsChanged(getProviderEventDetailsOrEmpty().getFlagsChanged()) diff --git c/openfeature-api/src/main/java/dev/openfeature/api/EventDetailsInterface.java i/openfeature-api/src/main/java/dev/openfeature/api/EventDetailsInterface.java index 9663e1b..c94f54c 100644 --- c/openfeature-api/src/main/java/dev/openfeature/api/EventDetailsInterface.java +++ i/openfeature-api/src/main/java/dev/openfeature/api/EventDetailsInterface.java @@ -29,7 +29,7 @@ public interface EventDetailsInterface { * * @return event metadata, or null if none */ - ImmutableMetadata getEventMetadata(); + Metadata getEventMetadata(); /** * Gets the error code associated with this event. diff --git c/openfeature-api/src/main/java/dev/openfeature/api/FeatureProvider.java i/openfeature-api/src/main/java/dev/openfeature/api/FeatureProvider.java index ab86447..500dfb2 100644 --- c/openfeature-api/src/main/java/dev/openfeature/api/FeatureProvider.java +++ i/openfeature-api/src/main/java/dev/openfeature/api/FeatureProvider.java @@ -9,7 +9,7 @@ import java.util.List; * should implement {@link EventProvider} */ public interface FeatureProvider { - Metadata getMetadata(); + ProviderMetadata getMetadata(); default List<Hook> getProviderHooks() { return new ArrayList<>(); diff --git c/openfeature-api/src/main/java/dev/openfeature/api/FlagEvaluationDetails.java i/openfeature-api/src/main/java/dev/openfeature/api/FlagEvaluationDetails.java index 16fec99..71b1114 100644 --- c/openfeature-api/src/main/java/dev/openfeature/api/FlagEvaluationDetails.java +++ i/openfeature-api/src/main/java/dev/openfeature/api/FlagEvaluationDetails.java @@ -1,178 +1,44 @@ package dev.openfeature.api; -import java.util.Objects; - /** * Contains information about how the provider resolved a flag, including the * resolved value. * * @param <T> the type of the flag being evaluated. */ -public class FlagEvaluationDetails<T> implements BaseEvaluation<T> { +public interface FlagEvaluationDetails<T> extends BaseEvaluation<T> { - private final String flagKey; - private final T value; - private final String variant; - private final String reason; - private final ErrorCode errorCode; - private final String errorMessage; - private final ImmutableMetadata flagMetadata; + FlagEvaluationDetails<?> EMPTY = new DefaultFlagEvaluationDetails<>(); - /** - * Private constructor for builder pattern only. - */ - private FlagEvaluationDetails() { - this(null, null, null, null, null, null, null); + String getFlagKey(); + + static <T> FlagEvaluationDetails<T> of(String key, T value, Reason reason) { + return of(key, value, null, reason); } - /** - * Private constructor for immutable FlagEvaluationDetails. - * - * @param flagKey the flag key - * @param value the resolved value - * @param variant the variant identifier - * @param reason the reason for the evaluation result - * @param errorCode the error code if applicable - * @param errorMessage the error message if applicable - * @param flagMetadata metadata associated with the flag - */ - private FlagEvaluationDetails( - String flagKey, + static <T> FlagEvaluationDetails<T> of(String key, T value, String variant, Reason reason) { + return of(key, value, variant, reason, null, null, null); + } + + static <T> FlagEvaluationDetails<T> of( + String key, + T value, + String variant, + Reason reason, + ErrorCode errorCode, + String errorMessage, + Metadata flagMetadata) { + return of(key, value, variant, reason.toString(), errorCode, errorMessage, flagMetadata); + } + + static <T> FlagEvaluationDetails<T> of( + String key, T value, String variant, String reason, ErrorCode errorCode, String errorMessage, - ImmutableMetadata flagMetadata) { - this.flagKey = flagKey; - this.value = value; - this.variant = variant; - this.reason = reason; - this.errorCode = errorCode; - this.errorMessage = errorMessage; - this.flagMetadata = flagMetadata != null - ? flagMetadata - : ImmutableMetadata.builder().build(); - } - - public String getFlagKey() { - return flagKey; - } - - public T getValue() { - return value; - } - - public String getVariant() { - return variant; - } - - public String getReason() { - return reason; - } - - public ErrorCode getErrorCode() { - return errorCode; - } - - public String getErrorMessage() { - return errorMessage; - } - - public ImmutableMetadata getFlagMetadata() { - return flagMetadata; - } - - public static <T> Builder<T> builder() { - return new Builder<>(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - FlagEvaluationDetails<?> that = (FlagEvaluationDetails<?>) obj; - return Objects.equals(flagKey, that.flagKey) - && Objects.equals(value, that.value) - && Objects.equals(variant, that.variant) - && Objects.equals(reason, that.reason) - && errorCode == that.errorCode - && Objects.equals(errorMessage, that.errorMessage) - && Objects.equals(flagMetadata, that.flagMetadata); - } - - @Override - public int hashCode() { - return Objects.hash(flagKey, value, variant, reason, errorCode, errorMessage, flagMetadata); - } - - @Override - public String toString() { - return "FlagEvaluationDetails{" + "flagKey='" - + flagKey + '\'' + ", value=" - + value + ", variant='" - + variant + '\'' + ", reason='" - + reason + '\'' + ", errorCode=" - + errorCode + ", errorMessage='" - + errorMessage + '\'' + ", flagMetadata=" - + flagMetadata + '}'; - } - - /** - * Builder class for creating instances of FlagEvaluationDetails. - * - * @param <T> the type of the flag value - */ - public static class Builder<T> { - private String flagKey; - private T value; - private String variant; - private String reason; - private ErrorCode errorCode; - private String errorMessage; - private ImmutableMetadata flagMetadata = ImmutableMetadata.builder().build(); - - public Builder<T> flagKey(String flagKey) { - this.flagKey = flagKey; - return this; - } - - public Builder<T> value(T value) { - this.value = value; - return this; - } - - public Builder<T> variant(String variant) { - this.variant = variant; - return this; - } - - public Builder<T> reason(String reason) { - this.reason = reason; - return this; - } - - public Builder<T> errorCode(ErrorCode errorCode) { - this.errorCode = errorCode; - return this; - } - - public Builder<T> errorMessage(String errorMessage) { - this.errorMessage = errorMessage; - return this; - } - - public Builder<T> flagMetadata(ImmutableMetadata flagMetadata) { - this.flagMetadata = flagMetadata; - return this; - } - - public FlagEvaluationDetails<T> build() { - return new FlagEvaluationDetails<>(flagKey, value, variant, reason, errorCode, errorMessage, flagMetadata); - } + Metadata flagMetadata) { + return new DefaultFlagEvaluationDetails<>(key, value, variant, reason, errorCode, errorMessage, flagMetadata); } } diff --git c/openfeature-api/src/main/java/dev/openfeature/api/HookContext.java i/openfeature-api/src/main/java/dev/openfeature/api/HookContext.java index 722569f..5ac4700 100644 --- c/openfeature-api/src/main/java/dev/openfeature/api/HookContext.java +++ i/openfeature-api/src/main/java/dev/openfeature/api/HookContext.java @@ -13,7 +13,7 @@ public final class HookContext<T> { private final T defaultValue; private final EvaluationContext ctx; private final ClientMetadata clientMetadata; - private final Metadata providerMetadata; + private final ProviderMetadata providerMetadata; private HookContext(Builder<T> builder) { this.flagKey = Objects.requireNonNull(builder.flagKey, "flagKey cannot be null"); @@ -44,7 +44,7 @@ public final class HookContext<T> { return clientMetadata; } - public Metadata getProviderMetadata() { + public ProviderMetadata getProviderMetadata() { return providerMetadata; } @@ -97,7 +97,7 @@ public final class HookContext<T> { private T defaultValue; private EvaluationContext ctx; private ClientMetadata clientMetadata; - private Metadata providerMetadata; + private ProviderMetadata providerMetadata; private Builder() {} @@ -126,7 +126,7 @@ public final class HookContext<T> { return this; } - public Builder<T> providerMetadata(Metadata providerMetadata) { + public Builder<T> providerMetadata(ProviderMetadata providerMetadata) { this.providerMetadata = providerMetadata; return this; } diff --git c/openfeature-api/src/main/java/dev/openfeature/api/ImmutableContext.java i/openfeature-api/src/main/java/dev/openfeature/api/ImmutableContext.java index a2ddf01..a676022 100644 --- c/openfeature-api/src/main/java/dev/openfeature/api/ImmutableContext.java +++ i/openfeature-api/src/main/java/dev/openfeature/api/ImmutableContext.java @@ -1,11 +1,9 @@ package dev.openfeature.api; -import dev.openfeature.api.internal.ExcludeFromGeneratedCoverageReport; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.function.Function; /** * The EvaluationContext is a container for arbitrary contextual data @@ -15,7 +13,7 @@ import java.util.function.Function; * not be modified after instantiation. */ @SuppressWarnings("PMD.BeanMembersShouldSerialize") -public final class ImmutableContext implements EvaluationContext { +final class ImmutableContext implements EvaluationContext { private final ImmutableStructure structure; @@ -23,7 +21,7 @@ public final class ImmutableContext implements EvaluationContext { * Create an immutable context with an empty targeting_key and attributes * provided. */ - public ImmutableContext() { + ImmutableContext() { this(new HashMap<>()); } @@ -32,7 +30,7 @@ public final class ImmutableContext implements EvaluationContext { * * @param targetingKey targeting key */ - public ImmutableContext(String targetingKey) { + ImmutableContext(String targetingKey) { this(targetingKey, new HashMap<>()); } @@ -41,7 +39,7 @@ public final class ImmutableContext implements EvaluationContext { * * @param attributes evaluation context attributes */ - public ImmutableContext(Map<String, Value> attributes) { + ImmutableContext(Map<String, Value> attributes) { this(null, attributes); } @@ -51,7 +49,7 @@ public final class ImmutableContext implements EvaluationContext { * @param targetingKey targeting key * @param attributes evaluation context attributes */ - public ImmutableContext(String targetingKey, Map<String, Value> attributes) { + ImmutableContext(String targetingKey, Map<String, Value> attributes) { if (targetingKey != null && !targetingKey.trim().isEmpty()) { this.structure = new ImmutableStructure(targetingKey, attributes); } else { @@ -142,32 +140,23 @@ public final class ImmutableContext implements EvaluationContext { return "ImmutableContext{" + "structure=" + structure + '}'; } - /** - * Returns a builder for creating ImmutableContext instances. - * - * @return a builder for ImmutableContext - */ - public static Builder builder() { - return new Builder(); - } - /** * Returns a builder initialized with the current state of this object. * * @return a builder for ImmutableContext */ - public Builder toBuilder() { - return builder().targetingKey(this.getTargetingKey()).attributes(this.structure.asMap()); + public ImmutableContextBuilder toBuilder() { + return new Builder().targetingKey(this.getTargetingKey()).attributes(this.structure.asMap()); } /** * Builder class for creating instances of ImmutableContext. */ - public static class Builder { + static class Builder implements ImmutableContextBuilder { private String targetingKey; private final Map<String, Value> attributes; - private Builder() { + Builder() { this.attributes = new HashMap<>(); } @@ -177,7 +166,8 @@ public final class ImmutableContext implements EvaluationContext { * @param targetingKey the targeting key * @return this builder */ - public Builder targetingKey(String targetingKey) { + @Override + public ImmutableContextBuilder targetingKey(String targetingKey) { this.targetingKey = targetingKey; return this; } @@ -188,7 +178,8 @@ public final class ImmutableContext implements EvaluationContext { * @param attributes map of attributes * @return this builder */ - public Builder attributes(Map<String, Value> attributes) { + @Override + public ImmutableContextBuilder attributes(Map<String, Value> attributes) { if (attributes != null) { this.attributes.clear(); this.attributes.putAll(attributes); @@ -203,7 +194,8 @@ public final class ImmutableContext implements EvaluationContext { * @param value attribute value * @return this builder */ - public Builder add(final String key, final String value) { + @Override + public ImmutableContextBuilder add(final String key, final String value) { attributes.put(key, Value.objectToValue(value)); return this; } @@ -215,7 +207,8 @@ public final class ImmutableContext implements EvaluationContext { * @param value attribute value * @return this builder */ - public Builder add(final String key, final Integer value) { + @Override + public ImmutableContextBuilder add(final String key, final Integer value) { attributes.put(key, Value.objectToValue(value)); return this; } @@ -227,7 +220,8 @@ public final class ImmutableContext implements EvaluationContext { * @param value attribute value * @return this builder */ - public Builder add(final String key, final Long value) { + @Override + public ImmutableContextBuilder add(final String key, final Long value) { attributes.put(key, Value.objectToValue(value)); return this; } @@ -239,7 +233,8 @@ public final class ImmutableContext implements EvaluationContext { * @param value attribute value * @return this builder */ - public Builder add(final String key, final Float value) { + @Override + public ImmutableContextBuilder add(final String key, final Float value) { attributes.put(key, Value.objectToValue(value)); return this; } @@ -251,7 +246,8 @@ public final class ImmutableContext implements EvaluationContext { * @param value attribute value * @return this builder */ - public Builder add(final String key, final Double value) { + @Override + public ImmutableContextBuilder add(final String key, final Double value) { attributes.put(key, Value.objectToValue(value)); return this; } @@ -263,7 +259,8 @@ public final class ImmutableContext implements EvaluationContext { * @param value attribute value * @return this builder */ - public Builder add(final String key, final Boolean value) { + @Override + public ImmutableContextBuilder add(final String key, final Boolean value) { attributes.put(key, Value.objectToValue(value)); return this; } @@ -275,7 +272,8 @@ public final class ImmutableContext implements EvaluationContext { * @param value attribute value * @return this builder */ - public Builder add(final String key, final Structure value) { + @Override + public ImmutableContextBuilder add(final String key, final Structure value) { attributes.put(key, Value.objectToValue(value)); return this; } @@ -287,7 +285,8 @@ public final class ImmutableContext implements EvaluationContext { * @param value attribute value * @return this builder */ - public Builder add(final String key, final Value value) { + @Override + public ImmutableContextBuilder add(final String key, final Value value) { attributes.put(key, value); return this; } @@ -297,19 +296,9 @@ public final class ImmutableContext implements EvaluationContext { * * @return a new ImmutableContext instance */ + @Override public ImmutableContext build() { return new ImmutableContext(targetingKey, new HashMap<>(attributes)); } } - - @SuppressWarnings("all") - private static class DelegateExclusions { - @ExcludeFromGeneratedCoverageReport - public <T extends Structure> Map<String, Value> merge( - Function<Map<String, Value>, Structure> newStructure, - Map<String, Value> base, - Map<String, Value> overriding) { - return null; - } - } } diff --git c/openfeature-api/src/main/java/dev/openfeature/api/ImmutableContextBuilder.java i/openfeature-api/src/main/java/dev/openfeature/api/ImmutableContextBuilder.java new file mode 100644 index 0000000..89744c5 --- /dev/null +++ i/openfeature-api/src/main/java/dev/openfeature/api/ImmutableContextBuilder.java @@ -0,0 +1,30 @@ +package dev.openfeature.api; + +import java.util.Map; + +/** + * Builder class for creating instances of ImmutableContext. + */ +public interface ImmutableContextBuilder { + ImmutableContextBuilder targetingKey(String targetingKey); + + ImmutableContextBuilder attributes(Map<String, Value> attributes); + + ImmutableContextBuilder add(String key, String value); + + ImmutableContextBuilder add(String key, Integer value); + + ImmutableContextBuilder add(String key, Long value); + + ImmutableContextBuilder add(String key, Float value); + + ImmutableContextBuilder add(String key, Double value); + + ImmutableContextBuilder add(String key, Boolean value); + + ImmutableContextBuilder add(String key, Structure value); + + ImmutableContextBuilder add(String key, Value value); + + EvaluationContext build(); +} diff --git c/openfeature-api/src/main/java/dev/openfeature/api/ImmutableMetadata.java i/openfeature-api/src/main/java/dev/openfeature/api/ImmutableMetadata.java index 6536033..49d2a6f 100644 --- c/openfeature-api/src/main/java/dev/openfeature/api/ImmutableMetadata.java +++ i/openfeature-api/src/main/java/dev/openfeature/api/ImmutableMetadata.java @@ -12,14 +12,16 @@ import org.slf4j.LoggerFactory; * Immutable Flag Metadata representation. Implementation is backed by a {@link Map} and immutability is provided * through builder and accessors. */ -public class ImmutableMetadata extends AbstractStructure { +final class ImmutableMetadata extends AbstractStructure implements Metadata { private static final Logger log = LoggerFactory.getLogger(ImmutableMetadata.class); - private ImmutableMetadata(Map<String, Value> attributes) { + ImmutableMetadata(Map<String, Value> attributes) { super(attributes); } + ImmutableMetadata() {} + @Override public Set<String> keySet() { return attributes.keySet(); @@ -33,6 +35,7 @@ public class ImmutableMetadata extends AbstractStructure { /** * Generic value retrieval for the given key. */ + @Override public <T> T getValue(final String key, final Class<T> type) { Value value = getValue(key); if (value == null) { @@ -60,6 +63,7 @@ public class ImmutableMetadata extends AbstractStructure { * * @param key flag metadata key to retrieve */ + @Override public String getString(final String key) { Value value = getValue(key); return value != null && value.isString() ? value.asString() : null; @@ -71,6 +75,7 @@ public class ImmutableMetadata extends AbstractStructure { * * @param key flag metadata key to retrieve */ + @Override public Integer getInteger(final String key) { Value value = getValue(key); if (value != null && value.isNumber()) { @@ -88,6 +93,7 @@ public class ImmutableMetadata extends AbstractStructure { * * @param key flag metadata key to retrieve */ + @Override public Long getLong(final String key) { Value value = getValue(key); if (value != null && value.isNumber()) { @@ -105,6 +111,7 @@ public class ImmutableMetadata extends AbstractStructure { * * @param key flag metadata key to retrieve */ + @Override public Float getFloat(final String key) { Value value = getValue(key); if (value != null && value.isNumber()) { @@ -122,6 +129,7 @@ public class ImmutableMetadata extends AbstractStructure { * * @param key flag metadata key to retrieve */ + @Override public Double getDouble(final String key) { Value value = getValue(key); if (value != null && value.isNumber()) { @@ -139,6 +147,7 @@ public class ImmutableMetadata extends AbstractStructure { * * @param key flag metadata key to retrieve */ + @Override public Boolean getBoolean(final String key) { Value value = getValue(key); return value != null && value.isBoolean() ? value.asBoolean() : null; @@ -148,10 +157,12 @@ public class ImmutableMetadata extends AbstractStructure { * Returns an unmodifiable map of metadata as primitive objects. * This provides backward compatibility for the original ImmutableMetadata API. */ + @Override public Map<String, Object> asUnmodifiableObjectMap() { return Collections.unmodifiableMap(asObjectMap()); } + @Override public boolean isNotEmpty() { return !isEmpty(); } @@ -176,19 +187,12 @@ public class ImmutableMetadata extends AbstractStructure { } /** - * Obtain a builder for {@link ImmutableMetadata}. + * Immutable builder for {@link Metadata}. */ - public static Builder builder() { - return new Builder(); - } - - /** - * Immutable builder for {@link ImmutableMetadata}. - */ - public static class Builder { + public static class Builder implements ImmutableMetadataBuilder { private final Map<String, Value> attributes; - private Builder() { + Builder() { attributes = new HashMap<>(); } @@ -198,7 +202,8 @@ public class ImmutableMetadata extends AbstractStructure { * @param key flag metadata key to add * @param value flag metadata value to add */ - public Builder addString(final String key, final String value) { + @Override + public ImmutableMetadataBuilder add(final String key, final String value) { attributes.put(key, Value.objectToValue(value)); return this; } @@ -209,7 +214,8 @@ public class ImmutableMetadata extends AbstractStructure { * @param key flag metadata key to add * @param value flag metadata value to add */ - public Builder addInteger(final String key, final Integer value) { + @Override + public ImmutableMetadataBuilder add(final String key, final Integer value) { attributes.put(key, Value.objectToValue(value)); return this; } @@ -220,7 +226,8 @@ public class ImmutableMetadata extends AbstractStructure { * @param key flag metadata key to add * @param value flag metadata value to add */ - public Builder addLong(final String key, final Long value) { + @Override + public ImmutableMetadataBuilder add(final String key, final Long value) { attributes.put(key, Value.objectToValue(value)); return this; } @@ -231,7 +238,8 @@ public class ImmutableMetadata extends AbstractStructure { * @param key flag metadata key to add * @param value flag metadata value to add */ - public Builder addFloat(final String key, final Float value) { + @Override + public ImmutableMetadataBuilder add(final String key, final Float value) { attributes.put(key, Value.objectToValue(value)); return this; } @@ -242,7 +250,8 @@ public class ImmutableMetadata extends AbstractStructure { * @param key flag metadata key to add * @param value flag metadata value to add */ - public Builder addDouble(final String key, final Double value) { + @Override + public ImmutableMetadataBuilder add(final String key, final Double value) { attributes.put(key, Value.objectToValue(value)); return this; } @@ -253,15 +262,17 @@ public class ImmutableMetadata extends AbstractStructure { * @param key flag metadata key to add * @param value flag metadata value to add */ - public Builder addBoolean(final String key, final Boolean value) { + @Override + public ImmutableMetadataBuilder add(final String key, final Boolean value) { attributes.put(key, Value.objectToValue(value)); return this; } /** - * Retrieve {@link ImmutableMetadata} with provided key,value pairs. + * Retrieve {@link Metadata} with provided key,value pairs. */ - public ImmutableMetadata build() { + @Override + public Metadata build() { return new ImmutableMetadata(new HashMap<>(this.attributes)); } } diff --git c/openfeature-api/src/main/java/dev/openfeature/api/ImmutableMetadataBuilder.java i/openfeature-api/src/main/java/dev/openfeature/api/ImmutableMetadataBuilder.java new file mode 100644 index 0000000..81909ba --- /dev/null +++ i/openfeature-api/src/main/java/dev/openfeature/api/ImmutableMetadataBuilder.java @@ -0,0 +1,20 @@ +package dev.openfeature.api; + +/** + * Immutable builder for {@link Metadata}. + */ +public interface ImmutableMetadataBuilder { + ImmutableMetadataBuilder add(String key, String value); + + ImmutableMetadataBuilder add(String key, Integer value); + + ImmutableMetadataBuilder add(String key, Long value); + + ImmutableMetadataBuilder add(String key, Float value); + + ImmutableMetadataBuilder add(String key, Double value); + + ImmutableMetadataBuilder add(String key, Boolean value); + + Metadata build(); +} diff --git c/openfeature-api/src/main/java/dev/openfeature/api/Metadata.java i/openfeature-api/src/main/java/dev/openfeature/api/Metadata.java index c665f0e..bbaa527 100644 --- c/openfeature-api/src/main/java/dev/openfeature/api/Metadata.java +++ i/openfeature-api/src/main/java/dev/openfeature/api/Metadata.java @@ -1,8 +1,43 @@ package dev.openfeature.api; +import java.util.Map; +import java.util.Set; + /** - * Holds identifying information about a given entity. + * Flag Metadata representation. */ -public interface Metadata { - String getName(); +public interface Metadata extends Structure { + + Metadata EMPTY = new ImmutableMetadata(); + + static ImmutableMetadataBuilder immutableBuilder() { + return new ImmutableMetadata.Builder(); + } + + @Override + Set<String> keySet(); + + @Override + Value getValue(String key); + + <T> T getValue(String key, Class<T> type); + + @Override + Map<String, Value> asMap(); + + String getString(String key); + + Integer getInteger(String key); + + Long getLong(String key); + + Float getFloat(String key); + + Double getDouble(String key); + + Boolean getBoolean(String key); + + Map<String, Object> asUnmodifiableObjectMap(); + + boolean isNotEmpty(); } diff --git c/openfeature-api/src/main/java/dev/openfeature/api/MutableContext.java i/openfeature-api/src/main/java/dev/openfeature/api/MutableContext.java index b6e178b..767ef9a 100644 --- c/openfeature-api/src/main/java/dev/openfeature/api/MutableContext.java +++ i/openfeature-api/src/main/java/dev/openfeature/api/MutableContext.java @@ -1,13 +1,11 @@ package dev.openfeature.api; -import dev.openfeature.api.internal.ExcludeFromGeneratedCoverageReport; import java.time.Instant; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.function.Function; /** * The EvaluationContext is a container for arbitrary contextual data @@ -173,52 +171,4 @@ public class MutableContext implements EvaluationContext { public String toString() { return "MutableContext{" + "structure=" + structure + '}'; } - - /** - * Hidden class to tell Lombok not to copy these methods over via delegation. - */ - @SuppressWarnings("all") - private static class DelegateExclusions { - - @ExcludeFromGeneratedCoverageReport - public <T extends Structure> Map<String, Value> merge( - Function<Map<String, Value>, Structure> newStructure, - Map<String, Value> base, - Map<String, Value> overriding) { - - return null; - } - - public MutableStructure add(String ignoredKey, Boolean ignoredValue) { - return null; - } - - public MutableStructure add(String ignoredKey, Double ignoredValue) { - return null; - } - - public MutableStructure add(String ignoredKey, String ignoredValue) { - return null; - } - - public MutableStructure add(String ignoredKey, Value ignoredValue) { - return null; - } - - public MutableStructure add(String ignoredKey, Integer ignoredValue) { - return null; - } - - public MutableStructure add(String ignoredKey, List<Value> ignoredValue) { - return null; - } - - public MutableStructure add(String ignoredKey, Structure ignoredValue) { - return null; - } - - public MutableStructure add(String ignoredKey, Instant ignoredValue) { - return null; - } - } } diff --git c/openfeature-api/src/main/java/dev/openfeature/api/OpenFeatureCore.java i/openfeature-api/src/main/java/dev/openfeature/api/OpenFeatureCore.java index 22254e8..cb72e12 100644 --- c/openfeature-api/src/main/java/dev/openfeature/api/OpenFeatureCore.java +++ i/openfeature-api/src/main/java/dev/openfeature/api/OpenFeatureCore.java @@ -98,7 +98,7 @@ public interface OpenFeatureCore { * * @return the provider metadata */ - Metadata getProviderMetadata(); + ProviderMetadata getProviderMetadata(); /** * Get metadata about a registered provider using the client name. @@ -107,5 +107,5 @@ public interface OpenFeatureCore { * @param domain an identifier which logically binds clients with providers * @return the provider metadata */ - Metadata getProviderMetadata(String domain); + ProviderMetadata getProviderMetadata(String domain); } diff --git c/openfeature-api/src/main/java/dev/openfeature/api/ProviderEvaluation.java i/openfeature-api/src/main/java/dev/openfeature/api/ProviderEvaluation.java index 66d991c..8ae6d72 100644 --- c/openfeature-api/src/main/java/dev/openfeature/api/ProviderEvaluation.java +++ i/openfeature-api/src/main/java/dev/openfeature/api/ProviderEvaluation.java @@ -1,160 +1,22 @@ package dev.openfeature.api; -import java.util.Objects; - /** * Contains information about how the a flag was evaluated, including the resolved value. * * @param <T> the type of the flag being evaluated. */ -public class ProviderEvaluation<T> implements BaseEvaluation<T> { - private final T value; - private final String variant; - private final String reason; - private final ErrorCode errorCode; - private final String errorMessage; - private final ImmutableMetadata flagMetadata; +public interface ProviderEvaluation<T> extends BaseEvaluation<T> { - /** - * Private constructor for builder pattern only. - */ - private ProviderEvaluation() { - this(null, null, null, null, null, null); + static <T> ProviderEvaluation<T> of(T value, String variant, String reason, Metadata flagMetadata) { + return of(value, variant, reason, null, null, flagMetadata); } - /** - * Private constructor for immutable ProviderEvaluation. - * - * @param value the resolved value - * @param variant the variant identifier - * @param reason the reason for the evaluation result - * @param errorCode the error code if applicable - * @param errorMessage the error message if applicable - * @param flagMetadata metadata associated with the flag - */ - private ProviderEvaluation( - T value, - String variant, - String reason, - ErrorCode errorCode, - String errorMessage, - ImmutableMetadata flagMetadata) { - this.value = value; - this.variant = variant; - this.reason = reason; - this.errorCode = errorCode; - this.errorMessage = errorMessage; - this.flagMetadata = flagMetadata != null - ? flagMetadata - : ImmutableMetadata.builder().build(); + static <T> ProviderEvaluation<T> of( + T value, String variant, String reason, ErrorCode errorCode, String errorMessage, Metadata flagMetadata) { + return new DefaultProviderEvaluation<T>(value, variant, reason, errorCode, errorMessage, flagMetadata); } - public T getValue() { - return value; - } - - public String getVariant() { - return variant; - } - - public String getReason() { - return reason; - } - - public ErrorCode getErrorCode() { - return errorCode; - } - - public String getErrorMessage() { - return errorMessage; - } - - public ImmutableMetadata getFlagMetadata() { - return flagMetadata; - } - - public static <T> Builder<T> builder() { - return new Builder<>(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - ProviderEvaluation<?> that = (ProviderEvaluation<?>) obj; - return Objects.equals(value, that.value) - && Objects.equals(variant, that.variant) - && Objects.equals(reason, that.reason) - && errorCode == that.errorCode - && Objects.equals(errorMessage, that.errorMessage) - && Objects.equals(flagMetadata, that.flagMetadata); - } - - @Override - public int hashCode() { - return Objects.hash(value, variant, reason, errorCode, errorMessage, flagMetadata); - } - - @Override - public String toString() { - return "ProviderEvaluation{" + "value=" - + value + ", variant='" - + variant + '\'' + ", reason='" - + reason + '\'' + ", errorCode=" - + errorCode + ", errorMessage='" - + errorMessage + '\'' + ", flagMetadata=" - + flagMetadata + '}'; - } - - /** - * Builder class for creating instances of ProviderEvaluation. - * - * @param <T> the type of the evaluation value - */ - public static class Builder<T> { - private T value; - private String variant; - private String reason; - private ErrorCode errorCode; - private String errorMessage; - private ImmutableMetadata flagMetadata = ImmutableMetadata.builder().build(); - - public Builder<T> value(T value) { - this.value = value; - return this; - } - - public Builder<T> variant(String variant) { - this.variant = variant; - return this; - } - - public Builder<T> reason(String reason) { - this.reason = reason; - return this; - } - - public Builder<T> errorCode(ErrorCode errorCode) { - this.errorCode = errorCode; - return this; - } - - public Builder<T> errorMessage(String errorMessage) { - this.errorMessage = errorMessage; - return this; - } - - public Builder<T> flagMetadata(ImmutableMetadata flagMetadata) { - this.flagMetadata = flagMetadata; - return this; - } - - public ProviderEvaluation<T> build() { - return new ProviderEvaluation<>(value, variant, reason, errorCode, errorMessage, flagMetadata); - } + static <T> ProviderEvaluation<T> of(ErrorCode errorCode, String errorMessage) { + return of(null, null, Reason.ERROR.toString(), errorCode, errorMessage, null); } } diff --git c/openfeature-api/src/main/java/dev/openfeature/api/ProviderEventDetails.java i/openfeature-api/src/main/java/dev/openfeature/api/ProviderEventDetails.java index a20ffa5..2ffc219 100644 --- c/openfeature-api/src/main/java/dev/openfeature/api/ProviderEventDetails.java +++ i/openfeature-api/src/main/java/dev/openfeature/api/ProviderEventDetails.java @@ -11,7 +11,7 @@ import java.util.Objects; public class ProviderEventDetails implements EventDetailsInterface { private final List<String> flagsChanged; private final String message; - private final ImmutableMetadata eventMetadata; + private final Metadata eventMetadata; private final ErrorCode errorCode; /** @@ -33,7 +33,7 @@ public class ProviderEventDetails implements EventDetailsInterface { * @param errorCode error code (should be populated for PROVIDER_ERROR events) */ private ProviderEventDetails( - List<String> flagsChanged, String message, ImmutableMetadata eventMetadata, ErrorCode errorCode) { + List<String> flagsChanged, String message, Metadata eventMetadata, ErrorCode errorCode) { this.flagsChanged = flagsChanged != null ? List.copyOf(flagsChanged) : null; this.message = message; this.eventMetadata = eventMetadata; @@ -48,7 +48,7 @@ public class ProviderEventDetails implements EventDetailsInterface { return message; } - public ImmutableMetadata getEventMetadata() { + public Metadata getEventMetadata() { return eventMetadata; } @@ -108,7 +108,7 @@ public class ProviderEventDetails implements EventDetailsInterface { public static class Builder { private List<String> flagsChanged; private String message; - private ImmutableMetadata eventMetadata; + private Metadata eventMetadata; private ErrorCode errorCode; private Builder() {} @@ -123,7 +123,7 @@ public class ProviderEventDetails implements EventDetailsInterface { return this; } - public Builder eventMetadata(ImmutableMetadata eventMetadata) { + public Builder eventMetadata(Metadata eventMetadata) { this.eventMetadata = eventMetadata; return this; } diff --git c/openfeature-api/src/main/java/dev/openfeature/api/ProviderMetadata.java i/openfeature-api/src/main/java/dev/openfeature/api/ProviderMetadata.java new file mode 100644 index 0000000..be970f9 --- /dev/null +++ i/openfeature-api/src/main/java/dev/openfeature/api/ProviderMetadata.java @@ -0,0 +1,8 @@ +package dev.openfeature.api; + +/** + * Holds identifying information about a given entity. + */ +public interface ProviderMetadata { + String getName(); +} diff --git c/openfeature-api/src/main/java/dev/openfeature/api/Telemetry.java i/openfeature-api/src/main/java/dev/openfeature/api/Telemetry.java index 457010a..89a57d7 100644 --- c/openfeature-api/src/main/java/dev/openfeature/api/Telemetry.java +++ i/openfeature-api/src/main/java/dev/openfeature/api/Telemetry.java @@ -41,7 +41,7 @@ public class Telemetry { */ public static EvaluationEvent createEvaluationEvent( HookContext<?> hookContext, FlagEvaluationDetails<?> evaluationDetails) { - EvaluationEvent.Builder evaluationEventBuilder = EvaluationEvent.builder() + DefaultEvaluationEvent.Builder evaluationEventBuilder = DefaultEvaluationEvent.builder() .name(FLAG_EVALUATION_EVENT_NAME) .attribute(TELEMETRY_KEY, hookContext.getFlagKey()) .attribute(TELEMETRY_PROVIDER, hookContext.getProviderMetadata().getName()); diff --git c/openfeature-api/src/main/java/dev/openfeature/api/internal/noop/NoOpClient.java i/openfeature-api/src/main/java/dev/openfeature/api/internal/noop/NoOpClient.java index 040215e..08c29ec 100644 --- c/openfeature-api/src/main/java/dev/openfeature/api/internal/noop/NoOpClient.java +++ i/openfeature-api/src/main/java/dev/openfeature/api/internal/noop/NoOpClient.java @@ -58,11 +58,7 @@ public class NoOpClient implements Client { @Override public FlagEvaluationDetails<Boolean> getBooleanDetails(String key, Boolean defaultValue) { - return FlagEvaluationDetails.<Boolean>builder() - .flagKey(key) - .value(defaultValue) - .reason(Reason.DEFAULT.toString()) - .build(); + return FlagEvaluationDetails.of(key, defaultValue, Reason.DEFAULT); } @Override @@ -94,11 +90,7 @@ public class NoOpClient implements Client { @Override public FlagEvaluationDetails<String> getStringDetails(String key, String defaultValue) { - return FlagEvaluationDetails.<String>builder() - .flagKey(key) - .value(defaultValue) - .reason(Reason.DEFAULT.toString()) - .build(); + return FlagEvaluationDetails.of(key, defaultValue, Reason.DEFAULT); } @Override @@ -130,11 +122,7 @@ public class NoOpClient implements Client { @Override public FlagEvaluationDetails<Integer> getIntegerDetails(String key, Integer defaultValue) { - return FlagEvaluationDetails.<Integer>builder() - .flagKey(key) - .value(defaultValue) - .reason(Reason.DEFAULT.toString()) - .build(); + return FlagEvaluationDetails.of(key, defaultValue, Reason.DEFAULT); } @Override @@ -166,11 +154,7 @@ public class NoOpClient implements Client { @Override public FlagEvaluationDetails<Double> getDoubleDetails(String key, Double defaultValue) { - return FlagEvaluationDetails.<Double>builder() - .flagKey(key) - .value(defaultValue) - .reason(Reason.DEFAULT.toString()) - .build(); + return FlagEvaluationDetails.of(key, defaultValue, Reason.DEFAULT); } @Override @@ -202,11 +186,7 @@ public class NoOpClient implements Client { @Override public FlagEvaluationDetails<Value> getObjectDetails(String key, Value defaultValue) { - return FlagEvaluationDetails.<Value>builder() - .flagKey(key) - .value(defaultValue) - .reason(Reason.DEFAULT.toString()) - .build(); + return FlagEvaluationDetails.of(key, defaultValue, Reason.DEFAULT); } @Override diff --git c/openfeature-api/src/main/java/dev/openfeature/api/internal/noop/NoOpOpenFeatureAPI.java i/openfeature-api/src/main/java/dev/openfeature/api/internal/noop/NoOpOpenFeatureAPI.java index d2a4a4d..fbd07b3 100644 --- c/openfeature-api/src/main/java/dev/openfeature/api/internal/noop/NoOpOpenFeatureAPI.java +++ i/openfeature-api/src/main/java/dev/openfeature/api/internal/noop/NoOpOpenFeatureAPI.java @@ -5,9 +5,9 @@ import dev.openfeature.api.EvaluationContext; import dev.openfeature.api.EventDetails; import dev.openfeature.api.FeatureProvider; import dev.openfeature.api.Hook; -import dev.openfeature.api.Metadata; import dev.openfeature.api.OpenFeatureAPI; import dev.openfeature.api.ProviderEvent; +import dev.openfeature.api.ProviderMetadata; import dev.openfeature.api.TransactionContextPropagator; import dev.openfeature.api.exceptions.OpenFeatureError; import dev.openfeature.api.internal.ExcludeFromGeneratedCoverageReport; @@ -76,12 +76,12 @@ public class NoOpOpenFeatureAPI extends OpenFeatureAPI { } @Override - public Metadata getProviderMetadata() { + public ProviderMetadata getProviderMetadata() { return () -> "No-op Provider"; } @Override - public Metadata getProviderMetadata(String domain) { + public ProviderMetadata getProviderMetadata(String domain) { return getProviderMetadata(); } diff --git c/openfeature-api/src/main/java/dev/openfeature/api/internal/noop/NoOpProvider.java i/openfeature-api/src/main/java/dev/openfeature/api/internal/noop/NoOpProvider.java index a1fac57..a0c66a5 100644 --- c/openfeature-api/src/main/java/dev/openfeature/api/internal/noop/NoOpProvider.java +++ i/openfeature-api/src/main/java/dev/openfeature/api/internal/noop/NoOpProvider.java @@ -2,8 +2,8 @@ package dev.openfeature.api.internal.noop; import dev.openfeature.api.EvaluationContext; import dev.openfeature.api.FeatureProvider; -import dev.openfeature.api.Metadata; import dev.openfeature.api.ProviderEvaluation; +import dev.openfeature.api.ProviderMetadata; import dev.openfeature.api.ProviderState; import dev.openfeature.api.Reason; import dev.openfeature.api.Value; @@ -31,53 +31,33 @@ public class NoOpProvider implements FeatureProvider { } @Override - public Metadata getMetadata() { + public ProviderMetadata getMetadata() { return () -> name; } @Override public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) { - return ProviderEvaluation.<Boolean>builder() - .value(defaultValue) - .variant(PASSED_IN_DEFAULT) - .reason(Reason.DEFAULT.toString()) - .build(); + return ProviderEvaluation.of(defaultValue, PASSED_IN_DEFAULT, Reason.DEFAULT.toString(), null); } @Override public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) { - return ProviderEvaluation.<String>builder() - .value(defaultValue) - .variant(PASSED_IN_DEFAULT) - .reason(Reason.DEFAULT.toString()) - .build(); + return ProviderEvaluation.of(defaultValue, PASSED_IN_DEFAULT, Reason.DEFAULT.toString(), null); } @Override public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) { - return ProviderEvaluation.<Integer>builder() - .value(defaultValue) - .variant(PASSED_IN_DEFAULT) - .reason(Reason.DEFAULT.toString()) - .build(); + return ProviderEvaluation.of(defaultValue, PASSED_…
1 parent 2a3a4ad commit 422997a

File tree

78 files changed

+1245
-1392
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+1245
-1392
lines changed

.github/workflows/release.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ jobs:
2424
id: release
2525
with:
2626
token: ${{secrets.RELEASE_PLEASE_ACTION_TOKEN}}
27+
prerelease: ${{ github.ref == 'refs/heads/beta' }}
28+
prerelease-type: "beta"
2729
outputs:
2830
release_created: ${{ fromJSON(steps.release.outputs.paths_released)[0] != null }} # if we have a single release path, do the release
2931

.release-please-manifest.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
{".":"1.18.0"}
1+
{
2+
"./sdk": "2.0.0-beta",
3+
"./api": "0.0.0-beta"
4+
}

openfeature-api/src/main/java/dev/openfeature/api/BaseEvaluation.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,6 @@ public interface BaseEvaluation<T> {
4141
* @return {String}
4242
*/
4343
String getErrorMessage();
44+
45+
Metadata getFlagMetadata();
4446
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package dev.openfeature.api;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
import java.util.Objects;
6+
7+
/**
8+
* Represents an evaluation event.
9+
* This class is immutable and thread-safe.
10+
*/
11+
class DefaultEvaluationEvent implements EvaluationEvent {
12+
13+
private final String name;
14+
private final Map<String, Object> attributes;
15+
16+
/**
17+
* Private constructor - use builder() to create instances.
18+
*/
19+
private DefaultEvaluationEvent(String name, Map<String, Object> attributes) {
20+
this.name = name;
21+
this.attributes = attributes != null ? new HashMap<>(attributes) : new HashMap<>();
22+
}
23+
24+
/**
25+
* Gets the name of the evaluation event.
26+
*
27+
* @return the event name
28+
*/
29+
@Override
30+
public String getName() {
31+
return name;
32+
}
33+
34+
/**
35+
* Gets a copy of the event attributes.
36+
*
37+
* @return a new map containing the event attributes
38+
*/
39+
@Override
40+
public Map<String, Object> getAttributes() {
41+
return new HashMap<>(attributes);
42+
}
43+
44+
public static Builder builder() {
45+
return new Builder();
46+
}
47+
48+
@Override
49+
public boolean equals(Object obj) {
50+
if (this == obj) {
51+
return true;
52+
}
53+
if (obj == null || getClass() != obj.getClass()) {
54+
return false;
55+
}
56+
DefaultEvaluationEvent that = (DefaultEvaluationEvent) obj;
57+
return Objects.equals(name, that.name) && Objects.equals(attributes, that.attributes);
58+
}
59+
60+
@Override
61+
public int hashCode() {
62+
return Objects.hash(name, attributes);
63+
}
64+
65+
@Override
66+
public String toString() {
67+
return "EvaluationEvent{" + "name='" + name + '\'' + ", attributes=" + attributes + '}';
68+
}
69+
70+
/**
71+
* Builder class for creating instances of EvaluationEvent.
72+
*/
73+
public static class Builder {
74+
private String name;
75+
private Map<String, Object> attributes = new HashMap<>();
76+
77+
public Builder name(String name) {
78+
this.name = name;
79+
return this;
80+
}
81+
82+
public Builder attributes(Map<String, Object> attributes) {
83+
this.attributes = attributes != null ? new HashMap<>(attributes) : new HashMap<>();
84+
return this;
85+
}
86+
87+
public Builder attribute(String key, Object value) {
88+
this.attributes.put(key, value);
89+
return this;
90+
}
91+
92+
public EvaluationEvent build() {
93+
return new DefaultEvaluationEvent(name, attributes);
94+
}
95+
}
96+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package dev.openfeature.api;
2+
3+
import java.util.Objects;
4+
5+
/**
6+
* Contains information about how the provider resolved a flag, including the
7+
* resolved value.
8+
*
9+
* @param <T> the type of the flag being evaluated.
10+
*/
11+
class DefaultFlagEvaluationDetails<T> implements FlagEvaluationDetails<T> {
12+
13+
private final String flagKey;
14+
private final T value;
15+
private final String variant;
16+
private final String reason;
17+
private final ErrorCode errorCode;
18+
private final String errorMessage;
19+
private final Metadata flagMetadata;
20+
21+
/**
22+
* Private constructor for builder pattern only.
23+
*/
24+
DefaultFlagEvaluationDetails() {
25+
this(null, null, null, null, null, null, null);
26+
}
27+
28+
/**
29+
* Private constructor for immutable FlagEvaluationDetails.
30+
*
31+
* @param flagKey the flag key
32+
* @param value the resolved value
33+
* @param variant the variant identifier
34+
* @param reason the reason for the evaluation result
35+
* @param errorCode the error code if applicable
36+
* @param errorMessage the error message if applicable
37+
* @param flagMetadata metadata associated with the flag
38+
*/
39+
DefaultFlagEvaluationDetails(
40+
String flagKey,
41+
T value,
42+
String variant,
43+
String reason,
44+
ErrorCode errorCode,
45+
String errorMessage,
46+
Metadata flagMetadata) {
47+
this.flagKey = flagKey;
48+
this.value = value;
49+
this.variant = variant;
50+
this.reason = reason;
51+
this.errorCode = errorCode;
52+
this.errorMessage = errorMessage;
53+
this.flagMetadata = flagMetadata != null ? flagMetadata : Metadata.EMPTY;
54+
}
55+
56+
public String getFlagKey() {
57+
return flagKey;
58+
}
59+
60+
public T getValue() {
61+
return value;
62+
}
63+
64+
public String getVariant() {
65+
return variant;
66+
}
67+
68+
public String getReason() {
69+
return reason;
70+
}
71+
72+
public ErrorCode getErrorCode() {
73+
return errorCode;
74+
}
75+
76+
public String getErrorMessage() {
77+
return errorMessage;
78+
}
79+
80+
public Metadata getFlagMetadata() {
81+
return flagMetadata;
82+
}
83+
84+
@Override
85+
public boolean equals(Object obj) {
86+
if (this == obj) {
87+
return true;
88+
}
89+
if (obj == null || getClass() != obj.getClass()) {
90+
return false;
91+
}
92+
FlagEvaluationDetails<?> that = (FlagEvaluationDetails<?>) obj;
93+
return Objects.equals(flagKey, that.getFlagKey())
94+
&& Objects.equals(value, that.getValue())
95+
&& Objects.equals(variant, that.getVariant())
96+
&& Objects.equals(reason, that.getReason())
97+
&& errorCode == that.getErrorCode()
98+
&& Objects.equals(errorMessage, that.getErrorMessage())
99+
&& Objects.equals(flagMetadata, that.getFlagMetadata());
100+
}
101+
102+
@Override
103+
public int hashCode() {
104+
return Objects.hash(flagKey, value, variant, reason, errorCode, errorMessage, flagMetadata);
105+
}
106+
107+
@Override
108+
public String toString() {
109+
return "FlagEvaluationDetails{" + "flagKey='"
110+
+ flagKey + '\'' + ", value="
111+
+ value + ", variant='"
112+
+ variant + '\'' + ", reason='"
113+
+ reason + '\'' + ", errorCode="
114+
+ errorCode + ", errorMessage='"
115+
+ errorMessage + '\'' + ", flagMetadata="
116+
+ flagMetadata + '}';
117+
}
118+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package dev.openfeature.api;
2+
3+
import java.util.Objects;
4+
5+
/**
6+
* Contains information about how the a flag was evaluated, including the resolved value.
7+
*
8+
* @param <T> the type of the flag being evaluated.
9+
*/
10+
class DefaultProviderEvaluation<T> implements ProviderEvaluation<T> {
11+
private final T value;
12+
private final String variant;
13+
private final String reason;
14+
private final ErrorCode errorCode;
15+
private final String errorMessage;
16+
private final Metadata flagMetadata;
17+
18+
/**
19+
* Private constructor for builder pattern only.
20+
*/
21+
DefaultProviderEvaluation() {
22+
this(null, null, null, null, null, null);
23+
}
24+
25+
/**
26+
* Private constructor for immutable ProviderEvaluation.
27+
*
28+
* @param value the resolved value
29+
* @param variant the variant identifier
30+
* @param reason the reason for the evaluation result
31+
* @param errorCode the error code if applicable
32+
* @param errorMessage the error message if applicable
33+
* @param flagMetadata metadata associated with the flag
34+
*/
35+
DefaultProviderEvaluation(
36+
T value, String variant, String reason, ErrorCode errorCode, String errorMessage, Metadata flagMetadata) {
37+
this.value = value;
38+
this.variant = variant;
39+
this.reason = reason;
40+
this.errorCode = errorCode;
41+
this.errorMessage = errorMessage;
42+
this.flagMetadata = flagMetadata != null ? flagMetadata : Metadata.EMPTY;
43+
}
44+
45+
public T getValue() {
46+
return value;
47+
}
48+
49+
public String getVariant() {
50+
return variant;
51+
}
52+
53+
public String getReason() {
54+
return reason;
55+
}
56+
57+
public ErrorCode getErrorCode() {
58+
return errorCode;
59+
}
60+
61+
public String getErrorMessage() {
62+
return errorMessage;
63+
}
64+
65+
public Metadata getFlagMetadata() {
66+
return flagMetadata;
67+
}
68+
69+
@Override
70+
public boolean equals(Object obj) {
71+
if (this == obj) {
72+
return true;
73+
}
74+
if (obj == null || getClass() != obj.getClass()) {
75+
return false;
76+
}
77+
ProviderEvaluation<?> that = (ProviderEvaluation<?>) obj;
78+
return Objects.equals(value, that.getValue())
79+
&& Objects.equals(variant, that.getVariant())
80+
&& Objects.equals(reason, that.getReason())
81+
&& errorCode == that.getErrorCode()
82+
&& Objects.equals(errorMessage, that.getErrorMessage())
83+
&& Objects.equals(flagMetadata, that.getFlagMetadata());
84+
}
85+
86+
@Override
87+
public int hashCode() {
88+
return Objects.hash(value, variant, reason, errorCode, errorMessage, flagMetadata);
89+
}
90+
91+
@Override
92+
public String toString() {
93+
return "ProviderEvaluation{" + "value="
94+
+ value + ", variant='"
95+
+ variant + '\'' + ", reason='"
96+
+ reason + '\'' + ", errorCode="
97+
+ errorCode + ", errorMessage='"
98+
+ errorMessage + '\'' + ", flagMetadata="
99+
+ flagMetadata + '}';
100+
}
101+
}

openfeature-api/src/main/java/dev/openfeature/api/EvaluationContext.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,22 @@ public interface EvaluationContext extends Structure {
1818
*/
1919
EvaluationContext EMPTY = new ImmutableContext();
2020

21+
static EvaluationContext immutableOf(Map<String, Value> attributes) {
22+
return new ImmutableContext(attributes);
23+
}
24+
25+
static EvaluationContext immutableOf(String targetingKey, Map<String, Value> attributes) {
26+
return new ImmutableContext(targetingKey, attributes);
27+
}
28+
29+
static ImmutableContextBuilder immutableBuilder() {
30+
return new ImmutableContext.Builder();
31+
}
32+
33+
static ImmutableContextBuilder immutableBuilder(EvaluationContext original) {
34+
return new ImmutableContext.Builder().attributes(original.asMap()).targetingKey(original.getTargetingKey());
35+
}
36+
2137
String getTargetingKey();
2238

2339
/**

0 commit comments

Comments
 (0)