Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sets validator #523

Merged
merged 4 commits into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions src/main/java/io/split/android/client/SplitFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,29 @@
import androidx.annotation.NonNull;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import io.split.android.client.validators.FlagSetsValidatorImpl;
import io.split.android.client.validators.SplitFilterValidator;

public class SplitFilter {
public enum Type {
// Filters here has to be defined in the order
// it will be in querystring
BY_NAME,
BY_SET,
BY_PREFIX;

@NonNull
@Override
public String toString() {
switch (this) {
case BY_NAME:
return "by split name";
case BY_PREFIX:
return "by split prefix";
case BY_SET:
return "by flag set";
default:
return "Invalid type";
}
Expand All @@ -31,6 +37,8 @@ public String queryStringField() {
return "names";
case BY_PREFIX:
return "prefixes";
case BY_SET:
return "sets";
default:
return "unknown";
}
Expand All @@ -42,6 +50,8 @@ public int maxValuesCount() {
return 400;
case BY_PREFIX:
return 50;
case BY_SET:
return 1000;
default:
return 0;
}
Expand All @@ -59,26 +69,30 @@ static public SplitFilter byPrefix(@NonNull List<String> values) {
return new SplitFilter(Type.BY_PREFIX, values);
}

static public SplitFilter bySet(@NonNull List<String> values) {
return new SplitFilter(Type.BY_SET, values, new FlagSetsValidatorImpl());
}

// This constructor is not private (but default) to allow Split Sync Config builder be agnostic when creating filters
// Also is not public to force SDK users to use static functions "byName" and "byPrefix"
SplitFilter(Type type, List<String> values) {
if(values == null) {
if (values == null) {
throw new IllegalArgumentException("Values can't be null for " + type.toString() + " filter");
}
mType = type;
mValues = new ArrayList<>(values);
}

SplitFilter(Type type, List<String> values, SplitFilterValidator validator) {
mType = type;
mValues = validator.cleanup(values);
}

public Type getType() {
return mType;
}

public List<String> getValues() {
return mValues;
}

public void updateValues(List<String> values) {
mValues.clear();
mValues.addAll(values);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.split.android.client.validators;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.TreeSet;

import io.split.android.client.utils.logger.Logger;

public class FlagSetsValidatorImpl implements SplitFilterValidator {

private static final String FLAG_SET_REGEX = "^[a-z][_a-z0-9]{0,49}$";

/**
* Validates the flag sets and returns a list of
* de-duplicated and alphanumerically ordered valid flag sets.
*
* @param values list of flag sets
* @return list of unique alphanumerically ordered valid flag sets
*/
@Override
public List<String> cleanup(List<String> values) {
if (values == null || values.isEmpty()) {
return Collections.emptyList();
}

TreeSet<String> cleanedUpSets = new TreeSet<>();
for (String set : values) {
if (set == null || set.isEmpty()) {
continue;
}

if (set.startsWith(" ") || set.endsWith(" ")) {
gthea marked this conversation as resolved.
Show resolved Hide resolved
Logger.w("SDK config: Flag Set name " + set + " has extra whitespace, trimming");
set = set.trim();
}

if (set.matches(FLAG_SET_REGEX)) {
cleanedUpSets.add(set);
} else {
Logger.w("SDK config: you passed "+ set +", Flag Set must adhere to the regular expressions "+ FLAG_SET_REGEX +". This means a Flag Set must be start with a letter, be in lowercase, alphanumeric and have a max length of 50 characters. "+ set +" was discarded.");
}
}

return new ArrayList<>(cleanedUpSets);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.split.android.client.validators;

import java.util.List;

public interface SplitFilterValidator {

List<String> cleanup(List<String> values);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package io.split.android.client.validators;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import org.junit.Test;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class FlagSetsValidatorImplTest {

private final FlagSetsValidatorImpl mValidator = new FlagSetsValidatorImpl();

@Test
public void nullInputReturnsEmptyList() {
List<String> result = mValidator.cleanup(null);
assertTrue(result.isEmpty());
}

@Test
public void emptyInputReturnsEmptyList() {
List<String> result = mValidator.cleanup(Collections.emptyList());
assertTrue(result.isEmpty());
}

@Test
public void duplicatedInputValuesAreRemoved() {
List<String> result = mValidator.cleanup(Arrays.asList("set1", "set1"));
assertEquals(1, result.size());
assertTrue(result.contains("set1"));
}

@Test
public void valuesAreSortedAlphanumerically() {
List<String> result = mValidator.cleanup(Arrays.asList("set2", "set1", "set_1"));
assertEquals(3, result.size());
assertEquals("set1", result.get(0));
assertEquals("set2", result.get(1));
assertEquals("set_1", result.get(2));
}

@Test
public void invalidValuesAreRemoved() {
List<String> result = mValidator.cleanup(Arrays.asList("set1", "set2", "set_1", "set-1", "set 1", "set 2"));
assertEquals(3, result.size());
assertEquals("set1", result.get(0));
assertEquals("set2", result.get(1));
assertEquals("set_1", result.get(2));
}

@Test
public void setWithMoreThan50CharsIsRemoved() {
String longSet = "abcdfghijklmnopqrstuvwxyz1234567890abcdfghijklmnopq";
List<String> result = mValidator.cleanup(Arrays.asList("set1", longSet));
assertEquals(51, longSet.length());
assertEquals(1, result.size());
assertEquals("set1", result.get(0));
}

@Test
public void setWithLessThanOneCharIsOrEmptyRemoved() {
List<String> result = mValidator.cleanup(Arrays.asList("set1", "", " "));
assertEquals(1, result.size());
assertEquals("set1", result.get(0));
}

@Test
public void nullSetIsRemoved() {
List<String> result = mValidator.cleanup(Arrays.asList("set1", null));
assertEquals(1, result.size());
assertEquals("set1", result.get(0));
}

@Test
public void setWithExtraWhitespaceIsTrimmed() {
List<String> result = mValidator.cleanup(Arrays.asList("set1 ", " set2", "set3", "set 4"));
assertEquals(3, result.size());
assertEquals("set1", result.get(0));
assertEquals("set2", result.get(1));
assertEquals("set3", result.get(2));
}
}