Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
c113675
fix(go-feature-flag): pin WASM evaluation engine to 0.2.4
thomaspoignant Sep 14, 2026
3095ec3
fix(go-feature-flag): preserve endpoint path prefix on the configurat…
thomaspoignant Sep 14, 2026
ae5eefd
fix(go-feature-flag): make a 304 structurally incapable of carrying a…
thomaspoignant Sep 14, 2026
49fdcbf
fix(go-feature-flag): treat a 200 carrying no flag map as a failed re…
thomaspoignant Sep 14, 2026
f002f43
fix(go-feature-flag): treat the flag configuration as opaque
thomaspoignant Sep 14, 2026
7945ab5
fix(go-feature-flag): make in-process initialization safe to call twice
thomaspoignant Sep 14, 2026
2b307bf
fix(go-feature-flag): reset the events publisher on re-initialization
thomaspoignant Sep 14, 2026
a92d6e8
fix(go-feature-flag): report PROVIDER_NOT_READY until a configuration…
thomaspoignant Sep 15, 2026
2fcc0c6
fix(go-feature-flag): put the provider in FATAL when credentials are …
thomaspoignant Sep 15, 2026
192961a
fix(go-feature-flag): let the engine decide about a missing targeting…
thomaspoignant Sep 15, 2026
ab4bcdd
fix(go-feature-flag): merge into the gofeatureflag namespace instead …
thomaspoignant Sep 15, 2026
e3e28e1
fix(go-feature-flag): accept any integral number in the float resolver
thomaspoignant Sep 15, 2026
595d735
fix(go-feature-flag): return the caller default when the engine value…
thomaspoignant Sep 15, 2026
1f7ccef
fix(go-feature-flag): discard and rebuild a WASM instance whose guest…
thomaspoignant Sep 15, 2026
5b36108
chore(go-feature-flag): use the non-deprecated Chicory HostFunction c…
thomaspoignant Sep 15, 2026
25fe65b
refactor(go-feature-flag): let WASI handle a zero exit code instead o…
thomaspoignant Sep 15, 2026
da5963d
fix(go-feature-flag): close the WASI instance when a WASM evaluator i…
thomaspoignant Sep 15, 2026
3e35473
fix(go-feature-flag): do not free the input buffer of a trapped WASM …
thomaspoignant Sep 15, 2026
afdc54a
fix(go-feature-flag): stop holding the buffer lock across the collect…
thomaspoignant Sep 16, 2026
5889dfb
fix(go-feature-flag): cap the events buffer at twice maxPendingEvents
thomaspoignant Sep 16, 2026
7d52e17
fix(go-feature-flag): send X-API-Key instead of Authorization Bearer
thomaspoignant Sep 16, 2026
d0e6e69
fix(go-feature-flag): preserve the endpoint path prefix on the evalua…
thomaspoignant Sep 16, 2026
4f97a2f
build(go-feature-flag): declare the ofrep and guava dependencies
thomaspoignant Sep 16, 2026
fef612d
refactor(go-feature-flag): resolve the option defaults in the getters
thomaspoignant Sep 16, 2026
5b3227f
style(go-feature-flag): mark the local variables of the enrich hook f…
thomaspoignant Sep 16, 2026
5ae2ca8
refactor(go-feature-flag)!: evaluate flags in the evaluators instead …
thomaspoignant Sep 16, 2026
7584758
test(go-feature-flag): move the evaluation specs onto the evaluators
thomaspoignant Sep 16, 2026
23ca95e
fix(go-feature-flag): raise every engine error as its SDK error type
thomaspoignant Sep 16, 2026
79f27b0
test(go-feature-flag): cover the remote evaluator
thomaspoignant Sep 17, 2026
b0cee62
fix(go-feature-flag): preserve the endpoint path prefix on the collec…
thomaspoignant Sep 17, 2026
b1e9a77
feat(go-feature-flag): support dataCollectorBaseURL
thomaspoignant Sep 17, 2026
7cc1749
test(go-feature-flag): cover the timeout on an overridden collector b…
thomaspoignant Sep 17, 2026
9f49f5b
fix(go-feature-flag): keep structured flag metadata parseable
thomaspoignant Sep 17, 2026
99821b3
test(go-feature-flag): pin the relay proxy metadata pass-through
thomaspoignant Sep 17, 2026
65a37ea
fix(go-feature-flag): keep the flag metadata when a flag is disabled
thomaspoignant Sep 17, 2026
6ced26e
fix(go-feature-flag): keep polling after a failed configuration refresh
thomaspoignant Sep 18, 2026
b941b30
fix(go-feature-flag): stop dropping null map entries on the way to th…
thomaspoignant Sep 18, 2026
38cfe2a
test(go-feature-flag): pin the tolerance of unrecognised configuratio…
thomaspoignant Sep 18, 2026
afedcf2
fix(go-feature-flag): report rejected credentials as fatal in remote …
thomaspoignant Sep 21, 2026
191bc6b
test(go-feature-flag): pin the content comparison behind a rotating ETag
thomaspoignant Sep 21, 2026
f49b5e9
feat(go-feature-flag): announce a stale configuration and its recovery
thomaspoignant Sep 21, 2026
b69fa5a
test(go-feature-flag): pin the edges of the stale and recovery announ…
thomaspoignant Sep 21, 2026
2352c8a
feat(go-feature-flag): ask the relay proxy when local evaluation fails
thomaspoignant Sep 22, 2026
8351a82
test(go-feature-flag): pin the fallback trigger to the raw engine code
thomaspoignant Sep 24, 2026
6d64f63
test(go-feature-flag): pin the fallback to every failed evaluation
thomaspoignant Sep 24, 2026
57afe93
fix(go-feature-flag): keep the engine's error when the relay proxy fa…
thomaspoignant Sep 25, 2026
baead9e
feat(go-feature-flag): mark a result the relay proxy produced
thomaspoignant Sep 25, 2026
b83a15b
fix(go-feature-flag): stop recording a flag the relay proxy already r…
thomaspoignant Sep 25, 2026
5115640
feat(go-feature-flag): warn when the relay proxy is asked to stand in
thomaspoignant Sep 25, 2026
7c6aaa5
test(go-feature-flag): pin the fallback to the configured credentials…
thomaspoignant Sep 25, 2026
9cb2ee1
test(go-feature-flag): cover trap handling against a real trap
thomaspoignant Sep 25, 2026
5c97194
fix(go-feature-flag): build the hooks on initialization instead of ad…
thomaspoignant Sep 25, 2026
a4b8cf9
fix(go-feature-flag): gate the error stage on the flag's trackability
thomaspoignant Sep 25, 2026
26dd0b2
fix(go-feature-flag): treat a flag absent from the configuration as t…
thomaspoignant Sep 25, 2026
804f068
fix(go-feature-flag): attribute remote mode events to this SDK
thomaspoignant Sep 25, 2026
29e000d
fix(go-feature-flag): decide anonymity by an identity test on the att…
thomaspoignant Sep 25, 2026
06877d8
fix(go-feature-flag): attribute an evaluation with no targeting key
thomaspoignant Sep 25, 2026
2398b9a
fix(go-feature-flag): flush buffered events on shutdown in every mode
thomaspoignant Sep 25, 2026
6a34f4b
test(go-feature-flag): pin what shutdown stops, at the provider
thomaspoignant Sep 25, 2026
b41861e
test(go-feature-flag): split the provider tests by concern
thomaspoignant Sep 25, 2026
619268f
fix(go-feature-flag): stop tracking when data collection is disabled
thomaspoignant Sep 25, 2026
de4359f
fix(go-feature-flag): attribute a tracking event by the feature event…
thomaspoignant Sep 25, 2026
d545104
fix(go-feature-flag)!: remove the connection pool options nothing reads
thomaspoignant Sep 25, 2026
8ff8d5e
test(go-feature-flag): run the engine against the canonical ABI vectors
thomaspoignant Sep 25, 2026
462099d
test(go-feature-flag): run the provider against a real relay proxy
thomaspoignant Sep 25, 2026
1fdf499
fix(go-feature-flag): attribute a feature event to in-process evaluation
thomaspoignant Sep 25, 2026
6d67fa2
fix(go-feature-flag): jitter the configuration polling interval
thomaspoignant Sep 28, 2026
62d53d6
docs(go-feature-flag): say the engine ships inside the provider jar
thomaspoignant Sep 28, 2026
ebf9959
feat(go-feature-flag): add custom headers for gateways in front of th…
thomaspoignant Sep 28, 2026
c6ee9e6
fix(go-feature-flag)!: remove the data collector hook's cache option
thomaspoignant Sep 28, 2026
b8ecca2
fix(go-feature-flag): log what the evaluation engine prints
thomaspoignant Sep 28, 2026
19ac502
fix(go-feature-flag): pass checkstyle and SpotBugs
thomaspoignant Sep 28, 2026
fd6ee17
fix: update Config defaults to boolean types and enhance environment …
matheusandre1 Sep 14, 2026
364f539
chore(main): release dev.openfeature.contrib.providers.flagd 0.14.2 (…
openfeaturebot Sep 14, 2026
c9e5c22
chore(main): release dev.openfeature.contrib.tools.flagdapitestkit 0.…
openfeaturebot Sep 23, 2026
498ab97
chore: standardize/update release please config (#1864)
toddbaert Sep 23, 2026
3cf9a8e
chore(deps): update dependency io.github.gzsombor:json-logic-java to …
renovate[bot] Sep 24, 2026
053c9d2
Merge branch 'main' into gofff-java-provider-spec-check
thomaspoignant Sep 28, 2026
033447d
fix(go-feature-flag): evaluate again after a shutdown and re-initiali…
thomaspoignant Sep 28, 2026
4fce088
fix(go-feature-flag): stop an evaluation waiting on a closed WASM pool
thomaspoignant Sep 28, 2026
dbfdfdf
fix(go-feature-flag): reject a data collector hook built without an e…
thomaspoignant Sep 28, 2026
94ac394
docs(go-feature-flag): correct the flush interval default and drop ca…
thomaspoignant Sep 28, 2026
072d631
test(go-feature-flag): give the polling change tests time to see a poll
thomaspoignant Sep 28, 2026
27e71c4
fix(go-feature-flag): keep the engine's reason and variant for a null…
thomaspoignant Sep 28, 2026
8a6cb6b
fix(go-feature-flag): accept integers beyond the long range in object…
thomaspoignant Sep 28, 2026
33ac6ff
fix(go-feature-flag): keep the relay proxy's TYPE_MISMATCH on the fal…
thomaspoignant Sep 28, 2026
d747a95
fix(go-feature-flag): announce a change to the evaluation context enr…
thomaspoignant Sep 28, 2026
58af456
fix(go-feature-flag): contain every engine fault and rebuild the WASM…
thomaspoignant Sep 28, 2026
bfe603d
fix(go-feature-flag): never post to the data collector on the evaluat…
thomaspoignant Sep 28, 2026
9ba6b0c
Merge branch 'main' into gofff-java-provider-spec-check
thomaspoignant Sep 29, 2026
8352217
fix(go-feature-flag): stop recording a mistyped relay proxy answer twice
thomaspoignant Sep 29, 2026
89ba11a
fix(go-feature-flag): keep the events buffer cap from overflowing
thomaspoignant Sep 29, 2026
df76995
fix(go-feature-flag): suppress the polling jitter warning inside the …
thomaspoignant Sep 29, 2026
0a25072
fix(go-feature-flag): reject a flag configuration with trailing tokens
thomaspoignant Sep 29, 2026
0207adb
docs(go-feature-flag): describe maxPendingEvents and polling as they …
thomaspoignant Sep 29, 2026
77f489a
Merge branch 'main' into gofff-java-provider-spec-check
thomaspoignant Sep 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 28 additions & 16 deletions providers/go-feature-flag/README.md

Large diffs are not rendered by default.

42 changes: 40 additions & 2 deletions providers/go-feature-flag/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
<!-- override module name defined in parent ("-" is not allowed) -->
<module-name>${groupId}.gofeatureflag</module-name>

<wasm.version>0.2.3</wasm.version>
<wasm.version>0.2.4</wasm.version>
<!-- exclusion expression for e2e tests -->
<testExclusions>**/e2e/*.java</testExclusions>
</properties>

<developers>
Expand Down Expand Up @@ -72,7 +74,7 @@
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.25.2</version>
<version>2.26.1</version>
<scope>test</scope>
</dependency>

Expand Down Expand Up @@ -103,12 +105,38 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>2.0.4</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-junit-jupiter</artifactId>
<version>2.0.4</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId>
<version>4.9.8</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>dev.openfeature.contrib.providers</groupId>
<artifactId>ofrep</artifactId>
<version>0.0.2</version>
</dependency>

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.4.8-jre</version>
</dependency>
</dependencies>
<build>
<plugins>
Expand All @@ -131,4 +159,14 @@
</plugin>
</plugins>
</build>

<profiles>
<profile>
<!-- runs the relay proxy integration tests, they need a docker daemon -->
<id>e2e</id>
<properties>
<testExclusions/>
</properties>
</profile>
</profiles>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import dev.openfeature.contrib.providers.gofeatureflag.hook.DataCollectorHook;
import dev.openfeature.contrib.providers.gofeatureflag.hook.DataCollectorHookOptions;
import dev.openfeature.contrib.providers.gofeatureflag.hook.EnrichEvaluationContextHook;
import dev.openfeature.contrib.providers.gofeatureflag.service.EvaluationService;
import dev.openfeature.contrib.providers.gofeatureflag.service.EventsPublisher;
import dev.openfeature.contrib.providers.gofeatureflag.util.Const;
import dev.openfeature.contrib.providers.gofeatureflag.util.EvaluationContextUtil;
Expand All @@ -20,6 +19,7 @@
import dev.openfeature.sdk.Hook;
import dev.openfeature.sdk.Metadata;
import dev.openfeature.sdk.ProviderEvaluation;
import dev.openfeature.sdk.ProviderEvent;
import dev.openfeature.sdk.ProviderEventDetails;
import dev.openfeature.sdk.Tracking;
import dev.openfeature.sdk.TrackingEventDetails;
Expand All @@ -29,6 +29,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
Expand All @@ -41,7 +42,7 @@ public final class GoFeatureFlagProvider extends EventProvider implements Tracki
/** Options to configure the provider. */
private final GoFeatureFlagProviderOptions options;
/** Service to evaluate the flags. */
private final EvaluationService evalService;
private final IEvaluator evaluator;
/** List of the hooks used by the provider. */
private final List<Hook> hooks = new ArrayList<>();
/** API layer to contact GO Feature Flag. */
Expand All @@ -50,8 +51,6 @@ public final class GoFeatureFlagProvider extends EventProvider implements Tracki
private final EventsPublisher<IEvent> eventsPublisher;
/** exporter metadata contains the metadata that we want to send to the exporter. */
private final Map<String, Object> exporterMetadata;
/** DataCollectorHook is the hook to send usage of the flags. */
private DataCollectorHook dataCollectorHook;

/**
* Constructor of the provider.
Expand All @@ -66,24 +65,16 @@ public GoFeatureFlagProvider(final GoFeatureFlagProviderOptions options) throws
options.validate();
this.options = options;
this.api = GoFeatureFlagApi.builder().options(options).build();
this.evalService = new EvaluationService(getEvaluator(this.api));
this.evaluator = getEvaluator();

long flushIntervalMs =
(options.getFlushIntervalMs() == null) ? Const.DEFAULT_FLUSH_INTERVAL_MS : options.getFlushIntervalMs();
int maxPendingEvents = (options.getMaxPendingEvents() == null)
? Const.DEFAULT_MAX_PENDING_EVENTS
: options.getMaxPendingEvents();
Consumer<List<IEvent>> publisher = this::publishEvents;
this.eventsPublisher = new EventsPublisher<>(publisher, flushIntervalMs, maxPendingEvents);

if (options.getExporterMetadata() == null) {
this.exporterMetadata = new HashMap<>();
} else {
val exp = new HashMap<>(options.getExporterMetadata());
exp.put("provider", "java");
exp.put("openfeature", true);
this.exporterMetadata = exp;
}
this.eventsPublisher =
new EventsPublisher<>(publisher, options.getFlushIntervalMs(), options.getMaxPendingEvents());

val exp = new HashMap<>(options.getExporterMetadata());
exp.put(Const.METADATA_PROVIDER, "java");
exp.put(Const.METADATA_OPENFEATURE, true);
this.exporterMetadata = exp;
}

@Override
Expand All @@ -99,59 +90,63 @@ public List<Hook> getProviderHooks() {
@Override
public ProviderEvaluation<Boolean> getBooleanEvaluation(
String key, Boolean defaultValue, EvaluationContext evaluationContext) {
return this.evalService.getEvaluation(key, defaultValue, evaluationContext, Boolean.class);
return this.evaluator.getBooleanEvaluation(key, defaultValue, evaluationContext);
}

@Override
public ProviderEvaluation<String> getStringEvaluation(
String key, String defaultValue, EvaluationContext evaluationContext) {
return this.evalService.getEvaluation(key, defaultValue, evaluationContext, String.class);
return this.evaluator.getStringEvaluation(key, defaultValue, evaluationContext);
}

@Override
public ProviderEvaluation<Integer> getIntegerEvaluation(
String key, Integer defaultValue, EvaluationContext evaluationContext) {
return this.evalService.getEvaluation(key, defaultValue, evaluationContext, Integer.class);
return this.evaluator.getIntegerEvaluation(key, defaultValue, evaluationContext);
}

@Override
public ProviderEvaluation<Double> getDoubleEvaluation(
String key, Double defaultValue, EvaluationContext evaluationContext) {
return this.evalService.getEvaluation(key, defaultValue, evaluationContext, Double.class);
return this.evaluator.getDoubleEvaluation(key, defaultValue, evaluationContext);
}

@Override
public ProviderEvaluation<Value> getObjectEvaluation(
String key, Value defaultValue, EvaluationContext evaluationContext) {
return this.evalService.getEvaluation(key, defaultValue, evaluationContext, Value.class);
return this.evaluator.getObjectEvaluation(key, defaultValue, evaluationContext);
}

@Override
public void initialize(EvaluationContext evaluationContext) throws Exception {
this.initialize(evaluationContext, "");
}

@Override
public void initialize(EvaluationContext evaluationContext, String domain) throws Exception {
super.initialize(evaluationContext);
this.evalService.init();
this.hooks.add(new EnrichEvaluationContextHook(this.options.getExporterMetadata()));
// re-initialization must reset the publisher: its shutdown flag and its scheduler are both
// one-shot, so without this a provider that is shut down and initialized again never flushes.
this.eventsPublisher.start();
this.evaluator.initialize(evaluationContext, domain);
this.hooks.clear();
this.hooks.add(new EnrichEvaluationContextHook(this.exporterMetadata));
// In case of remote evaluation, we don't need to send the data to the collector
// because the relay-proxy will collect events directly server side.
if (!this.options.isDisableDataCollection() && this.options.getEvaluationType() != EvaluationType.REMOTE) {
this.dataCollectorHook = new DataCollectorHook(DataCollectorHookOptions.builder()
this.hooks.add(new DataCollectorHook(DataCollectorHookOptions.builder()
.eventsPublisher(this.eventsPublisher)
.collectUnCachedEvaluation(true)
.evalService(this.evalService)
.build());

this.hooks.add(this.dataCollectorHook);
.evaluator(this.evaluator)
.build()));
}
log.info("finishing initializing provider");
}

@Override
public void shutdown() {
super.shutdown();
this.evalService.destroy();
if (this.dataCollectorHook != null) {
this.dataCollectorHook.shutdown();
}
this.evaluator.shutdown();
this.eventsPublisher.shutdown();
}

@Override
Expand All @@ -171,10 +166,14 @@ public void track(final String eventName, final TrackingEventDetails trackingEve

@Override
public void track(final String eventName, final EvaluationContext context, final TrackingEventDetails details) {
if (this.options.isDisableDataCollection()) {
return;
}

val trackingEvent = TrackingEvent.builder()
.evaluationContext((context != null) ? context.asObjectMap() : Collections.emptyMap())
.userKey(context != null ? context.getTargetingKey() : "undefined-targetingKey")
.contextKind(EvaluationContextUtil.isAnonymousUser(context) ? "anonymousUser" : "user")
.userKey(EvaluationContextUtil.userKey(context))
.contextKind(EvaluationContextUtil.contextKind(context))
.kind("tracking")
.key(eventName)
.trackingEventDetails(details != null ? details.asObjectMap() : Collections.emptyMap())
Expand All @@ -185,17 +184,16 @@ public void track(final String eventName, final EvaluationContext context, final

/**
* Get the evaluator based on the evaluation type.
* It will initialize the evaluator based on the evaluation type.
*
* @return the evaluator
*/
private IEvaluator getEvaluator(GoFeatureFlagApi api) {
private IEvaluator getEvaluator() {
// Select the evaluator based on the evaluation type
BiConsumer<ProviderEvent, ProviderEventDetails> emitter = this::emit;
if (options.getEvaluationType() == null || options.getEvaluationType() == EvaluationType.IN_PROCESS) {
Consumer<ProviderEventDetails> emitProviderConfigurationChanged = this::emitProviderConfigurationChanged;
return new InProcessEvaluator(api, this.options, emitProviderConfigurationChanged);
return new InProcessEvaluator(this.api, this.options, emitter);
}
return new RemoteEvaluator(api);
return new RemoteEvaluator(this.options, emitter);
}

/**
Expand Down
Loading
Loading