1
0
mirror of https://github.com/google/nomulus synced 2025-12-23 14:25:44 +00:00

Remove more old-console-related files (#2866)

This commit is contained in:
gbrodman
2025-11-05 14:43:59 -05:00
committed by GitHub
parent 34bea69a48
commit 7b8d07954b
20 changed files with 6 additions and 2012 deletions

View File

@@ -49,7 +49,6 @@ import google.registry.request.Modules.UrlConnectionServiceModule;
import google.registry.request.RequestHandler;
import google.registry.request.auth.AuthModule;
import google.registry.request.auth.RequestAuthenticator;
import google.registry.ui.ConsoleDebug.ConsoleConfigModule;
import google.registry.util.UtilsModule;
import jakarta.inject.Provider;
import jakarta.inject.Singleton;
@@ -63,7 +62,6 @@ import jakarta.inject.Singleton;
BigqueryModule.class,
CloudTasksUtilsModule.class,
ConfigModule.class,
ConsoleConfigModule.class,
CredentialModule.class,
CustomLogicFactoryModule.class,
DirectoryModule.class,

View File

@@ -35,7 +35,6 @@ import google.registry.privileges.secretmanager.SecretManagerModule;
import google.registry.request.Modules.GsonModule;
import google.registry.request.Modules.NetHttpTransportModule;
import google.registry.request.auth.AuthModule;
import google.registry.ui.ConsoleDebug.ConsoleConfigModule;
import google.registry.util.UtilsModule;
import jakarta.inject.Singleton;
@@ -46,7 +45,6 @@ import jakarta.inject.Singleton;
AuthModule.class,
CloudTasksUtilsModule.class,
ConfigModule.class,
ConsoleConfigModule.class,
CredentialModule.class,
CustomLogicFactoryModule.class,
CloudTasksUtilsModule.class,

View File

@@ -1,60 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.ui;
import dagger.Module;
import dagger.Provides;
/** Enum defining which JS/CSS files get rendered in a soy templates. */
public enum ConsoleDebug {
/** Use compiled CSS and JS. */
PRODUCTION,
/** Use debug compiled CSS and JS, where symbols are only <i>slightly</i> mangled. */
DEBUG,
/**
* Use debug compiled CSS and raw JS from internal source code dependency-managed directory
* structure.
*/
RAW,
/** Don't use any CSS or JS. This is used by JSTD unit tests. */
TEST;
private static final String PROPERTY = "console.debug";
private static final String DEFAULT = PRODUCTION.name();
/** Returns value configured by system property {@code #PROPERTY}. */
public static ConsoleDebug get() {
return valueOf(System.getProperty(PROPERTY, DEFAULT));
}
/** Sets the global {@link ConsoleDebug} state. */
public static void set(ConsoleDebug value) {
System.setProperty(PROPERTY, value.toString());
}
/** Dagger module for ConsoleDebug. */
@Module
public static final class ConsoleConfigModule {
@Provides
static ConsoleDebug provideConsoleDebug() {
return ConsoleDebug.get();
}
}
}

View File

@@ -1,58 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.ui.forms;
import static com.google.common.base.Preconditions.checkNotNull;
import javax.annotation.Detainted;
import javax.annotation.concurrent.NotThreadSafe;
/**
* Exception thrown when a form is invalid. Problems with a specific
* form field should use {@link FormFieldException} instead.
*
* <p>You can safely throw {@code FormException} from within your form
* validator, and the message will automatically be propagated to the
* client form interface.
*/
@NotThreadSafe
public class FormException extends RuntimeException {
/**
* Creates a new {@link FormException}
*
* @param userMessage should be a friendly message that's safe to show to the user.
*/
public FormException(@Detainted String userMessage) {
super(checkNotNull(userMessage, "userMessage"), null);
}
/**
* Creates a new {@link FormException}
*
* @param userMessage should be a friendly message that's safe to show to the user.
* @param cause the original cause of this exception. May be null.
*/
public FormException(@Detainted String userMessage, Throwable cause) {
super(checkNotNull(userMessage, "userMessage"), cause);
}
/** Returns an error message that's safe to display to the user. */
@Override
@Detainted
public String getMessage() {
return super.getMessage();
}
}

View File

@@ -1,762 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.ui.forms;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import com.google.common.base.Ascii;
import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Range;
import com.google.common.collect.Streams;
import com.google.re2j.Pattern;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import javax.annotation.Detainted;
import javax.annotation.Nullable;
import javax.annotation.Tainted;
import javax.annotation.concurrent.Immutable;
/**
* Declarative functional fluent form field converter / validator.
*
* <p>This class is responsible for converting arbitrary data, sent to us by the web browser, into
* validated data structures that the server-side code can use. For example:
*
* <pre>{@code
* private enum Gender { MALE, FEMALE }
*
* private static final FormField<String, String> NAME_FIELD = FormField.named("name")
* .matches("[a-z]+")
* .range(atMost(16))
* .required()
* .build();
*
* private static final FormField<String, Gender> GENDER_FIELD = FormField.named("gender")
* .asEnum(Gender.class)
* .required()
* .build();
*
* public Person makePerson(Map<String, String> params) {
* Person.Builder person = new Person.Builder();
* for (String name : NAME_FIELD.extract(params).asSet()) {
* person.setName(name);
* }
* for (Gender name : GENDER_FIELD.extract(params).asSet()) {
* person.setGender(name);
* }
* return person.build();
* }
* }</pre>
*
* <p>This class provides <b>full type-safety</b> <i>if and only if</i> you statically initialize
* your FormField objects and write a unit test that causes the class to be loaded.
*
* <h2>Exception Handling</h2>
*
* <p>When values passed to {@link #convert} or {@link #extract} don't meet the contract, {@link
* FormFieldException} will be thrown, which provides the field name and a short error message
* that's safe to pass along to the client.
*
* <p>You can safely throw {@code FormFieldException} from within your validator functions, and the
* field name will automatically be propagated into the exception object for you.
*
* <p>In situations when you're validating lists or maps, you'll end up with a hierarchical field
* naming structure. For example, if you were validating a list of maps, an error generated by the
* {@code bar} field of the fifth item in the {@code foo} field would have a fully-qualified field
* name of: {@code foo[5][bar]}.
*
* <h3>Library Definitions</h3>
*
* <p>You should never assign a partially constructed {@code FormField.Builder} to a variable or
* constant. Instead, you should use {@link #asBuilder()} or {@link #asBuilderNamed(String)}.
*
* <p>Here is an example of how you might go about defining library definitions:
*
* <pre>{@code
* final class FormFields {
* private static final FormField<String, String> COUNTRY_CODE =
* FormField.named("countryCode")
* .range(Range.singleton(2))
* .uppercased()
* .in(ImmutableSet.copyOf(Locale.getISOCountries()))
* .build();
* }
*
* final class Form {
* private static final FormField<String, String> COUNTRY_CODE_FIELD =
* FormFields.COUNTRY_CODE.asBuilder()
* .required()
* .build();
* }
* }</pre>
*
* @param <I> input value type
* @param <O> output value type
*/
@Immutable
public final class FormField<I, O> {
private final String name;
private final Class<I> typeIn;
private final Class<O> typeOut;
private final Function<I, O> converter;
private FormField(String name, Class<I> typeIn, Class<O> typeOut, Function<I, O> converter) {
this.name = name;
this.typeIn = typeIn;
this.typeOut = typeOut;
this.converter = converter;
}
/** Returns an optional string form field named {@code name}. */
public static Builder<String, String> named(String name) {
return named(name, String.class);
}
/** Returns an optional form field named {@code name} with a specific {@code inputType}. */
public static <T> Builder<T, T> named(String name, Class<T> typeIn) {
checkArgument(!name.isEmpty());
return new Builder<>(name, checkNotNull(typeIn), typeIn, x -> x);
}
/**
* Returns a form field builder for validating JSON nested maps.
*
* <p>Here's an example of how you'd use this feature:
*
* <pre>
* private static final FormField&lt;String, String&gt; REGISTRAR_NAME_FIELD =
* FormField.named("name")
* .emptyToNull()
* .required()
* .build();
*
* private static final FormField&lt;Map&lt;String, ?&gt;, Registrar&gt; REGISTRAR_FIELD =
* FormField.mapNamed("registrar")
* .transform(Registrar.class, new Function&lt;Map&lt;String, ?&gt;, Registrar&gt;() {
* &#064;Nullable
* &#064;Override
* public Registrar apply(&#064;Nullable Map&lt;String, ?&gt; params) {
* Registrar.Builder builder = new Registrar.Builder();
* for (String name : REGISTRAR_NAME_FIELD.extractUntyped(params).asSet()) {
* builder.setName(name);
* }
* return builder.build();
* }})
* .build();</pre>
*
* <p>When a {@link FormFieldException} is thrown, it'll be propagated to create a fully-qualified
* field name. For example, if the JSON input is <pre>{registrar: {name: ""}}</pre> then the
* {@link FormFieldException#getFieldName() field name} will be {@code registrar.name}.
*/
public static Builder<Map<String, ?>, Map<String, ?>> mapNamed(String name) {
@SuppressWarnings("unchecked")
Class<Map<String, ?>> typeIn = (Class<Map<String, ?>>) (Class<?>) Map.class;
return named(name, typeIn);
}
/** Returns the name of this field. */
public String name() {
return name;
}
/**
* Convert and validate a raw user-supplied value.
*
* @throws FormFieldException if value does not meet expected contracts.
*/
@Detainted
public Optional<O> convert(@Tainted @Nullable I value) {
try {
return Optional.ofNullable(converter.apply(value));
} catch (FormFieldException e) {
throw e.propagate(name);
}
}
/**
* Convert and validate a raw user-supplied value from a map.
*
* <p>This is the same as saying: {@code field.convert(valueMap.get(field.name())}
*
* @throws FormFieldException if value does not meet expected contracts.
*/
@Detainted
public Optional<O> extract(@Tainted Map<String, I> valueMap) {
return convert(valueMap.get(name));
}
/**
* Convert and validate a raw user-supplied value from an untyped JSON map.
*
* @throws FormFieldException if value is wrong type or does not meet expected contracts.
*/
@Detainted
public Optional<O> extractUntyped(@Tainted Map<String, ?> jsonMap) {
Object value = jsonMap.get(name);
I castedValue;
try {
castedValue = typeIn.cast(value);
} catch (ClassCastException e) {
throw new FormFieldException(String.format("Type error: got: %s, expected: %s",
value.getClass().getSimpleName(),
typeIn.getSimpleName())).propagate(name);
}
return convert(castedValue);
}
/**
* Returns a builder of this object, which can be used to further restrict validation.
*
* @see #asBuilderNamed(String)
*/
public Builder<I, O> asBuilder() {
return new Builder<>(name, typeIn, typeOut, converter);
}
/** Same as {@link #asBuilder()} but changes the field name. */
public Builder<I, O> asBuilderNamed(String newName) {
checkArgument(!newName.isEmpty());
return new Builder<>(newName, typeIn, typeOut, converter);
}
/**
* Mutable builder for {@link FormField}.
*
* @param <I> input value type
* @param <O> output value type
*/
public static final class Builder<I, O> {
private final String name;
private final Class<I> typeIn;
private final Class<O> typeOut;
private Function<I, O> converter;
private Builder(String name, Class<I> typeIn, Class<O> typeOut, Function<I, O> converter) {
this.name = name;
this.typeIn = typeIn;
this.typeOut = typeOut;
this.converter = converter;
}
/** Causes {@code defaultValue} to be substituted if value is {@code null}. */
public Builder<I, O> withDefault(O defaultValue) {
return transform(new DefaultFunction<>(checkNotNull(defaultValue)));
}
/** Ensure value is not {@code null}. */
public Builder<I, O> required() {
return transform(Builder::checkNotNullTransform);
}
/**
* Transform empty values into {@code null}.
*
* @throws IllegalStateException if current output type is not a {@link CharSequence} or
* {@link Collection}.
*/
public Builder<I, O> emptyToNull() {
checkState(CharSequence.class.isAssignableFrom(typeOut)
|| Collection.class.isAssignableFrom(typeOut));
return transform(
input ->
((input instanceof CharSequence) && (((CharSequence) input).length() == 0))
|| ((input instanceof Collection) && ((Collection<?>) input).isEmpty())
? null
: input);
}
/**
* Modify {@link String} input to remove whitespace around the sides.
*
* <p>{@code null} values are passed through.
*
* @throws IllegalStateException if current output type is not a String.
*/
public Builder<I, String> trimmed() {
checkState(String.class.isAssignableFrom(typeOut));
@SuppressWarnings("unchecked")
Function<O, String> trimFunction =
(Function<O, String>)
((Function<String, String>) input -> input != null ? input.trim() : null);
return transform(String.class, trimFunction);
}
/**
* Modify {@link String} input to be uppercase.
*
* <p>{@code null} values are passed through.
*
* @throws IllegalStateException if current output type is not a String.
*/
public Builder<I, String> uppercased() {
checkState(String.class.isAssignableFrom(typeOut));
@SuppressWarnings("unchecked")
Function<O, String> funk =
(Function<O, String>)
((Function<String, String>)
input -> input != null ? input.toUpperCase(Locale.ENGLISH) : null);
return transform(String.class, funk);
}
/**
* Modify {@link String} input to be lowercase.
*
* <p>{@code null} values are passed through.
*
* @throws IllegalStateException if current output type is not a String.
*/
public Builder<I, String> lowercased() {
checkState(String.class.isAssignableFrom(typeOut));
@SuppressWarnings("unchecked")
Function<O, String> funk =
(Function<O, String>)
((Function<String, String>)
input -> input != null ? input.toLowerCase(Locale.ENGLISH) : null);
return transform(String.class, funk);
}
/**
* Ensure input matches {@code pattern}.
*
* <p>{@code null} values are passed through.
*
* @param pattern is used to validate the user input. It matches against the whole string, so
* you don't need to use the ^$ characters.
* @param errorMessage is a helpful error message, which should include an example. If this is
* not provided, a default error message will be shown that includes the regexp pattern.
* @throws IllegalStateException if current output type is not a {@link CharSequence}.
* @see #matches(Pattern)
*/
public Builder<I, O> matches(Pattern pattern, @Nullable String errorMessage) {
checkState(CharSequence.class.isAssignableFrom(typeOut));
return transform(
new MatchesFunction<>(checkNotNull(pattern), Optional.ofNullable(errorMessage)));
}
/** Alias for {@link #matches(Pattern, String) matches(pattern, null)} */
public Builder<I, O> matches(Pattern pattern) {
return matches(pattern, null);
}
/**
* Removes all characters not in {@code matcher}.
*
* <p>{@code null} values are passed through.
*
* @param matcher indicates which characters are to be retained
* @throws IllegalStateException if current output type is not a {@link CharSequence}
*/
public Builder<I, String> retains(CharMatcher matcher) {
checkState(CharSequence.class.isAssignableFrom(typeOut));
@SuppressWarnings("unchecked") // safe due to checkState call
Function<O, String> function =
(Function<O, String>) new RetainFunction(checkNotNull(matcher));
return transform(String.class, function);
}
/**
* Enforce value length/size/value is within {@code range}.
*
* <p>The following input value types are supported:
*
* <ul>
* <li>{@link CharSequence}: Length must be within {@code range}.
* <li>{@link Collection}: Size must be within {@code range}.
* <li>{@link Number}: Value must be within {@code range}.
* </ul>
*
* <p>{@code null} values are passed through. Please note that setting a lower bound on your
* range does not imply {@link #required()}, as range checking only applies to non-{@code null}
* values.
*
* @throws IllegalStateException if current output type is not one of the above types.
*/
public Builder<I, O> range(Range<Integer> range) {
checkState(CharSequence.class.isAssignableFrom(typeOut)
|| Collection.class.isAssignableFrom(typeOut)
|| Number.class.isAssignableFrom(typeOut));
return transform(new RangeFunction<>(checkNotNull(range)));
}
/**
* Enforce value be a member of {@code values}.
*
* <p>{@code null} values are passed through.
*
* @throws IllegalArgumentException if {@code values} is empty.
*/
public Builder<I, O> in(Set<O> values) {
checkArgument(!values.isEmpty());
return transform(new InFunction<>(values));
}
/**
* Performs arbitrary type transformation from {@code O} to {@code T}.
*
* <p>Your {@code transform} function is expected to pass-through {@code null} values as a
* no-op, since it's up to {@link #required()} to block them. You might also want to consider
* using a try block that rethrows exceptions as {@link FormFieldException}.
*
* <p>Here's an example of how you'd convert from String to Integer:
*
* <pre>
* FormField.named("foo", String.class)
* .transform(Integer.class, new Function&lt;String, Integer&gt;() {
* &#064;Nullable
* &#064;Override
* public Integer apply(&#064;Nullable String input) {
* try {
* return input != null ? Integer.parseInt(input) : null;
* } catch (IllegalArgumentException e) {
* throw new FormFieldException("Invalid number.", e);
* }
* }})
* .build();</pre>
*
* @see #transform(Function)
*/
public <T> Builder<I, T> transform(Class<T> newType, Function<O, T> transform) {
return new Builder<>(
name, typeIn, checkNotNull(newType), this.converter.andThen(checkNotNull(transform)));
}
/**
* Manipulates values without changing type.
*
* <p>Please see {@link #transform(Class, Function)} for information about the contract to
* which {@code transform} is expected to conform.
*/
public Builder<I, O> transform(Function<O, O> transform) {
this.converter = this.converter.andThen(checkNotNull(transform));
return this;
}
/**
* Uppercases value and converts to an enum field of {@code enumClass}.
*
* <p>{@code null} values are passed through.
*
* @throws IllegalArgumentException if {@code enumClass} is not an enum class.
* @throws IllegalStateException if current output type is not a String.
*/
public <C extends Enum<C>> Builder<I, C> asEnum(Class<C> enumClass) {
checkArgument(enumClass.isEnum());
checkState(String.class.isAssignableFrom(typeOut));
return transform(enumClass, new ToEnumFunction<>(enumClass));
}
/**
* Turns this form field into something that processes lists.
*
* <p>The current object definition will be applied to each item in the list. If a
* {@link FormFieldException} is thrown when processing an item, then its
* {@link FormFieldException#getFieldName() fieldName} will be rewritten to include the index,
* e.g. {@code name} becomes {@code name[0]}.
*
* <p>The outputted list will be an {@link ImmutableList}. This is not reflected in the generic
* typing for the sake of brevity.
*
* <p>A {@code null} value for list will be passed through. List items that convert to
* {@code null} will be discarded (since {@code ImmutableList} does not permit {@code null}
* values).
*/
public Builder<List<I>, List<O>> asList() {
@SuppressWarnings("unchecked") Class<List<I>> in = (Class<List<I>>) (Class<I>) List.class;
@SuppressWarnings("unchecked") Class<List<O>> out = (Class<List<O>>) (Class<O>) List.class;
return new Builder<>(name, in, out, new ToListFunction<>(build()));
}
/**
* Turns this form field into a split string list that applies itself to each item.
*
* <p>The behavior of this method is counter-intuitive. It behaves similar to {@link #asList()}
* in the sense that all transforms specified <i>before</i> this method will be applied to the
* individual resulting list items.
*
* <p>For example, to turn a comma-delimited string into an enum list:<pre> {@code
*
* private static final FormField<String, List<State>> STATES_FIELD =
* FormField.named("states")
* .uppercased()
* .asEnum(State.class)
* .asList(Splitter.on(',').omitEmptyStrings().trimResults())
* .build();}</pre>
*
* <p>You'll notice that the transforms specified before this method are applied to each list
* item. However unlike {@link #asList()}, if an error is thrown on an individual item, then
* {@link FormFieldException#getFieldName()} will <i>not</i> contain the index.
*
* @throws IllegalStateException If either the current input type isn't String.
*/
public Builder<String, List<O>> asList(Splitter splitter) {
checkNotNull(splitter);
checkState(String.class.isAssignableFrom(typeIn));
@SuppressWarnings("unchecked") Class<List<O>> out = (Class<List<O>>) (Class<O>) List.class;
@SuppressWarnings("unchecked") FormField<String, O> inField = (FormField<String, O>) build();
return new Builder<>(name, String.class, out, new SplitToListFunction<>(inField, splitter));
}
/**
* Same as {@link #asList()} but outputs an {@link ImmutableSet} instead.
*
* @throws IllegalStateException if you called asList() before calling this method.
*/
public Builder<List<I>, Set<O>> asSet() {
checkState(!List.class.isAssignableFrom(typeOut));
@SuppressWarnings("unchecked")
Class<Set<O>> setOut = (Class<Set<O>>) (Class<O>) Set.class;
@SuppressWarnings("unchecked")
Function<List<O>, Set<O>> toSetFunction =
(Function<List<O>, Set<O>>)
(Function<O, O>)
((Function<List<Object>, Set<Object>>)
input -> input != null ? ImmutableSet.copyOf(input) : null);
return asList().transform(setOut, toSetFunction);
}
/**
* Same as {@link #asList(Splitter)} but outputs an {@link ImmutableSet} instead.
*
* @throws IllegalStateException If the current input type isn't String.
*/
public Builder<String, Set<O>> asSet(Splitter splitter) {
checkNotNull(splitter);
checkState(String.class.isAssignableFrom(typeIn));
@SuppressWarnings("unchecked") Class<Set<O>> out = (Class<Set<O>>) (Class<O>) Set.class;
@SuppressWarnings("unchecked") FormField<String, O> inField = (FormField<String, O>) build();
return new Builder<>(name, String.class, out, new SplitToSetFunction<>(inField, splitter));
}
/** Creates a new {@link FormField} instance. */
public FormField<I, O> build() {
return new FormField<>(name, typeIn, typeOut, converter);
}
private static <O> O checkNotNullTransform(@Nullable O input) {
if (input == null) {
throw new FormFieldException("This field is required.");
}
return input;
}
private static final class DefaultFunction<O> implements Function<O, O> {
private final O defaultValue;
DefaultFunction(O defaultValue) {
this.defaultValue = defaultValue;
}
@Nullable
@Override
public O apply(@Nullable O input) {
return input != null ? input : defaultValue;
}
}
private static final class RangeFunction<O> implements Function<O, O> {
private final Range<Integer> range;
RangeFunction(Range<Integer> range) {
this.range = range;
}
@Nullable
@Override
public O apply(@Nullable O input) {
if (input == null) {
return null;
}
if (input instanceof CharSequence) {
checkRangeContains(range, ((CharSequence) input).length(), "Number of characters");
} else if (input instanceof Collection) {
checkRangeContains(range, ((Collection<?>) input).size(), "Number of items");
} else if (input instanceof Number) {
checkRangeContains(range, ((Number) input).intValue(), "Value");
} else {
throw new AssertionError();
}
return input;
}
private void checkRangeContains(Range<Integer> range, int value, String message) {
if (!range.contains(value)) {
throw new FormFieldException(
String.format("%s (%,d) not in range %s", message, value, range));
}
}
}
private static final class InFunction<O> implements Function<O, O> {
private final Set<O> values;
InFunction(Set<O> values) {
this.values = values;
}
@Nullable
@Override
public O apply(@Nullable O input) {
if (input == null) {
return null;
}
if (!values.contains(input)) {
throw new FormFieldException("Unrecognized value.");
}
return input;
}
}
private static final class MatchesFunction<O> implements Function<O, O> {
private final Pattern pattern;
private final Optional<String> errorMessage;
MatchesFunction(Pattern pattern, Optional<String> errorMessage) {
this.pattern = pattern;
this.errorMessage = errorMessage;
}
@Nullable
@Override
public O apply(@Nullable O input) {
if (input == null) {
return null;
}
if (!pattern.matcher((CharSequence) input).matches()) {
throw new FormFieldException(errorMessage.orElse("Must match pattern: " + pattern));
}
return input;
}
}
private static final class RetainFunction implements Function<CharSequence, String> {
private final CharMatcher matcher;
RetainFunction(CharMatcher matcher) {
this.matcher = matcher;
}
@Nullable
@Override
public String apply(@Nullable CharSequence input) {
if (input == null) {
return null;
}
return matcher.retainFrom(input);
}
}
private static final class ToEnumFunction<O, C extends Enum<C>> implements Function<O, C> {
private final Class<C> enumClass;
ToEnumFunction(Class<C> enumClass) {
this.enumClass = enumClass;
}
@Nullable
@Override
public C apply(@Nullable O input) {
try {
return input != null ? Enum.valueOf(enumClass, Ascii.toUpperCase((String) input)) : null;
} catch (IllegalArgumentException e) {
throw new FormFieldException(
String.format("Enum %s does not contain '%s'", enumClass.getSimpleName(), input));
}
}
}
private static final class ToListFunction<I, O> implements Function<List<I>, List<O>> {
private final FormField<I, O> itemField;
ToListFunction(FormField<I, O> itemField) {
this.itemField = itemField;
}
@Nullable
@Override
public List<O> apply(@Nullable List<I> input) {
if (input == null) {
return null;
}
ImmutableList.Builder<O> builder = new ImmutableList.Builder<>();
for (int i = 0; i < input.size(); i++) {
I inputItem = itemField.typeIn.cast(input.get(i));
O outputItem;
try {
outputItem = itemField.converter.apply(inputItem);
} catch (FormFieldException e) {
throw e.propagate(i);
}
if (outputItem != null) {
builder.add(outputItem);
}
}
return builder.build();
}
}
private static final class SplitToListFunction<O> implements Function<String, List<O>> {
private final FormField<String, O> itemField;
private final Splitter splitter;
SplitToListFunction(FormField<String, O> itemField, Splitter splitter) {
this.itemField = itemField;
this.splitter = splitter;
}
@Nullable
@Override
public List<O> apply(@Nullable String input) {
return input == null
? null
: Streams.stream(splitter.split(input))
.map(itemField.converter)
.collect(toImmutableList());
}
}
private static final class SplitToSetFunction<O> implements Function<String, Set<O>> {
private final FormField<String, O> itemField;
private final Splitter splitter;
SplitToSetFunction(FormField<String, O> itemField, Splitter splitter) {
this.itemField = itemField;
this.splitter = splitter;
}
@Nullable
@Override
public Set<O> apply(@Nullable String input) {
return input == null
? null
: Streams.stream(splitter.split(input))
.map(itemField.converter)
.collect(toImmutableSet());
}
}
}
}

View File

@@ -1,178 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.ui.forms;
import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import javax.annotation.CheckReturnValue;
import javax.annotation.Detainted;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
/**
* Exception thrown when a form field contains a bad value.
*
* <p>You can safely throw {@code FormFieldException} from within your validator functions, and the
* field name will automatically be propagated into the exception object for you.
*
* <p>The way that field names work is a bit complicated, because we need to support complex nested
* field names like {@code foo[3].bar}. So what happens is the original exception will be thrown by
* a {@link FormField} validator without the field set. Then as the exception bubbles up the stack,
* it'll be caught by the {@link FormField#convert(Object) convert} method, which then prepends the
* name of that component. Then when the exception reaches the user, the {@link #getFieldName()}
* method will produce the fully-qualified field name.
*
* <p>This propagation mechanism is also very important when writing {@link
* FormField.Builder#transform} functions, which oftentimes will not know the name of the field
* they're validating.
*/
@NotThreadSafe
@SuppressWarnings("OverrideThrowableToString")
public final class FormFieldException extends FormException {
private final List<Object> names = new ArrayList<>();
@Nullable
private String lazyFieldName;
/**
* Creates a new {@link FormFieldException}
*
* <p>This exception should only be thrown from within a {@link FormField} converter function.
* The field name will automatically be propagated into the exception object for you.
*
* @param userMessage should be a friendly message that's safe to show to the user.
*/
public FormFieldException(@Detainted String userMessage) {
super(checkNotNull(userMessage, "userMessage"), null);
}
/**
* Creates a new {@link FormFieldException}
*
* <p>This exception should only be thrown from within a {@link FormField} converter function.
* The field name will automatically be propagated into the exception object for you.
*
* @param userMessage should be a friendly message that's safe to show to the user.
* @param cause the original cause of this exception (non-null).
*/
public FormFieldException(@Detainted String userMessage, Throwable cause) {
super(checkNotNull(userMessage, "userMessage"), checkNotNull(cause, "cause"));
}
/**
* Creates a new {@link FormFieldException} for a particular form field.
*
* <p>This exception should only be thrown from within a {@link FormField} MAP converter function
* in situations where you're performing additional manual validation.
*
* @param userMessage should be a friendly message that's safe to show to the user.
*/
public FormFieldException(FormField<?, ?> field, @Detainted String userMessage) {
this(field.name(), userMessage);
}
/**
* Creates a new {@link FormFieldException} for a particular field name.
*
* @param field name corresponding to a {@link FormField#name()}
* @param userMessage friendly message that's safe to show to the user
*/
public FormFieldException(String field, @Detainted String userMessage) {
super(checkNotNull(userMessage, "userMessage"), null);
propagateImpl(field);
}
/** Returns the fully-qualified name (JavaScript syntax) of the form field causing this error. */
public String getFieldName() {
String fieldName = lazyFieldName;
if (fieldName == null) {
lazyFieldName = fieldName = getFieldNameImpl();
}
return fieldName;
}
private String getFieldNameImpl() {
checkState(!names.isEmpty(),
"FormFieldException was thrown outside FormField infrastructure!");
Iterator<Object> namesIterator = Lists.reverse(names).iterator();
StringBuilder result = new StringBuilder((String) namesIterator.next());
while (namesIterator.hasNext()) {
Object name = namesIterator.next();
if (name instanceof String) {
result.append('.').append(name);
} else if (name instanceof Integer) {
result.append('[').append(name).append(']');
} else {
throw new AssertionError();
}
}
return result.toString();
}
/**
* Returns self with {@code name} prepended, for propagating exceptions up the stack.
*
* <p>This would be package-private except that it needs to be called by a test class in another
* package.
*/
@CheckReturnValue
public FormFieldException propagate(String name) {
return propagateImpl(name);
}
/** Returns self with {@code index} prepended, for propagating exceptions up the stack. */
@CheckReturnValue
FormFieldException propagate(int index) {
return propagateImpl(index);
}
/** Returns self with {@code name} prepended, for propagating exceptions up the stack. */
private FormFieldException propagateImpl(Object name) {
lazyFieldName = null;
names.add(checkNotNull(name));
return this;
}
@Override
public boolean equals(@Nullable Object obj) {
return this == obj
|| (obj instanceof FormFieldException
&& Objects.equals(getCause(), ((FormFieldException) obj).getCause())
&& Objects.equals(getMessage(), ((FormFieldException) obj).getMessage())
&& Objects.equals(names, ((FormFieldException) obj).names));
}
@Override
public int hashCode() {
return Objects.hash(getCause(), getMessage(), getFieldName());
}
@Override
public String toString() {
return toStringHelper(getClass())
.add("fieldName", getFieldName())
.add("message", getMessage())
.add("cause", getCause())
.toString();
}
}

View File

@@ -1,116 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.ui.forms;
import static com.google.common.collect.Range.atMost;
import static com.google.common.collect.Range.closed;
import static com.google.common.collect.Range.singleton;
import static java.util.Locale.getISOCountries;
import com.google.common.collect.ImmutableSet;
import com.google.re2j.Pattern;
/** Utility class of {@link FormField} objects for validating EPP related things. */
public final class FormFields {
private static final Pattern WHITESPACE = Pattern.compile("[ \\t\\r\\n]+");
/**
* Form field that applies XML Schema Token cleanup to input.
*
* <p>This trims the input and collapses whitespace.
*
* @see <a href="http://www.w3.org/TR/xmlschema11-2/#token">XSD Datatypes - token</a>
*/
public static final FormField<String, String> XS_TOKEN =
FormField.named("xsToken")
.emptyToNull()
.trimmed()
.transform(input -> input != null ? WHITESPACE.matcher(input).replaceAll(" ") : null)
.build();
/**
* Form field that ensures input does not contain tabs, line feeds, or carriage returns.
*
* @see <a href="http://www.w3.org/TR/xmlschema11-2/#normalizedString">
* XSD Datatypes - normalizedString</a>
*/
public static final FormField<String, String> XS_NORMALIZED_STRING =
FormField.named("xsNormalizedString")
.emptyToNull()
.matches(Pattern.compile("[^\\t\\r\\n]*"), "Must not contain tabs or multiple lines.")
.build();
/**
* Form field for +E164 phone numbers with a dot after the country prefix.
*
* @see <a href="http://tools.ietf.org/html/rfc5733#section-4">RFC 5733 - EPP - Formal Syntax</a>
*/
public static final FormField<String, String> PHONE_NUMBER =
XS_TOKEN.asBuilderNamed("phoneNumber")
.range(atMost(17))
.matches(Pattern.compile("(\\+[0-9]{1,3}\\.[0-9]{1,14})?"),
"Must be a valid +E.164 phone number, e.g. +1.2125650000")
.build();
/** Form field for EPP client identifiers. */
public static final FormField<String, String> CLID = XS_TOKEN.asBuilderNamed("clid")
.range(closed(3, 16))
.build();
/** Form field for passwords (see pwType in epp.xsd). */
public static final FormField<String, String> PASSWORD = XS_TOKEN.asBuilderNamed("password")
.range(closed(6, 16))
.build();
/** Form field for non-empty tokens (see minToken in eppcom.xsd). */
public static final FormField<String, String> MIN_TOKEN = XS_TOKEN.asBuilderNamed("minToken")
.emptyToNull()
.build();
/** Form field for nameType (see rde-registrar/notification). */
public static final FormField<String, String> NAME = XS_NORMALIZED_STRING.asBuilderNamed("name")
.range(closed(1, 255))
.build();
/** Form field for {@code labelType} from {@code eppcom.xsd}. */
public static final FormField<String, String> LABEL = XS_TOKEN.asBuilderNamed("label")
.range(closed(1, 255))
.build();
/** Email address form field. */
public static final FormField<String, String> EMAIL = XS_TOKEN.asBuilderNamed("email")
.matches(Pattern.compile("[^@]+@[^@.]+\\.[^@]+"), "Please enter a valid email address.")
.build();
/** Two-letter ISO country code form field. */
public static final FormField<String, String> COUNTRY_CODE =
XS_TOKEN.asBuilderNamed("countryCode")
.range(singleton(2))
.uppercased()
.in(ImmutableSet.copyOf(getISOCountries()))
.build();
/**
* Ensure value is an EPP Repository Object IDentifier (ROID).
*
* @see <a href="http://tools.ietf.org/html/rfc5730#section-4.2">Shared Structure Schema</a>
*/
public static final FormField<String, String> ROID = XS_TOKEN.asBuilderNamed("roid")
.matches(Pattern.compile("(\\w|_){1,80}-\\w{1,8}"),
"Please enter a valid EPP ROID, e.g. SH8013-REP")
.build();
private FormFields() {}
}

View File

@@ -1,17 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/** Web application backend form processing utilities. */
@javax.annotation.ParametersAreNonnullByDefault
package google.registry.ui.forms;

View File

@@ -1,15 +0,0 @@
<!doctype html>
<meta charset=utf-8>
<title>Server Error</title>
<style>
*{margin:0;padding:0}
html,code{font:15px/22px arial,sans-serif}
html{background:#fff;color:#222;padding:15px}
body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}
* > body{background:url(/manager/img/ui/robot.png) 100% 5px no-repeat;padding-right:205px}
p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}
a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}
</style>
<a href="/"><img src="/assets/images/logo_sm.gif" alt=Google></a>
<p><b>500.</b> <ins>That's an error.</ins>
<p>Sorry, but the server encountered an error while processing your request.

