Skip to content

Commit

Permalink
feat: add flag metadata (#459)
Browse files Browse the repository at this point in the history
Signed-off-by: Kavindu Dodanduwa <kavindudodanduwa@gmail.com>
  • Loading branch information
Kavindu-Dodan authored Jun 7, 2023
1 parent 5f173ff commit 3ed40a3
Show file tree
Hide file tree
Showing 6 changed files with 275 additions and 12 deletions.
11 changes: 8 additions & 3 deletions src/main/java/dev/openfeature/sdk/FlagEvaluationDetails.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,27 @@

/**
* 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) {
Expand All @@ -33,6 +37,7 @@ public static <T> FlagEvaluationDetails<T> from(ProviderEvaluation<T> providerEv
.variant(providerEval.getVariant())
.reason(providerEval.getReason())
.errorCode(providerEval.getErrorCode())
.flagMetadata(providerEval.getFlagMetadata())
.build();
}
}
188 changes: 188 additions & 0 deletions src/main/java/dev/openfeature/sdk/FlagMetadata.java
Original file line number Diff line number Diff line change
@@ -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);
}

}
}
2 changes: 2 additions & 0 deletions src/main/java/dev/openfeature/sdk/ProviderEvaluation.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
17 changes: 13 additions & 4 deletions src/test/java/dev/openfeature/sdk/DoSomethingProvider.java
Original file line number Diff line number Diff line change
@@ -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;
}

Expand All @@ -18,13 +21,16 @@ public Metadata getMetadata() {
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();
}

Expand All @@ -33,6 +39,7 @@ public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defa
savedContext = ctx;
return ProviderEvaluation.<Integer>builder()
.value(defaultValue * 100)
.flagMetadata(flagMetadata)
.build();
}

Expand All @@ -41,6 +48,7 @@ public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double default
savedContext = ctx;
return ProviderEvaluation.<Double>builder()
.value(defaultValue * 100)
.flagMetadata(flagMetadata)
.build();
}

Expand All @@ -49,6 +57,7 @@ public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultVa
savedContext = invocationContext;
return ProviderEvaluation.<Value>builder()
.value(null)
.flagMetadata(flagMetadata)
.build();
}
}
16 changes: 11 additions & 5 deletions src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -15,18 +17,18 @@
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;
Expand Down Expand Up @@ -150,6 +152,7 @@ void getApiInstance() {
.flagKey(key)
.value(false)
.variant(null)
.flagMetadata(flagMetadata)
.build();
assertEquals(bd, c.getBooleanDetails(key, true));
assertEquals(bd, c.getBooleanDetails(key, true, new ImmutableContext()));
Expand All @@ -159,6 +162,7 @@ void getApiInstance() {
.flagKey(key)
.value("tset")
.variant(null)
.flagMetadata(flagMetadata)
.build();
assertEquals(sd, c.getStringDetails(key, "test"));
assertEquals(sd, c.getStringDetails(key, "test", new ImmutableContext()));
Expand All @@ -167,6 +171,7 @@ void getApiInstance() {
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()));
Expand All @@ -175,6 +180,7 @@ void getApiInstance() {
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()));
Expand Down
Loading

0 comments on commit 3ed40a3

Please sign in to comment.