Skip to content

Commit 20ca053

Browse files
toddbaertKavindu-Dodanthisthat
authored
feat!: use new eval/sync protos (requires flagd v0.7.3+) (#683)
Signed-off-by: Todd Baert <[email protected]> Co-authored-by: Kavindu Dodanduwa <[email protected]> Co-authored-by: Giovanni Liva <[email protected]>
1 parent 4de6c46 commit 20ca053

32 files changed

+296
-234
lines changed

.github/workflows/ci.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ jobs:
2424
- 8014:8013
2525
# sync-testbed for flagd in-process provider e2e tests
2626
sync:
27-
image: ghcr.io/open-feature/sync-testbed:v0.4.11
27+
image: ghcr.io/open-feature/sync-testbed:v0.5.1
2828
ports:
2929
- 9090:9090
3030
# sync-testbed for flagd in-process provider reconnect e2e tests
3131
sync-unstable:
32-
image: ghcr.io/open-feature/sync-testbed-unstable:v0.4.11
32+
image: ghcr.io/open-feature/sync-testbed-unstable:v0.5.1
3333
ports:
3434
- 9091:9090
3535

providers/flagd/README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# flagd Provider for OpenFeature
22

3-
This provider is designed to use flagd's [evaluation protocol](https://github.com/open-feature/schemas/blob/main/protobuf/schema/v1/schema.proto), or locally evaluate flags defined in a flagd [flag definition](https://github.com/open-feature/schemas/blob/main/json/flagd-definitions.json).
3+
This provider is designed to use flagd's [evaluation protocol](https://github.com/open-feature/schemas/blob/main/protobuf/schema/v1/schema.proto), or locally evaluate flags defined in a flagd [flag definition](https://github.com/open-feature/schemas/blob/main/json/flags.json).
44

55
## Installation
66
<!-- x-release-please-start-version -->
@@ -45,7 +45,7 @@ FlagdProvider flagdProvider = new FlagdProvider(
4545
.build());
4646
```
4747

48-
In the above example, in-process handlers attempt to connect to a sync service on address `localhost:8013` to obtain [flag definitions](https://github.com/open-feature/schemas/blob/main/json/flagd-definitions.json).
48+
In the above example, in-process handlers attempt to connect to a sync service on address `localhost:8013` to obtain [flag definitions](https://github.com/open-feature/schemas/blob/main/json/flags.json).
4949

5050
#### Offline mode
5151

providers/flagd/pom.xml

+18-3
Original file line numberDiff line numberDiff line change
@@ -228,16 +228,31 @@
228228
</configuration>
229229
</execution>
230230
<execution>
231-
<id>copy-schema-json</id>
231+
<id>copy-flags-json-schema</id>
232232
<phase>validate</phase>
233233
<goals>
234234
<goal>exec</goal>
235235
</goals>
236236
<configuration>
237-
<!-- run: cp schemas/json/flagd-definitions.json src/main/resources/ -->
237+
<!-- run: cp schemas/json/flags.json src/main/resources/ -->
238238
<executable>cp</executable>
239239
<arguments>
240-
<argument>schemas/json/flagd-definitions.json</argument>
240+
<argument>schemas/json/flags.json</argument>
241+
<argument>src/main/resources/</argument>
242+
</arguments>
243+
</configuration>
244+
</execution>
245+
<execution>
246+
<id>copy-flags-targeting-schema</id>
247+
<phase>validate</phase>
248+
<goals>
249+
<goal>exec</goal>
250+
</goals>
251+
<configuration>
252+
<!-- run: cp schemas/json/targeting.json src/main/resources/ -->
253+
<executable>cp</executable>
254+
<arguments>
255+
<argument>schemas/json/targeting.json</argument>
241256
<argument>src/main/resources/</argument>
242257
</arguments>
243258
</configuration>

providers/flagd/schemas

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/common/Util.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import java.util.concurrent.atomic.AtomicBoolean;
44

5+
import dev.openfeature.sdk.exceptions.GeneralError;
6+
57
/**
68
* Utils for flagd resolvers.
79
*/
@@ -21,7 +23,7 @@ public static void busyWaitAndCheck(final Long deadline, final AtomicBoolean che
2123

2224
do {
2325
if (deadline <= System.currentTimeMillis() - start) {
24-
throw new RuntimeException(
26+
throw new GeneralError(
2527
String.format("Deadline exceeded. Condition did not complete within the %d deadline", deadline));
2628
}
2729

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/grpc/EventStreamObserver.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import com.google.protobuf.Value;
44
import dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.Cache;
5-
import dev.openfeature.flagd.grpc.Schema.EventStreamResponse;
5+
import dev.openfeature.flagd.grpc.evaluation.Evaluation.EventStreamResponse;
66
import dev.openfeature.sdk.ProviderState;
77
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
88
import io.grpc.stub.StreamObserver;

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/grpc/GrpcConnector.java

+11-10
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
package dev.openfeature.contrib.providers.flagd.resolver.grpc;
22

3+
import java.util.Random;
4+
import java.util.concurrent.TimeUnit;
5+
import java.util.concurrent.atomic.AtomicBoolean;
6+
import java.util.function.Consumer;
7+
38
import dev.openfeature.contrib.providers.flagd.FlagdOptions;
4-
import dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.Cache;
59
import dev.openfeature.contrib.providers.flagd.resolver.common.ChannelBuilder;
610
import dev.openfeature.contrib.providers.flagd.resolver.common.Util;
7-
import dev.openfeature.flagd.grpc.Schema;
8-
import dev.openfeature.flagd.grpc.ServiceGrpc;
11+
import dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.Cache;
12+
import dev.openfeature.flagd.grpc.evaluation.Evaluation.EventStreamRequest;
13+
import dev.openfeature.flagd.grpc.evaluation.Evaluation.EventStreamResponse;
14+
import dev.openfeature.flagd.grpc.evaluation.ServiceGrpc;
915
import dev.openfeature.sdk.ProviderState;
1016
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
1117
import io.grpc.ManagedChannel;
1218
import io.grpc.stub.StreamObserver;
1319
import lombok.extern.slf4j.Slf4j;
1420

15-
import java.util.Random;
16-
import java.util.concurrent.TimeUnit;
17-
import java.util.concurrent.atomic.AtomicBoolean;
18-
import java.util.function.Consumer;
19-
2021
/**
2122
* Class that abstracts the gRPC communication with flagd.
2223
*/
@@ -116,9 +117,9 @@ public ServiceGrpc.ServiceBlockingStub getResolver() {
116117
*/
117118
private void observeEventStream() {
118119
while (this.eventStreamAttempt <= this.maxEventStreamRetries) {
119-
final StreamObserver<Schema.EventStreamResponse> responseObserver =
120+
final StreamObserver<EventStreamResponse> responseObserver =
120121
new EventStreamObserver(sync, this.cache, this::grpcStateConsumer);
121-
this.serviceStub.eventStream(Schema.EventStreamRequest.getDefaultInstance(), responseObserver);
122+
this.serviceStub.eventStream(EventStreamRequest.getDefaultInstance(), responseObserver);
122123

123124
try {
124125
synchronized (sync) {

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/grpc/GrpcResolver.java

+10-6
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@
2828
import dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.Cache;
2929
import dev.openfeature.contrib.providers.flagd.resolver.grpc.strategy.ResolveFactory;
3030
import dev.openfeature.contrib.providers.flagd.resolver.grpc.strategy.ResolveStrategy;
31-
import dev.openfeature.flagd.grpc.Schema;
31+
import dev.openfeature.flagd.grpc.evaluation.Evaluation.ResolveBooleanRequest;
32+
import dev.openfeature.flagd.grpc.evaluation.Evaluation.ResolveFloatRequest;
33+
import dev.openfeature.flagd.grpc.evaluation.Evaluation.ResolveIntRequest;
34+
import dev.openfeature.flagd.grpc.evaluation.Evaluation.ResolveObjectRequest;
35+
import dev.openfeature.flagd.grpc.evaluation.Evaluation.ResolveStringRequest;
3236
import dev.openfeature.sdk.EvaluationContext;
3337
import dev.openfeature.sdk.ImmutableMetadata;
3438
import dev.openfeature.sdk.MutableStructure;
@@ -92,7 +96,7 @@ public void shutdown() throws Exception {
9296
*/
9397
public ProviderEvaluation<Boolean> booleanEvaluation(String key, Boolean defaultValue,
9498
EvaluationContext ctx) {
95-
Schema.ResolveBooleanRequest request = Schema.ResolveBooleanRequest.newBuilder().buildPartial();
99+
ResolveBooleanRequest request = ResolveBooleanRequest.newBuilder().buildPartial();
96100

97101
return resolve(key, ctx, request, this.connector.getResolver()::resolveBoolean, null);
98102
}
@@ -102,7 +106,7 @@ public ProviderEvaluation<Boolean> booleanEvaluation(String key, Boolean default
102106
*/
103107
public ProviderEvaluation<String> stringEvaluation(String key, String defaultValue,
104108
EvaluationContext ctx) {
105-
Schema.ResolveStringRequest request = Schema.ResolveStringRequest.newBuilder().buildPartial();
109+
ResolveStringRequest request = ResolveStringRequest.newBuilder().buildPartial();
106110

107111
return resolve(key, ctx, request, this.connector.getResolver()::resolveString, null);
108112
}
@@ -112,7 +116,7 @@ public ProviderEvaluation<String> stringEvaluation(String key, String defaultVal
112116
*/
113117
public ProviderEvaluation<Double> doubleEvaluation(String key, Double defaultValue,
114118
EvaluationContext ctx) {
115-
Schema.ResolveFloatRequest request = Schema.ResolveFloatRequest.newBuilder().buildPartial();
119+
ResolveFloatRequest request = ResolveFloatRequest.newBuilder().buildPartial();
116120

117121
return resolve(key, ctx, request, this.connector.getResolver()::resolveFloat, null);
118122
}
@@ -123,7 +127,7 @@ public ProviderEvaluation<Double> doubleEvaluation(String key, Double defaultVal
123127
public ProviderEvaluation<Integer> integerEvaluation(String key, Integer defaultValue,
124128
EvaluationContext ctx) {
125129

126-
Schema.ResolveIntRequest request = Schema.ResolveIntRequest.newBuilder().buildPartial();
130+
ResolveIntRequest request = ResolveIntRequest.newBuilder().buildPartial();
127131

128132
return resolve(key, ctx, request, this.connector.getResolver()::resolveInt,
129133
(Object value) -> ((Long) value).intValue());
@@ -135,7 +139,7 @@ public ProviderEvaluation<Integer> integerEvaluation(String key, Integer default
135139
public ProviderEvaluation<Value> objectEvaluation(String key, Value defaultValue,
136140
EvaluationContext ctx) {
137141

138-
Schema.ResolveObjectRequest request = Schema.ResolveObjectRequest.newBuilder().buildPartial();
142+
ResolveObjectRequest request = ResolveObjectRequest.newBuilder().buildPartial();
139143

140144
return resolve(key, ctx, request, this.connector.getResolver()::resolveObject,
141145
(Object value) -> convertObjectResponse((Struct) value));

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/model/FlagParser.java

+29-29
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
package dev.openfeature.contrib.providers.flagd.resolver.process.model;
22

3+
import java.io.IOException;
4+
import java.net.URI;
5+
import java.util.HashMap;
6+
import java.util.Iterator;
7+
import java.util.Map;
8+
import java.util.Set;
9+
import java.util.regex.Pattern;
10+
311
import com.fasterxml.jackson.core.JsonParser;
412
import com.fasterxml.jackson.core.TreeNode;
513
import com.fasterxml.jackson.databind.ObjectMapper;
614
import com.networknt.schema.JsonSchema;
715
import com.networknt.schema.JsonSchemaFactory;
816
import com.networknt.schema.SpecVersion;
917
import com.networknt.schema.ValidationMessage;
18+
1019
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
1120
import lombok.extern.slf4j.Slf4j;
1221

13-
import java.io.ByteArrayOutputStream;
14-
import java.io.IOException;
15-
import java.io.InputStream;
16-
import java.util.HashMap;
17-
import java.util.Iterator;
18-
import java.util.Map;
19-
import java.util.Set;
20-
import java.util.regex.Pattern;
21-
2222
/**
2323
* flagd feature flag configuration parser.
2424
*/
@@ -29,7 +29,6 @@ public class FlagParser {
2929
private static final String FLAG_KEY = "flags";
3030
private static final String EVALUATOR_KEY = "$evaluators";
3131
private static final String REPLACER_FORMAT = "\"\\$ref\":(\\s)*\"%s\"";
32-
private static final String SCHEMA_RESOURCE = "flagd-definitions.json";
3332

3433
private static final ObjectMapper MAPPER = new ObjectMapper();
3534
private static final Map<String, Pattern> PATTERN_MAP = new HashMap<>();
@@ -40,37 +39,39 @@ private FlagParser() {
4039
}
4140

4241
static {
43-
try (InputStream schema = FlagParser.class.getClassLoader().getResourceAsStream(SCHEMA_RESOURCE)) {
44-
if (schema == null) {
45-
log.warn(String.format("Resource %s not found", SCHEMA_RESOURCE));
46-
} else {
47-
final ByteArrayOutputStream result = new ByteArrayOutputStream();
48-
byte[] buffer = new byte[512];
49-
for (int size; 0 < (size = schema.read(buffer)); ) {
50-
result.write(buffer, 0, size);
51-
}
52-
53-
JsonSchemaFactory instance = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
54-
SCHEMA_VALIDATOR = instance.getSchema(result.toString("UTF-8"));
55-
}
42+
try {
43+
// load both schemas from resources (root (flags.json) and referenced (targeting.json)
44+
// we don't want to resolve anything from the network
45+
Map<String, String> mappings = new HashMap<>();
46+
mappings.put("https://flagd.dev/schema/v0/targeting.json", "classpath:targeting.json");
47+
mappings.put("https://flagd.dev/schema/v0/flags.json", "classpath:flags.json");
48+
49+
SCHEMA_VALIDATOR = JsonSchemaFactory
50+
.getInstance(SpecVersion.VersionFlag.V7,
51+
builder -> builder
52+
.schemaMappers(schemaMappers -> schemaMappers.mappings(mappings)))
53+
.getSchema(new URI("https://flagd.dev/schema/v0/flags.json"));
5654
} catch (Throwable e) {
5755
// log only, do not throw
58-
log.warn(String.format("Error loading resource %s, schema validation will be skipped", SCHEMA_RESOURCE), e);
56+
log.warn(String.format("Error loading schema resources, schema validation will be skipped"));
5957
}
6058
}
6159

6260
/**
6361
* Parse {@link String} for feature flags.
6462
*/
65-
public static Map<String, FeatureFlag> parseString(final String configuration) throws IOException {
63+
public static Map<String, FeatureFlag> parseString(final String configuration, boolean throwIfInvalid)
64+
throws IOException {
6665
if (SCHEMA_VALIDATOR != null) {
6766
try (JsonParser parser = MAPPER.createParser(configuration)) {
6867
Set<ValidationMessage> validationMessages = SCHEMA_VALIDATOR.validate(parser.readValueAsTree());
6968

7069
if (!validationMessages.isEmpty()) {
71-
throw new IllegalArgumentException(
72-
String.format("Failed to parse configurations. %d validation error(s) reported.",
73-
validationMessages.size()));
70+
String message = String.format("Invalid flag configuration: %s", validationMessages.toArray());
71+
log.warn(message);
72+
if (throwIfInvalid) {
73+
throw new IllegalArgumentException(message);
74+
}
7475
}
7576
}
7677
}
@@ -128,5 +129,4 @@ private static String transposeEvaluators(final String configuration) throws IOE
128129
return replacedConfigurations;
129130
}
130131
}
131-
132132
}

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/storage/FlagStore.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,15 @@ public class FlagStore implements Storage {
3232
private final Map<String, FeatureFlag> flags = new HashMap<>();
3333

3434
private final Connector connector;
35+
private final boolean throwIfInvalid;
3536

3637
public FlagStore(final Connector connector) {
38+
this(connector, false);
39+
}
40+
41+
public FlagStore(final Connector connector, final boolean throwIfInvalid) {
3742
this.connector = connector;
43+
this.throwIfInvalid = throwIfInvalid;
3844
}
3945

4046
/**
@@ -94,7 +100,7 @@ private void streamerListener(final Connector connector) throws InterruptedExcep
94100
switch (take.getType()) {
95101
case DATA:
96102
try {
97-
Map<String, FeatureFlag> flagMap = FlagParser.parseString(take.getData());
103+
Map<String, FeatureFlag> flagMap = FlagParser.parseString(take.getData(), throwIfInvalid);
98104
writeLock.lock();
99105
try {
100106
flags.clear();

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/storage/connector/grpc/GrpcResponseModel.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
package dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.grpc;
22

3-
import dev.openfeature.flagd.sync.SyncService;
3+
import dev.openfeature.flagd.grpc.sync.Sync.SyncFlagsResponse;
44
import lombok.Getter;
55

66
@Getter
77
class GrpcResponseModel {
8-
private final SyncService.SyncFlagsResponse syncFlagsResponse;
8+
private final SyncFlagsResponse syncFlagsResponse;
99
private final Throwable error;
1010
private final boolean complete;
1111

1212
public GrpcResponseModel(final Throwable error) {
1313
this(null, error, false);
1414
}
1515

16-
public GrpcResponseModel(final SyncService.SyncFlagsResponse syncFlagsResponse) {
16+
public GrpcResponseModel(final SyncFlagsResponse syncFlagsResponse) {
1717
this(syncFlagsResponse, null, false);
1818
}
1919

2020
public GrpcResponseModel(final Boolean complete) {
2121
this(null, null, complete);
2222
}
2323

24-
GrpcResponseModel(SyncService.SyncFlagsResponse syncFlagsResponse, Throwable error, boolean complete) {
24+
GrpcResponseModel(SyncFlagsResponse syncFlagsResponse, Throwable error, boolean complete) {
2525
this.syncFlagsResponse = syncFlagsResponse;
2626
this.error = error;
2727
this.complete = complete;

0 commit comments

Comments
 (0)