diff --git a/src/main/java/com/launchdarkly/client/LDUser.java b/src/main/java/com/launchdarkly/client/LDUser.java index d526a783c..3887540bc 100644 --- a/src/main/java/com/launchdarkly/client/LDUser.java +++ b/src/main/java/com/launchdarkly/client/LDUser.java @@ -690,7 +690,28 @@ public Builder customNumber(String k, List vs) { custom.put(k, array); return this; } - + + /** + * Add a custom attribute with a list of arbitrary JSON values. When set to one of the + * built-in + * user attribute keys, this custom attribute will be ignored. + * + * @param k the key for the list + * @param vs the values for the attribute + * @return the builder + */ + public Builder customValues(String k, List vs) { + checkCustomAttribute(k); + JsonArray array = new JsonArray(); + for (JsonElement v : vs) { + if (v != null) { + array.add(v); + } + } + custom.put(k, array); + return this; + } + /** * Add a {@link java.lang.String}-valued custom attribute that will not be sent back to LaunchDarkly. * When set to one of the @@ -766,6 +787,21 @@ public Builder privateCustomNumber(String k, List vs) { return customNumber(k, vs); } + /** + * Add a custom attribute with a list of arbitrary JSON values. When set to one of the + * + * built-in user attribute keys, this custom attribute will be ignored. The custom attribute value will not be sent + * back to LaunchDarkly in analytics events. + * + * @param k the key for the list + * @param vs the values for the attribute + * @return the builder + */ + public Builder privateCustomValues(String k, List vs) { + privateAttrNames.add(k); + return customValues(k, vs); + } + private void checkCustomAttribute(String key) { for (UserAttribute a : UserAttribute.values()) { if (a.name().equals(key)) { diff --git a/src/main/java/com/launchdarkly/client/VersionedDataKind.java b/src/main/java/com/launchdarkly/client/VersionedDataKind.java index 05ce87f33..f4aba3f7e 100644 --- a/src/main/java/com/launchdarkly/client/VersionedDataKind.java +++ b/src/main/java/com/launchdarkly/client/VersionedDataKind.java @@ -1,7 +1,14 @@ package com.launchdarkly.client; +import com.google.common.collect.ImmutableList; + /** * The descriptor for a specific kind of {@link VersionedData} objects that may exist in a {@link FeatureStore}. + * You will not need to refer to this type unless you are directly manipulating a {@code FeatureStore} + * or writing your own {@code FeatureStore} implementation. If you are implementing a custom store, for + * maximum forward compatibility you should only refer to {@link VersionedData}, {@link VersionedDataKind}, + * and {@link VersionedDataKind#ALL}, and avoid any dependencies on specific type descriptor instances + * or any specific fields of the types they describe. * @since 3.0.0 */ public abstract class VersionedDataKind { @@ -41,7 +48,9 @@ String getKeyFromStreamApiPath(String path) { return path.startsWith(getStreamApiPath()) ? path.substring(getStreamApiPath().length()) : null; } - + /** + * The {@link VersionedDataKind} instance that describes feature flag data. + */ public static VersionedDataKind FEATURES = new VersionedDataKind() { public String getNamespace() { @@ -61,6 +70,9 @@ public FeatureFlag makeDeletedItem(String key, int version) { } }; + /** + * The {@link VersionedDataKind} instance that describes user segment data. + */ public static VersionedDataKind SEGMENTS = new VersionedDataKind() { public String getNamespace() { @@ -79,4 +91,10 @@ public Segment makeDeletedItem(String key, int version) { return new Segment.Builder(key).deleted(true).version(version).build(); } }; + + /** + * A list of all existing instances of {@link VersionedDataKind}. + * @since 4.1.0 + */ + public static Iterable> ALL = ImmutableList.of(FEATURES, SEGMENTS); } diff --git a/src/test/java/com/launchdarkly/client/LDUserTest.java b/src/test/java/com/launchdarkly/client/LDUserTest.java index f57ce8d05..cc4ad9cd7 100644 --- a/src/test/java/com/launchdarkly/client/LDUserTest.java +++ b/src/test/java/com/launchdarkly/client/LDUserTest.java @@ -1,7 +1,10 @@ package com.launchdarkly.client; +import com.google.common.collect.ImmutableList; import com.google.gson.Gson; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.google.gson.reflect.TypeToken; @@ -10,6 +13,10 @@ import java.lang.reflect.Type; import java.util.Map; +import static com.launchdarkly.client.TestUtil.jbool; +import static com.launchdarkly.client.TestUtil.jdouble; +import static com.launchdarkly.client.TestUtil.jint; +import static com.launchdarkly.client.TestUtil.js; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -233,4 +240,44 @@ public void getValueReturnsNullIfNotFound() { .build(); assertNull(user.getValueForEvaluation("height")); } + + @Test + public void canAddCustomAttrWithListOfStrings() { + LDUser user = new LDUser.Builder("key") + .customString("foo", ImmutableList.of("a", "b")) + .build(); + JsonElement expectedAttr = makeCustomAttrWithListOfValues("foo", js("a"), js("b")); + JsonObject jo = LDConfig.DEFAULT.gson.toJsonTree(user).getAsJsonObject(); + assertEquals(expectedAttr, jo.get("custom")); + } + + @Test + public void canAddCustomAttrWithListOfNumbers() { + LDUser user = new LDUser.Builder("key") + .customNumber("foo", ImmutableList.of(new Integer(1), new Double(2))) + .build(); + JsonElement expectedAttr = makeCustomAttrWithListOfValues("foo", jint(1), jdouble(2)); + JsonObject jo = LDConfig.DEFAULT.gson.toJsonTree(user).getAsJsonObject(); + assertEquals(expectedAttr, jo.get("custom")); + } + + @Test + public void canAddCustomAttrWithListOfMixedValues() { + LDUser user = new LDUser.Builder("key") + .customValues("foo", ImmutableList.of(js("a"), jint(1), jbool(true))) + .build(); + JsonElement expectedAttr = makeCustomAttrWithListOfValues("foo", js("a"), jint(1), jbool(true)); + JsonObject jo = LDConfig.DEFAULT.gson.toJsonTree(user).getAsJsonObject(); + assertEquals(expectedAttr, jo.get("custom")); + } + + private JsonElement makeCustomAttrWithListOfValues(String name, JsonElement... values) { + JsonObject ret = new JsonObject(); + JsonArray a = new JsonArray(); + for (JsonElement v: values) { + a.add(v); + } + ret.add(name, a); + return ret; + } }