Class FormField<I,O>
- java.lang.Object
-
- google.registry.ui.forms.FormField<I,O>
-
- Type Parameters:
I
- input value typeO
- output value type
@Immutable public final class FormField<I,O> extends java.lang.Object
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<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(); }
This class provides full type-safety if 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
convert(I)
orextract(java.util.Map<java.lang.String, I>)
don't meet the contract,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
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
bar
field of the fifth item in thefoo
field would have a fully-qualified field name of:foo[5][bar]
.Library Definitions
You should never assign a partially constructed
FormField.Builder
to a variable or constant. Instead, you should useasBuilder()
orasBuilderNamed(String)
.Here is an example of how you might go about defining library definitions:
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(); }
-
-
Nested Class Summary
Nested Classes Modifier and Type Class Description static class
FormField.Builder<I,O>
Mutable builder forFormField
.
-
Method Summary
All Methods Static Methods Instance Methods Concrete Methods Modifier and Type Method Description FormField.Builder<I,O>
asBuilder()
Returns a builder of this object, which can be used to further restrict validation.FormField.Builder<I,O>
asBuilderNamed(java.lang.String newName)
Same asasBuilder()
but changes the field name.java.util.Optional<O>
convert(I value)
Convert and validate a raw user-supplied value.java.util.Optional<O>
extract(java.util.Map<java.lang.String,I> valueMap)
Convert and validate a raw user-supplied value from a map.java.util.Optional<O>
extractUntyped(java.util.Map<java.lang.String,?> jsonMap)
Convert and validate a raw user-supplied value from an untyped JSON map.static FormField.Builder<java.util.Map<java.lang.String,?>,java.util.Map<java.lang.String,?>>
mapNamed(java.lang.String name)
Returns a form field builder for validating JSON nested maps.java.lang.String
name()
Returns the name of this field.static FormField.Builder<java.lang.String,java.lang.String>
named(java.lang.String name)
Returns an optional string form field namedname
.static <T> FormField.Builder<T,T>
named(java.lang.String name, java.lang.Class<T> typeIn)
Returns an optional form field namedname
with a specificinputType
.
-
-
-
Method Detail
-
named
public static FormField.Builder<java.lang.String,java.lang.String> named(java.lang.String name)
Returns an optional string form field namedname
.
-
named
public static <T> FormField.Builder<T,T> named(java.lang.String name, java.lang.Class<T> typeIn)
Returns an optional form field namedname
with a specificinputType
.
-
mapNamed
public static FormField.Builder<java.util.Map<java.lang.String,?>,java.util.Map<java.lang.String,?>> mapNamed(java.lang.String name)
Returns a form field builder for validating JSON nested maps.Here's an example of how you'd use this feature:
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();
When a
FormFieldException
is thrown, it'll be propagated to create a fully-qualified field name. For example, if the JSON input is{registrar: {name: ""}}
then thefield name
will beregistrar.name
.
-
name
public java.lang.String name()
Returns the name of this field.
-
convert
@Detainted public java.util.Optional<O> convert(@Tainted @Nullable I value)
Convert and validate a raw user-supplied value.- Throws:
FormFieldException
- if value does not meet expected contracts.
-
extract
@Detainted public java.util.Optional<O> extract(@Tainted java.util.Map<java.lang.String,I> valueMap)
Convert and validate a raw user-supplied value from a map.This is the same as saying:
field.convert(valueMap.get(field.name())
- Throws:
FormFieldException
- if value does not meet expected contracts.
-
extractUntyped
@Detainted public java.util.Optional<O> extractUntyped(@Tainted java.util.Map<java.lang.String,?> jsonMap)
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.
-
asBuilder
public FormField.Builder<I,O> asBuilder()
Returns a builder of this object, which can be used to further restrict validation.- See Also:
asBuilderNamed(String)
-
asBuilderNamed
public FormField.Builder<I,O> asBuilderNamed(java.lang.String newName)
Same asasBuilder()
but changes the field name.
-
-