Skip to content

Commit a741568

Browse files
liran2000toddbaert
andauthored
feat: In-memory provider for e2e testing and minimal usage (open-feature#546)
* Adds InMemoryProvider to enable simple testing and basic usage Signed-off-by: liran2000 <liran2000@gmail.com> Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
1 parent 6c52ee4 commit a741568

File tree

13 files changed

+489
-69
lines changed

13 files changed

+489
-69
lines changed

.github/workflows/pullrequest.yml

-7
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,6 @@ permissions:
88
jobs:
99
build:
1010
runs-on: ubuntu-latest
11-
# TODO: this can be removed with https://github.com/open-feature/java-sdk/issues/523
12-
services:
13-
flagd:
14-
image: ghcr.io/open-feature/flagd-testbed:latest
15-
ports:
16-
- 8013:8013
17-
1811
steps:
1912
- name: Check out the code
2013
uses: actions/checkout@96f53100ba2a5449eb71d2e6604bbcd94b9449b5

CONTRIBUTING.md

+2-7
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,9 @@ If you're adding tests to cover something in the spec, use the `@Specification`
1818

1919
## End-to-End Tests
2020

21-
<!-- TODO: this section should be updated with https://github.com/open-feature/java-sdk/issues/523 -->
21+
The continuous integration runs a set of [gherkin e2e tests](https://github.com/open-feature/test-harness/blob/main/features/evaluation.feature) using `InMemoryProvider`.
2222

23-
The continuous integration runs a set of [gherkin e2e tests](https://github.com/open-feature/test-harness/blob/main/features/evaluation.feature) using [`flagd`](https://github.com/open-feature/flagd). These tests do not run with the default maven profile. If you'd like to run them locally, you can start the flagd testbed with
24-
25-
```
26-
docker run -p 8013:8013 ghcr.io/open-feature/flagd-testbed:latest
27-
```
28-
and then run
23+
to run alone:
2924
```
3025
mvn test -P e2e-test
3126
```

pom.xml

+28-39
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111
<maven.compiler.source>1.8</maven.compiler.source>
1212
<maven.compiler.target>${maven.compiler.source}</maven.compiler.target>
1313
<junit.jupiter.version>5.10.0</junit.jupiter.version>
14-
<!-- exclusion expression for e2e tests -->
15-
<testExclusions>**/e2e/*.java</testExclusions>
1614
<module-name>${groupId}.${artifactId}</module-name>
1715
</properties>
1816

@@ -21,10 +19,10 @@
2119
<url>https://openfeature.dev</url>
2220
<developers>
2321
<developer>
24-
<id>abrahms</id>
25-
<name>Justin Abrahms</name>
26-
<organization>eBay</organization>
27-
<url>https://justin.abrah.ms/</url>
22+
<id>abrahms</id>
23+
<name>Justin Abrahms</name>
24+
<organization>eBay</organization>
25+
<url>https://justin.abrah.ms/</url>
2826
</developer>
2927
</developers>
3028
<licenses>
@@ -120,9 +118,9 @@
120118
</dependency>
121119

122120
<dependency>
123-
<groupId>io.cucumber</groupId>
124-
<artifactId>cucumber-junit-platform-engine</artifactId>
125-
<scope>test</scope>
121+
<groupId>io.cucumber</groupId>
122+
<artifactId>cucumber-junit-platform-engine</artifactId>
123+
<scope>test</scope>
126124
</dependency>
127125

128126
<dependency>
@@ -139,39 +137,33 @@
139137
<scope>test</scope>
140138
</dependency>
141139

142-
<dependency>
143-
<groupId>dev.openfeature.contrib.providers</groupId>
144-
<artifactId>flagd</artifactId>
145-
<version>0.5.10</version>
146-
<scope>test</scope>
147-
</dependency>
148-
149140
<dependency>
150141
<groupId>org.awaitility</groupId>
151142
<artifactId>awaitility</artifactId>
152143
<version>4.2.0</version>
153144
<scope>test</scope>
154145
</dependency>
146+
155147
</dependencies>
156148

157149
<dependencyManagement>
158150
<dependencies>
159151

160-
<dependency>
161-
<groupId>io.cucumber</groupId>
162-
<artifactId>cucumber-bom</artifactId>
163-
<version>7.13.0</version>
164-
<type>pom</type>
165-
<scope>import</scope>
166-
</dependency>
167-
168-
<dependency>
169-
<groupId>org.junit</groupId>
170-
<artifactId>junit-bom</artifactId>
171-
<version>5.10.0</version>
172-
<type>pom</type>
173-
<scope>import</scope>
174-
</dependency>
152+
<dependency>
153+
<groupId>io.cucumber</groupId>
154+
<artifactId>cucumber-bom</artifactId>
155+
<version>7.13.0</version>
156+
<type>pom</type>
157+
<scope>import</scope>
158+
</dependency>
159+
160+
<dependency>
161+
<groupId>org.junit</groupId>
162+
<artifactId>junit-bom</artifactId>
163+
<version>5.10.0</version>
164+
<type>pom</type>
165+
<scope>import</scope>
166+
</dependency>
175167

176168
</dependencies>
177169
</dependencyManagement>
@@ -203,7 +195,7 @@
203195
</execution>
204196
</executions>
205197
</plugin>
206-
198+
207199
<plugin>
208200
<artifactId>maven-dependency-plugin</artifactId>
209201
<version>3.6.0</version>
@@ -249,7 +241,7 @@
249241
<excludes>
250242
<!-- tests to exclude -->
251243
<exclude>${testExclusions}</exclude>
252-
</excludes>
244+
</excludes>
253245
</configuration>
254246
</plugin>
255247

@@ -271,7 +263,7 @@
271263

272264
<executions>
273265
<execution>
274-
<id>prepare-agent</id>
266+
<id>prepare-agent</id>
275267
<goals>
276268
<goal>prepare-agent</goal>
277269
</goals>
@@ -319,7 +311,7 @@
319311
</rule>
320312
</rules>
321313
</configuration>
322-
</execution>
314+
</execution>
323315

324316
</executions>
325317
</plugin>
@@ -496,14 +488,11 @@
496488
</profile>
497489

498490
<profile>
499-
<!-- this profile handles running the flagd e2e tests -->
500-
<!-- TODO: this profile can likely be removed with TODO: this section should be updated with https://github.com/open-feature/java-sdk/issues/523 -->
501-
<!-- TODO: we should pull the submodule and run these tests unconditionall once flagd isn't required -->
502491
<id>e2e-test</id>
503492
<properties>
504493
<!-- run the e2e tests by clearing the exclusions -->
505494
<testExclusions/>
506-
</properties>
495+
</properties>
507496
<build>
508497
<plugins>
509498
<!-- pull the gherkin tests as a git submodule -->

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

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public static <T> FlagEvaluationDetails<T> from(ProviderEvaluation<T> providerEv
4040
.value(providerEval.getValue())
4141
.variant(providerEval.getVariant())
4242
.reason(providerEval.getReason())
43+
.errorMessage(providerEval.getErrorMessage())
4344
.errorCode(providerEval.getErrorCode())
4445
.flagMetadata(providerEval.getFlagMetadata())
4546
.build();

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

+14
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import java.util.function.Function;
1010
import java.util.stream.Collectors;
1111

12+
import static dev.openfeature.sdk.Value.objectToValue;
13+
1214
/**
1315
* {@link Structure} represents a potentially nested object type which is used to represent
1416
* structured data.
@@ -123,4 +125,16 @@ default <T extends Structure> Map<String, Value> merge(Function<Map<String, Valu
123125
}
124126
return merged;
125127
}
128+
129+
/**
130+
* Transform an object map to a {@link Structure} type.
131+
*
132+
* @param map map of objects
133+
* @return a Structure object in the SDK format
134+
*/
135+
static Structure mapToStructure(Map<String, Object> map) {
136+
return new MutableStructure(map.entrySet().stream()
137+
.filter(e -> e.getValue() != null)
138+
.collect(Collectors.toMap(Map.Entry::getKey, e -> objectToValue(e.getValue()))));
139+
}
126140
}

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

+37
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
import java.util.Map;
66
import java.util.stream.Collectors;
77

8+
import dev.openfeature.sdk.exceptions.TypeMismatchError;
89
import lombok.EqualsAndHashCode;
910
import lombok.SneakyThrows;
1011
import lombok.ToString;
1112

13+
import static dev.openfeature.sdk.Structure.mapToStructure;
14+
1215
/**
1316
* Values serve as a generic return type for structure data from providers.
1417
* Providers may deal in JSON, protobuf, XML or some other data-interchange format.
@@ -280,4 +283,38 @@ protected Value clone() {
280283
}
281284
return new Value(this.asObject());
282285
}
286+
287+
/**
288+
* Wrap an object into a Value.
289+
*
290+
* @param object the object to wrap
291+
* @return the wrapped object
292+
*/
293+
public static Value objectToValue(Object object) {
294+
if (object instanceof Value) {
295+
return (Value) object;
296+
} else if (object == null) {
297+
return null;
298+
} else if (object instanceof String) {
299+
return new Value((String) object);
300+
} else if (object instanceof Boolean) {
301+
return new Value((Boolean) object);
302+
} else if (object instanceof Integer) {
303+
return new Value((Integer) object);
304+
} else if (object instanceof Double) {
305+
return new Value((Double) object);
306+
} else if (object instanceof Structure) {
307+
return new Value((Structure) object);
308+
} else if (object instanceof List) {
309+
return new Value(((List<Object>) object).stream()
310+
.map(o -> objectToValue(o))
311+
.collect(Collectors.toList()));
312+
} else if (object instanceof Instant) {
313+
return new Value((Instant) object);
314+
} else if (object instanceof Map) {
315+
return new Value(mapToStructure((Map<String, Object>) object));
316+
} else {
317+
throw new TypeMismatchError("Flag value " + object + " had unexpected type " + object.getClass() + ".");
318+
}
319+
}
283320
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package dev.openfeature.sdk.providers.memory;
2+
3+
import dev.openfeature.sdk.EvaluationContext;
4+
5+
/**
6+
* Context evaluator - use for resolving flag according to evaluation context, for handling targeting.
7+
* @param <T> expected value type
8+
*/
9+
public interface ContextEvaluator<T> {
10+
11+
T evaluate(Flag flag, EvaluationContext evaluationContext);
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package dev.openfeature.sdk.providers.memory;
2+
3+
import lombok.Builder;
4+
import lombok.Getter;
5+
import lombok.Singular;
6+
import lombok.ToString;
7+
8+
import java.util.Map;
9+
10+
/**
11+
* Flag representation for the in-memory provider.
12+
*/
13+
@ToString
14+
@Builder
15+
@Getter
16+
public class Flag<T> {
17+
@Singular
18+
private Map<String, Object> variants;
19+
private String defaultVariant;
20+
private ContextEvaluator<T> contextEvaluator;
21+
}

0 commit comments

Comments
 (0)