Skip to content

Commit 557074e

Browse files
feat: [go-feature-flag] Support flag metadata (#367)
Signed-off-by: Thomas Poignant <[email protected]>
1 parent dd7e43d commit 557074e

File tree

14 files changed

+134
-24
lines changed

14 files changed

+134
-24
lines changed

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

+33-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
package dev.openfeature.contrib.providers.gofeatureflag;
22

3-
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
4-
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
5-
63
import com.fasterxml.jackson.databind.DeserializationFeature;
74
import com.fasterxml.jackson.databind.ObjectMapper;
85
import com.fasterxml.jackson.databind.SerializationFeature;
@@ -16,6 +13,7 @@
1613
import dev.openfeature.sdk.EvaluationContext;
1714
import dev.openfeature.sdk.FeatureProvider;
1815
import dev.openfeature.sdk.Hook;
16+
import dev.openfeature.sdk.ImmutableMetadata;
1917
import dev.openfeature.sdk.Metadata;
2018
import dev.openfeature.sdk.MutableStructure;
2119
import dev.openfeature.sdk.ProviderEvaluation;
@@ -34,13 +32,17 @@
3432
import okhttp3.RequestBody;
3533
import okhttp3.Response;
3634
import okhttp3.ResponseBody;
35+
3736
import java.io.IOException;
3837
import java.time.Instant;
3938
import java.util.List;
4039
import java.util.Map;
4140
import java.util.concurrent.TimeUnit;
4241
import java.util.stream.Collectors;
4342

43+
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
44+
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
45+
4446
/**
4547
* GoFeatureFlagProvider is the JAVA provider implementation for the feature flag solution GO Feature Flag.
4648
*/
@@ -226,14 +228,15 @@ private <T> ProviderEvaluation<T> resolveEvaluationGoFeatureFlagProxy(
226228

227229
if (flagValue.getClass() != expectedType) {
228230
throw new TypeMismatchError("Flag value " + key + " had unexpected type "
229-
+ flagValue.getClass() + ", expected " + expectedType + ".");
231+
+ flagValue.getClass() + ", expected " + expectedType + ".");
230232
}
231233

232234
return ProviderEvaluation.<T>builder()
233235
.errorCode(mapErrorCode(goffResp.getErrorCode()))
234236
.reason(goffResp.getReason())
235237
.value(flagValue)
236238
.variant(goffResp.getVariationType())
239+
.flagMetadata(this.convertFlagMetadata(goffResp.getMetadata()))
237240
.build();
238241

239242
}
@@ -256,6 +259,32 @@ private ErrorCode mapErrorCode(String errorCode) {
256259
}
257260
}
258261

262+
/**
263+
* convertFlagMetadata is converting the flagMetadata object received from the server
264+
* to an ImmutableMetadata format known by Open Feature.
265+
*
266+
* @param flagMetadata - metadata received from the server
267+
* @return a converted metadata object.
268+
*/
269+
private ImmutableMetadata convertFlagMetadata(Map<String, Object> flagMetadata) {
270+
ImmutableMetadata.ImmutableMetadataBuilder builder = ImmutableMetadata.builder();
271+
flagMetadata.forEach((k, v) -> {
272+
if (v instanceof Long) {
273+
builder.addLong(k, (Long) v);
274+
} else if (v instanceof Integer) {
275+
builder.addInteger(k, (Integer) v);
276+
} else if (v instanceof Float) {
277+
builder.addFloat(k, (Float) v);
278+
} else if (v instanceof Double) {
279+
builder.addDouble(k, (Double) v);
280+
} else if (v instanceof Boolean) {
281+
builder.addBoolean(k, (Boolean) v);
282+
} else {
283+
builder.addString(k, v.toString());
284+
}
285+
});
286+
return builder.build();
287+
}
259288

260289
/**
261290
* convertValue is converting the object return by the proxy response in the right type.

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

+5-5
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ public class GoFeatureFlagProviderOptions {
4040
private Long keepAliveDuration;
4141

4242
/**
43-
* (optional) If the relay proxy is configured to authenticate the requests, you should provide
44-
* an API Key to the provider.
45-
* Please ask the administrator of the relay proxy to provide an API Key.
46-
* (This feature is available only if you are using GO Feature Flag relay proxy v1.7.0 or above)
47-
* Default: null
43+
* (optional) If the relay proxy is configured to authenticate the requests, you should provide
44+
* an API Key to the provider.
45+
* Please ask the administrator of the relay proxy to provide an API Key.
46+
* (This feature is available only if you are using GO Feature Flag relay proxy v1.7.0 or above)
47+
* Default: null
4848
*/
4949
@Getter
5050
private String apiKey;

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

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import lombok.Setter;
55
import lombok.ToString;
66