View File

@@ -1,6 +0,0 @@
<!doctype html>
<meta http-equiv="refresh" content="0;URL=/console">
<title>Nomulus</title>
<body lang="en-US">
If this page doesn't change automatically, please go
to <a href="/console">https://www.registry.google/console</a>

View File

@@ -1,16 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
@javax.annotation.ParametersAreNonnullByDefault
package google.registry.ui;

View File

@@ -1,82 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.ui.server;
import com.google.common.collect.ImmutableBiMap;
/**
* Bimap of state codes and names for the US Regime.
*
* @see <a href="http://statetable.com/">State Table</a>
*/
public final class StateCode {
public static final ImmutableBiMap<String, String> US_MAP =
new ImmutableBiMap.Builder<String, String>()
.put("AL", "Alabama")
.put("AK", "Alaska")
.put("AZ", "Arizona")
.put("AR", "Arkansas")
.put("CA", "California")
.put("CO", "Colorado")
.put("CT", "Connecticut")
.put("DE", "Delaware")
.put("FL", "Florida")
.put("GA", "Georgia")
.put("HI", "Hawaii")
.put("ID", "Idaho")
.put("IL", "Illinois")
.put("IN", "Indiana")
.put("IA", "Iowa")
.put("KS", "Kansas")
.put("KY", "Kentucky")
.put("LA", "Louisiana")
.put("ME", "Maine")
.put("MD", "Maryland")
.put("MA", "Massachusetts")
.put("MI", "Michigan")
.put("MN", "Minnesota")
.put("MS", "Mississippi")
.put("MO", "Missouri")
.put("MT", "Montana")
.put("NE", "Nebraska")
.put("NV", "Nevada")
.put("NH", "New Hampshire")
.put("NJ", "New Jersey")
.put("NM", "New Mexico")
.put("NY", "New York")
.put("NC", "North Carolina")
.put("ND", "North Dakota")
.put("OH", "Ohio")
.put("OK", "Oklahoma")
.put("OR", "Oregon")
.put("PA", "Pennsylvania")
.put("RI", "Rhode Island")
.put("SC", "South Carolina")
.put("SD", "South Dakota")
.put("TN", "Tennessee")
.put("TX", "Texas")
.put("UT", "Utah")
.put("VT", "Vermont")
.put("VA", "Virginia")
.put("WA", "Washington")
.put("WV", "West Virginia")
.put("WI", "Wisconsin")
.put("WY", "Wyoming")
.put("DC", "Washington DC")
.build();
private StateCode() {}
}

