feat: add flag metadata (#459)
Signed-off-by: Kavindu Dodanduwa <kavindudodanduwa@gmail.com>
This commit is contained in:
parent
5f173ff860
commit
3ed40a3887
|
|
@ -7,23 +7,27 @@ import javax.annotation.Nullable;
|
|||
|
||||
/**
|
||||
* Contains information about how the evaluation happened, including any resolved values.
|
||||
*
|
||||
* @param <T> the type of the flag being evaluated.
|
||||
*/
|
||||
@Data @Builder
|
||||
@Data
|
||||
@Builder
|
||||
public class FlagEvaluationDetails<T> implements BaseEvaluation<T> {
|
||||
|
||||
private String flagKey;
|
||||
private T value;
|
||||
@Nullable private String variant;
|
||||
@Nullable private String reason;
|
||||
private ErrorCode errorCode;
|
||||
@Nullable private String errorMessage;
|
||||
@Builder.Default private FlagMetadata flagMetadata = FlagMetadata.builder().build();
|
||||
|
||||
/**
|
||||
* Generate detail payload from the provider response.
|
||||
*
|
||||
* @param providerEval provider response
|
||||
* @param flagKey key for the flag being evaluated
|
||||
* @param <T> type of flag being returned
|
||||
* @param flagKey key for the flag being evaluated
|
||||
* @param <T> type of flag being returned
|
||||
* @return detail payload
|
||||
*/
|
||||
public static <T> FlagEvaluationDetails<T> from(ProviderEvaluation<T> providerEval, String flagKey) {
|
||||
|
|
@ -33,6 +37,7 @@ public class FlagEvaluationDetails<T> implements BaseEvaluation<T> {
|
|||
.variant(providerEval.getVariant())
|
||||
.reason(providerEval.getReason())
|
||||
.errorCode(providerEval.getErrorCode())
|
||||
.flagMetadata(providerEval.getFlagMetadata())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,188 @@
|
|||
package dev.openfeature.sdk;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Immutable Flag Metadata representation. Implementation is backed by a {@link Map} and immutability is provided
|
||||
* through builder and accessors.
|
||||
*/
|
||||
@Slf4j
|
||||
public class FlagMetadata {
|
||||
private final Map<String, Object> metadata;
|
||||
|
||||
private FlagMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link String} value for the given key. A {@code null} value is returned if the key does not exist
|
||||
* or if the value is of a different type.
|
||||
*
|
||||
* @param key flag metadata key to retrieve
|
||||
*/
|
||||
public String getString(final String key) {
|
||||
return getValue(key, String.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link Integer} value for the given key. A {@code null} value is returned if the key does not exist
|
||||
* or if the value is of a different type.
|
||||
*
|
||||
* @param key flag metadata key to retrieve
|
||||
*/
|
||||
public Integer getInteger(final String key) {
|
||||
return getValue(key, Integer.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link Long} value for the given key. A {@code null} value is returned if the key does not exist
|
||||
* or if the value is of a different type.
|
||||
*
|
||||
* @param key flag metadata key to retrieve
|
||||
*/
|
||||
public Long getLong(final String key) {
|
||||
return getValue(key, Long.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link Float} value for the given key. A {@code null} value is returned if the key does not exist
|
||||
* or if the value is of a different type.
|
||||
*
|
||||
* @param key flag metadata key to retrieve
|
||||
*/
|
||||
public Float getFloat(final String key) {
|
||||
return getValue(key, Float.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link Double} value for the given key. A {@code null} value is returned if the key does not exist
|
||||
* or if the value is of a different type.
|
||||
*
|
||||
* @param key flag metadata key to retrieve
|
||||
*/
|
||||
public Double getDouble(final String key) {
|
||||
return getValue(key, Double.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link Boolean} value for the given key. A {@code null} value is returned if the key does not exist
|
||||
* or if the value is of a different type.
|
||||
*
|
||||
* @param key flag metadata key to retrieve
|
||||
*/
|
||||
public Boolean getBoolean(final String key) {
|
||||
return getValue(key, Boolean.class);
|
||||
}
|
||||
|
||||
private <T> T getValue(final String key, final Class<T> type) {
|
||||
final Object o = metadata.get(key);
|
||||
|
||||
if (o == null) {
|
||||
log.debug("Metadata key " + key + "does not exist");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return type.cast(o);
|
||||
} catch (ClassCastException e) {
|
||||
log.debug("Error retrieving value for key " + key, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtain a builder for {@link FlagMetadata}.
|
||||
*/
|
||||
public static FlagMetadataBuilder builder() {
|
||||
return new FlagMetadataBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Immutable builder for {@link FlagMetadata}.
|
||||
*/
|
||||
public static class FlagMetadataBuilder {
|
||||
private final Map<String, Object> metadata;
|
||||
|
||||
private FlagMetadataBuilder() {
|
||||
metadata = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add String value to the metadata.
|
||||
*
|
||||
* @param key flag metadata key to add
|
||||
* @param value flag metadata value to add
|
||||
*/
|
||||
public FlagMetadataBuilder addString(final String key, final String value) {
|
||||
metadata.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Integer value to the metadata.
|
||||
*
|
||||
* @param key flag metadata key to add
|
||||
* @param value flag metadata value to add
|
||||
*/
|
||||
public FlagMetadataBuilder addInteger(final String key, final Integer value) {
|
||||
metadata.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Long value to the metadata.
|
||||
*
|
||||
* @param key flag metadata key to add
|
||||
* @param value flag metadata value to add
|
||||
*/
|
||||
public FlagMetadataBuilder addLong(final String key, final Long value) {
|
||||
metadata.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Float value to the metadata.
|
||||
*
|
||||
* @param key flag metadata key to add
|
||||
* @param value flag metadata value to add
|
||||
*/
|
||||
public FlagMetadataBuilder addFloat(final String key, final Float value) {
|
||||
metadata.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Double value to the metadata.
|
||||
*
|
||||
* @param key flag metadata key to add
|
||||
* @param value flag metadata value to add
|
||||
*/
|
||||
public FlagMetadataBuilder addDouble(final String key, final Double value) {
|
||||
metadata.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Boolean value to the metadata.
|
||||
*
|
||||
* @param key flag metadata key to add
|
||||
* @param value flag metadata value to add
|
||||
*/
|
||||
public FlagMetadataBuilder addBoolean(final String key, final Boolean value) {
|
||||
metadata.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve {@link FlagMetadata} with provided key,value pairs.
|
||||
*/
|
||||
public FlagMetadata build() {
|
||||
return new FlagMetadata(this.metadata);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -13,4 +13,6 @@ public class ProviderEvaluation<T> implements BaseEvaluation<T> {
|
|||
@Nullable private String reason;
|
||||
ErrorCode errorCode;
|
||||
@Nullable private String errorMessage;
|
||||
@Builder.Default
|
||||
private FlagMetadata flagMetadata = FlagMetadata.builder().build();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
package dev.openfeature.sdk;
|
||||
|
||||
public class DoSomethingProvider implements FeatureProvider {
|
||||
class DoSomethingProvider implements FeatureProvider {
|
||||
|
||||
static final String name = "Something";
|
||||
// Flag evaluation metadata
|
||||
static final FlagMetadata flagMetadata = FlagMetadata.builder().build();
|
||||
|
||||
public static final String name = "Something";
|
||||
private EvaluationContext savedContext;
|
||||
|
||||
public EvaluationContext getMergedContext() {
|
||||
EvaluationContext getMergedContext() {
|
||||
return savedContext;
|
||||
}
|
||||
|
||||
|
|
@ -18,13 +21,16 @@ public class DoSomethingProvider implements FeatureProvider {
|
|||
public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
|
||||
savedContext = ctx;
|
||||
return ProviderEvaluation.<Boolean>builder()
|
||||
.value(!defaultValue).build();
|
||||
.value(!defaultValue)
|
||||
.flagMetadata(flagMetadata)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
|
||||
return ProviderEvaluation.<String>builder()
|
||||
.value(new StringBuilder(defaultValue).reverse().toString())
|
||||
.flagMetadata(flagMetadata)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
|
@ -33,6 +39,7 @@ public class DoSomethingProvider implements FeatureProvider {
|
|||
savedContext = ctx;
|
||||
return ProviderEvaluation.<Integer>builder()
|
||||
.value(defaultValue * 100)
|
||||
.flagMetadata(flagMetadata)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
|
@ -41,6 +48,7 @@ public class DoSomethingProvider implements FeatureProvider {
|
|||
savedContext = ctx;
|
||||
return ProviderEvaluation.<Double>builder()
|
||||
.value(defaultValue * 100)
|
||||
.flagMetadata(flagMetadata)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
|
@ -49,6 +57,7 @@ public class DoSomethingProvider implements FeatureProvider {
|
|||
savedContext = invocationContext;
|
||||
return ProviderEvaluation.<Value>builder()
|
||||
.value(null)
|
||||
.flagMetadata(flagMetadata)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package dev.openfeature.sdk;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static dev.openfeature.sdk.DoSomethingProvider.flagMetadata;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
|
@ -15,18 +17,18 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import dev.openfeature.sdk.exceptions.FlagNotFoundError;
|
||||
import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import dev.openfeature.sdk.fixtures.HookFixtures;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.Mockito;
|
||||
import org.simplify4u.slf4jmock.LoggerMock;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import dev.openfeature.sdk.exceptions.FlagNotFoundError;
|
||||
import dev.openfeature.sdk.fixtures.HookFixtures;
|
||||
import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
|
||||
|
||||
class FlagEvaluationSpecTest implements HookFixtures {
|
||||
|
||||
private Logger logger;
|
||||
|
|
@ -150,6 +152,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
.flagKey(key)
|
||||
.value(false)
|
||||
.variant(null)
|
||||
.flagMetadata(flagMetadata)
|
||||
.build();
|
||||
assertEquals(bd, c.getBooleanDetails(key, true));
|
||||
assertEquals(bd, c.getBooleanDetails(key, true, new ImmutableContext()));
|
||||
|
|
@ -159,6 +162,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
.flagKey(key)
|
||||
.value("tset")
|
||||
.variant(null)
|
||||
.flagMetadata(flagMetadata)
|
||||
.build();
|
||||
assertEquals(sd, c.getStringDetails(key, "test"));
|
||||
assertEquals(sd, c.getStringDetails(key, "test", new ImmutableContext()));
|
||||
|
|
@ -167,6 +171,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
FlagEvaluationDetails<Integer> id = FlagEvaluationDetails.<Integer>builder()
|
||||
.flagKey(key)
|
||||
.value(400)
|
||||
.flagMetadata(flagMetadata)
|
||||
.build();
|
||||
assertEquals(id, c.getIntegerDetails(key, 4));
|
||||
assertEquals(id, c.getIntegerDetails(key, 4, new ImmutableContext()));
|
||||
|
|
@ -175,6 +180,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
FlagEvaluationDetails<Double> dd = FlagEvaluationDetails.<Double>builder()
|
||||
.flagKey(key)
|
||||
.value(40.0)
|
||||
.flagMetadata(flagMetadata)
|
||||
.build();
|
||||
assertEquals(dd, c.getDoubleDetails(key, .4));
|
||||
assertEquals(dd, c.getDoubleDetails(key, .4, new ImmutableContext()));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
package dev.openfeature.sdk;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class FlagMetadataTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("Test metadata payload construction and retrieval")
|
||||
public void builder_validation() {
|
||||
// given
|
||||
FlagMetadata flagMetadata = FlagMetadata.builder()
|
||||
.addString("string", "string")
|
||||
.addInteger("integer", 1)
|
||||
.addLong("long", 1L)
|
||||
.addFloat("float", 1.5f)
|
||||
.addDouble("double", Double.MAX_VALUE)
|
||||
.addBoolean("boolean", Boolean.FALSE)
|
||||
.build();
|
||||
|
||||
// then
|
||||
assertThat(flagMetadata.getString("string")).isEqualTo("string");
|
||||
assertThat(flagMetadata.getInteger("integer")).isEqualTo(1);
|
||||
assertThat(flagMetadata.getLong("long")).isEqualTo(1L);
|
||||
assertThat(flagMetadata.getFloat("float")).isEqualTo(1.5f);
|
||||
assertThat(flagMetadata.getDouble("double")).isEqualTo(Double.MAX_VALUE);
|
||||
assertThat(flagMetadata.getBoolean("boolean")).isEqualTo(Boolean.FALSE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Value type mismatch returns a null")
|
||||
public void value_type_validation() {
|
||||
// given
|
||||
FlagMetadata flagMetadata = FlagMetadata.builder()
|
||||
.addString("string", "string")
|
||||
.build();
|
||||
|
||||
// then
|
||||
assertThat(flagMetadata.getBoolean("string")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("A null is returned if key does not exist")
|
||||
public void notfound_error_validation() {
|
||||
// given
|
||||
FlagMetadata flagMetadata = FlagMetadata.builder().build();
|
||||
|
||||
// then
|
||||
assertThat(flagMetadata.getBoolean("string")).isNull();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue