summaryrefslogtreecommitdiffstats
path: root/flags
diff options
context:
space:
mode:
authorHåkon Hallingstad <hakon@oath.com>2019-01-03 19:54:11 +0100
committerHåkon Hallingstad <hakon@oath.com>2019-01-03 19:54:11 +0100
commitcf70bfb260fa8731617a327b56be433128aa0450 (patch)
treeddf55127282b97088ce301c51b46737c5ca7692e /flags
parent5d9957af203d16c448ca34a9d167e3587b3c7820 (diff)
Typed flag classes
This reintroduces the non-generic flag classes: - a value() returns the primitive type for flags wrapping a primitive type - easier to use in testing - Serializer is moved to internals of typed class Defines the flag backed by boolean BooleanFlag instead of FeatureFlag since not all boolean flags are necessarily guarding a feature.
Diffstat (limited to 'flags')
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/BooleanFlag.java18
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/Flag.java47
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/FlagDefinition.java15
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/FlagImpl.java47
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/Flags.java123
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/IntFlag.java18
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/JacksonFlag.java18
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/LongFlag.java18
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/OrderedFlagSource.java1
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/StringFlag.java18
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/UnboundBooleanFlag.java27
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/UnboundFlag.java45
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/UnboundFlagImpl.java58
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/UnboundIntFlag.java23
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/UnboundJacksonFlag.java21
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/UnboundLongFlag.java23
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/UnboundStringFlag.java23
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/FlagData.java17
-rw-r--r--flags/src/test/java/com/yahoo/vespa/flags/FileFlagSourceTest.java30
-rw-r--r--flags/src/test/java/com/yahoo/vespa/flags/FlagsTest.java19
20 files changed, 452 insertions, 157 deletions
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/BooleanFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/BooleanFlag.java
new file mode 100644
index 00000000000..1245aca65b8
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/BooleanFlag.java
@@ -0,0 +1,18 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * @author hakonhall
+ */
+@Immutable
+public class BooleanFlag extends FlagImpl<Boolean, BooleanFlag> {
+ public BooleanFlag(FlagId id, boolean defaultValue, FetchVector vector, FlagSerializer<Boolean> serializer, FlagSource source) {
+ super(id, defaultValue, vector, serializer, source, BooleanFlag::new);
+ }
+
+ public boolean value() {
+ return boxedValue();
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flag.java b/flags/src/main/java/com/yahoo/vespa/flags/Flag.java
index 18c1db0d756..7bf491a39c7 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/Flag.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/Flag.java
@@ -1,44 +1,23 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.flags;
-import javax.annotation.concurrent.Immutable;
-
/**
+ * Interface for flag.
+ *
+ * @param <T> The type of the flag value (boxed for primitives)
+ * @param <F> The concrete subclass type of the flag
* @author hakonhall
*/
-@Immutable
-public class Flag<T> {
- private final FlagId id;
- private final T defaultValue;
- private final FlagSource source;
- private final Deserializer<T> deserializer;
- private final FetchVector fetchVector;
-
- public Flag(String flagId, T defaultValue, FlagSource source, Deserializer<T> deserializer) {
- this(new FlagId(flagId), defaultValue, source, deserializer);
- }
-
- public Flag(FlagId id, T defaultValue, FlagSource source, Deserializer<T> deserializer) {
- this(id, defaultValue, deserializer, new FetchVector(), source);
- }
-
- public Flag(FlagId id, T defaultValue, Deserializer<T> deserializer, FetchVector fetchVector, FlagSource source) {
- this.id = id;
- this.defaultValue = defaultValue;
- this.source = source;
- this.deserializer = deserializer;
- this.fetchVector = fetchVector;
- }
+public interface Flag<T, F> {
+ /** The flag ID. */
+ FlagId id();
- public FlagId id() {
- return id;
- }
+ /** Returns the flag serializer. */
+ FlagSerializer<T> serializer();
- public Flag<T> with(FetchVector.Dimension dimension, String value) {
- return new Flag<>(id, defaultValue, deserializer, fetchVector.with(dimension, value), source);
- }
+ /** Returns an immutable clone of the current object, except with the dimension set accordingly. */
+ F with(FetchVector.Dimension dimension, String dimensionValue);
- public T value() {
- return source.fetch(id, fetchVector).map(deserializer::deserialize).orElse(defaultValue);
- }
+ /** Returns the value, boxed if the flag wraps a primitive type. */
+ T boxedValue();
}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/FlagDefinition.java b/flags/src/main/java/com/yahoo/vespa/flags/FlagDefinition.java
index a3f490e2f96..0dce61cf0bb 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/FlagDefinition.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/FlagDefinition.java
@@ -1,7 +1,8 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.flags;
import javax.annotation.concurrent.Immutable;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -9,21 +10,21 @@ import java.util.List;
* @author hakonhall
*/
@Immutable
-public class FlagDefinition<T> {
- private final UnboundFlag<T> unboundFlag;
+public class FlagDefinition {
+ private final UnboundFlag<?, ?, ?> unboundFlag;
private final String description;
private final String modificationEffect;
private final List<FetchVector.Dimension> dimensions;
- public FlagDefinition(UnboundFlag<T> unboundFlag, String description, String modificationEffect,
- List<FetchVector.Dimension> dimensions) {
+ public FlagDefinition(UnboundFlag<?, ?, ?> unboundFlag, String description, String modificationEffect,
+ FetchVector.Dimension... dimensions) {
this.unboundFlag = unboundFlag;
this.description = description;
this.modificationEffect = modificationEffect;
- this.dimensions = Collections.unmodifiableList(dimensions);
+ this.dimensions = Collections.unmodifiableList(Arrays.asList(dimensions));
}
- public UnboundFlag<T> getUnboundFlag() {
+ public UnboundFlag<?, ?, ?> getUnboundFlag() {
return unboundFlag;
}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/FlagImpl.java b/flags/src/main/java/com/yahoo/vespa/flags/FlagImpl.java
new file mode 100644
index 00000000000..79da03e4982
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/FlagImpl.java
@@ -0,0 +1,47 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+/**
+ * @author hakonhall
+ */
+public abstract class FlagImpl<T, F extends FlagImpl<T, F>> implements Flag<T, F> {
+ private final FlagId id;
+ private final T defaultValue;
+ private final FlagSerializer<T> serializer;
+ private final FetchVector fetchVector;
+ private final UnboundFlagImpl.FlagFactory<T, F> factory;
+ private final FlagSource source;
+
+ protected FlagImpl(FlagId id,
+ T defaultValue,
+ FetchVector fetchVector, FlagSerializer<T> serializer,
+ FlagSource source,
+ UnboundFlagImpl.FlagFactory<T, F> factory) {
+ this.id = id;
+ this.defaultValue = defaultValue;
+ this.serializer = serializer;
+ this.fetchVector = fetchVector;
+ this.factory = factory;
+ this.source = source;
+ }
+
+ @Override
+ public FlagId id() {
+ return id;
+ }
+
+ @Override
+ public F with(FetchVector.Dimension dimension, String dimensionValue) {
+ return factory.create(id, defaultValue, fetchVector.with(dimension, dimensionValue), serializer, source);
+ }
+
+ @Override
+ public T boxedValue() {
+ return source.fetch(id, fetchVector).map(serializer::deserialize).orElse(defaultValue);
+ }
+
+ @Override
+ public FlagSerializer<T> serializer() {
+ return serializer;
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
index 50cb5ffdf7c..50f65e5ce13 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
@@ -1,53 +1,33 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.flags;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.node.BooleanNode;
-import com.fasterxml.jackson.databind.node.IntNode;
-import com.fasterxml.jackson.databind.node.LongNode;
-import com.fasterxml.jackson.databind.node.TextNode;
import com.yahoo.vespa.defaults.Defaults;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
/**
- * Definitions of most/all flags.
- *
- * <p>The flags are centrally defined in this module to allow 1. all code to access flags that may be used in
- * quite different modules and processes, and 2. in particular allow the config server to access all flags
- * so operators have a nicer UI for setting, modifying, or removing flag values.
- *
- * <p>This class should have been an enum, but unfortunately enums cannot be generic, which will eventually be
- * fixed with <a href="https://openjdk.java.net/jeps/301">JEP 301: Enhanced Enums</a>.
- *
* @author hakonhall
*/
public class Flags {
- public static final FlagSerializer<Boolean> BOOLEAN_SERIALIZER = new SimpleFlagSerializer<>(BooleanNode::valueOf, JsonNode::isBoolean, JsonNode::asBoolean);
- public static final FlagSerializer<String> STRING_SERIALIZER = new SimpleFlagSerializer<>(TextNode::new, JsonNode::isTextual, JsonNode::asText);
- public static final FlagSerializer<Integer> INT_SERIALIZER = new SimpleFlagSerializer<>(IntNode::new, JsonNode::isIntegralNumber, JsonNode::asInt);
- public static final FlagSerializer<Long> LONG_SERIALIZER = new SimpleFlagSerializer<>(LongNode::new, JsonNode::isIntegralNumber, JsonNode::asLong);
+ private static volatile ConcurrentHashMap<FlagId, FlagDefinition> flags = new ConcurrentHashMap<>();
- private static volatile ConcurrentHashMap<FlagId, FlagDefinition<?>> flags = new ConcurrentHashMap<>();
-
- public static final UnboundFlag<Boolean> HEALTHMONITOR_MONITOR_INFRA = defineBoolean(
+ public static final UnboundBooleanFlag HEALTHMONITOR_MONITOR_INFRA = defineFeatureFlag(
"healthmonitor-monitorinfra", true,
- "Whether the health monitor in service monitor monitors the health of infrastructure applications.",
- "Affects all applications activated after the value is changed.",
+ "Whether the health monitor in service monitor monitors the health of infrastructure applications.",
+ "Affects all applications activated after the value is changed.",
FetchVector.Dimension.HOSTNAME);
- public static final UnboundFlag<Boolean> DUPERMODEL_CONTAINS_INFRA = defineBoolean(
+ public static final UnboundBooleanFlag DUPERMODEL_CONTAINS_INFRA = defineFeatureFlag(
"dupermodel-contains-infra", true,
"Whether the DuperModel in config server/controller includes active infrastructure applications " +
"(except from controller/config apps).",
"Requires restart of config server/controller to take effect.",
FetchVector.Dimension.HOSTNAME);
- public static final UnboundFlag<Boolean> DUPERMODEL_USE_CONFIGSERVERCONFIG = defineBoolean(
+ public static final UnboundBooleanFlag DUPERMODEL_USE_CONFIGSERVERCONFIG = defineFeatureFlag(
"dupermodel-use-configserverconfig", true,
"For historical reasons, the ApplicationInfo in the DuperModel for controllers and config servers " +
"is based on the ConfigserverConfig (this flag is true). We want to transition to use the " +
@@ -55,75 +35,86 @@ public class Flags {
"Requires restart of config server/controller to take effect.",
FetchVector.Dimension.HOSTNAME);
- public static final UnboundFlag<Boolean> USE_CONFIG_SERVER_CACHE = defineBoolean(
+ public static final UnboundBooleanFlag USE_CONFIG_SERVER_CACHE = defineFeatureFlag(
"use-config-server-cache", true,
"Whether config server will use cache to answer config requests.",
"Takes effect immediately when changed.",
FetchVector.Dimension.HOSTNAME, FetchVector.Dimension.APPLICATION_ID);
- public static final UnboundFlag<Boolean> CONFIG_SERVER_BOOTSTRAP_IN_SEPARATE_THREAD = defineBoolean(
+ public static final UnboundBooleanFlag CONFIG_SERVER_BOOTSTRAP_IN_SEPARATE_THREAD = defineFeatureFlag(
"config-server-bootstrap-in-separate-thread", true,
"Whether to run config server/controller bootstrap in a separate thread.",
"Takes effect only at bootstrap of config server/controller",
FetchVector.Dimension.HOSTNAME);
- public static final UnboundFlag<Boolean> PROXYHOST_USES_REAL_ORCHESTRATOR = defineBoolean(
+ public static final UnboundBooleanFlag PROXYHOST_USES_REAL_ORCHESTRATOR = defineFeatureFlag(
"proxyhost-uses-real-orchestrator", true,
"Whether proxy hosts uses the real Orchestrator when suspending/resuming, or a synthetic.",
"Takes effect immediately when changed.",
FetchVector.Dimension.HOSTNAME);
- public static final UnboundFlag<Boolean> CONFIGHOST_USES_REAL_ORCHESTRATOR = defineBoolean(
+ public static final UnboundBooleanFlag CONFIGHOST_USES_REAL_ORCHESTRATOR = defineFeatureFlag(
"confighost-uses-real-orchestrator", false,
"Whether the config server hosts uses the real Orchestrator when suspending/resuming, or a synthetic.",
"Takes effect immediately when changed.",
FetchVector.Dimension.HOSTNAME);
- public static final UnboundFlag<Boolean> ENABLE_DOCKER_1_13 = defineBoolean(
+ public static final UnboundBooleanFlag ENABLE_DOCKER_1_13 = defineFeatureFlag(
"enable-docker-1.13", true,
"Whether to upgrade to Docker version 1.13.",
"Takes effect on next host admin tick.",
FetchVector.Dimension.HOSTNAME);
- public static final UnboundFlag<Boolean> ENABLE_CROWDSTRIKE = defineBoolean(
+ public static final UnboundBooleanFlag ENABLE_CROWDSTRIKE = defineFeatureFlag(
"enable-crowdstrike", true,
"Whether to enable CrowdStrike.", "Takes effect on next host admin tick");
- public static final UnboundFlag<Boolean> ENABLE_NESSUS = defineBoolean(
+ public static final UnboundBooleanFlag ENABLE_NESSUS = defineFeatureFlag(
"enable-nessus", true,
"Whether to enable Nessus.", "Takes effect on next host admin tick");
- public static UnboundFlag<Boolean> defineBoolean(String flagId, boolean defaultValue, String description,
+ /** WARNING: public for testing: All flags should be defined in {@link Flags}. */
+ public static UnboundBooleanFlag defineFeatureFlag(String flagId, boolean defaultValue, String description,
+ String modificationEffect, FetchVector.Dimension... dimensions) {
+ return define(UnboundBooleanFlag::new, flagId, defaultValue, description, modificationEffect, dimensions);
+ }
+
+ /** WARNING: public for testing: All flags should be defined in {@link Flags}. */
+ public static UnboundStringFlag defineStringFlag(String flagId, String defaultValue, String description,
String modificationEffect, FetchVector.Dimension... dimensions) {
- return define(flagId, defaultValue, BOOLEAN_SERIALIZER, description, modificationEffect, dimensions);
+ return define(UnboundStringFlag::new, flagId, defaultValue, description, modificationEffect, dimensions);
}
- public static UnboundFlag<String> defineString(String flagId, String defaultValue, String description,
- String modificationEffect, FetchVector.Dimension... dimensions) {
- return define(flagId, defaultValue, STRING_SERIALIZER, description, modificationEffect, dimensions);
+ /** WARNING: public for testing: All flags should be defined in {@link Flags}. */
+ public static UnboundIntFlag defineIntFlag(String flagId, int defaultValue, String description,
+ String modificationEffect, FetchVector.Dimension... dimensions) {
+ return define(UnboundIntFlag::new, flagId, defaultValue, description, modificationEffect, dimensions);
}
- public static UnboundFlag<Integer> defineInt(String flagId, Integer defaultValue, String description,
+ /** WARNING: public for testing: All flags should be defined in {@link Flags}. */
+ public static UnboundLongFlag defineLongFlag(String flagId, long defaultValue, String description,
String modificationEffect, FetchVector.Dimension... dimensions) {
- return define(flagId, defaultValue, INT_SERIALIZER, description, modificationEffect, dimensions);
+ return define(UnboundLongFlag::new, flagId, defaultValue, description, modificationEffect, dimensions);
}
- public static UnboundFlag<Long> defineLong(String flagId, Long defaultValue, String description,
- String modificationEffect, FetchVector.Dimension... dimensions) {
- return define(flagId, defaultValue, LONG_SERIALIZER, description, modificationEffect, dimensions);
+ /** WARNING: public for testing: All flags should be defined in {@link Flags}. */
+ public static <T> UnboundJacksonFlag<T> defineJacksonFlag(String flagId, T defaultValue, Class<T> jacksonClass, String description,
+ String modificationEffect, FetchVector.Dimension... dimensions) {
+ return define((id2, defaultValue2, vector2) -> new UnboundJacksonFlag<>(id2, defaultValue2, vector2, jacksonClass),
+ flagId, defaultValue, description, modificationEffect, dimensions);
}
- public static <T> UnboundFlag<T> defineJackson(String flagId, Class<T> jacksonClass, T defaultValue, String description,
- String modificationEffect, FetchVector.Dimension... dimensions) {
- return define(flagId, defaultValue, new JacksonSerializer<>(jacksonClass), description, modificationEffect, dimensions);
+ @FunctionalInterface
+ private interface TypedUnboundFlagFactory<T, U extends UnboundFlag<?, ?, ?>> {
+ U create(FlagId id, T defaultVale, FetchVector defaultFetchVector);
}
/**
* Defines a Flag.
*
+ * @param factory Factory for creating unbound flag of type U
* @param flagId The globally unique FlagId.
* @param defaultValue The default value if none is present after resolution.
- * @param deserializer Deserialize JSON to value type.
* @param description Description of how the flag is used.
* @param modificationEffect What is required for the flag to take effect? A restart of process? immediately? etc.
* @param dimensions What dimensions will be set in the {@link FetchVector} when fetching
@@ -132,34 +123,46 @@ public class Flags {
* For instance, if APPLICATION is one of the dimensions here, you should make sure
* APPLICATION is set to the ApplicationId in the fetch vector when fetching the RawFlag
* from the FlagSource.
- * @param <T> The type of the flag value, typically Boolean for flags guarding features.
+ * @param <T> The boxed type of the flag value, e.g. Boolean for flags guarding features.
+ * @param <U> The type of the unbound flag, e.g. UnboundBooleanFlag.
* @return An unbound flag with {@link FetchVector.Dimension#HOSTNAME HOSTNAME} environment. The ZONE environment
* is typically implicit.
*/
- private static <T> UnboundFlag<T> define(String flagId, T defaultValue, Deserializer<T> deserializer,
- String description, String modificationEffect,
- FetchVector.Dimension... dimensions) {
- UnboundFlag<T> flag = new UnboundFlag<>(flagId, defaultValue, deserializer)
- .with(FetchVector.Dimension.HOSTNAME, Defaults.getDefaults().vespaHostname());
- FlagDefinition<T> definition = new FlagDefinition<>(flag, description, modificationEffect, Arrays.asList(dimensions));
- flags.put(flag.id(), definition);
- return flag;
+ private static <T, U extends UnboundFlag<?, ?, ?>> U define(TypedUnboundFlagFactory<T, U> factory,
+ String flagId,
+ T defaultValue,
+ String description,
+ String modificationEffect,
+ FetchVector.Dimension[] dimensions) {
+ FlagId id = new FlagId(flagId);
+ FetchVector vector = new FetchVector().with(FetchVector.Dimension.HOSTNAME, Defaults.getDefaults().vespaHostname());
+ U unboundFlag = factory.create(id, defaultValue, vector);
+ FlagDefinition definition = new FlagDefinition(unboundFlag, description, modificationEffect, dimensions);
+ flags.put(id, definition);
+ return unboundFlag;
}
- public static List<FlagDefinition<?>> getAllFlags() {
+ public static List<FlagDefinition> getAllFlags() {
return new ArrayList<>(flags.values());
}
- public static Optional<FlagDefinition<?>> getFlag(FlagId flagId) {
+ public static Optional<FlagDefinition> getFlag(FlagId flagId) {
return Optional.ofNullable(flags.get(flagId));
}
+ /**
+ * Allows the statically defined flags to be controlled in a test.
+ *
+ * <p>Returns a Replacer instance to be used with e.g. a try-with-resources block. Within the block,
+ * the flags starts out as cleared. Flags can be defined, etc. When leaving the block, the flags from
+ * before the block is reinserted.
+ * */
public static Replacer clearFlagsForTesting() {
return new Replacer();
}
public static class Replacer implements AutoCloseable {
- private final ConcurrentHashMap<FlagId, FlagDefinition<?>> savedFlags;
+ private final ConcurrentHashMap<FlagId, FlagDefinition> savedFlags;
private Replacer() {
this.savedFlags = Flags.flags;
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/IntFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/IntFlag.java
new file mode 100644
index 00000000000..4a59c34e8d0
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/IntFlag.java
@@ -0,0 +1,18 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * @author hakonhall
+ */
+@Immutable
+public class IntFlag extends FlagImpl<Integer, IntFlag> {
+ public IntFlag(FlagId id, int defaultValue, FetchVector vector, FlagSerializer<Integer> serializer, FlagSource source) {
+ super(id, defaultValue, vector, serializer, source, IntFlag::new);
+ }
+
+ public int value() {
+ return boxedValue();
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/JacksonFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/JacksonFlag.java
new file mode 100644
index 00000000000..e4afbd231a1
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/JacksonFlag.java
@@ -0,0 +1,18 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * @author hakonhall
+ */
+@Immutable
+public class JacksonFlag<T> extends FlagImpl<T, JacksonFlag<T>> {
+ public JacksonFlag(FlagId id, T defaultValue, FetchVector vector, FlagSerializer<T> serializer, FlagSource source) {
+ super(id, defaultValue, vector, serializer, source, JacksonFlag::new);
+ }
+
+ public T value() {
+ return boxedValue();
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/LongFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/LongFlag.java
new file mode 100644
index 00000000000..8a1707c9e6a
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/LongFlag.java
@@ -0,0 +1,18 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * @author hakonhall
+ */
+@Immutable
+public class LongFlag extends FlagImpl<Long, LongFlag> {
+ public LongFlag(FlagId id, Long defaultValue, FetchVector vector, FlagSerializer<Long> serializer, FlagSource source) {
+ super(id, defaultValue, vector, serializer, source, LongFlag::new);
+ }
+
+ public long value() {
+ return boxedValue();
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/OrderedFlagSource.java b/flags/src/main/java/com/yahoo/vespa/flags/OrderedFlagSource.java
index 6ca74715999..1c6e113f489 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/OrderedFlagSource.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/OrderedFlagSource.java
@@ -15,7 +15,6 @@ public class OrderedFlagSource implements FlagSource {
private final List<FlagSource> sources;
/**
- *
* @param sources Flag sources in descending priority order.
*/
public OrderedFlagSource(FlagSource... sources) {
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/StringFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/StringFlag.java
new file mode 100644
index 00000000000..925af130b95
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/StringFlag.java
@@ -0,0 +1,18 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * @author hakonhall
+ */
+@Immutable
+public class StringFlag extends FlagImpl<String, StringFlag> {
+ public StringFlag(FlagId id, String defaultValue, FetchVector vector, FlagSerializer<String> serializer, FlagSource source) {
+ super(id, defaultValue, vector, serializer, source, StringFlag::new);
+ }
+
+ public String value() {
+ return boxedValue();
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/UnboundBooleanFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/UnboundBooleanFlag.java
new file mode 100644
index 00000000000..c228364434c
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/UnboundBooleanFlag.java
@@ -0,0 +1,27 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.BooleanNode;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * @author hakonhall
+ */
+@Immutable
+public class UnboundBooleanFlag extends UnboundFlagImpl<Boolean, BooleanFlag, UnboundBooleanFlag> {
+ public UnboundBooleanFlag(FlagId id) {
+ this(id, false);
+ }
+
+ public UnboundBooleanFlag(FlagId id, boolean defaultValue) {
+ this(id, defaultValue, new FetchVector());
+ }
+
+ public UnboundBooleanFlag(FlagId id, boolean defaultValue, FetchVector defaultFetchVector) {
+ super(id, defaultValue, defaultFetchVector,
+ new SimpleFlagSerializer<>(BooleanNode::valueOf, JsonNode::isBoolean, JsonNode::asBoolean),
+ UnboundBooleanFlag::new, BooleanFlag::new);
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/UnboundFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/UnboundFlag.java
index 597e19080ab..28307536233 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/UnboundFlag.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/UnboundFlag.java
@@ -1,38 +1,27 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.flags;
-import javax.annotation.concurrent.Immutable;
-
/**
* @author hakonhall
*/
-@Immutable
-public class UnboundFlag<T> {
- private final FlagId id;
- private final T defaultValue;
- private final Deserializer<T> deserializer;
- private final FetchVector fetchVector;
-
- public UnboundFlag(String flagId, T defaultValue, Deserializer<T> deserializer) {
- this(new FlagId(flagId), defaultValue, deserializer, new FetchVector());
- }
- public UnboundFlag(FlagId id, T defaultValue, Deserializer<T> deserializer, FetchVector fetchVector) {
- this.id = id;
- this.defaultValue = defaultValue;
- this.deserializer = deserializer;
- this.fetchVector = fetchVector;
- }
+/**
+ * Interface of an unbound flag.
+ *
+ * @param <T> Type of boxed value, e.g. Integer
+ * @param <F> Type of flag, e.g. IntFlag
+ * @param <U> Type of unbound flag, e.g. UnboundIntFlag
+ */
+public interface UnboundFlag<T, F extends Flag<T, F>, U extends UnboundFlag<T, F, U>> {
+ /** The flag ID. */
+ FlagId id();
- public FlagId id() {
- return id;
- }
+ /** Returns the flag serializer. */
+ FlagSerializer<T> serializer();
- public UnboundFlag<T> with(FetchVector.Dimension dimension, String value) {
- return new UnboundFlag<>(id, defaultValue, deserializer, fetchVector.with(dimension, value));
- }
+ /** Returns a clone of the unbound flag, but with the dimension set accordingly. */
+ U with(FetchVector.Dimension dimension, String dimensionValue);
- public Flag<T> bindTo(FlagSource source) {
- return new Flag<>(id, defaultValue, deserializer, fetchVector, source);
- }
+ /** Binds to a flag source, returning a (bound) flag. */
+ F bindTo(FlagSource source);
}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/UnboundFlagImpl.java b/flags/src/main/java/com/yahoo/vespa/flags/UnboundFlagImpl.java
new file mode 100644
index 00000000000..513712d8753
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/UnboundFlagImpl.java
@@ -0,0 +1,58 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+/**
+ * @author hakonhall
+ */
+public abstract class UnboundFlagImpl<T, F extends Flag<T, F>, U extends UnboundFlag<T, F, U>>
+ implements UnboundFlag<T, F, U> {
+
+ private final FlagId id;
+ private final T defaultValue;
+ private final FlagSerializer<T> serializer;
+ private final FetchVector defaultFetchVector;
+ private final UnboundFlagFactory<T, F, U> unboundFlagFactory;
+ private final FlagFactory<T, F> flagFactory;
+
+ public interface UnboundFlagFactory<T1, F1 extends Flag<T1, F1>, U1 extends UnboundFlag<T1, F1, U1>> {
+ U1 create(FlagId id, T1 defaultValue, FetchVector fetchVector);
+ }
+
+ public interface FlagFactory<T2, F2 extends Flag<T2, F2>> {
+ F2 create(FlagId id, T2 defaultValue, FetchVector fetchVector, FlagSerializer<T2> serializer, FlagSource source);
+ }
+
+ protected UnboundFlagImpl(FlagId id,
+ T defaultValue,
+ FetchVector defaultFetchVector,
+ FlagSerializer<T> serializer,
+ UnboundFlagFactory<T, F, U> unboundFlagFactory,
+ FlagFactory<T, F> flagFactory) {
+ this.id = id;
+ this.defaultValue = defaultValue;
+ this.serializer = serializer;
+ this.defaultFetchVector = defaultFetchVector;
+ this.unboundFlagFactory = unboundFlagFactory;
+ this.flagFactory = flagFactory;
+ }
+
+ @Override
+ public FlagId id() {
+ return id;
+ }
+
+ @Override
+ public U with(FetchVector.Dimension dimension, String dimensionValue) {
+ return unboundFlagFactory.create(id, defaultValue, defaultFetchVector.with(dimension, dimensionValue));
+ }
+
+ @Override
+ public F bindTo(FlagSource source) {
+ return flagFactory.create(id, defaultValue, defaultFetchVector, serializer, source);
+ }
+
+ @Override
+ public FlagSerializer<T> serializer() {
+ return serializer;
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/UnboundIntFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/UnboundIntFlag.java
new file mode 100644
index 00000000000..01b660fe625
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/UnboundIntFlag.java
@@ -0,0 +1,23 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.IntNode;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * @author hakonhall
+ */
+@Immutable
+public class UnboundIntFlag extends UnboundFlagImpl<Integer, IntFlag, UnboundIntFlag> {
+ public UnboundIntFlag(FlagId id, int defaultValue) {
+ this(id, defaultValue, new FetchVector());
+ }
+
+ public UnboundIntFlag(FlagId id, int defaultValue, FetchVector defaultFetchVector) {
+ super(id, defaultValue, defaultFetchVector,
+ new SimpleFlagSerializer<>(IntNode::new, JsonNode::isIntegralNumber, JsonNode::asInt),
+ UnboundIntFlag::new, IntFlag::new);
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/UnboundJacksonFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/UnboundJacksonFlag.java
new file mode 100644
index 00000000000..1cd303d1ede
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/UnboundJacksonFlag.java
@@ -0,0 +1,21 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * @author hakonhall
+ */
+@Immutable
+public class UnboundJacksonFlag<T> extends UnboundFlagImpl<T, JacksonFlag<T>, UnboundJacksonFlag<T>> {
+ public UnboundJacksonFlag(FlagId id, T defaultValue, Class<T> jacksonClass) {
+ this(id, defaultValue, new FetchVector(), jacksonClass);
+ }
+
+ public UnboundJacksonFlag(FlagId id, T defaultValue, FetchVector defaultFetchVector, Class<T> jacksonClass) {
+ super(id, defaultValue, defaultFetchVector,
+ new JacksonSerializer<>(jacksonClass),
+ (id2, defaultValue2, vector2) -> new UnboundJacksonFlag<>(id2, defaultValue2, vector2, jacksonClass),
+ JacksonFlag::new);
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/UnboundLongFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/UnboundLongFlag.java
new file mode 100644
index 00000000000..732a9073fc8
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/UnboundLongFlag.java
@@ -0,0 +1,23 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.LongNode;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * @author hakonhall
+ */
+@Immutable
+public class UnboundLongFlag extends UnboundFlagImpl<Long, LongFlag, UnboundLongFlag> {
+ public UnboundLongFlag(FlagId id, long defaultValue) {
+ this(id, defaultValue, new FetchVector());
+ }
+
+ public UnboundLongFlag(FlagId id, Long defaultValue, FetchVector defaultFetchVector) {
+ super(id, defaultValue, defaultFetchVector,
+ new SimpleFlagSerializer<>(LongNode::new, JsonNode::isIntegralNumber, JsonNode::asLong),
+ UnboundLongFlag::new, LongFlag::new);
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/UnboundStringFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/UnboundStringFlag.java
new file mode 100644
index 00000000000..67c4bceb3e9
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/UnboundStringFlag.java
@@ -0,0 +1,23 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * @author hakonhall
+ */
+@Immutable
+public class UnboundStringFlag extends UnboundFlagImpl<String, StringFlag, UnboundStringFlag> {
+ public UnboundStringFlag(FlagId id, String defaultValue) {
+ this(id, defaultValue, new FetchVector());
+ }
+
+ public UnboundStringFlag(FlagId id, String defaultValue, FetchVector defaultFetchVector) {
+ super(id, defaultValue, defaultFetchVector,
+ new SimpleFlagSerializer<>(TextNode::new, JsonNode::isTextual, JsonNode::asText),
+ UnboundStringFlag::new, StringFlag::new);
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/json/FlagData.java b/flags/src/main/java/com/yahoo/vespa/flags/json/FlagData.java
index bfd959e82f9..b5bc2032bae 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/json/FlagData.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/json/FlagData.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.flags.json;
import com.fasterxml.jackson.databind.JsonNode;
+import com.yahoo.vespa.flags.Deserializer;
import com.yahoo.vespa.flags.FetchVector;
import com.yahoo.vespa.flags.FlagId;
import com.yahoo.vespa.flags.FlagSource;
@@ -16,9 +17,9 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* A data structure containing all data for a single flag, that can be serialized to/from JSON,
@@ -100,8 +101,12 @@ public class FlagData {
}
private static FlagData fromWire(WireFlagData wireFlagData) {
+ if (wireFlagData.id == null) {
+ throw new IllegalArgumentException("Flag ID missing");
+ }
+
return new FlagData(
- new FlagId(Objects.requireNonNull(wireFlagData.id)),
+ new FlagId(wireFlagData.id),
FetchVectorHelper.fromWire(wireFlagData.defaultFetchVector),
rulesFromWire(wireFlagData.rules)
);
@@ -111,5 +116,13 @@ public class FlagData {
if (wireRules == null) return Collections.emptyList();
return wireRules.stream().map(Rule::fromWire).collect(Collectors.toList());
}
+
+ /** E.g. verify all RawFlag can be deserialized. */
+ public void validate(Deserializer<?> deserializer) {
+ rules.stream()
+ .flatMap(rule -> rule.getValueToApply().map(Stream::of).orElse(null))
+ .forEach(deserializer::deserialize);
+
+ }
}
diff --git a/flags/src/test/java/com/yahoo/vespa/flags/FileFlagSourceTest.java b/flags/src/test/java/com/yahoo/vespa/flags/FileFlagSourceTest.java
index 7d9d7868308..e19436069b5 100644
--- a/flags/src/test/java/com/yahoo/vespa/flags/FileFlagSourceTest.java
+++ b/flags/src/test/java/com/yahoo/vespa/flags/FileFlagSourceTest.java
@@ -24,44 +24,44 @@ public class FileFlagSourceTest {
@Test
public void testFeatureLikeFlags() throws IOException {
- Flag<Boolean> featureFlag = new Flag<>(id, false, source, Flags.BOOLEAN_SERIALIZER);
- Flag<Boolean> byDefaultTrue = new Flag<>(id, true, source, Flags.BOOLEAN_SERIALIZER);
+ BooleanFlag booleanFlag = new UnboundBooleanFlag(id).bindTo(source);
+ BooleanFlag byDefaultTrue = new UnboundBooleanFlag(id, true).bindTo(source);
- assertFalse(featureFlag.value());
+ assertFalse(booleanFlag.value());
assertTrue(byDefaultTrue.value());
writeFlag(id.toString(), "true\n");
- assertTrue(featureFlag.value());
+ assertTrue(booleanFlag.value());
assertTrue(byDefaultTrue.value());
writeFlag(id.toString(), "false\n");
- assertFalse(featureFlag.value());
+ assertFalse(booleanFlag.value());
assertFalse(byDefaultTrue.value());
}
@Test
public void testIntegerLikeFlags() throws IOException {
- Flag<Integer> intFlag = new Flag<>(id, -1, source, Flags.INT_SERIALIZER);
- Flag<Long> longFlag = new Flag<>(id, -2L, source, Flags.LONG_SERIALIZER);
+ IntFlag intFlag = new UnboundIntFlag(id, -1).bindTo(source);
+ LongFlag longFlag = new UnboundLongFlag(id, -2L).bindTo(source);
assertFalse(fetch().isPresent());
assertFalse(fetch().isPresent());
- assertEquals(-1, (int) intFlag.value());
- assertEquals(-2L, (long) longFlag.value());
+ assertEquals(-1, intFlag.value());
+ assertEquals(-2L, longFlag.value());
writeFlag(id.toString(), "1\n");
assertTrue(fetch().isPresent());
assertTrue(fetch().isPresent());
- assertEquals(1, (int) intFlag.value());
- assertEquals(1L, (long) longFlag.value());
+ assertEquals(1, intFlag.value());
+ assertEquals(1L, longFlag.value());
}
@Test
public void testStringFlag() throws IOException {
- Flag<String> stringFlag = new Flag<>(id, "default", source, Flags.STRING_SERIALIZER);
+ StringFlag stringFlag = new UnboundStringFlag(id, "default").bindTo(source);
assertFalse(fetch().isPresent());
assertEquals("default", stringFlag.value());
@@ -71,11 +71,11 @@ public class FileFlagSourceTest {
@Test
public void parseFailure() throws IOException {
- Flag<Boolean> featureFlag = new Flag<>(id, false, source, Flags.BOOLEAN_SERIALIZER);
- writeFlag(featureFlag.id().toString(), "garbage");
+ BooleanFlag booleanFlag = new UnboundBooleanFlag(id).bindTo(source);
+ writeFlag(booleanFlag.id().toString(), "garbage");
try {
- featureFlag.value();
+ booleanFlag.value();
} catch (UncheckedIOException e) {
assertThat(e.getMessage(), containsString("garbage"));
}
diff --git a/flags/src/test/java/com/yahoo/vespa/flags/FlagsTest.java b/flags/src/test/java/com/yahoo/vespa/flags/FlagsTest.java
index 4f7d797e07d..591198f5e50 100644
--- a/flags/src/test/java/com/yahoo/vespa/flags/FlagsTest.java
+++ b/flags/src/test/java/com/yahoo/vespa/flags/FlagsTest.java
@@ -31,7 +31,7 @@ public class FlagsTest {
public void testBoolean() {
final boolean defaultValue = false;
FlagSource source = mock(FlagSource.class);
- Flag<Boolean> booleanFlag = Flags.defineBoolean("id", defaultValue, "description",
+ BooleanFlag booleanFlag = Flags.defineFeatureFlag("id", defaultValue, "description",
"modification effect", FetchVector.Dimension.ZONE_ID, FetchVector.Dimension.HOSTNAME)
.with(FetchVector.Dimension.ZONE_ID, "a-zone")
.bindTo(source);
@@ -65,19 +65,19 @@ public class FlagsTest {
@Test
public void testString() {
- testGeneric(Flags.defineString("string-id", "default value", "description",
+ testGeneric(Flags.defineStringFlag("string-id", "default value", "description",
"modification effect", FetchVector.Dimension.ZONE_ID, FetchVector.Dimension.HOSTNAME),
"default value", "other value");
}
@Test
public void testInt() {
- testGeneric(Flags.defineInt("int-id", 2, "desc", "mod"), 2, 3);
+ testGeneric(Flags.defineIntFlag("int-id", 2, "desc", "mod"), 2, 3);
}
@Test
public void testLong() {
- testGeneric(Flags.defineLong("long-id", 1L, "desc", "mod"), 1L, 2L);
+ testGeneric(Flags.defineLongFlag("long-id", 1L, "desc", "mod"), 1L, 2L);
}
@Test
@@ -87,25 +87,24 @@ public class FlagsTest {
instance.integer = -2;
instance.string = "foo";
- testGeneric(Flags.defineJackson("jackson-id", ExampleJacksonClass.class, defaultInstance,
+ testGeneric(Flags.defineJacksonFlag("jackson-id", defaultInstance, ExampleJacksonClass.class,
"description", "modification effect", FetchVector.Dimension.HOSTNAME),
defaultInstance, instance);
}
- private <T> void testGeneric(UnboundFlag<T> unboundFlag, T defaultValue, T value) {
+ private <T> void testGeneric(UnboundFlag<?, ?, ?> unboundFlag, T defaultValue, T value) {
FlagSource source = mock(FlagSource.class);
- Flag<T> flag = unboundFlag.bindTo(source);
+ Flag<?, ?> flag = unboundFlag.bindTo(source);
when(source.fetch(any(), any())).thenReturn(Optional.empty());
- assertThat(flag.value(), equalTo(defaultValue));
+ assertThat(flag.boxedValue(), equalTo(defaultValue));
when(source.fetch(any(), any())).thenReturn(Optional.of(JsonNodeRawFlag.fromJacksonClass(value)));
- assertThat(flag.value(), equalTo(value));
+ assertThat(flag.boxedValue(), equalTo(value));
assertTrue(Flags.getFlag(unboundFlag.id()).isPresent());
}
-
@JsonIgnoreProperties(ignoreUnknown = true)
private static class ExampleJacksonClass {
@JsonProperty("integer")