Skip to content

Commit 3ed40a3

Browse files
feat: add flag metadata (#459)
Signed-off-by: Kavindu Dodanduwa <[email protected]>
1 parent 5f173ff commit 3ed40a3

File tree

6 files changed

+275
-12
lines changed

6 files changed

+275
-12
lines changed

src/main/java/dev/openfeature/sdk/FlagEvaluationDetails.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,27 @@
77

88
/**
99
* Contains information about how the evaluation happened, including any resolved values.
10+
*
1011
* @param <T> the type of the flag being evaluated.
1112
*/
12-
@Data @Builder
13+
@Data
14+
@Builder
1315
public class FlagEvaluationDetails<T> implements BaseEvaluation<T> {
16+
1417
private String flagKey;
1518
private T value;
1619
@Nullable private String variant;
1720
@Nullable private String reason;
1821
private ErrorCode errorCode;
1922
@Nullable private String errorMessage;
23+
@Builder.Default private FlagMetadata flagMetadata = FlagMetadata.builder().build();
2024

2125
/**
2226
* Generate detail payload from the provider response.
2327
*
2428
* @param providerEval provider response
25-
* @param flagKey key for the flag being evaluated
26-
* @param <T> type of flag being returned
29+
* @param flagKey key for the flag being evaluated
30+
* @param <T> type of flag being returned
2731
* @return detail payload
2832
*/
2933
public static <T> FlagEvaluationDetails<T> from(ProviderEvaluation<T> providerEval, String flagKey) {
@@ -33,6 +37,7 @@ public static <T> FlagEvaluationDetails<T> from(ProviderEvaluation<T> providerEv
3337
.variant(providerEval.getVariant())
3438
.reason(providerEval.getReason())
3539
.errorCode(providerEval.getErrorCode())
40+
.flagMetadata(providerEval.getFlagMetadata())
3641
.build();
3742
}
3843
}
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package dev.openfeature.sdk;
2+
3+
import lombok.extern.slf4j.Slf4j;
4+
5+
import java.util.HashMap;
6+
import java.util.Map;
7+
8+
/**
9+
* Immutable Flag Metadata representation. Implementation is backed by a {@link Map} and immutability is provided
10+
* through builder and accessors.
11+
*/
12+
@Slf4j
13+
public class FlagMetadata {
14+
private final Map<String, Object> metadata;
15+
16+
private FlagMetadata(Map<String, Object> metadata) {
17+
this.metadata = metadata;
18+
}
19+
20+
/**
21+
* Retrieve a {@link String} value for the given key. A {@code null} value is returned if the key does not exist
22+
* or if the value is of a different type.
23+
*
24+
* @param key flag metadata key to retrieve
25+
*/
26+
public String getString(final String key) {
27+
return getValue(key, String.class);
28+
}
29+
30+
/**
31+
* Retrieve a {@link Integer} value for the given key. A {@code null} value is returned if the key does not exist
32+
* or if the value is of a different type.
33+
*
34+
* @param key flag metadata key to retrieve
35+
*/
36+
public Integer getInteger(final String key) {
37+
return getValue(key, Integer.class);
38+
}
39+
40+
/**
41+
* Retrieve a {@link Long} value for the given key. A {@code null} value is returned if the key does not exist
42+
* or if the value is of a different type.
43+
*
44+
* @param key flag metadata key to retrieve
45+
*/
46+
public Long getLong(final String key) {
47+
return getValue(key, Long.class);
48+
}
49+
50+
/**
51+
* Retrieve a {@link Float} value for the given key. A {@code null} value is returned if the key does not exist
52+
* or if the value is of a different type.
53+
*
54+
* @param key flag metadata key to retrieve
55+
*/
56+
public Float getFloat(final String key) {
57+
return getValue(key, Float.class);
58+
}
59+
60+
/**
61+
* Retrieve a {@link Double} value for the given key. A {@code null} value is returned if the key does not exist
62+
* or if the value is of a different type.
63+
*
64+
* @param key flag metadata key to retrieve
65+
*/
66+
public Double getDouble(final String key) {
67+
return getValue(key, Double.class);
68+
}
69+
70+
/**
71+
* Retrieve a {@link Boolean} value for the given key. A {@code null} value is returned if the key does not exist
72+
* or if the value is of a different type.
73+
*
74+
* @param key flag metadata key to retrieve
75+
*/
76+
public Boolean getBoolean(final String key) {
77+
return getValue(key, Boolean.class);
78+
}
79+
80+
private <T> T getValue(final String key, final Class<T> type) {
81+
final Object o = metadata.get(key);
82+
83+
if (o == null) {
84+
log.debug("Metadata key " + key + "does not exist");
85+
return null;
86+
}
87+
88+
try {
89+
return type.cast(o);
90+
} catch (ClassCastException e) {
91+
log.debug("Error retrieving value for key " + key, e);
92+
return null;
93+
}
94+
}
95+
96+
97+
/**
98+
* Obtain a builder for {@link FlagMetadata}.
99+
*/
100+
public static FlagMetadataBuilder builder() {
101+
return new FlagMetadataBuilder();
102+
}
103+
104+
/**
105+
* Immutable builder for {@link FlagMetadata}.
106+
*/
107+
public static class FlagMetadataBuilder {
108+
private final Map<String, Object> metadata;
109+
110+
private FlagMetadataBuilder() {
111+
metadata = new HashMap<>();
112+
}
113+
114+
/**
115+
* Add String value to the metadata.
116+
*
117+
* @param key flag metadata key to add
118+
* @param value flag metadata value to add
119+
*/
120+
public FlagMetadataBuilder addString(final String key, final String value) {
121+
metadata.put(key, value);
122+
return this;
123+
}
124+
125+
/**
126+
* Add Integer value to the metadata.
127+
*
128+
* @param key flag metadata key to add
129+
* @param value flag metadata value to add
130+
*/
131+
public FlagMetadataBuilder addInteger(final String key, final Integer value) {
132+
metadata.put(key, value);
133+
return this;
134+
}
135+
136+
/**
137+
* Add Long value to the metadata.
138+
*
139+
* @param key flag metadata key to add
140+
* @param value flag metadata value to add
141+
*/
142+
public FlagMetadataBuilder addLong(final String key, final Long value) {
143+
metadata.put(key, value);
144+
return this;
145+
}
146+
147+
/**
148+
* Add Float value to the metadata.
149+
*
150+
* @param key flag metadata key to add
151+
* @param value flag metadata value to add
152+
*/
153+
public FlagMetadataBuilder addFloat(final String key, final Float value) {
154+
metadata.put(key, value);
155+
return this;
156+
}
157+
158+
/**
159+
* Add Double value to the metadata.
160+
*
161+
* @param key flag metadata key to add
162+
* @param value flag metadata value to add
163+
*/
164+
public FlagMetadataBuilder addDouble(final String key, final Double value) {
165+
metadata.put(key, value);
166+
return this;
167+
}
168+
169+
/**
170+
* Add Boolean value to the metadata.
171+
*
172+
* @param key flag metadata key to add
173+
* @param value flag metadata value to add
174+
*/
175+
public FlagMetadataBuilder addBoolean(final String key, final Boolean value) {
176+
metadata.put(key, value);
177+
return this;
178+
}
179+
180+
/**
181+
* Retrieve {@link FlagMetadata} with provided key,value pairs.
182+
*/
183+
public FlagMetadata build() {
184+
return new FlagMetadata(this.metadata);
185+
}
186+
187+
}
188+
}

src/main/java/dev/openfeature/sdk/ProviderEvaluation.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@ public class ProviderEvaluation<T> implements BaseEvaluation<T> {
1313
@Nullable private String reason;
1414
ErrorCode errorCode;
1515
@Nullable private String errorMessage;
16+
@Builder.Default
17+
private FlagMetadata flagMetadata = FlagMetadata.builder().build();
1618
}

src/test/java/dev/openfeature/sdk/DoSomethingProvider.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package dev.openfeature.sdk;
22

3-
public class DoSomethingProvider implements FeatureProvider {
3+
class DoSomethingProvider implements FeatureProvider {
4+
5+
static final String name = "Something";
6+
// Flag evaluation metadata
7+
static final FlagMetadata flagMetadata = FlagMetadata.builder().build();
48

5-
public static final String name = "Something";
69
private EvaluationContext savedContext;
710

8-
public EvaluationContext getMergedContext() {
11+
EvaluationContext getMergedContext() {
912
return savedContext;
1013
}
1114

@@ -18,13 +21,16 @@ public Metadata getMetadata() {
1821
public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
1922
savedContext = ctx;
2023
return ProviderEvaluation.<Boolean>builder()
21-
.value(!defaultValue).build();
24+
.value(!defaultValue)
25+
.flagMetadata(flagMetadata)
26+
.build();
2227
}
2328

2429
@Override
2530
public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
2631
return ProviderEvaluation.<String>builder()
2732
.value(new StringBuilder(defaultValue).reverse().toString())
33+
.flagMetadata(flagMetadata)
2834
.build();
2935
}
3036

@@ -33,6 +39,7 @@ public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defa
3339
savedContext = ctx;
3440
return ProviderEvaluation.<Integer>builder()
3541
.value(defaultValue * 100)
42+
.flagMetadata(flagMetadata)
3643
.build();
3744
}
3845

@@ -41,6 +48,7 @@ public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double default
4148
savedContext = ctx;
4249
return ProviderEvaluation.<Double>builder()
4350
.value(defaultValue * 100)
51+
.flagMetadata(flagMetadata)
4452
.build();
4553
}
4654

@@ -49,6 +57,7 @@ public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultVa
4957
savedContext = invocationContext;
5058
return ProviderEvaluation.<Value>builder()
5159
.value(null)
60+
.flagMetadata(flagMetadata)
5261
.build();
5362
}
5463
}