View File

@@ -40,7 +40,6 @@ import google.registry.request.Action.GaeService;
import google.registry.request.Action.GkeService;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.ui.forms.FormException;
import google.registry.ui.server.console.ConsoleApiAction;
import google.registry.ui.server.console.ConsoleApiParams;
import jakarta.inject.Inject;
@@ -166,7 +165,7 @@ public class ContactAction extends ConsoleApiAction {
try {
checkContactRequirements(oldContacts, newContacts);
} catch (FormException e) {
} catch (ContactRequirementException e) {
logger.atWarning().withCause(e).log(
"Error processing contacts post request for registrar: %s", registrarId);
throw new IllegalArgumentException(e);
@@ -196,7 +195,7 @@ public class ContactAction extends ConsoleApiAction {
/**
* Enforces business logic checks on registrar contacts.
*
* @throws FormException if the checks fail.
* @throws ContactRequirementException if the checks fail.
*/
private static void checkContactRequirements(
ImmutableSet<RegistrarPoc> existingContacts, ImmutableSet<RegistrarPoc> updatedContacts) {
@@ -299,7 +298,7 @@ public class ContactAction extends ConsoleApiAction {
}
/** Thrown when a set of contacts doesn't meet certain constraints. */
private static class ContactRequirementException extends FormException {
private static class ContactRequirementException extends RuntimeException {
ContactRequirementException(String msg) {
super(msg);
}

View File

@@ -1,16 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
@javax.annotation.ParametersAreNonnullByDefault
package google.registry.ui.server;

View File

@@ -30,7 +30,6 @@ import google.registry.monitoring.whitebox.StackdriverModule;
import google.registry.privileges.secretmanager.SecretManagerModule;
import google.registry.request.Modules;
import google.registry.request.auth.AuthModule;
import google.registry.ui.ConsoleDebug;
import google.registry.util.UtilsModule;
import jakarta.inject.Singleton;
@@ -40,7 +39,6 @@ import jakarta.inject.Singleton;
AuthModule.class,
CloudTasksUtilsModule.class,
RegistryConfig.ConfigModule.class,
ConsoleDebug.ConsoleConfigModule.class,
CredentialModule.class,
CustomLogicFactoryModule.class,
CloudTasksUtilsModule.class,

View File

@@ -35,12 +35,6 @@ public final class RegistryTestServer {
public static final ImmutableMap<String, Path> RUNFILES =
new ImmutableMap.Builder<String, Path>()
.put(
"/index.html",
PROJECT_ROOT.resolve("core/src/main/java/google/registry/ui/html/index.html"))
.put(
"/error.html",
PROJECT_ROOT.resolve("core/src/main/java/google/registry/ui/html/error.html"))
.put("/console/*", PROJECT_ROOT.resolve("console-webapp/staged/dist"))
.build();

View File

@@ -28,7 +28,6 @@ import google.registry.request.auth.AuthResult;
import google.registry.request.auth.OidcTokenAuthenticationMechanism;
import google.registry.testing.DatabaseHelper;
import google.registry.tools.params.HostAndPortParameter;
import google.registry.ui.ConsoleDebug;
import java.util.List;
/** Command-line interface for {@link RegistryTestServer}. */
@@ -42,11 +41,6 @@ public final class RegistryTestServerMain {
private static final String LIGHT_PURPLE = "\u001b[38;5;139m";
private static final String ORANGE = "\u001b[1;38;5;172m";
@Parameter(
names = "--mode",
description = "UI console debug mode. RAW allows live editing; DEBUG allows rename testing.")
private ConsoleDebug mode = ConsoleDebug.PRODUCTION;
@Parameter(
names = "--address",
description = "Listening address.",
@@ -67,14 +61,10 @@ public final class RegistryTestServerMain {
arity = 1)
private boolean loginIsAdmin = true;
@Parameter(
names = "--jetty_debug",
description = "Enables Jetty debug logging.")
@Parameter(names = "--jetty_debug", description = "Enables Jetty debug logging.")
private boolean jettyDebug;
@Parameter(
names = "--jetty_verbose",
description = "Enables Jetty verbose logging.")
@Parameter(names = "--jetty_verbose", description = "Enables Jetty verbose logging.")
private boolean jettyVerbose;
@Parameter(
@@ -96,7 +86,6 @@ public final class RegistryTestServerMain {
}
private void run() throws Throwable {
ConsoleDebug.set(mode);
if (jettyDebug) {
System.setProperty("DEBUG", "true");
}
@@ -105,7 +94,7 @@ public final class RegistryTestServerMain {
}
System.out.printf(
"""
"""
CHARLESTON ROAD REGISTRY SHARED REGISTRATION SYSTEM
ICANN-GTLD-AGB-20120604

View File

@@ -1,62 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.ui.forms;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.testing.NullPointerTester;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link FormFieldException}. */
class FormFieldExceptionTest {
@Test
void testGetFieldName_multiplePropagations_joinsUsingJsonNotation() {
assertThat(
new FormFieldException("This field is required.")
.propagate("attack")
.propagate("cat")
.propagate(0)
.propagate("lol")
.getFieldName())
.isEqualTo("lol[0].cat.attack");
}
@Test
void testGetFieldName_singlePropagations_noFancyJoining() {
assertThat(
new FormFieldException("This field is required.")
.propagate("cat")
.getFieldName())
.isEqualTo("cat");
}
@Test
void testGetFieldName_noPropagations_throwsIse() {
assertThrows(
IllegalStateException.class,
() -> new FormFieldException("This field is required.").getFieldName());
}
@Test
void testNullness() {
NullPointerTester tester = new NullPointerTester()
.setDefault(FormField.class, FormField.named("love").build());
tester.testAllPublicConstructors(FormFieldException.class);
tester.testAllPublicStaticMethods(FormFieldException.class);
tester.testAllPublicInstanceMethods(new FormFieldException("lol"));
}
}

View File

@@ -1,486 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.ui.forms;
import static com.google.common.collect.Range.atLeast;
import static com.google.common.collect.Range.atMost;
import static com.google.common.collect.Range.closed;
import static com.google.common.truth.Truth.assertThat;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.testing.NullPointerTester;
import com.google.re2j.Pattern;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link FormField}. */
class FormFieldTest {
private enum ICanHazEnum {
LOL,
CAT
}
@Test
void testConvert_nullString_notPresent() {
assertThat(FormField.named("lol").build().convert(null)).isEmpty();
}
@Test
void testConvert_emptyString_returnsEmpty() {
assertThat(FormField.named("lol").build().convert("")).hasValue("");
}
@Test
void testWithDefault_hasValue_returnsValue() {
assertThat(FormField.named("lol").withDefault("default").build().convert("return me!"))
.hasValue("return me!");
}
@Test
void testWithDefault_nullValue_returnsDefault() {
assertThat(FormField.named("lol").withDefault("default").build().convert(null))
.hasValue("default");
}
@Test
void testEmptyToNull_emptyString_notPresent() {
assertThat(FormField.named("lol").emptyToNull().build().convert("")).isEmpty();
}
@Test
void testEmptyToNullRequired_emptyString_throwsFfe() {
FormFieldException thrown =
assertThrows(
FormFieldException.class,
() -> FormField.named("lol").emptyToNull().required().build().convert(""));
assertThat(thrown, equalTo(new FormFieldException("This field is required.").propagate("lol")));
}
@Test
void testEmptyToNull_typeMismatch() {
assertThrows(
IllegalStateException.class, () -> FormField.named("lol", Object.class).emptyToNull());
}
@Test
void testNamedLong() {
assertThat(FormField.named("lol", Long.class).build().convert(666L)).hasValue(666L);
}
@Test
void testUppercased() {
FormField<String, String> field = FormField.named("lol").uppercased().build();
assertThat(field.convert(null)).isEmpty();
assertThat(field.convert("foo")).hasValue("FOO");
assertThat(field.convert("BAR")).hasValue("BAR");
}
@Test
void testLowercased() {
FormField<String, String> field = FormField.named("lol").lowercased().build();
assertThat(field.convert(null)).isEmpty();
assertThat(field.convert("foo")).hasValue("foo");
assertThat(field.convert("BAR")).hasValue("bar");
}
@Test
void testIn_passesThroughNull() {
FormField<String, String> field =
FormField.named("lol").in(ImmutableSet.of("foo", "bar")).build();
assertThat(field.convert(null)).isEmpty();
}
@Test
void testIn_valueIsContainedInSet() {
FormField<String, String> field =
FormField.named("lol").in(ImmutableSet.of("foo", "bar")).build();
assertThat(field.convert("foo")).hasValue("foo");
assertThat(field.convert("bar")).hasValue("bar");
}
@Test
void testIn_valueMissingFromSet() {
FormField<String, String> field =
FormField.named("lol").in(ImmutableSet.of("foo", "bar")).build();
FormFieldException thrown = assertThrows(FormFieldException.class, () -> field.convert("omfg"));
assertThat(thrown, equalTo(new FormFieldException("Unrecognized value.").propagate("lol")));
}
@Test
void testRange_hasLowerBound_nullValue_passesThrough() {
assertThat(FormField.named("lol").range(atLeast(5)).build().convert(null)).isEmpty();
}
@Test
void testRange_minimum_stringLengthEqualToMinimum_doesNothing() {
assertThat(FormField.named("lol").range(atLeast(5)).build().convert("hello")).hasValue("hello");
}
@Test
void testRange_minimum_stringLengthShorterThanMinimum_throwsFfe() {
FormFieldException thrown =
assertThrows(
FormFieldException.class,
() -> FormField.named("lol").range(atLeast(4)).build().convert("lol"));
assertThat(thrown).hasMessageThat().contains("Number of characters (3) not in range [4");
}
@Test
void testRange_noLowerBound_nullValue_passThrough() {
assertThat(FormField.named("lol").range(atMost(5)).build().convert(null)).isEmpty();
}
@Test
void testRange_maximum_stringLengthEqualToMaximum_doesNothing() {
assertThat(FormField.named("lol").range(atMost(5)).build().convert("hello")).hasValue("hello");
}
@Test
void testRange_maximum_stringLengthShorterThanMaximum_throwsFfe() {
FormFieldException thrown =
assertThrows(
FormFieldException.class,
() -> FormField.named("lol").range(atMost(5)).build().convert("omgomg"));
assertThat(thrown).hasMessageThat().contains("Number of characters (6) not in range");
}
@Test
void testRange_numericTypes() {
FormField.named("lol", Byte.class).range(closed(5, 10)).build().convert((byte) 7);
FormField.named("lol", Short.class).range(closed(5, 10)).build().convert((short) 7);
FormField.named("lol", Integer.class).range(closed(5, 10)).build().convert(7);
FormField.named("lol", Long.class).range(closed(5, 10)).build().convert(7L);
FormField.named("lol", Float.class).range(closed(5, 10)).build().convert(7F);
FormField.named("lol", Double.class).range(closed(5, 10)).build().convert(7D);
}
@Test
void testRange_typeMismatch() {
assertThrows(
IllegalStateException.class, () -> FormField.named("lol", Object.class).range(atMost(5)));
}
@Test
void testMatches_matches_doesNothing() {
assertThat(FormField.named("lol").matches(Pattern.compile("[a-z]+")).build().convert("abc"))
.hasValue("abc");
}
@Test
void testMatches_mismatch_throwsFfeAndShowsDefaultErrorMessageWithPattern() {
FormFieldException thrown =
assertThrows(
FormFieldException.class,
() ->
FormField.named("lol")
.matches(Pattern.compile("[a-z]+"))
.build()
.convert("123abc456"));
assertThat(
thrown, equalTo(new FormFieldException("Must match pattern: [a-z]+").propagate("lol")));
}
@Test
void testMatches_typeMismatch() {
assertThrows(
IllegalStateException.class,
() -> FormField.named("lol", Object.class).matches(Pattern.compile(".")));
}
@Test
void testRetains() {
assertThat(
FormField.named("lol")
.retains(CharMatcher.anyOf("0123456789"))
.build()
.convert(" 123 1593-43 453 45 4 4 \t"))
.hasValue("1231593434534544");
}
@Test
void testCast() {
assertThat(
FormField.named("lol")
.transform(Integer.class, Integer::parseInt)
.build()
.convert("123"))
.hasValue(123);
}
@Test
void testCast_twice() {
assertThat(
FormField.named("lol")
.transform(Object.class, Integer::parseInt)
.transform(String.class, Object::toString)
.build()
.convert("123"))
.hasValue("123");
}
@Test
void testAsList_null_notPresent() {
assertThat(FormField.named("lol").asList().build().convert(null)).isEmpty();
}
@Test
void testAsList_empty_returnsEmpty() {
assertThat(FormField.named("lol").asList().build().convert(ImmutableList.of()))
.hasValue(ImmutableList.of());
}
@Test
void testAsListEmptyToNullRequired_empty_throwsFfe() {
FormFieldException thrown =
assertThrows(
FormFieldException.class,
() ->
FormField.named("lol")
.asList()
.emptyToNull()
.required()
.build()
.convert(ImmutableList.of()));
assertThat(thrown, equalTo(new FormFieldException("This field is required.").propagate("lol")));
}
@Test
void testListEmptyToNull_empty_notPresent() {
assertThat(FormField.named("lol").asList().emptyToNull().build().convert(ImmutableList.of()))
.isEmpty();
}
@Test
void testAsEnum() {
FormField<String, ICanHazEnum> omgField =
FormField.named("omg").asEnum(ICanHazEnum.class).build();
assertThat(omgField.convert("LOL")).hasValue(ICanHazEnum.LOL);
assertThat(omgField.convert("CAT")).hasValue(ICanHazEnum.CAT);
}
@Test
void testAsEnum_lowercase_works() {
FormField<String, ICanHazEnum> omgField =
FormField.named("omg").asEnum(ICanHazEnum.class).build();
assertThat(omgField.convert("lol")).hasValue(ICanHazEnum.LOL);
assertThat(omgField.convert("cat")).hasValue(ICanHazEnum.CAT);
}
@Test
void testAsEnum_badInput_throwsFfe() {
FormField<String, ICanHazEnum> omgField =
FormField.named("omg").asEnum(ICanHazEnum.class).build();
FormFieldException thrown =
assertThrows(FormFieldException.class, () -> omgField.convert("helo"));
assertThat(
thrown,
equalTo(
new FormFieldException("Enum ICanHazEnum does not contain 'helo'").propagate("omg")));
}
@Test
void testSplitList() {
FormField<String, List<String>> field =
FormField.named("lol").asList(Splitter.on(',').omitEmptyStrings()).build();
assertThat(field.convert("oh,my,goth").get()).containsExactly("oh", "my", "goth").inOrder();
assertThat(field.convert("").get()).isEmpty();
assertThat(field.convert(null)).isEmpty();
}
@Test
void testSplitSet() {
FormField<String, Set<String>> field =
FormField.named("lol").uppercased().asSet(Splitter.on(',').omitEmptyStrings()).build();
assertThat(field.convert("oh,my,goth").get()).containsExactly("OH", "MY", "GOTH").inOrder();
assertThat(field.convert("").get()).isEmpty();
assertThat(field.convert(null)).isEmpty();
}
@Test
void testAsList() {
assertThat(
FormField.named("lol")
.asList()
.build()
.convert(ImmutableList.of("lol", "cat", ""))
.get())
.containsExactly("lol", "cat", "")
.inOrder();
}
@Test
void testAsList_trimmedEmptyToNullOnItems() {
assertThat(
FormField.named("lol")
.trimmed()
.emptyToNull()
.matches(Pattern.compile("[a-z]+"))
.asList()
.range(closed(1, 2))
.build()
.convert(ImmutableList.of("lol\n", "\tcat "))
.get())
.containsExactly("lol", "cat")
.inOrder();
}
@Test
void testAsList_nullElements_getIgnored() {
assertThat(
FormField.named("lol")
.emptyToNull()
.asList()
.build()
.convert(ImmutableList.of("omg", ""))
.get())
.containsExactly("omg");
}
@Test
void testAsListRequiredElements_nullElement_throwsFfeWithIndex() {
FormFieldException thrown =
assertThrows(
FormFieldException.class,
() ->
FormField.named("lol")
.emptyToNull()
.required()
.asList()
.build()
.convert(ImmutableList.of("omg", "")));
assertThat(
thrown,
equalTo(new FormFieldException("This field is required.").propagate(1).propagate("lol")));
}
@Test
void testMapAsListRequiredElements_nullElement_throwsFfeWithIndexAndKey() {
FormFieldException thrown =
assertThrows(
FormFieldException.class,
() ->
FormField.mapNamed("lol")
.transform(
String.class,
input ->
FormField.named("cat")
.emptyToNull()
.required()
.build()
.extractUntyped(input)
.get())
.asList()
.build()
.convert(ImmutableList.of(ImmutableMap.of("cat", ""))));
assertThat(
thrown,
equalTo(
new FormFieldException("This field is required.")
.propagate("cat")
.propagate(0)
.propagate("lol")));
}
@Test
void testAsListTrimmed_typeMismatch() {
FormField.named("lol").trimmed().asList();
assertThrows(IllegalStateException.class, () -> FormField.named("lol").asList().trimmed());
}
@Test
void testAsMatrix() {
assertThat(
FormField.named("lol", Integer.class)
.transform(input -> input * 2)
.asList()
.asList()
.build()
.convert(
Lists.cartesianProduct(
ImmutableList.of(ImmutableList.of(1, 2), ImmutableList.of(3, 4))))
.get())
.containsExactly(
ImmutableList.of(2, 6),
ImmutableList.of(2, 8),
ImmutableList.of(4, 6),
ImmutableList.of(4, 8))
.inOrder();
}
@Test
void testAsSet() {
assertThat(
FormField.named("lol")
.asSet()
.build()
.convert(ImmutableList.of("lol", "cat", "cat"))
.get())
.containsExactly("lol", "cat");
}
@Test
void testTrimmed() {
assertThat(FormField.named("lol").trimmed().build().convert(" \thello \t\n")).hasValue("hello");
}
@Test
void testTrimmed_typeMismatch() {
assertThrows(IllegalStateException.class, () -> FormField.named("lol", Object.class).trimmed());
}
@Test
void testAsBuilder() {
FormField<String, String> field = FormField.named("omg").uppercased().build();
assertThat(field.name()).isEqualTo("omg");
assertThat(field.convert("hello")).hasValue("HELLO");
field = field.asBuilder().build();
assertThat(field.name()).isEqualTo("omg");
assertThat(field.convert("hello")).hasValue("HELLO");
}
@Test
void testAsBuilderNamed() {
FormField<String, String> field = FormField.named("omg").uppercased().build();
assertThat(field.name()).isEqualTo("omg");
assertThat(field.convert("hello")).hasValue("HELLO");
field = field.asBuilderNamed("bog").build();
assertThat(field.name()).isEqualTo("bog");
assertThat(field.convert("hello")).hasValue("HELLO");
}
@Test
void testNullness() {
NullPointerTester tester =
new NullPointerTester()
.setDefault(Class.class, Object.class)
.setDefault(Function.class, x -> x)
.setDefault(Pattern.class, Pattern.compile("."))
.setDefault(String.class, "hello.com");
tester.testAllPublicStaticMethods(FormField.class);
tester.testAllPublicInstanceMethods(FormField.named("lol"));
tester.testAllPublicInstanceMethods(FormField.named("lol").build());
}
}

View File

@@ -1,108 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.ui.forms;
import static com.google.common.truth.Truth.assertThat;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.testing.NullPointerTester;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link FormFields}. */
class FormFieldsTest {
@Test
void testXsToken_collapsesAndTrimsWhitespace() {
assertThat(FormFields.XS_TOKEN.convert(" hello \r\n\t there\n")).hasValue("hello there");
}
@Test
void testXsNormalizedString_extraSpaces_doesntCare() {
assertThat(FormFields.XS_NORMALIZED_STRING.convert("hello there")).hasValue("hello there");
}
@Test
void testXsNormalizedString_sideSpaces_doesntCare() {
assertThat(FormFields.XS_NORMALIZED_STRING.convert(" hello there ")).hasValue(" hello there ");
}
@Test
void testXsNormalizedString_containsNonSpaceWhitespace_fails() {
FormFieldException thrown =
assertThrows(
FormFieldException.class,
() -> FormFields.XS_NORMALIZED_STRING.convert(" hello \r\n\t there\n"));
assertThat(
thrown,
equalTo(
new FormFieldException("Must not contain tabs or multiple lines.")
.propagate("xsNormalizedString")));
}
@Test
void testXsEppE164PhoneNumber_nanpaNumber_validates() {
assertThat(FormFields.XS_NORMALIZED_STRING.convert("+1.2125650000")).hasValue("+1.2125650000");
}
@Test
void testXsEppE164PhoneNumber_londonNumber_validates() {
assertThat(FormFields.XS_NORMALIZED_STRING.convert("+44.2011112222"))
.hasValue("+44.2011112222");
}
@Test
void testXsEppE164PhoneNumber_localizedNumber_fails() {
FormFieldException thrown =
assertThrows(
FormFieldException.class, () -> FormFields.PHONE_NUMBER.convert("(212) 565-0000"));
assertThat(thrown)
.hasMessageThat()
.contains("Must be a valid +E.164 phone number, e.g. +1.2125650000");
}
@Test
void testXsEppE164PhoneNumber_appliesXsTokenTransform() {
assertThat(FormFields.PHONE_NUMBER.convert(" +1.2125650000 \r")).hasValue("+1.2125650000");
}
@Test
void testXsEppRoid_correctSyntax_validates() {
assertThat(FormFields.ROID.convert("SH8013-REP")).hasValue("SH8013-REP");
}
@Test
void testXsEppRoid_lowerCase_validates() {
assertThat(FormFields.ROID.convert("sh8013-rep")).hasValue("sh8013-rep");
}
@Test
void testXsEppRoid_missingHyphen_fails() {
FormFieldException thrown =
assertThrows(FormFieldException.class, () -> FormFields.ROID.convert("SH8013REP"));
assertThat(thrown).hasMessageThat().contains("Please enter a valid EPP ROID, e.g. SH8013-REP");
}
@Test
void testXsEppRoid_appliesXsTokenTransform() {
assertThat(FormFields.ROID.convert("\n FOO-BAR \r")).hasValue("FOO-BAR");
}
@Test
void testNullness() {
new NullPointerTester().testAllPublicStaticMethods(FormFields.class);
}
}