Don't allow duplicates in premium/reserved lists

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=148458642
This commit is contained in:
mcilwain 2017-02-24 07:26:43 -08:00 committed by Ben McIlwain
parent ea3a8dfa9d
commit dd400f30f5
6 changed files with 52 additions and 14 deletions

View file

@ -7,8 +7,9 @@ prices of domain labels (i.e. the part of the domain name without the TLD) by
checking for their presence on a list of prices in Datastore. `nomulus` is used checking for their presence on a list of prices in Datastore. `nomulus` is used
to load and update these lists from flat text files. The format of this list is to load and update these lists from flat text files. The format of this list is
simple: It is a newline-delimited CSV text file with each line containing the simple: It is a newline-delimited CSV text file with each line containing the
label and its price (including currency specifier in ISO-4217 format). As an label and its price (including currency specifier in ISO-4217 format). Any
example: individual label may not appear more than once in the file. Here's an example of
the formatting:
``` ```
premium,USD 100 premium,USD 100

View file

@ -6,7 +6,7 @@ for various reasons, usually because of potential abuse.
## Reserved list file format ## Reserved list file format
Reserved lists are handled in a similar way to [premium Reserved lists are handled in a similar way to [premium
lists](./premium-list-management.md), except that rather than each label having lists](./premium-list-management.md), except that instead of each label having
a price, it has a reservation type. The valid values for reservation types are: a price, it has a reservation type. The valid values for reservation types are:
* **`UNRESERVED`** - The default value for any label that isn't reserved. * **`UNRESERVED`** - The default value for any label that isn't reserved.
@ -29,11 +29,11 @@ a price, it has a reservation type. The valid values for reservation types are:
specified. specified.
The reservation types are listed in order of increasing precedence, so if a The reservation types are listed in order of increasing precedence, so if a
label is included on the same list multiple times, or on different lists that label is included on different lists that are applied to a single TLD, whichever
are applied to a single TLD, whichever reservation type is later in the list reservation type is later in the list takes precedence. E.g. a label being fully
takes precedence. E.g. a label being fully blocked in one list always supersedes blocked in one list always supersedes it being allowed in sunrise from another
it being allowed in sunrise from another list. In general `FULLY_BLOCKED` is by list. In general `FULLY_BLOCKED` is by far the most widely used reservation type
far the most widely used reservation type for typical TLD use cases. for typical TLD use cases.
Here's an example of a small reserved list. Note that Here's an example of a small reserved list. Note that
`RESERVED_FOR_ANCHOR_TENANT` is the only reservation type that has a third entry `RESERVED_FOR_ANCHOR_TENANT` is the only reservation type that has a third entry

View file

@ -23,10 +23,11 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.cache.CacheLoader.InvalidCacheLoadException; import com.google.common.cache.CacheLoader.InvalidCacheLoadException;
import com.google.common.cache.LoadingCache; import com.google.common.cache.LoadingCache;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Ordering; import com.google.common.collect.Multiset;
import com.google.common.util.concurrent.UncheckedExecutionException; import com.google.common.util.concurrent.UncheckedExecutionException;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Id; import com.googlecode.objectify.annotation.Id;
@ -85,16 +86,26 @@ public abstract class BaseDomainLabelList<T extends Comparable<?>, R extends Dom
@VisibleForTesting @VisibleForTesting
protected ImmutableMap<String, R> parse(Iterable<String> lines) { protected ImmutableMap<String, R> parse(Iterable<String> lines) {
Map<String, R> labelsToEntries = new HashMap<>(); Map<String, R> labelsToEntries = new HashMap<>();
Multiset<String> duplicateLabels = HashMultiset.create();
for (String line : lines) { for (String line : lines) {
R entry = createFromLine(line); R entry = createFromLine(line);
if (entry == null) { if (entry == null) {
continue; continue;
} }
String label = entry.getLabel(); String label = entry.getLabel();
// Adds the label to the list of all labels if it (a) doesn't already exist, or (b) already // Check if the label was already processed for this list (which is an error), and if so,
// exists, but the new value has higher priority (as determined by sort order). // accumulate it so that a list of all duplicates can be thrown.
labelsToEntries.put( if (labelsToEntries.containsKey(label)) {
label, Ordering.natural().nullsFirst().max(labelsToEntries.get(label), entry)); duplicateLabels.add(label, duplicateLabels.contains(label) ? 1 : 2);
} else {
labelsToEntries.put(label, entry);
}
}
if (!duplicateLabels.isEmpty()) {
throw new IllegalStateException(
String.format(
"List '%s' cannot contain duplicate labels. Dupes (with counts) were: %s",
name, duplicateLabels));
} }
return ImmutableMap.copyOf(labelsToEntries); return ImmutableMap.copyOf(labelsToEntries);
} }

View file

@ -273,6 +273,18 @@ public class PremiumListTest {
} }
} }
@Test
public void testParse_cannotIncludeDuplicateLabels() {
thrown.expect(
IllegalStateException.class,
"List 'tld' cannot contain duplicate labels. Dupes (with counts) were: [lol x 2]");
PremiumList.get("tld")
.get()
.parse(
ImmutableList.of(
"lol,USD 100", "rofl,USD 90", "paper,USD 80", "wood,USD 70", "lol,USD 200"));
}
/** Persists a premium list with a specified number of nonsense entries. */ /** Persists a premium list with a specified number of nonsense entries. */
private PremiumList persistHumongousPremiumList(String name, int size) { private PremiumList persistHumongousPremiumList(String name, int size) {
String[] entries = new String[size]; String[] entries = new String[size];

View file

@ -291,6 +291,21 @@ public class ReservedListTest {
.build()); .build());
} }
@Test
public void testParse_cannotIncludeDuplicateLabels() {
ReservedList rl = new ReservedList.Builder().setName("blah").build();
thrown.expect(
IllegalStateException.class,
"List 'blah' cannot contain duplicate labels. Dupes (with counts) were: [lol x 2]");
rl.parse(
ImmutableList.of(
"lol,FULLY_BLOCKED",
"rofl,FULLY_BLOCKED",
"paper,FULLY_BLOCKED",
"wood,FULLY_BLOCKED",
"lol,FULLY_BLOCKED"));
}
/** Gets the name of a reserved list. */ /** Gets the name of a reserved list. */
public static final Function<Key<ReservedList>, String> GET_NAME_FUNCTION = public static final Function<Key<ReservedList>, String> GET_NAME_FUNCTION =
new Function<Key<ReservedList>, String>() { new Function<Key<ReservedList>, String>() {

View file

@ -10,4 +10,3 @@ palladium,USD 877
aluminum,USD 11 aluminum,USD 11
copper,USD 15 copper,USD 15
brass,USD 20 brass,USD 20
platinum,USD 80000 #this should be ignored as a cheaper duplicate

1 rich USD 100
10 aluminum USD 11
11 copper USD 15
12 brass USD 20
platinum USD 80000 #this should be ignored as a cheaper duplicate