// 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 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 com.google.common.base.Ascii;
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 com.google.re2j.Pattern;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
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.
*
*
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:
*
* private enum Gender { MALE, FEMALE }
*
* private static final FormField NAME_FIELD = FormField.named("name")
* .matches("[a-z]+")
* .range(atMost(16))
* .required()
* .build();
*
* private static final FormField GENDER_FIELD = FormField.named("gender")
* .asEnum(Gender.class)
* .required()
* .build();
*
* public Person makePerson(Map 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();
* }
*
*
This class provides full type-safetyif and only if you statically initialize
* your FormField objects and write a unit test that causes the class to be loaded.
*
*
Exception Handling
*
*
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.
*
*
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.
*
*
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]}.
*
*
Library Definitions
*
*
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)}.
*
*
Here is an example of how you might go about defining library definitions:
*
* final class FormFields {
* private static final FormField COUNTRY_CODE =
* FormField.named("countryCode")
* .range(Range.singleton(2))
* .uppercased()
* .in(ImmutableSet.copyOf(Locale.getISOCountries()))
* .build();
* }
*
* final class Form {
* private static final FormField COUNTRY_CODE_FIELD =
* FormFields.COUNTRY_CODE.asBuilder()
* .required()
* .build();
* }
*
* @param input value type
* @param output value type
*/
@Immutable
public final class FormField {
private final String name;
private final Class typeIn;
private final Class typeOut;
private final Function converter;
private FormField(String name, Class typeIn, Class typeOut, Function 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 named(String name) {
return named(name, String.class);
}
/** Returns an optional form field named {@code name} with a specific {@code inputType}. */
public static Builder named(String name, Class typeIn) {
checkArgument(!name.isEmpty());
return new Builder<>(name, checkNotNull(typeIn), typeIn, Functions.identity());
}
/**
* Returns a form field builder for validating JSON nested maps.
*
*
Here's an example of how you'd use this feature:
*
*