Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
# These are manipulated using the "nomulus" tool
# {create,update,delete,list}_premium_list commands.
foo,USD 100 # comment after the item
bar,JPY 14
bar,USD 14
Original file line number Diff line number Diff line change
Expand Up @@ -200,16 +200,23 @@ PremiumEntry createFromLine(String originalLine) {
List<String> parts = Splitter.on(',').trimResults().splitToList(line);
checkArgument(parts.size() == 2, "Could not parse line in premium list: %s", originalLine);
List<String> moneyParts = Splitter.on(' ').trimResults().splitToList(parts.get(1));
if (moneyParts.size() == 2 && this.currency != null) {
if (!Money.parse(parts.get(1)).getCurrencyUnit().equals(this.currency)) {
BigDecimal price;
if (moneyParts.size() == 2) {
Money money = Money.parse(parts.get(1));
// If no currency was explicitly set on the PremiumList prior to parsing (e.g., when creating
// a new list via `update_premium_list --upsert` or validating a raw file), infer the list's
// currency from the first entry that includes a currency code and enforce that all
// subsequent entries use the same currency.
if (this.currency == null) {
this.currency = money.getCurrencyUnit();
} else if (!money.getCurrencyUnit().equals(this.currency)) {
throw new IllegalArgumentException(
String.format("The currency unit must be %s", this.currency.getCode()));
}
price = money.getAmount();
} else {
price = new BigDecimal(parts.get(1));
}
BigDecimal price =
moneyParts.size() == 2
? Money.parse(parts.get(1)).getAmount()
: new BigDecimal(parts.get(1));
return new PremiumEntry.Builder()
.setLabel(parts.get(0))
.setPrice(price)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,51 @@

package google.registry.model.tld.label;

import static com.google.common.base.Preconditions.checkArgument;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import google.registry.model.tld.label.PremiumList.PremiumEntry;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import org.joda.money.CurrencyUnit;

/** Static utility methods for {@link PremiumList}. */
public class PremiumListUtils {

/**
* Parses the given CSV input lines into a {@link PremiumList}.
*
* <p>The {@code currencyUnit} is explicitly passed in when creating a list via {@code
* create_premium_list} (which requires the {@code --currency} flag) or when updating an existing
* list in the database (to ensure the updated entries match the existing list's persisted
* currency, and to support bare-number price entries that omit the currency code).
*
* <p>When {@code currencyUnit} is {@code null} (such as when creating a new list via {@code
* update_premium_list --upsert}, which does not take a {@code --currency} flag and has no
* existing database revision to consult), {@link PremiumList#createFromLine} infers the list's
* currency from the first input line that specifies a currency code and validates that all
* subsequent lines match it. If {@code currencyUnit} is {@code null} and no input line specifies
* a currency code, an {@link IllegalArgumentException} is thrown.
*/
public static PremiumList parseToPremiumList(
String name, CurrencyUnit currencyUnit, List<String> inputData, Instant creationTime) {
String name,
@Nullable CurrencyUnit currencyUnit,
List<String> inputData,
Instant creationTime) {
PremiumList partialPremiumList =
new PremiumList.Builder()
.setName(name)
.setCurrency(currencyUnit)
.setCreationTimestamp(creationTime)
.build();
ImmutableMap<String, PremiumEntry> prices = partialPremiumList.parse(inputData);
checkArgument(
partialPremiumList.getCurrency() != null,
"Could not determine currency for premium list from input file");
Map<String, BigDecimal> priceAmounts = Maps.transformValues(prices, PremiumEntry::getValue);
return partialPremiumList.asBuilder().setLabelsToPrices(priceAmounts).build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,9 @@ abstract class CreateOrUpdatePremiumListCommand extends ConfirmingCommand {
public String execute() throws Exception {
String message =
String.format("Saved premium list %s with %d entries.", name, inputData.size());
try {
logger.atInfo().log("Saving premium list for TLD %s.", name);
tm().transact(() -> PremiumListDao.save(name, currency, inputData));
logger.atInfo().log(message);
} catch (Throwable e) {
message = "Unexpected error saving premium list from nomulus tool command.";
logger.atSevere().withCause(e).log(message);
}
logger.atInfo().log("Saving premium list for TLD %s.", name);
tm().transact(() -> PremiumListDao.save(name, currency, inputData));
logger.atInfo().log(message);
return message;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,9 @@ protected String execute() {
String.format(
"Saved reserved list %s with %d entries.",
name, reservedList.getReservedListEntries().size());
try {
logger.atInfo().log("Saving reserved list for TLD %s.", name);
ReservedListDao.save(reservedList);
logger.atInfo().log(message);
} catch (Throwable e) {
message = "Unexpected error saving reserved list from nomulus tool command.";
logger.atSevere().withCause(e).log(message);
}
logger.atInfo().log("Saving reserved list for TLD %s.", name);
ReservedListDao.save(reservedList);
logger.atInfo().log(message);
return message;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.tld.Tlds.assertTldExists;
import static google.registry.model.tld.label.PremiumListUtils.parseToPremiumList;
import static google.registry.util.ListNamingUtils.convertFilePathToName;
import static java.nio.charset.StandardCharsets.UTF_8;

Expand Down Expand Up @@ -56,6 +57,8 @@ protected String prompt() throws Exception {
+ " (unless --override is specified), yet TLD %s does not exist");
}
inputData = Files.readAllLines(inputFile, UTF_8);
checkArgument(!inputData.isEmpty(), "New premium list data cannot be empty");
parseToPremiumList(name, currency, inputData, clock.now());
return String.format("Create new premium list for %s?", name);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,50 +15,65 @@
package google.registry.tools;

import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.tld.label.PremiumListUtils.parseToPremiumList;
import static google.registry.util.ListNamingUtils.convertFilePathToName;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.base.Strings;
import google.registry.model.tld.label.PremiumList;
import google.registry.model.tld.label.PremiumListDao;
import google.registry.model.tld.label.PremiumListUtils;
import java.nio.file.Files;
import java.util.Optional;

/** Command to safely update {@link PremiumList} in Database for a given TLD. */
@Parameters(separators = " =", commandDescription = "Update a PremiumList in Database.")
class UpdatePremiumListCommand extends CreateOrUpdatePremiumListCommand {

@Parameter(
names = {"-u", "--upsert"},
description = "Create the premium list if it does not already exist.")
boolean upsert;

// Indicates if there is a new change made by this command
private boolean newChange = false;

@Override
protected String prompt() throws Exception {
name = Strings.isNullOrEmpty(name) ? convertFilePathToName(inputFile) : name;
PremiumList existingList =
PremiumListDao.getLatestRevision(name)
.orElseThrow(
() ->
new IllegalArgumentException(
String.format(
"Could not update premium list %s because it doesn't exist", name)));
Optional<PremiumList> existingList = PremiumListDao.getLatestRevision(name);
checkArgument(
upsert || existingList.isPresent(),
"Could not update premium list %s because it doesn't exist",
name);
inputData = Files.readAllLines(inputFile, UTF_8);
checkArgument(!inputData.isEmpty(), "New premium list data cannot be empty");
currency = existingList.getCurrency();
// If the list already exists in the database, pass its persisted currency so that all entries
// are validated against it. Otherwise (when creating a new list via --upsert), pass null so
// that parseToPremiumList infers the currency from the input file entries.
PremiumList updatedPremiumList =
PremiumListUtils.parseToPremiumList(name, currency, inputData, clock.now());
parseToPremiumList(
name, existingList.map(PremiumList::getCurrency).orElse(null), inputData, clock.now());
currency = updatedPremiumList.getCurrency();
if (existingList.isEmpty()) {
newChange = true;
return String.format(
"Create new premium list for %s?\n New List: %s", name, updatedPremiumList);
}
if (!existingList
.get()
.getLabelsToPrices()
.entrySet()
.equals(updatedPremiumList.getLabelsToPrices().entrySet())) {
newChange = true;
return String.format(
"Update premium list for %s?\n Old List: %s\n New List: %s",
name, existingList, updatedPremiumList);
name, existingList.get(), updatedPremiumList);
} else {
return String.format(
"This update contains no changes to the premium list for %s.\n List Contents: %s",
name, existingList);
name, existingList.get());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,48 +14,64 @@

package google.registry.tools;

import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.util.DiffUtils.prettyPrintEntityDeepDiff;
import static google.registry.util.ListNamingUtils.convertFilePathToName;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.base.Strings;
import google.registry.model.tld.label.ReservedList;
import java.nio.file.Files;
import java.util.List;
import java.util.Optional;

/** Command to safely update {@link ReservedList}. */
@Parameters(separators = " =", commandDescription = "Update a ReservedList.")
final class UpdateReservedListCommand extends CreateOrUpdateReservedListCommand {

@Parameter(
names = {"-u", "--upsert"},
description = "Create the reserved list if it does not already exist.")
boolean upsert;

// indicates if there is a new change made by this command
private boolean newChange = true;

@Override
protected String prompt() throws Exception {
name = Strings.isNullOrEmpty(name) ? convertFilePathToName(input) : name;
ReservedList existingReservedList =
ReservedList.get(name)
.orElseThrow(
() ->
new IllegalArgumentException(
String.format(
"Could not update reserved list %s because it doesn't exist.", name)));
Optional<ReservedList> existingReservedList = ReservedList.get(name);
checkArgument(
upsert || existingReservedList.isPresent(),
"Could not update reserved list %s because it doesn't exist.",
name);
List<String> allLines = Files.readAllLines(input, UTF_8);
ReservedList.Builder updated =
existingReservedList.asBuilder().setReservedListMapFromLines(allLines);
if (existingReservedList.isEmpty()) {
newChange = true;
reservedList =
new ReservedList.Builder()
.setName(name)
.setReservedListMapFromLines(allLines)
.setCreationTimestamp(clock.now())
.build();
return String.format(
"Create new reserved list for %s?\n%s\nreservedListMap=%s\n",
name, reservedList, outputReservedListEntries(reservedList));
}
ReservedList existing = existingReservedList.get();
ReservedList.Builder updated = existing.asBuilder().setReservedListMapFromLines(allLines);
reservedList = updated.build();
boolean reservedListEntriesChanged =
!existingReservedList
.getReservedListEntries()
.equals(reservedList.getReservedListEntries());
!existing.getReservedListEntries().equals(reservedList.getReservedListEntries());
if (!reservedListEntriesChanged) {
newChange = false;
return "No entity changes to apply.";
}
return String.format("Update reserved list for %s?\n", name)
+ prettyPrintEntityDeepDiff(
existingReservedList.getReservedListEntries(), reservedList.getReservedListEntries());
existing.getReservedListEntries(), reservedList.getReservedListEntries());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.tld.label.PremiumListUtils.parseToPremiumList;
import static org.joda.money.CurrencyUnit.EUR;
import static org.joda.money.CurrencyUnit.USD;
import static org.junit.jupiter.api.Assertions.assertThrows;

Expand Down Expand Up @@ -56,6 +57,66 @@ void parseInputToPremiumList_throwsOnInconsistentCurrencies() {
assertThat(thrown).hasMessageThat().isEqualTo("The currency unit must be USD");
}

@Test
void parseInputToPremiumList_nullCurrency_infersFromInput() {
PremiumList premiumList =
parseToPremiumList(
"testlist",
null,
ImmutableList.of("# leading comment", " ", "foo,EUR 99.50 # inline", "bar,EUR 30"),
SAMPLE_TIME);
assertThat(premiumList.getName()).isEqualTo("testlist");
assertThat(premiumList.getCurrency()).isEqualTo(EUR);
assertThat(premiumList.getLabelsToPrices())
.containsExactly("foo", twoDigits(99.50), "bar", twoDigits(30));
}

@Test
void parseInputToPremiumList_nullCurrency_throwsOnInconsistentCurrencies() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
parseToPremiumList(
"testlist",
null,
ImmutableList.of("foo,EUR 99.50", "bar,USD 30"),
SAMPLE_TIME));
assertThat(thrown).hasMessageThat().isEqualTo("The currency unit must be EUR");
}

@Test
void parseInputToPremiumList_nullCurrency_throwsWhenCurrencyCannotBeDetermined() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
parseToPremiumList(
"testlist",
null,
ImmutableList.of("# comment only", "foo,99.50"),
SAMPLE_TIME));
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Could not determine currency for premium list from input file");
}

@Test
void parseInputToPremiumList_nullCurrency_throwsOnMalformedLine() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
parseToPremiumList(
"testlist",
null,
ImmutableList.of("malformed_line_without_comma"),
SAMPLE_TIME));
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Could not parse line in premium list: malformed_line_without_comma");
}

private static BigDecimal twoDigits(double num) {
return BigDecimal.valueOf((long) (num * 100.0), 2);
}
Expand Down
Loading
Loading