7+
import java.util.Map;
8+
79
/**
810
* GoFeatureFlagResponse is the response returned by the relay proxy.
911
*/
@@ -18,6 +20,7 @@ public class GoFeatureFlagResponse {
1820
private String reason;
1921
private String errorCode;
2022
private Object value;
23+
private Map<String, Object> metadata;
2124
}
2225

2326

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

+44-6
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,19 @@
77
import java.util.Arrays;
88
import java.util.List;
99

10+
import dev.openfeature.sdk.ImmutableMetadata;
11+
import dev.openfeature.sdk.MutableContext;
12+
import dev.openfeature.sdk.MutableStructure;
13+
import dev.openfeature.sdk.ProviderEvaluation;
14+
import dev.openfeature.sdk.Reason;
15+
import dev.openfeature.sdk.Value;
1016
import org.junit.jupiter.api.AfterEach;
1117
import org.junit.jupiter.api.BeforeEach;
1218
import org.junit.jupiter.api.Test;
1319

1420
import dev.openfeature.contrib.providers.gofeatureflag.exception.InvalidEndpoint;
1521
import dev.openfeature.contrib.providers.gofeatureflag.exception.InvalidOptions;
1622
import dev.openfeature.contrib.providers.gofeatureflag.exception.InvalidTargetingKey;
17-
import dev.openfeature.sdk.MutableContext;
18-
import dev.openfeature.sdk.MutableStructure;
19-
import dev.openfeature.sdk.ProviderEvaluation;
20-
import dev.openfeature.sdk.Reason;
21-
import dev.openfeature.sdk.Value;
2223
import dev.openfeature.sdk.exceptions.FlagNotFoundError;
2324
import dev.openfeature.sdk.exceptions.GeneralError;
2425
import dev.openfeature.sdk.exceptions.TypeMismatchError;
@@ -29,7 +30,11 @@
2930
import okhttp3.mockwebserver.MockWebServer;
3031
import okhttp3.mockwebserver.RecordedRequest;
3132

32-
import static org.junit.jupiter.api.Assertions.*;
33+
import static org.junit.jupiter.api.Assertions.assertAll;
34+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
35+
import static org.junit.jupiter.api.Assertions.assertEquals;
36+
import static org.junit.jupiter.api.Assertions.assertNull;
37+
import static org.junit.jupiter.api.Assertions.assertThrows;
3338

3439
class GoFeatureFlagProviderTest {
3540
// Dispatcher is the configuration of the mock server to test the provider.
@@ -56,6 +61,12 @@ public MockResponse dispatch(RecordedRequest request) {
5661
private HttpUrl baseUrl;
5762
private MutableContext evaluationContext;
5863

64+
private final static ImmutableMetadata defaultMetadata =
65+
ImmutableMetadata.builder()
66+
.addString("pr_link", "https://github.com/thomaspoignant/go-feature-flag/pull/916")
67+
.addInteger("version", 1)
68+
.build();
69+
5970
@BeforeEach
6071
void beforeEach() throws IOException {
6172
this.server = new MockWebServer();
@@ -153,6 +164,9 @@ void should_resolve_a_valid_boolean_flag_with_TARGETING_MATCH_reason() throws In
153164
assertNull(res.getErrorCode());
154165
assertEquals(Reason.TARGETING_MATCH.toString(), res.getReason());
155166
assertEquals("True", res.getVariant());
167+
assertAll("Test flagMetadata",
168+
() -> assertEquals(defaultMetadata.getInteger("version"), res.getFlagMetadata().getInteger("version")),
169+
() -> assertEquals(defaultMetadata.getString("pr_link"), res.getFlagMetadata().getString("pr_link")));
156170
}
157171

158172
@Test
@@ -163,6 +177,9 @@ void should_return_custom_reason_if_returned_by_relay_proxy() throws InvalidOpti
163177
assertNull(res.getErrorCode());
164178
assertEquals("CUSTOM_REASON", res.getReason());
165179
assertEquals("True", res.getVariant());
180+
assertAll("Test flagMetadata",
181+
() -> assertEquals(defaultMetadata.getInteger("version"), res.getFlagMetadata().getInteger("version")),
182+
() -> assertEquals(defaultMetadata.getString("pr_link"), res.getFlagMetadata().getString("pr_link")));
166183
}
167184

168185
@Test
@@ -187,6 +204,9 @@ void should_resolve_a_valid_string_flag_with_TARGETING_MATCH_reason() throws Inv
187204
assertNull(res.getErrorCode());
188205
assertEquals(Reason.TARGETING_MATCH.toString(), res.getReason());
189206
assertEquals("True", res.getVariant());
207+
assertAll("Test flagMetadata",
208+
() -> assertEquals(defaultMetadata.getInteger("version"), res.getFlagMetadata().getInteger("version")),
209+
() -> assertEquals(defaultMetadata.getString("pr_link"), res.getFlagMetadata().getString("pr_link")));
190210
}
191211

