Skip to content

Commit 16a8287

Browse files
feat(go-feature-flag): Add exporterMetadata in the evaluation call (#1193)
Signed-off-by: Thomas Poignant <[email protected]>
1 parent c431cfb commit 16a8287

File tree

5 files changed

+102
-3
lines changed

5 files changed

+102
-3
lines changed

providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProvider.java

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import dev.openfeature.contrib.providers.gofeatureflag.exception.InvalidTypeInCache;
1111
import dev.openfeature.contrib.providers.gofeatureflag.hook.DataCollectorHook;
1212
import dev.openfeature.contrib.providers.gofeatureflag.hook.DataCollectorHookOptions;
13+
import dev.openfeature.contrib.providers.gofeatureflag.hook.EnrichEvaluationContextHook;
1314
import dev.openfeature.sdk.EvaluationContext;
1415
import dev.openfeature.sdk.EventProvider;
1516
import dev.openfeature.sdk.Hook;
@@ -104,6 +105,7 @@ public void initialize(EvaluationContext evaluationContext) throws Exception {
104105
super.initialize(evaluationContext);
105106
this.gofeatureflagController =
106107
GoFeatureFlagController.builder().options(options).build();
108+
this.hooks.add(new EnrichEvaluationContextHook(options.getExporterMetadata()));
107109

108110
if (options.getEnableCache() == null || options.getEnableCache()) {
109111
this.cacheCtrl = CacheController.builder().options(options).build();

providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/controller/GoFeatureFlagController.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,9 @@ public class GoFeatureFlagController {
9090
@Builder
9191
private GoFeatureFlagController(final GoFeatureFlagProviderOptions options) throws InvalidOptions {
9292
this.apiKey = options.getApiKey();
93-
this.exporterMetadata = options.getExporterMetadata();
93+
this.exporterMetadata = options.getExporterMetadata() == null ? new HashMap<>() : options.getExporterMetadata();
94+
this.exporterMetadata.put("provider", "java");
95+
this.exporterMetadata.put("openfeature", true);
9496

9597
this.parsedEndpoint = HttpUrl.parse(options.getEndpoint());
9698
if (this.parsedEndpoint == null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package dev.openfeature.contrib.providers.gofeatureflag.hook;
2+
3+
import dev.openfeature.sdk.EvaluationContext;
4+
import dev.openfeature.sdk.Hook;
5+
import dev.openfeature.sdk.HookContext;
6+
import dev.openfeature.sdk.MutableContext;
7+
import dev.openfeature.sdk.MutableStructure;
8+
import dev.openfeature.sdk.Value;
9+
import java.util.HashMap;
10+
import java.util.Map;
11+
import java.util.Optional;
12+
13+
/**
14+
* EnrichEvaluationContextHook is an OpenFeature Hook in charge of enriching the evaluation context.
15+
*/
16+
public class EnrichEvaluationContextHook implements Hook<String> {
17+
private final Map<String, Object> exporterMetadata;
18+
19+
public EnrichEvaluationContextHook(Map<String, Object> exporterMetadata) {
20+
this.exporterMetadata = Optional.ofNullable(exporterMetadata).orElseGet(HashMap::new);
21+
}
22+
23+
@Override
24+
public Optional<EvaluationContext> before(HookContext<String> ctx, Map<String, Object> hints) {
25+
if (ctx == null) {
26+
return Optional.empty();
27+
}
28+
29+
MutableContext mutableContext =
30+
new MutableContext(ctx.getCtx().getTargetingKey(), ctx.getCtx().asMap());
31+
32+
MutableStructure metadata = new MutableStructure();
33+
for (Map.Entry<String, Object> entry : exporterMetadata.entrySet()) {
34+
switch (entry.getValue().getClass().getSimpleName()) {
35+
case "String":
36+
metadata.add(entry.getKey(), (String) entry.getValue());
37+
break;
38+
case "Boolean":
39+
metadata.add(entry.getKey(), (Boolean) entry.getValue());
40+
break;
41+
case "Integer":
42+
metadata.add(entry.getKey(), (Integer) entry.getValue());
43+
break;
44+
case "Double":
45+
metadata.add(entry.getKey(), (Double) entry.getValue());
46+
break;
47+
default:
48+
throw new IllegalArgumentException(
49+
"Unsupported type: " + entry.getValue().getClass().getSimpleName());
50+
}
51+
}
52+
Map<String, Value> expMetadata = new HashMap<>();
53+
expMetadata.put("exporterMetadata", new Value(metadata));
54+
mutableContext.add("gofeatureflag", new MutableStructure(expMetadata));
55+
return Optional.of(mutableContext);
56+
}
57+
}

providers/go-feature-flag/src/main/java/dev/openfeature/contrib/providers/gofeatureflag/hook/events/Events.java

-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ public class Events {
2727
*/
2828
public Events(List<Event> events, Map<String, Object> exporterMetadata) {
2929
this.events = new ArrayList<>(events);
30-
this.meta.put("provider", "java");
31-
this.meta.put("openfeature", true);
3230
if (exporterMetadata != null) {
3331
this.meta.putAll(exporterMetadata);
3432
}

providers/go-feature-flag/src/test/java/dev/openfeature/contrib/providers/gofeatureflag/GoFeatureFlagProviderTest.java

+40
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static org.junit.jupiter.api.Assertions.assertEquals;
66
import static org.junit.jupiter.api.Assertions.assertThrows;
77

8+
import com.fasterxml.jackson.databind.ObjectMapper;
89
import com.github.benmanes.caffeine.cache.Caffeine;
910
import com.google.common.net.HttpHeaders;
1011
import dev.openfeature.contrib.providers.gofeatureflag.exception.InvalidEndpoint;
@@ -21,6 +22,7 @@
2122
import dev.openfeature.sdk.Value;
2223
import java.io.IOException;
2324
import java.net.URL;
25+
import java.nio.charset.Charset;
2426
import java.nio.charset.StandardCharsets;
2527
import java.nio.file.Files;
2628
import java.nio.file.Paths;
@@ -48,12 +50,14 @@ class GoFeatureFlagProviderTest {
4850
private Map exporterMetadata;
4951
private int flagChangeCallCounter = 0;
5052
private boolean flagChanged404 = false;
53+
private List<RecordedRequest> requests = new ArrayList<>();
5154

5255
// Dispatcher is the configuration of the mock server to test the provider.
5356
final Dispatcher dispatcher = new Dispatcher() {
5457
@NotNull @SneakyThrows
5558
@Override
5659
public MockResponse dispatch(RecordedRequest request) {
60+
requests.add(request);
5761
assert request.getPath() != null;
5862
if (request.getPath().contains("fail_500")) {
5963
return new MockResponse().setResponseCode(500);
@@ -987,6 +991,42 @@ void should_send_exporter_metadata() {
987991
"we should have the exporter metadata in the last event sent to the data collector");
988992
}
989993

994+
@SneakyThrows
995+
@Test
996+
void should_add_exporter_metadata_into_evaluation_call() {
997+
Map<String, Object> customExporterMetadata = new HashMap<>();
998+
customExporterMetadata.put("version", "1.0.0");
999+
customExporterMetadata.put("intTest", 1234567890);
1000+
customExporterMetadata.put("doubleTest", 12345.67890);
1001+
GoFeatureFlagProvider g = new GoFeatureFlagProvider(GoFeatureFlagProviderOptions.builder()
1002+
.endpoint(this.baseUrl.toString())
1003+
.timeout(1000)
1004+
.enableCache(true)
1005+
.flushIntervalMs(150L)
1006+
.exporterMetadata(customExporterMetadata)
1007+
.build());
1008+
String providerName = this.testName;
1009+
OpenFeatureAPI.getInstance().setProviderAndWait(providerName, g);
1010+
Client client = OpenFeatureAPI.getInstance().getClient(providerName);
1011+
client.getBooleanDetails("bool_targeting_match", false, this.evaluationContext);
1012+
ObjectMapper objectMapper = new ObjectMapper();
1013+
String want = objectMapper
1014+
.readValue(
1015+
"{ \"user\" : { \"key\" : \"d45e303a-38c2-11ed-a261-0242ac120002\", "
1016+
+ "\"anonymous\" : false, \"custom\" : { \"firstname\" : \"john\", \"gofeatureflag\" : { "
1017+
+ "\"exporterMetadata\" : { \"openfeature\" : true, \"provider\" : \"java\", \"doubleTest\" : 12345.6789, "
1018+
+ "\"intTest\" : 1234567890, \"version\" : \"1.0.0\" } }, \"rate\" : 3.14, \"targetingKey\" : "
1019+
+ "\"d45e303a-38c2-11ed-a261-0242ac120002\", \"company_info\" : { \"size\" : 120, \"name\" : \"my_company\" }, "
1020+
+ "\"email\" : \"[email protected]\", \"age\" : 30, \"lastname\" : \"doe\", \"professional\" : true, "
1021+
+ "\"labels\" : [ \"pro\", \"beta\" ] } }, \"defaultValue\" : false }",
1022+
Object.class)
1023+
.toString();
1024+
String got = objectMapper
1025+
.readValue(this.requests.get(0).getBody().readString(Charset.defaultCharset()), Object.class)
1026+
.toString();
1027+
assertEquals(want, got, "we should have the exporter metadata in the last event sent to the data collector");
1028+
}
1029+
9901030
private String readMockResponse(String filename) throws Exception {
9911031
URL url = getClass().getClassLoader().getResource("mock_responses/" + filename);
9921032
assert url != null;

0 commit comments

Comments
 (0)