src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package dev.openfeature.sdk;
22

3-
import static org.assertj.core.api.Assertions.*;
3+
import static dev.openfeature.sdk.DoSomethingProvider.flagMetadata;
4+
import static org.assertj.core.api.Assertions.assertThat;
5+
import static org.assertj.core.api.Assertions.assertThatCode;
46
import static org.junit.jupiter.api.Assertions.assertEquals;
57
import static org.junit.jupiter.api.Assertions.assertFalse;
68
import static org.junit.jupiter.api.Assertions.assertNull;
@@ -15,18 +17,18 @@
1517
import java.util.List;
1618
import java.util.Map;
1719

18-
import dev.openfeature.sdk.exceptions.FlagNotFoundError;
19-
import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
2020
import org.junit.jupiter.api.AfterEach;
2121
import org.junit.jupiter.api.BeforeEach;
2222
import org.junit.jupiter.api.Test;
23-
24-
import dev.openfeature.sdk.fixtures.HookFixtures;
2523
import org.mockito.ArgumentMatchers;
2624
import org.mockito.Mockito;
2725
import org.simplify4u.slf4jmock.LoggerMock;
2826
import org.slf4j.Logger;
2927

28+
import dev.openfeature.sdk.exceptions.FlagNotFoundError;
29+
import dev.openfeature.sdk.fixtures.HookFixtures;
30+
import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
31+
3032
class FlagEvaluationSpecTest implements HookFixtures {
3133

3234
private Logger logger;
@@ -150,6 +152,7 @@ void getApiInstance() {
150152
.flagKey(key)
151153
.value(false)
152154
.variant(null)
155+
.flagMetadata(flagMetadata)
153156
.build();
154157
assertEquals(bd, c.getBooleanDetails(key, true));
155158
assertEquals(bd, c.getBooleanDetails(key, true, new ImmutableContext()));
@@ -159,6 +162,7 @@ void getApiInstance() {
159162
.flagKey(key)
160163
.value("tset")
161164
.variant(null)
165+
.flagMetadata(flagMetadata)
162166
.build();
163167
assertEquals(sd, c.getStringDetails(key, "test"));
164168
assertEquals(sd, c.getStringDetails(key, "test", new ImmutableContext()));
@@ -167,6 +171,7 @@ void getApiInstance() {
167171
FlagEvaluationDetails<Integer> id = FlagEvaluationDetails.<Integer>builder()
168172
.flagKey(key)
169173
.value(400)
174+
.flagMetadata(flagMetadata)
170175
.build();
171176
assertEquals(id, c.getIntegerDetails(key, 4));
172177
assertEquals(id, c.getIntegerDetails(key, 4, new ImmutableContext()));
@@ -175,6 +180,7 @@ void getApiInstance() {
175180
FlagEvaluationDetails<Double> dd = FlagEvaluationDetails.<Double>builder()
176181
.flagKey(key)
177182
.value(40.0)
183+
.flagMetadata(flagMetadata)
178184
.build();
179185
assertEquals(dd, c.getDoubleDetails(key, .4));
180186
assertEquals(dd, c.getDoubleDetails(key, .4, new ImmutableContext()));

0 commit comments

Comments
 (0)