192212
@Test
@@ -211,6 +231,9 @@ void should_resolve_a_valid_integer_flag_with_TARGETING_MATCH_reason() throws In
211231
assertNull(res.getErrorCode());
212232
assertEquals(Reason.TARGETING_MATCH.toString(), res.getReason());
213233
assertEquals("True", res.getVariant());
234+
assertAll("Test flagMetadata",
235+
() -> assertEquals(defaultMetadata.getInteger("version"), res.getFlagMetadata().getInteger("version")),
236+
() -> assertEquals(defaultMetadata.getString("pr_link"), res.getFlagMetadata().getString("pr_link")));
214237
}
215238

216239
@Test
@@ -235,6 +258,9 @@ void should_resolve_a_valid_double_flag_with_TARGETING_MATCH_reason() throws Inv
235258
assertNull(res.getErrorCode());
236259
assertEquals(Reason.TARGETING_MATCH.toString(), res.getReason());
237260
assertEquals("True", res.getVariant());
261+
assertAll("Test flagMetadata",
262+
() -> assertEquals(defaultMetadata.getInteger("version"), res.getFlagMetadata().getInteger("version")),
263+
() -> assertEquals(defaultMetadata.getString("pr_link"), res.getFlagMetadata().getString("pr_link")));
238264
}
239265

240266
@Test
@@ -254,6 +280,9 @@ void should_resolve_a_valid_value_flag_with_TARGETING_MATCH_reason() throws Inva
254280
assertNull(res.getErrorCode());
255281
assertEquals(Reason.TARGETING_MATCH.toString(), res.getReason());
256282
assertEquals("True", res.getVariant());
283+
assertAll("Test flagMetadata",
284+
() -> assertEquals(defaultMetadata.getInteger("version"), res.getFlagMetadata().getInteger("version")),
285+
() -> assertEquals(defaultMetadata.getString("pr_link"), res.getFlagMetadata().getString("pr_link")));
257286
}
258287

259288
@Test
@@ -265,6 +294,9 @@ void should_wrap_into_value_if_wrong_type() throws InvalidOptions {
265294
assertNull(res.getErrorCode());
266295
assertEquals(Reason.TARGETING_MATCH.toString(), res.getReason());
267296
assertEquals("True", res.getVariant());
297+
assertAll("Test flagMetadata",
298+
() -> assertEquals(defaultMetadata.getInteger("version"), res.getFlagMetadata().getInteger("version")),
299+
() -> assertEquals(defaultMetadata.getString("pr_link"), res.getFlagMetadata().getString("pr_link")));
268300
}
269301

270302
@Test
@@ -296,6 +328,9 @@ void should_throw_an_error_if_no_targeting_key() throws InvalidOptions {
296328
assertNull(res.getErrorCode());
297329
assertEquals(Reason.TARGETING_MATCH.toString(), res.getReason());
298330
assertEquals("True", res.getVariant());
331+
assertAll("Test flagMetadata",
332+
() -> assertEquals(defaultMetadata.getInteger("version"), res.getFlagMetadata().getInteger("version")),
333+
() -> assertEquals(defaultMetadata.getString("pr_link"), res.getFlagMetadata().getString("pr_link")));
299334
}
300335

301336
@Test
@@ -306,6 +341,9 @@ void should_not_fail_if_receive_an_unknown_field_in_response() throws InvalidOpt
306341
assertNull(res.getErrorCode());
307342
assertEquals(Reason.TARGETING_MATCH.toString(), res.getReason());
308343
assertEquals("True", res.getVariant());
344+
assertAll("Test flagMetadata",
345+
() -> assertEquals(defaultMetadata.getInteger("version"), res.getFlagMetadata().getInteger("version")),
346+
() -> assertEquals(defaultMetadata.getString("pr_link"), res.getFlagMetadata().getString("pr_link")));
309347
}
310348

