From d127cb5567bb26ae1c362e0e998e4bff0768b692 Mon Sep 17 00:00:00 2001 From: chrfwow Date: Thu, 2 Jan 2025 09:40:06 +0100 Subject: [PATCH 1/3] feat: Update in-process resolver to support flag metadata #1102 Signed-off-by: christian.lutnik --- providers/flagd/schemas | 2 +- .../resolver/process/InProcessResolver.java | 75 +++++++-- .../resolver/process/model/FeatureFlag.java | 6 +- .../process/InProcessResolverTest.java | 142 +++++++++++++----- .../flagd/resolver/process/MockFlags.java | 22 +-- .../flagd/resolver/process/TestUtils.java | 1 + .../process/model/FlagParserTest.java | 37 ++++- .../flagConfigurations/invalid-metadata.json | 21 +++ .../flagConfigurations/valid-long.json | 7 +- .../flagConfigurations/valid-simple.json | 7 +- .../FlagsmithProviderTest.java | 24 +-- 11 files changed, 264 insertions(+), 80 deletions(-) create mode 100644 providers/flagd/src/test/resources/flagConfigurations/invalid-metadata.json diff --git a/providers/flagd/schemas b/providers/flagd/schemas index b81a56eea..58aeed308 160000 --- a/providers/flagd/schemas +++ b/providers/flagd/schemas @@ -1 +1 @@ -Subproject commit b81a56eea3b2c4c543a50d4f7f79a8f32592a0af +Subproject commit 58aeed308d1a851ce47f17266128b99b28761af6 diff --git a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java index 39c77f01b..8a5fa99c8 100644 --- a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java +++ b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java @@ -2,6 +2,7 @@ import static dev.openfeature.contrib.providers.flagd.resolver.process.model.FeatureFlag.EMPTY_TARGETING_STRING; +import java.util.Map; import java.util.function.Consumer; import java.util.function.Supplier; @@ -39,8 +40,9 @@ public class InProcessResolver implements Resolver { private final Consumer onConnectionEvent; private final Operator operator; private final long deadline; - private final ImmutableMetadata metadata; + private final ImmutableMetadata fallBackMetadata; private final Supplier connectedSupplier; + private final String scope; /** * Resolves flag values using @@ -54,16 +56,21 @@ public class InProcessResolver implements Resolver { * connection/stream */ public InProcessResolver(FlagdOptions options, final Supplier connectedSupplier, - Consumer onConnectionEvent) { + Consumer onConnectionEvent) { this.flagStore = new FlagStore(getConnector(options)); this.deadline = options.getDeadline(); this.onConnectionEvent = onConnectionEvent; this.operator = new Operator(); this.connectedSupplier = connectedSupplier; - this.metadata = options.getSelector() == null ? null - : ImmutableMetadata.builder() - .addString("scope", options.getSelector()) - .build(); + if (options.getSelector() == null) { + this.scope = null; + this.fallBackMetadata = null; + } else { + this.scope = options.getSelector(); + this.fallBackMetadata = ImmutableMetadata.builder() + .addString("scope", this.scope) + .build(); + } } /** @@ -113,8 +120,11 @@ public void shutdown() throws InterruptedException { /** * Resolve a boolean flag. */ - public ProviderEvaluation booleanEvaluation(String key, Boolean defaultValue, - EvaluationContext ctx) { + public ProviderEvaluation booleanEvaluation( + String key, + Boolean defaultValue, + EvaluationContext ctx + ) { return resolve(Boolean.class, key, ctx); } @@ -172,6 +182,7 @@ private ProviderEvaluation resolve(Class type, String key, EvaluationC return ProviderEvaluation.builder() .errorMessage("flag: " + key + " not found") .errorCode(ErrorCode.FLAG_NOT_FOUND) + .flagMetadata(fallBackMetadata) .build(); } @@ -180,6 +191,7 @@ private ProviderEvaluation resolve(Class type, String key, EvaluationC return ProviderEvaluation.builder() .errorMessage("flag: " + key + " is disabled") .errorCode(ErrorCode.FLAG_NOT_FOUND) + .flagMetadata(getFlagMetadata(flag)) .build(); } @@ -226,12 +238,51 @@ private ProviderEvaluation resolve(Class type, String key, EvaluationC throw new TypeMismatchError(message); } - final ProviderEvaluation.ProviderEvaluationBuilder evaluationBuilder = ProviderEvaluation.builder() + return ProviderEvaluation.builder() .value((T) value) .variant(resolvedVariant) - .reason(reason); + .reason(reason) + .flagMetadata(getFlagMetadata(flag)) + .build(); + } + + private ImmutableMetadata getFlagMetadata(FeatureFlag flag) { + if (flag == null) { + return fallBackMetadata; + } + + ImmutableMetadata.ImmutableMetadataBuilder metadataBuilder = ImmutableMetadata.builder(); + if (scope != null) { + metadataBuilder.addString("scope", scope); + } + + for (Map.Entry entry : flag.getMetadata().entrySet()) { + Object value = entry.getValue(); + if (value instanceof Number) { + if (value instanceof Long) { + metadataBuilder.addLong(entry.getKey(), (Long) value); + continue; + } else if (value instanceof Double) { + metadataBuilder.addDouble(entry.getKey(), (Double) value); + continue; + } else if (value instanceof Integer) { + metadataBuilder.addInteger(entry.getKey(), (Integer) value); + continue; + } else if (value instanceof Float) { + metadataBuilder.addFloat(entry.getKey(), (Float) value); + continue; + } + } else if (value instanceof Boolean) { + metadataBuilder.addBoolean(entry.getKey(), (Boolean) value); + continue; + } else if (value instanceof String) { + metadataBuilder.addString(entry.getKey(), (String) value); + continue; + } + throw new IllegalArgumentException("The type of the Metadata entry with key " + entry.getKey() + + " and value " + entry.getValue() + " is not supported"); + } - return this.metadata == null ? evaluationBuilder.build() - : evaluationBuilder.flagMetadata(this.metadata).build(); + return metadataBuilder.build(); } } diff --git a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/model/FeatureFlag.java b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/model/FeatureFlag.java index 4e687c369..b08d1e6ed 100644 --- a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/model/FeatureFlag.java +++ b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/model/FeatureFlag.java @@ -25,6 +25,7 @@ public class FeatureFlag { private final String defaultVariant; private final Map variants; private final String targeting; + private final Map metadata; /** * Construct a flagd feature flag. @@ -33,11 +34,14 @@ public class FeatureFlag { public FeatureFlag(@JsonProperty("state") String state, @JsonProperty("defaultVariant") String defaultVariant, @JsonProperty("variants") Map variants, - @JsonProperty("targeting") @JsonDeserialize(using = StringSerializer.class) String targeting) { + @JsonProperty("targeting") @JsonDeserialize(using = StringSerializer.class) String targeting, + @JsonProperty("metadata") Map metadata + ) { this.state = state; this.defaultVariant = defaultVariant; this.variants = variants; this.targeting = targeting; + this.metadata = metadata; } /** diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolverTest.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolverTest.java index 4b9bd824e..362330ae5 100644 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolverTest.java +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolverTest.java @@ -10,6 +10,8 @@ import static dev.openfeature.contrib.providers.flagd.resolver.process.MockFlags.INT_FLAG; import static dev.openfeature.contrib.providers.flagd.resolver.process.MockFlags.OBJECT_FLAG; import static dev.openfeature.contrib.providers.flagd.resolver.process.MockFlags.VARIANT_MISMATCH_FLAG; +import static dev.openfeature.contrib.providers.flagd.resolver.process.MockFlags.stringVariants; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -79,7 +81,7 @@ public void eventHandling() throws Throwable { final MutableStructure syncMetadata = new MutableStructure(); syncMetadata.add(key, val); - InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(new HashMap<>(), sender), + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(new HashMap<>(), sender), (connectionEvent) -> receiver.offer(new StorageStateChange( connectionEvent.isConnected() ? StorageState.OK : StorageState.ERROR, connectionEvent.getFlagsChanged(), connectionEvent.getSyncMetadata()))); @@ -118,7 +120,7 @@ public void simpleBooleanResolving() throws Exception { final Map flagMap = new HashMap<>(); flagMap.put("booleanFlag", BOOLEAN_FLAG); - InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), (connectionEvent) -> { }); @@ -139,7 +141,7 @@ public void simpleDoubleResolving() throws Exception { final Map flagMap = new HashMap<>(); flagMap.put("doubleFlag", DOUBLE_FLAG); - InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), (connectionEvent) -> { }); @@ -159,7 +161,7 @@ public void fetchIntegerAsDouble() throws Exception { final Map flagMap = new HashMap<>(); flagMap.put("doubleFlag", DOUBLE_FLAG); - InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), (connectionEvent) -> { }); @@ -179,7 +181,7 @@ public void fetchDoubleAsInt() throws Exception { final Map flagMap = new HashMap<>(); flagMap.put("integerFlag", INT_FLAG); - InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), (connectionEvent) -> { }); @@ -199,7 +201,7 @@ public void simpleIntResolving() throws Exception { final Map flagMap = new HashMap<>(); flagMap.put("integerFlag", INT_FLAG); - InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), (connectionEvent) -> { }); @@ -219,7 +221,7 @@ public void simpleObjectResolving() throws Exception { final Map flagMap = new HashMap<>(); flagMap.put("objectFlag", OBJECT_FLAG); - InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), (connectionEvent) -> { }); @@ -246,7 +248,7 @@ public void missingFlag() throws Exception { // given final Map flagMap = new HashMap<>(); - InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), (connectionEvent) -> { }); @@ -262,7 +264,7 @@ public void disabledFlag() throws Exception { final Map flagMap = new HashMap<>(); flagMap.put("disabledFlag", DISABLED_FLAG); - InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), (connectionEvent) -> { }); @@ -278,7 +280,7 @@ public void variantMismatchFlag() throws Exception { final Map flagMap = new HashMap<>(); flagMap.put("mismatchFlag", VARIANT_MISMATCH_FLAG); - InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), (connectionEvent) -> { }); @@ -294,7 +296,7 @@ public void typeMismatchEvaluation() throws Exception { final Map flagMap = new HashMap<>(); flagMap.put("stringFlag", BOOLEAN_FLAG); - InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), (connectionEvent) -> { }); @@ -310,7 +312,7 @@ public void booleanShorthandEvaluation() throws Exception { final Map flagMap = new HashMap<>(); flagMap.put("shorthand", FLAG_WIH_SHORTHAND_TARGETING); - InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), (connectionEvent) -> { }); @@ -329,7 +331,7 @@ public void targetingMatchedEvaluationFlag() throws Exception { final Map flagMap = new HashMap<>(); flagMap.put("stringFlag", FLAG_WIH_IF_IN_TARGET); - InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), (connectionEvent) -> { }); @@ -350,7 +352,7 @@ public void targetingUnmatchedEvaluationFlag() throws Exception { final Map flagMap = new HashMap<>(); flagMap.put("stringFlag", FLAG_WIH_IF_IN_TARGET); - InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), (connectionEvent) -> { }); @@ -371,7 +373,7 @@ public void explicitTargetingKeyHandling() throws NoSuchFieldException, IllegalA final Map flagMap = new HashMap<>(); flagMap.put("stringFlag", FLAG_WITH_TARGETING_KEY); - InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), (connectionEvent) -> { }); @@ -391,7 +393,7 @@ public void targetingErrorEvaluationFlag() throws Exception { final Map flagMap = new HashMap<>(); flagMap.put("targetingErrorFlag", FLAG_WIH_INVALID_TARGET); - InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), (connectionEvent) -> { }); @@ -408,7 +410,7 @@ public void validateMetadataInEvaluationResult() throws Exception { final Map flagMap = new HashMap<>(); flagMap.put("booleanFlag", BOOLEAN_FLAG); - InProcessResolver inProcessResolver = getInProcessResolverWth( + InProcessResolver inProcessResolver = getInProcessResolverWith( FlagdOptions.builder().selector(scope).build(), new MockStorage(flagMap)); @@ -423,23 +425,95 @@ public void validateMetadataInEvaluationResult() throws Exception { assertEquals(scope, metadata.getString("scope")); } - private InProcessResolver getInProcessResolverWth(final FlagdOptions options, final MockStorage storage) - throws NoSuchFieldException, IllegalAccessException { - - final InProcessResolver resolver = new InProcessResolver(options, () -> true, - (connectionEvent) -> { - }); - return injectFlagStore(resolver, storage); - } - - private InProcessResolver getInProcessResolverWth(final MockStorage storage, - final Consumer onConnectionEvent) - throws NoSuchFieldException, IllegalAccessException { - - final InProcessResolver resolver = new InProcessResolver( - FlagdOptions.builder().deadline(1000).build(), () -> true, onConnectionEvent); - return injectFlagStore(resolver, storage); - } + @Test + void selectorIsAddedToFlagMetadata() throws Exception { + // given + final Map flagMap = new HashMap<>(); + flagMap.put("flag", INT_FLAG); + + InProcessResolver inProcessResolver = getInProcessResolverWith( + new MockStorage(flagMap), + connectionEvent -> { + }, + "selector"); + + // when + ProviderEvaluation providerEvaluation = inProcessResolver.integerEvaluation( + "flag", + 0, + new ImmutableContext() + ); + + // then + assertThat(providerEvaluation.getFlagMetadata()).isNotNull(); + assertThat(providerEvaluation.getFlagMetadata().getString("scope")).isEqualTo("selector"); + } + + @Test + void selectorIsOverwrittenByFlagMetadata() throws Exception { + // given + final Map flagMap = new HashMap<>(); + final Map flagMetadata = new HashMap<>(); + flagMetadata.put("scope", "new selector"); + flagMap.put( + "flag", + new FeatureFlag( + "stage", + "loop", + stringVariants, + "", + flagMetadata + ) + ); + + InProcessResolver inProcessResolver = getInProcessResolverWith( + new MockStorage(flagMap), + connectionEvent -> { + }, + "selector"); + + // when + ProviderEvaluation providerEvaluation = inProcessResolver.stringEvaluation( + "flag", + "def", + new ImmutableContext() + ); + + // then + assertThat(providerEvaluation.getFlagMetadata()).isNotNull(); + assertThat(providerEvaluation.getFlagMetadata().getString("scope")).isEqualTo("new selector"); + } + + private InProcessResolver getInProcessResolverWith(final FlagdOptions options, final MockStorage storage) + throws NoSuchFieldException, IllegalAccessException { + + final InProcessResolver resolver = new InProcessResolver( + options, + () -> true, + connectionEvent -> { + } + ); + return injectFlagStore(resolver, storage); + } + + private InProcessResolver getInProcessResolverWith(final MockStorage storage, + final Consumer onConnectionEvent) + throws NoSuchFieldException, IllegalAccessException { + + final InProcessResolver resolver = new InProcessResolver( + FlagdOptions.builder().deadline(1000).build(), () -> true, onConnectionEvent); + return injectFlagStore(resolver, storage); + } + + private InProcessResolver getInProcessResolverWith(final MockStorage storage, + final Consumer onConnectionEvent, + String selector) + throws NoSuchFieldException, IllegalAccessException { + + final InProcessResolver resolver = new InProcessResolver( + FlagdOptions.builder().selector(selector).deadline(1000).build(), () -> true, onConnectionEvent); + return injectFlagStore(resolver, storage); + } // helper to inject flagStore override private InProcessResolver injectFlagStore(final InProcessResolver resolver, final MockStorage storage) diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/MockFlags.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/MockFlags.java index 52255ea25..ce1314207 100644 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/MockFlags.java +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/MockFlags.java @@ -49,38 +49,38 @@ public class MockFlags { } // correct flag - boolean - static final FeatureFlag BOOLEAN_FLAG = new FeatureFlag("ENABLED", "on", booleanVariant, null); + static final FeatureFlag BOOLEAN_FLAG = new FeatureFlag("ENABLED", "on", booleanVariant, null, new HashMap<>()); // correct flag - boolean - static final FeatureFlag SHORTHAND_FLAG = new FeatureFlag("ENABLED", "false", booleanVariant, null); + static final FeatureFlag SHORTHAND_FLAG = new FeatureFlag("ENABLED", "false", booleanVariant, null, new HashMap<>()); // correct flag - double - static final FeatureFlag DOUBLE_FLAG = new FeatureFlag("ENABLED", "one", doubleVariants, null); + static final FeatureFlag DOUBLE_FLAG = new FeatureFlag("ENABLED", "one", doubleVariants, null, new HashMap<>()); // correct flag - int - static final FeatureFlag INT_FLAG = new FeatureFlag("ENABLED", "one", intVariants, null); + static final FeatureFlag INT_FLAG = new FeatureFlag("ENABLED", "one", intVariants, null, new HashMap<>()); // correct flag - object - static final FeatureFlag OBJECT_FLAG = new FeatureFlag("ENABLED", "typeA", objectVariants, null); + static final FeatureFlag OBJECT_FLAG = new FeatureFlag("ENABLED", "typeA", objectVariants, null, new HashMap<>()); // flag in disabled state - static final FeatureFlag DISABLED_FLAG = new FeatureFlag("DISABLED", "on", booleanVariant, null); + static final FeatureFlag DISABLED_FLAG = new FeatureFlag("DISABLED", "on", booleanVariant, null, new HashMap<>()); // incorrect flag - variant mismatch - static final FeatureFlag VARIANT_MISMATCH_FLAG = new FeatureFlag("ENABLED", "true", stringVariants, null); + static final FeatureFlag VARIANT_MISMATCH_FLAG = new FeatureFlag("ENABLED", "true", stringVariants, null, new HashMap<>()); // flag with targeting rule - string static final FeatureFlag FLAG_WIH_IF_IN_TARGET = new FeatureFlag("ENABLED", "loop", stringVariants, - "{\"if\":[{\"in\":[\"@faas.com\",{\"var\":[\"email\"]}]},\"binet\",null]}"); + "{\"if\":[{\"in\":[\"@faas.com\",{\"var\":[\"email\"]}]},\"binet\",null]}", new HashMap<>()); static final FeatureFlag FLAG_WITH_TARGETING_KEY = new FeatureFlag("ENABLED", "loop", stringVariants, - "{\"if\":[{\"==\":[{\"var\":\"targetingKey\"},\"xyz\"]},\"binet\",null]}"); + "{\"if\":[{\"==\":[{\"var\":\"targetingKey\"},\"xyz\"]},\"binet\",null]}", new HashMap<>()); // flag with incorrect targeting rule static final FeatureFlag FLAG_WIH_INVALID_TARGET = new FeatureFlag("ENABLED", "loop", stringVariants, - "{if this, then that}"); + "{if this, then that}", new HashMap<>()); // flag with shorthand rule static final FeatureFlag FLAG_WIH_SHORTHAND_TARGETING = new FeatureFlag("ENABLED", "false", shorthandVariant, - "{ \"if\": [true, true, false] }"); + "{ \"if\": [true, true, false] }", new HashMap<>()); } diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/TestUtils.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/TestUtils.java index e489e6d0d..d65f557b7 100644 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/TestUtils.java +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/TestUtils.java @@ -15,6 +15,7 @@ public class TestUtils { public static final String VALID_SIMPLE_EXTRA_FIELD = "flagConfigurations/valid-simple-with-extra-fields.json"; public static final String VALID_LONG = "flagConfigurations/valid-long.json"; public static final String INVALID_FLAG = "flagConfigurations/invalid-flag.json"; + public static final String INVALID_FLAG_METADATA = "flagConfigurations/invalid-metadata.json"; public static final String INVALID_CFG = "flagConfigurations/invalid-configuration.json"; public static final String UPDATABLE_FILE = "flagConfigurations/updatableFlags.json"; diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/model/FlagParserTest.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/model/FlagParserTest.java index 27d42dd7b..f8caace2d 100644 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/model/FlagParserTest.java +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/model/FlagParserTest.java @@ -7,17 +7,19 @@ import static dev.openfeature.contrib.providers.flagd.resolver.process.TestUtils.INVALID_CFG; import static dev.openfeature.contrib.providers.flagd.resolver.process.TestUtils.INVALID_FLAG; +import static dev.openfeature.contrib.providers.flagd.resolver.process.TestUtils.INVALID_FLAG_METADATA; import static dev.openfeature.contrib.providers.flagd.resolver.process.TestUtils.VALID_LONG; import static dev.openfeature.contrib.providers.flagd.resolver.process.TestUtils.VALID_SIMPLE; import static dev.openfeature.contrib.providers.flagd.resolver.process.TestUtils.VALID_SIMPLE_EXTRA_FIELD; import static dev.openfeature.contrib.providers.flagd.resolver.process.TestUtils.getFlagsFromResource; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; class FlagParserTest { @Test - public void validJsonConfigurationParsing() throws IOException { + void validJsonConfigurationParsing() throws IOException { Map flagMap = FlagParser.parseString(getFlagsFromResource(VALID_SIMPLE), true); FeatureFlag boolFlag = flagMap.get("myBoolFlag"); @@ -29,10 +31,21 @@ public void validJsonConfigurationParsing() throws IOException { assertEquals(true, variants.get("on")); assertEquals(false, variants.get("off")); + + Map metadata = boolFlag.getMetadata(); + + assertInstanceOf(String.class, metadata.get("string")); + assertEquals("string", metadata.get("string")); + + assertInstanceOf(Boolean.class, metadata.get("boolean")); + assertEquals(true, metadata.get("boolean")); + + assertInstanceOf(Double.class, metadata.get("float")); + assertEquals(1.234, metadata.get("float")); } @Test - public void validJsonConfigurationWithExtraFieldsParsing() throws IOException { + void validJsonConfigurationWithExtraFieldsParsing() throws IOException { Map flagMap = FlagParser.parseString(getFlagsFromResource(VALID_SIMPLE_EXTRA_FIELD), true); FeatureFlag boolFlag = flagMap.get("myBoolFlag"); @@ -47,7 +60,7 @@ public void validJsonConfigurationWithExtraFieldsParsing() throws IOException { } @Test - public void validJsonConfigurationWithTargetingRulesParsing() throws IOException { + void validJsonConfigurationWithTargetingRulesParsing() throws IOException { Map flagMap = FlagParser.parseString(getFlagsFromResource(VALID_LONG), true); FeatureFlag stringFlag = flagMap.get("fibAlgo"); @@ -68,16 +81,26 @@ public void validJsonConfigurationWithTargetingRulesParsing() throws IOException @Test - public void invalidFlagThrowsError() { + void invalidFlagThrowsError() throws IOException { + String flagString = getFlagsFromResource(INVALID_FLAG); + assertThrows(IllegalArgumentException.class, () -> { + FlagParser.parseString(flagString, true); + }); + } + + @Test + void invalidFlagMetadataThrowsError() throws IOException { + String flagString = getFlagsFromResource(INVALID_FLAG_METADATA); assertThrows(IllegalArgumentException.class, () -> { - FlagParser.parseString(getFlagsFromResource(INVALID_FLAG), true); + FlagParser.parseString(flagString, true); }); } @Test - public void invalidConfigurationsThrowsError() { + void invalidConfigurationsThrowsError() throws IOException { + String flagString = getFlagsFromResource(INVALID_CFG); assertThrows(IllegalArgumentException.class, () -> { - FlagParser.parseString(getFlagsFromResource(INVALID_CFG), true); + FlagParser.parseString(flagString, true); }); } } diff --git a/providers/flagd/src/test/resources/flagConfigurations/invalid-metadata.json b/providers/flagd/src/test/resources/flagConfigurations/invalid-metadata.json new file mode 100644 index 000000000..299eac071 --- /dev/null +++ b/providers/flagd/src/test/resources/flagConfigurations/invalid-metadata.json @@ -0,0 +1,21 @@ +{ + "$schema": "../../../main/resources/flagd/schemas/flags.json", + "flags": { + "myBoolFlag": { + "state": "ENABLED", + "variants": { + "on": true, + "off": false + }, + "defaultVariant": "on", + "metadata": { + "string": "string", + "boolean": true, + "float": 1.234, + "invalid": { + "a": "a" + } + } + } + } +} diff --git a/providers/flagd/src/test/resources/flagConfigurations/valid-long.json b/providers/flagd/src/test/resources/flagConfigurations/valid-long.json index 142eec2cf..3aecd81c9 100644 --- a/providers/flagd/src/test/resources/flagConfigurations/valid-long.json +++ b/providers/flagd/src/test/resources/flagConfigurations/valid-long.json @@ -7,7 +7,12 @@ "on": true, "off": false }, - "defaultVariant": "on" + "defaultVariant": "on", + "metadata": { + "string": "string", + "boolean": true, + "float": 1.234 + } }, "myStringFlag": { "state": "ENABLED", diff --git a/providers/flagd/src/test/resources/flagConfigurations/valid-simple.json b/providers/flagd/src/test/resources/flagConfigurations/valid-simple.json index 811abbc37..37d997f3b 100644 --- a/providers/flagd/src/test/resources/flagConfigurations/valid-simple.json +++ b/providers/flagd/src/test/resources/flagConfigurations/valid-simple.json @@ -7,7 +7,12 @@ "on": true, "off": false }, - "defaultVariant": "on" + "defaultVariant": "on", + "metadata": { + "string": "string", + "boolean": true, + "float": 1.234 + } } } } diff --git a/providers/flagsmith/src/test/java/dev.openfeature.contrib.providers.flagsmith/FlagsmithProviderTest.java b/providers/flagsmith/src/test/java/dev.openfeature.contrib.providers.flagsmith/FlagsmithProviderTest.java index 74ab12d9c..85127f479 100644 --- a/providers/flagsmith/src/test/java/dev.openfeature.contrib.providers.flagsmith/FlagsmithProviderTest.java +++ b/providers/flagsmith/src/test/java/dev.openfeature.contrib.providers.flagsmith/FlagsmithProviderTest.java @@ -13,16 +13,6 @@ import dev.openfeature.sdk.Value; import dev.openfeature.sdk.exceptions.FlagNotFoundError; import dev.openfeature.sdk.exceptions.GeneralError; -import java.io.IOException; -import java.lang.reflect.Method; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.stream.Stream; import lombok.SneakyThrows; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; @@ -35,6 +25,17 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; + import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -395,8 +396,7 @@ private String readMockResponse(String filename) throws IOException { } private String getResultString(Object responseValue, Class expectedType) - throws JsonProcessingException { - String resultString = ""; + throws JsonProcessingException { if (expectedType == Value.class) { Value value = (Value) responseValue; try { From 0bedec3d633c859d7cf0a3c197df414bff1d8caf Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Thu, 2 Jan 2025 10:18:00 +0100 Subject: [PATCH 2/3] fixup! feat: Update in-process resolver to support flag metadata #1102 Signed-off-by: christian.lutnik --- .../FlagsmithProviderTest.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/providers/flagsmith/src/test/java/dev.openfeature.contrib.providers.flagsmith/FlagsmithProviderTest.java b/providers/flagsmith/src/test/java/dev.openfeature.contrib.providers.flagsmith/FlagsmithProviderTest.java index 85127f479..74ab12d9c 100644 --- a/providers/flagsmith/src/test/java/dev.openfeature.contrib.providers.flagsmith/FlagsmithProviderTest.java +++ b/providers/flagsmith/src/test/java/dev.openfeature.contrib.providers.flagsmith/FlagsmithProviderTest.java @@ -13,6 +13,16 @@ import dev.openfeature.sdk.Value; import dev.openfeature.sdk.exceptions.FlagNotFoundError; import dev.openfeature.sdk.exceptions.GeneralError; +import java.io.IOException; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; import lombok.SneakyThrows; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; @@ -25,17 +35,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; - -import java.io.BufferedReader; -import java.io.FileReader; -import java.io.IOException; -import java.lang.reflect.Method; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.stream.Stream; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -396,7 +395,8 @@ private String readMockResponse(String filename) throws IOException { } private String getResultString(Object responseValue, Class expectedType) - throws JsonProcessingException { + throws JsonProcessingException { + String resultString = ""; if (expectedType == Value.class) { Value value = (Value) responseValue; try { From 1fc48914a985f0b0e5331224db28afb7b32a0e9b Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Mon, 6 Jan 2025 14:26:19 -0500 Subject: [PATCH 3/3] fixup: submodule head Signed-off-by: Todd Baert --- providers/flagd/schemas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/flagd/schemas b/providers/flagd/schemas index 58aeed308..b81a56eea 160000 --- a/providers/flagd/schemas +++ b/providers/flagd/schemas @@ -1 +1 @@ -Subproject commit 58aeed308d1a851ce47f17266128b99b28761af6 +Subproject commit b81a56eea3b2c4c543a50d4f7f79a8f32592a0af