mirror of
https://github.com/google/nomulus.git
synced 2025-05-17 01:47:14 +02:00
mv com/google/domain/registry google/registry
This change renames directories in preparation for the great package rename. The repository is now in a broken state because the code itself hasn't been updated. However this should ensure that git correctly preserves history for each file.
This commit is contained in:
parent
a41677aea1
commit
5012893c1d
2396 changed files with 0 additions and 0 deletions
14
java/google/registry/ui/forms/BUILD
Normal file
14
java/google/registry/ui/forms/BUILD
Normal file
|
@ -0,0 +1,14 @@
|
|||
package(default_visibility = ["//java/com/google/domain/registry:registry_project"])
|
||||
|
||||
|
||||
java_library(
|
||||
name = "forms",
|
||||
srcs = glob(["*.java"]),
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//java/com/google/common/annotations",
|
||||
"//java/com/google/common/base",
|
||||
"//java/com/google/common/collect",
|
||||
"//third_party/java/jsr305_annotations",
|
||||
],
|
||||
)
|
58
java/google/registry/ui/forms/FormException.java
Normal file
58
java/google/registry/ui/forms/FormException.java
Normal file
|
@ -0,0 +1,58 @@
|
|||
// Copyright 2016 The Domain Registry 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 com.google.domain.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();
|
||||
}
|
||||
}
|
790
java/google/registry/ui/forms/FormField.java
Normal file
790
java/google/registry/ui/forms/FormField.java
Normal file
|
@ -0,0 +1,790 @@
|
|||
// Copyright 2016 The Domain Registry 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 com.google.domain.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 com.google.common.base.CharMatcher;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Functions;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.Detainted;
|
||||
import javax.annotation.Nonnull;
|
||||
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>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* <h3>Exception Handling</h3>
|
||||
*
|
||||
* <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>
|
||||
*
|
||||
* 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, Functions.<T>identity());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<String, String> REGISTRAR_NAME_FIELD =
|
||||
* FormField.named("name")
|
||||
* .emptyToNull()
|
||||
* .required()
|
||||
* .build();
|
||||
*
|
||||
* private static final FormField<Map<String, ?>, Registrar> REGISTRAR_FIELD =
|
||||
* FormField.mapNamed("registrar")
|
||||
* .transform(Registrar.class, new Function<Map<String, ?>, Registrar>() {
|
||||
* @Nullable
|
||||
* @Override
|
||||
* public Registrar apply(@Nullable Map<String, ?> 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.fromNullable(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() {
|
||||
@SuppressWarnings("unchecked")
|
||||
Function<O, O> requiredFunction = (Function<O, O>) REQUIRED_FUNCTION;
|
||||
return transform(requiredFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
@SuppressWarnings("unchecked")
|
||||
Function<O, O> emptyToNullFunction = (Function<O, O>) EMPTY_TO_NULL_FUNCTION;
|
||||
return transform(emptyToNullFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>) TRIM_FUNCTION;
|
||||
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>) UPPERCASE_FUNCTION;
|
||||
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>) LOWERCASE_FUNCTION;
|
||||
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<O>(checkNotNull(pattern), Optional.fromNullable(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<O>(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<String, Integer>() {
|
||||
* @Nullable
|
||||
* @Override
|
||||
* public Integer apply(@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),
|
||||
Functions.compose(checkNotNull(transform), converter));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = Functions.compose(checkNotNull(transform), converter);
|
||||
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<O, C>(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>) TO_SET_FUNCTION;
|
||||
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 final Function<List<Object>, Set<Object>> TO_SET_FUNCTION =
|
||||
new Function<List<Object>, Set<Object>>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public Set<Object> apply(@Nullable List<Object> input) {
|
||||
return input != null ? ImmutableSet.copyOf(input) : null;
|
||||
}};
|
||||
|
||||
private static final Function<String, String> TRIM_FUNCTION =
|
||||
new Function<String, String>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public String apply(@Nullable String input) {
|
||||
return input != null ? input.trim() : null;
|
||||
}};
|
||||
|
||||
private static final Function<String, String> UPPERCASE_FUNCTION =
|
||||
new Function<String, String>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public String apply(@Nullable String input) {
|
||||
return input != null ? input.toUpperCase() : null;
|
||||
}};
|
||||
|
||||
private static final Function<String, String> LOWERCASE_FUNCTION =
|
||||
new Function<String, String>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public String apply(@Nullable String input) {
|
||||
return input != null ? input.toLowerCase() : null;
|
||||
}};
|
||||
|
||||
private static final Function<Object, Object> REQUIRED_FUNCTION =
|
||||
new Function<Object, Object>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public Object apply(@Nullable Object input) {
|
||||
if (input == null) {
|
||||
throw new FormFieldException("This field is required.");
|
||||
}
|
||||
return input;
|
||||
}};
|
||||
|
||||
private static final Function<Object, Object> EMPTY_TO_NULL_FUNCTION =
|
||||
new Function<Object, Object>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public Object apply(@Nullable Object input) {
|
||||
return input instanceof CharSequence && ((CharSequence) input).length() == 0
|
||||
|| input instanceof Collection && ((Collection<?>) input).isEmpty()
|
||||
? null : 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.or("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, ((String) input).toUpperCase()) : 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 : FluentIterable
|
||||
.from(splitter.split(input))
|
||||
.transform(itemField.converter)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
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 : FluentIterable
|
||||
.from(splitter.split(input))
|
||||
.transform(itemField.converter)
|
||||
.toSet();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
176
java/google/registry/ui/forms/FormFieldException.java
Normal file
176
java/google/registry/ui/forms/FormFieldException.java
Normal file
|
@ -0,0 +1,176 @@
|
|||
// Copyright 2016 The Domain Registry 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 com.google.domain.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.annotations.VisibleForTesting;
|
||||
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(com.google.common.base.Function) transform} functions, which
|
||||
* oftentimes will not know the name of the field they're validating.
|
||||
*/
|
||||
@NotThreadSafe
|
||||
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. */
|
||||
@CheckReturnValue
|
||||
@VisibleForTesting
|
||||
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();
|
||||
}
|
||||
}
|
126
java/google/registry/ui/forms/FormFields.java
Normal file
126
java/google/registry/ui/forms/FormFields.java
Normal file
|
@ -0,0 +1,126 @@
|
|||
// Copyright 2016 The Domain Registry 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 com.google.domain.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.base.Function;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** 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]+");
|
||||
private static final Function<String, String> COLLAPSE_WHITESPACE =
|
||||
new Function<String, String>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public String apply(@Nullable String input) {
|
||||
return input != null ? WHITESPACE.matcher(input).replaceAll(" ") : null;
|
||||
}};
|
||||
|
||||
/**
|
||||
* Form field that applies XML Schema Token cleanup to input.
|
||||
*
|
||||
* <p>This trims the input and collapses whitespace.
|
||||
*
|
||||
* @see "http://www.w3.org/TR/xmlschema11-2/#token"
|
||||
*/
|
||||
public static final FormField<String, String> XS_TOKEN = FormField.named("xsToken")
|
||||
.emptyToNull()
|
||||
.trimmed()
|
||||
.transform(COLLAPSE_WHITESPACE)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Form field that ensures input does not contain tabs, line feeds, or carriage returns.
|
||||
*
|
||||
* @see "http://www.w3.org/TR/xmlschema11-2/#normalizedString"
|
||||
*/
|
||||
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 "http://tools.ietf.org/html/rfc5733#section-4"
|
||||
*/
|
||||
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 "http://tools.ietf.org/html/rfc5730#section-4.2"
|
||||
*/
|
||||
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() {}
|
||||
}
|
17
java/google/registry/ui/forms/package-info.java
Normal file
17
java/google/registry/ui/forms/package-info.java
Normal file
|
@ -0,0 +1,17 @@
|
|||
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.ui.forms;
|
Loading…
Add table
Add a link
Reference in a new issue