311349
private String readMockResponse(String filename) throws IOException {

providers/go-feature-flag/src/test/resources/mock_responses/bool_targeting_match.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,9 @@
55
"version": 0,
66
"reason": "TARGETING_MATCH",
77
"errorCode": "",
8-
"value": true
8+
"value": true,
9+
"metadata": {
10+
"pr_link": "https://github.com/thomaspoignant/go-feature-flag/pull/916",
11+
"version": 1
12+
}
913
}

providers/go-feature-flag/src/test/resources/mock_responses/disabled.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,9 @@
55
"version": 0,
66
"reason": "DISABLED",
77
"errorCode": "",
8-
"value": true
8+
"value": true,
9+
"metadata": {
10+
"pr_link": "https://github.com/thomaspoignant/go-feature-flag/pull/916",
11+
"version": 1
12+
}
913
}

providers/go-feature-flag/src/test/resources/mock_responses/double_key.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,9 @@
55
"version": 0,
66
"reason": "TARGETING_MATCH",
77
"errorCode": "",
8-
"value": 100.25
8+
"value": 100.25,
9+
"metadata": {
10+
"pr_link": "https://github.com/thomaspoignant/go-feature-flag/pull/916",
11+
"version": 1
12+
}
913
}

providers/go-feature-flag/src/test/resources/mock_responses/flag_not_found.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,9 @@
55
"version": 0,
66
"reason": "ERROR",
77
"errorCode": "FLAG_NOT_FOUND",
8-
"value": "false"
8+
"value": "false",
9+
"metadata": {
10+
"pr_link": "https://github.com/thomaspoignant/go-feature-flag/pull/916",
11+
"version": 1
12+
}
913
}

providers/go-feature-flag/src/test/resources/mock_responses/integer_key.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,9 @@
55
"version": 0,
66
"reason": "TARGETING_MATCH",
77
"errorCode": "",
8-
"value": 100
8+
"value": 100,
9+
"metadata": {
10+
"pr_link": "https://github.com/thomaspoignant/go-feature-flag/pull/916",
11+
"version": 1
12+
}
913
}

providers/go-feature-flag/src/test/resources/mock_responses/list_key.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,9 @@
1111
"test2",
1212
"false",
1313
"test3"
14-
]
14+
],
15+
"metadata": {
16+
"pr_link": "https://github.com/thomaspoignant/go-feature-flag/pull/916",
17+
"version": 1
18+
}
1519
}

providers/go-feature-flag/src/test/resources/mock_responses/object_key.json

+4
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,9 @@
1111
"test3": 123.3,
1212
"test4": 1,
1313
"test5": null
14+
},
15+
"metadata": {
16+
"pr_link": "https://github.com/thomaspoignant/go-feature-flag/pull/916",
17+
"version": 1
1418
}
1519
}

providers/go-feature-flag/src/test/resources/mock_responses/string_key.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,9 @@
55
"version": 0,
66
"reason": "TARGETING_MATCH",
77
"errorCode": "",
8-
"value": "CC0000"
8+
"value": "CC0000",
9+
"metadata": {
10+
"pr_link": "https://github.com/thomaspoignant/go-feature-flag/pull/916",
11+
"version": 1
12+
}
913
}

providers/go-feature-flag/src/test/resources/mock_responses/unknown_field.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,9 @@
77
"errorCode": "",
88
"value": true,
99
"unknown_field": true,
10-
"unknown_field2": "unknown_field2"
10+
"unknown_field2": "unknown_field2",
11+
"metadata": {
12+
"pr_link": "https://github.com/thomaspoignant/go-feature-flag/pull/916",
13+
"version": 1
14+
}
1115
}

providers/go-feature-flag/src/test/resources/mock_responses/unknown_reason.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,9 @@
55
"version": 0,
66
"reason": "CUSTOM_REASON",
77
"errorCode": "",
8-
"value": true
8+
"value": true,
9+
"metadata": {
10+
"pr_link": "https://github.com/thomaspoignant/go-feature-flag/pull/916",
11+
"version": 1
12+
}
913
}

0 commit comments

Comments
 (0)