Change-Id: I0641ea6118cd873c803d3c21d82b14fe8db4baa2tags/8.0.0.alpha1
@@ -322,7 +322,7 @@ Even if the user has not edited a field, all validation error will be shown if w | |||
binder.load(new Person()); | |||
// This will make all current validation errors visible | |||
Set<BinderResult> validationErrors = binder.validate(); | |||
List<ValidationError<?>> validationErrors = binder.validate(); | |||
if (!validationErrors.isEmpty()) { | |||
Notification.show("Validation error count: " | |||
@@ -594,7 +594,7 @@ Button saveButton = new Button("Save", event -> { | |||
// Create non-shared copy to use for validation | |||
Person copy = new Person(person); | |||
Set<BinderResult> errors = binder.validateWithBean(copy); | |||
List<ValidationError<?>> errors = binder.validateWithBean(copy); | |||
if (errors.isEmpty()) { | |||
// Write new values to the actual bean | |||
@@ -16,14 +16,20 @@ | |||
package com.vaadin.data; | |||
import java.io.Serializable; | |||
import java.util.ArrayList; | |||
import java.util.LinkedHashSet; | |||
import java.util.List; | |||
import java.util.Objects; | |||
import java.util.Optional; | |||
import java.util.Set; | |||
import java.util.function.BiConsumer; | |||
import java.util.function.Function; | |||
import java.util.function.Predicate; | |||
import java.util.stream.Collectors; | |||
import com.vaadin.event.Registration; | |||
import com.vaadin.server.UserError; | |||
import com.vaadin.ui.AbstractComponent; | |||
/** | |||
* Connects one or more {@code Field} components to properties of a backing data | |||
@@ -105,6 +111,42 @@ public class Binder<T> implements Serializable { | |||
* if {@code bind} has already been called on this binding | |||
*/ | |||
public void bind(Function<T, V> getter, BiConsumer<T, V> setter); | |||
/** | |||
* Adds a validator to this binding. Validators are applied, in | |||
* registration order, when the field value is saved to the backing | |||
* property. If any validator returns a failure, the property value is | |||
* not updated. | |||
* | |||
* @param validator | |||
* the validator to add, not null | |||
* @return this binding, for chaining | |||
* @throws IllegalStateException | |||
* if {@code bind} has already been called | |||
*/ | |||
public Binding<T, V> withValidator(Validator<? super V> validator); | |||
/** | |||
* A convenience method to add a validator to this binding using the | |||
* {@link Validator#from(Predicate, String)} factory method. | |||
* <p> | |||
* Validators are applied, in registration order, when the field value | |||
* is saved to the backing property. If any validator returns a failure, | |||
* the property value is not updated. | |||
* | |||
* @see #withValidator(Validator) | |||
* @see Validator#from(Predicate, String) | |||
* | |||
* @param predicate | |||
* the predicate performing validation, not null | |||
* @param message | |||
* the error message to report in case validation failure | |||
* @return this binding, for chaining | |||
* @throws IllegalStateException | |||
* if {@code bind} has already been called | |||
*/ | |||
public Binding<T, V> withValidator(Predicate<? super V> predicate, | |||
String message); | |||
} | |||
/** | |||
@@ -121,6 +163,8 @@ public class Binder<T> implements Serializable { | |||
private Function<T, V> getter; | |||
private BiConsumer<T, V> setter; | |||
private List<Validator<? super V>> validators = new ArrayList<>(); | |||
/** | |||
* Creates a new binding associated with the given field. | |||
* | |||
@@ -144,12 +188,35 @@ public class Binder<T> implements Serializable { | |||
} | |||
} | |||
@Override | |||
public Binding<T, V> withValidator(Validator<? super V> validator) { | |||
checkUnbound(); | |||
Objects.requireNonNull(validator, "validator cannot be null"); | |||
validators.add(validator); | |||
return this; | |||
} | |||
@Override | |||
public Binding<T, V> withValidator(Predicate<? super V> predicate, | |||
String message) { | |||
return withValidator(Validator.from(predicate, message)); | |||
} | |||
private void bind(T bean) { | |||
setFieldValue(bean); | |||
onValueChange = field | |||
.addValueChangeListener(e -> storeFieldValue(bean)); | |||
} | |||
private List<ValidationError<V>> validate() { | |||
return validators.stream() | |||
.map(validator -> validator.apply(field.getValue())) | |||
.filter(Result::isError) | |||
.map(result -> new ValidationError<>(field, | |||
result.getMessage().orElse(null))) | |||
.collect(Collectors.toList()); | |||
} | |||
private void unbind() { | |||
onValueChange.remove(); | |||
} | |||
@@ -186,6 +253,7 @@ public class Binder<T> implements Serializable { | |||
"cannot modify binding: already bound to a property"); | |||
} | |||
} | |||
} | |||
private T bean; | |||
@@ -286,6 +354,27 @@ public class Binder<T> implements Serializable { | |||
bindings.forEach(b -> b.bind(bean)); | |||
} | |||
/** | |||
* Validates the values of all bound fields and returns the result of the | |||
* validation as a set of validation errors. | |||
* <p> | |||
* Validation is successful if the resulting set is empty. | |||
* | |||
* @return the validation result. | |||
*/ | |||
public List<ValidationError<?>> validate() { | |||
List<ValidationError<?>> resultErrors = new ArrayList<>(); | |||
for (BindingImpl<?> binding : bindings) { | |||
clearError(binding.field); | |||
List<? extends ValidationError<?>> errors = binding.validate(); | |||
resultErrors.addAll(errors); | |||
if (!errors.isEmpty()) { | |||
handleError(binding.field, errors.get(0).getMessage()); | |||
} | |||
} | |||
return resultErrors; | |||
} | |||
/** | |||
* Unbinds the currently bound bean if any. If there is no bound bean, does | |||
* nothing. | |||
@@ -293,7 +382,7 @@ public class Binder<T> implements Serializable { | |||
public void unbind() { | |||
if (bean != null) { | |||
bean = null; | |||
bindings.forEach(b -> b.unbind()); | |||
bindings.forEach(BindingImpl::unbind); | |||
} | |||
} | |||
@@ -308,7 +397,10 @@ public class Binder<T> implements Serializable { | |||
*/ | |||
public void load(T bean) { | |||
Objects.requireNonNull(bean, "bean cannot be null"); | |||
bindings.forEach(binding -> binding.setFieldValue(bean)); | |||
bindings.forEach( | |||
binding -> binding.setFieldValue(bean)); | |||
} | |||
/** | |||
@@ -323,7 +415,10 @@ public class Binder<T> implements Serializable { | |||
*/ | |||
public void save(T bean) { | |||
Objects.requireNonNull(bean, "bean cannot be null"); | |||
bindings.forEach(binding -> binding.storeFieldValue(bean)); | |||
bindings.forEach( | |||
binding -> binding.storeFieldValue(bean)); | |||
} | |||
/** | |||
@@ -341,4 +436,36 @@ public class Binder<T> implements Serializable { | |||
return b; | |||
} | |||
/** | |||
* Clears the error condition of the given field, if any. The default | |||
* implementation clears the | |||
* {@link AbstractComponent#setComponentError(ErrorMessage) component error} | |||
* of the field if it is a Component, otherwise does nothing. | |||
* | |||
* @param field | |||
* the field with an invalid value | |||
*/ | |||
protected void clearError(HasValue<?> field) { | |||
if (field instanceof AbstractComponent) { | |||
((AbstractComponent) field).setComponentError(null); | |||
} | |||
} | |||
/** | |||
* Handles a validation error emitted when trying to save the value of the | |||
* given field. The default implementation sets the | |||
* {@link AbstractComponent#setComponentError(ErrorMessage) component error} | |||
* of the field if it is a Component, otherwise does nothing. | |||
* | |||
* @param field | |||
* the field with the invalid value | |||
* @param error | |||
* the error message to set | |||
*/ | |||
protected void handleError(HasValue<?> field, String error) { | |||
if (field instanceof AbstractComponent) { | |||
((AbstractComponent) field).setComponentError(new UserError(error)); | |||
} | |||
} | |||
} |
@@ -0,0 +1,170 @@ | |||
/* | |||
* Copyright 2000-2014 Vaadin Ltd. | |||
* | |||
* 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.vaadin.data; | |||
import java.io.Serializable; | |||
import java.util.Objects; | |||
import java.util.Optional; | |||
import java.util.function.Consumer; | |||
import java.util.function.Function; | |||
import java.util.function.Supplier; | |||
/** | |||
* Represents the result of an operation that might fail, such as input | |||
* validation or type conversion. A result may contain either a value, | |||
* signifying a successful operation, or an error message in case of a failure. | |||
* <p> | |||
* Result instances are created using the factory methods {@link #ok(R)} and | |||
* {@link #error(String)}, denoting success and failure respectively. | |||
* <p> | |||
* Unless otherwise specified, {@code Result} method arguments cannot be null. | |||
* | |||
* @param <R> | |||
* the result value type | |||
*/ | |||
public interface Result<R> extends Serializable { | |||
/** | |||
* Returns a successful result wrapping the given value. | |||
* | |||
* @param <R> | |||
* the result value type | |||
* @param value | |||
* the result value, can be null | |||
* @return a successful result | |||
*/ | |||
public static <R> Result<R> ok(R value) { | |||
return new SimpleResult<>(value, null); | |||
} | |||
/** | |||
* Returns a failure result wrapping the given error message. | |||
* | |||
* @param <R> | |||
* the result value type | |||
* @param message | |||
* the error message | |||
* @return a failure result | |||
*/ | |||
public static <R> Result<R> error(String message) { | |||
Objects.requireNonNull(message, "message cannot be null"); | |||
return new SimpleResult<R>(null, message); | |||
} | |||
/** | |||
* Returns a Result representing the result of invoking the given supplier. | |||
* If the supplier returns a value, returns a {@code Result.ok} of the | |||
* value; if an exception is thrown, returns the message in a | |||
* {@code Result.error}. | |||
* | |||
* @param <R> | |||
* the result value type | |||
* @param supplier | |||
* the supplier to run | |||
* @param onError | |||
* the function to provide the error message | |||
* @return the result of invoking the supplier | |||
*/ | |||
public static <R> Result<R> of(Supplier<R> supplier, | |||
Function<Exception, String> onError) { | |||
Objects.requireNonNull(supplier, "supplier cannot be null"); | |||
Objects.requireNonNull(onError, "onError cannot be null"); | |||
try { | |||
return ok(supplier.get()); | |||
} catch (Exception e) { | |||
return error(onError.apply(e)); | |||
} | |||
} | |||
/** | |||
* If this Result has a value, returns a Result of applying the given | |||
* function to the value. Otherwise, returns a Result bearing the same error | |||
* as this one. Note that any exceptions thrown by the mapping function are | |||
* not wrapped but allowed to propagate. | |||
* | |||
* @param <S> | |||
* the type of the mapped value | |||
* @param mapper | |||
* the mapping function | |||
* @return the mapped result | |||
*/ | |||
public default <S> Result<S> map(Function<R, S> mapper) { | |||
return flatMap(value -> ok(mapper.apply(value))); | |||
} | |||
/** | |||
* If this Result has a value, applies the given Result-returning function | |||
* to the value. Otherwise, returns a Result bearing the same error as this | |||
* one. Note that any exceptions thrown by the mapping function are not | |||
* wrapped but allowed to propagate. | |||
* | |||
* @param <S> | |||
* the type of the mapped value | |||
* @param mapper | |||
* the mapping function | |||
* @return the mapped result | |||
*/ | |||
public <S> Result<S> flatMap(Function<R, Result<S>> mapper); | |||
/** | |||
* Invokes either the first callback or the second one, depending on whether | |||
* this Result denotes a success or a failure, respectively. | |||
* | |||
* @param ifOk | |||
* the function to call if success | |||
* @param ifError | |||
* the function to call if failure | |||
*/ | |||
public void handle(Consumer<R> ifOk, Consumer<String> ifError); | |||
/** | |||
* Applies the {@code consumer} if result is not an error. | |||
* | |||
* @param consumer | |||
* consumer to apply in case it's not an error | |||
*/ | |||
public default void ifOk(Consumer<R> consumer) { | |||
handle(consumer, error -> { | |||
}); | |||
} | |||
/** | |||
* Applies the {@code consumer} if result is an error. | |||
* | |||
* @param consumer | |||
* consumer to apply in case it's an error | |||
*/ | |||
public default void ifError(Consumer<String> consumer) { | |||
handle(value -> { | |||
}, consumer); | |||
} | |||
/** | |||
* Returns {@code true} if result is an error. | |||
* | |||
* @return whether the result is an error | |||
*/ | |||
public boolean isError(); | |||
/** | |||
* Returns an Optional of the result message, or an empty Optional if none. | |||
* | |||
* @return the optional message | |||
*/ | |||
public Optional<String> getMessage(); | |||
} |
@@ -0,0 +1,96 @@ | |||
/* | |||
* Copyright 2000-2014 Vaadin Ltd. | |||
* | |||
* 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.vaadin.data; | |||
import java.util.Objects; | |||
import java.util.Optional; | |||
import java.util.function.Consumer; | |||
import java.util.function.Function; | |||
/** | |||
* An internal implementation of {@code Result}. | |||
* | |||
* @param <R> | |||
* the result value type | |||
*/ | |||
class SimpleResult<R> implements Result<R> { | |||
private final R value; | |||
private final String message; | |||
/** | |||
* Creates a new {@link Result} instance using {@code value} for a non error | |||
* {@link Result} and {@code message} for an error {@link Result}. | |||
* <p> | |||
* If {@code message} is null then {@code value} is ignored and result is an | |||
* error. | |||
* | |||
* @param value | |||
* the value of the result, may be {@code null} | |||
* @param message | |||
* the error message of the result, may be {@code null} | |||
*/ | |||
SimpleResult(R value, String message) { | |||
// value != null => message == null | |||
assert value == null | |||
|| message == null : "Message must be null if value is provided"; | |||
this.value = value; | |||
this.message = message; | |||
} | |||
@Override | |||
@SuppressWarnings("unchecked") | |||
public <S> Result<S> flatMap(Function<R, Result<S>> mapper) { | |||
Objects.requireNonNull(mapper, "mapper cannot be null"); | |||
if (isError()) { | |||
// Safe cast; valueless | |||
return (Result<S>) this; | |||
} else { | |||
return mapper.apply(value); | |||
} | |||
} | |||
@Override | |||
public void handle(Consumer<R> ifOk, Consumer<String> ifError) { | |||
Objects.requireNonNull(ifOk, "ifOk cannot be null"); | |||
Objects.requireNonNull(ifError, "ifError cannot be null"); | |||
if (isError()) { | |||
ifError.accept(message); | |||
} else { | |||
ifOk.accept(value); | |||
} | |||
} | |||
@Override | |||
public Optional<String> getMessage() { | |||
return Optional.ofNullable(message); | |||
} | |||
@Override | |||
public boolean isError() { | |||
return message != null; | |||
} | |||
@Override | |||
public String toString() { | |||
if (isError()) { | |||
return "error(" + message + ")"; | |||
} else { | |||
return "ok(" + value + ")"; | |||
} | |||
} | |||
} |
@@ -0,0 +1,68 @@ | |||
/* | |||
* Copyright 2000-2014 Vaadin Ltd. | |||
* | |||
* 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.vaadin.data; | |||
import java.io.Serializable; | |||
import java.util.Objects; | |||
/** | |||
* Represents a validation error. An error contains a reference to a field whose | |||
* value is invalid and a message describing a validation failure. | |||
* | |||
* @author Vaadin Ltd | |||
* @since 8.0 | |||
* | |||
* @param <V> | |||
* the field value type | |||
*/ | |||
public class ValidationError<V> implements Serializable { | |||
private HasValue<V> field; | |||
private String message; | |||
/** | |||
* Creates a new instance of ValidationError with provided validated field | |||
* and error message. | |||
* | |||
* @param field | |||
* the validated field | |||
* @param message | |||
* the validation error message, not {@code null} | |||
*/ | |||
public ValidationError(HasValue<V> field, String message) { | |||
Objects.requireNonNull(message, "message cannot be null"); | |||
this.field = field; | |||
this.message = message; | |||
} | |||
/** | |||
* Returns a reference to the validated field. | |||
* | |||
* @return the validated field | |||
*/ | |||
public HasValue<V> getField() { | |||
return field; | |||
} | |||
/** | |||
* Returns a validation error message. | |||
* | |||
* @return the validation error message | |||
*/ | |||
public String getMessage() { | |||
return message; | |||
} | |||
} |
@@ -0,0 +1,136 @@ | |||
/* | |||
* Copyright 2000-2014 Vaadin Ltd. | |||
* | |||
* 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.vaadin.data; | |||
import java.io.Serializable; | |||
import java.util.Objects; | |||
import java.util.function.Function; | |||
import java.util.function.Predicate; | |||
/** | |||
* A functional interface for validating user input or other potentially invalid | |||
* data. When a validator instance is applied to a value of the corresponding | |||
* type, it returns a <i>result</i> signifying that the value either passed or | |||
* failed the validation. | |||
* <p> | |||
* For instance, the following validator checks if a number is positive: | |||
* | |||
* <pre> | |||
* Validator<Integer> v = num -> { | |||
* if (num >= 0) | |||
* return Result.ok(num); | |||
* else | |||
* return Result.error("number must be positive"); | |||
* }; | |||
* </pre> | |||
* | |||
* @author Vaadin Ltd. | |||
* | |||
* @param <T> | |||
* the type of the value to validate | |||
* | |||
* @see Result | |||
*/ | |||
@FunctionalInterface | |||
public interface Validator<T> extends Function<T, Result<T>>, Serializable { | |||
/** | |||
* Returns a validator that chains this validator with the given function. | |||
* Specifically, the function may be another validator. The resulting | |||
* validator first applies this validator, and if the value passes, then the | |||
* given validator. | |||
* <p> | |||
* For instance, the following chained validator checks if a number is | |||
* between 0 and 10, inclusive: | |||
* | |||
* <pre> | |||
* Validator<Integer> v = Validator.from(num -> num >= 0, "number must be >= 0") | |||
* .chain(Validator.from(num -> num <= 10, "number must be <= 10")); | |||
* </pre> | |||
* | |||
* @param next | |||
* the validator to apply next, not null | |||
* @return a chained validator | |||
* | |||
* @see #from(Predicate, String) | |||
*/ | |||
public default Validator<T> chain(Function<T, Result<T>> next) { | |||
Objects.requireNonNull(next, "next cannot be null"); | |||
return val -> apply(val).flatMap(next); | |||
} | |||
/** | |||
* Validates the given value. Returns a {@code Result} instance representing | |||
* the outcome of the validation. | |||
* | |||
* @param value | |||
* the input value to validate | |||
* @return the validation result | |||
*/ | |||
@Override | |||
public Result<T> apply(T value); | |||
/** | |||
* Returns a validator that passes any value. | |||
* | |||
* @param <T> | |||
* the value type | |||
* @return an always-passing validator | |||
*/ | |||
public static <T> Validator<T> alwaysPass() { | |||
return v -> Result.ok(v); | |||
} | |||
/** | |||
* Builds a validator out of a conditional function and an error message. If | |||
* the function returns true, the validator returns {@code Result.ok()}; if | |||
* it returns false or throws an exception, {@code Result.error()} is | |||
* returned with the given message. | |||
* <p> | |||
* For instance, the following validator checks if a number is between 0 and | |||
* 10, inclusive: | |||
* | |||
* <pre> | |||
* Validator<Integer> v = Validator.from(num -> num >= 0 && num <= 10, | |||
* "number must be between 0 and 10"); | |||
* </pre> | |||
* | |||
* @param <T> | |||
* the value type | |||
* @param guard | |||
* the function used to validate, not null | |||
* @param errorMessage | |||
* the message returned if validation fails, not null | |||
* @return the new validator using the function | |||
*/ | |||
public static <T> Validator<T> from(Predicate<T> guard, | |||
String errorMessage) { | |||
Objects.requireNonNull(guard, "guard cannot be null"); | |||
Objects.requireNonNull(errorMessage, "errorMessage cannot be null"); | |||
return value -> { | |||
try { | |||
if (guard.test(value)) { | |||
return Result.ok(value); | |||
} else { | |||
return Result.error(errorMessage); | |||
} | |||
} catch (Exception e) { | |||
return Result.error(errorMessage); | |||
} | |||
}; | |||
} | |||
} |
@@ -4,10 +4,18 @@ import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertFalse; | |||
import static org.junit.Assert.assertSame; | |||
import java.util.List; | |||
import java.util.Set; | |||
import java.util.stream.Collectors; | |||
import org.junit.Assert; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import com.vaadin.data.Binder.Binding; | |||
import com.vaadin.server.AbstractErrorMessage; | |||
import com.vaadin.server.ErrorMessage; | |||
import com.vaadin.server.UserError; | |||
import com.vaadin.tests.data.bean.Person; | |||
import com.vaadin.ui.AbstractField; | |||
@@ -189,6 +197,66 @@ public class BinderTest { | |||
Assert.assertEquals("", nameField.getValue()); | |||
} | |||
@Test | |||
public void validate_notBound_noErrors() { | |||
Binder<Person> binder = new Binder<>(); | |||
List<ValidationError<?>> errors = binder.validate(); | |||
Assert.assertTrue(errors.isEmpty()); | |||
} | |||
@Test | |||
public void bound_validatorsAreOK_noErrors() { | |||
Binder<Person> binder = new Binder<>(); | |||
Binding<Person, String> binding = binder.forField(nameField); | |||
binding.withValidator(Validator.alwaysPass()).bind(Person::getFirstName, | |||
Person::setFirstName); | |||
nameField.setComponentError(new UserError("")); | |||
List<ValidationError<?>> errors = binder.validate(); | |||
Assert.assertTrue(errors.isEmpty()); | |||
Assert.assertNull(nameField.getComponentError()); | |||
} | |||
@SuppressWarnings("serial") | |||
@Test | |||
public void bound_validatorsFail_errors() { | |||
Binder<Person> binder = new Binder<>(); | |||
Binding<Person, String> binding = binder.forField(nameField); | |||
binding.withValidator(Validator.alwaysPass()); | |||
String msg1 = "foo"; | |||
String msg2 = "bar"; | |||
binding.withValidator(new Validator<String>() { | |||
@Override | |||
public Result<String> apply(String value) { | |||
return new SimpleResult<>(null, msg1); | |||
} | |||
}); | |||
binding.withValidator(value -> false, msg2); | |||
binding.bind(Person::getFirstName, Person::setFirstName); | |||
List<ValidationError<?>> errors = binder.validate(); | |||
Assert.assertEquals(2, errors.size()); | |||
Set<String> errorMessages = errors.stream() | |||
.map(ValidationError::getMessage).collect(Collectors.toSet()); | |||
Assert.assertTrue(errorMessages.contains(msg1)); | |||
Assert.assertTrue(errorMessages.contains(msg2)); | |||
Set<?> fields = errors.stream().map(ValidationError::getField) | |||
.collect(Collectors.toSet()); | |||
Assert.assertEquals(1, fields.size()); | |||
Assert.assertTrue(fields.contains(nameField)); | |||
ErrorMessage componentError = nameField.getComponentError(); | |||
Assert.assertNotNull(componentError); | |||
Assert.assertEquals("foo", | |||
((AbstractErrorMessage) componentError).getMessage()); | |||
} | |||
private void bindName() { | |||
binder.bind(nameField, Person::getFirstName, Person::setFirstName); | |||
binder.bind(p); |
@@ -0,0 +1,105 @@ | |||
/* | |||
* Copyright 2000-2014 Vaadin Ltd. | |||
* | |||
* 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.vaadin.data; | |||
import java.util.function.Function; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
/** | |||
* @author Vaadin Ltd | |||
* | |||
*/ | |||
public class ResultTest { | |||
@Test | |||
public void testOk() { | |||
String value = "foo"; | |||
Result<String> ok = Result.ok(value); | |||
Assert.assertFalse(ok.isError()); | |||
Assert.assertFalse(ok.getMessage().isPresent()); | |||
ok.ifOk(v -> Assert.assertEquals(value, v)); | |||
ok.ifError(msg -> Assert.fail()); | |||
} | |||
@Test | |||
public void testError() { | |||
String message = "foo"; | |||
Result<String> error = Result.error(message); | |||
Assert.assertTrue(error.isError()); | |||
Assert.assertTrue(error.getMessage().isPresent()); | |||
error.ifOk(v -> Assert.fail()); | |||
error.ifError(msg -> Assert.assertEquals(message, msg)); | |||
Assert.assertEquals(message, error.getMessage().get()); | |||
} | |||
@Test | |||
public void of_noException() { | |||
Result<String> result = Result.of(() -> "", exception -> null); | |||
Assert.assertTrue(result instanceof SimpleResult); | |||
Assert.assertFalse(((SimpleResult<?>) result).isError()); | |||
} | |||
@Test | |||
public void of_exception() { | |||
String message = "foo"; | |||
Result<String> result = Result.<String> of(() -> { | |||
throw new RuntimeException(); | |||
}, exception -> message); | |||
Assert.assertTrue(result instanceof SimpleResult); | |||
Assert.assertTrue(((SimpleResult<?>) result).isError()); | |||
Assert.assertEquals(message, result.getMessage().get()); | |||
} | |||
@SuppressWarnings("serial") | |||
@Test | |||
public void map_norError_mapperIsApplied() { | |||
Result<String> result = new SimpleResult<String>("foo", null) { | |||
@Override | |||
public <S> Result<S> flatMap(Function<String, Result<S>> mapper) { | |||
return mapper.apply("foo"); | |||
} | |||
}; | |||
Result<String> mapResult = result.map(value -> { | |||
Assert.assertEquals("foo", value); | |||
return "bar"; | |||
}); | |||
Assert.assertTrue(mapResult instanceof SimpleResult); | |||
Assert.assertFalse(((SimpleResult<?>) mapResult).isError()); | |||
mapResult.ifOk(v -> Assert.assertEquals("bar", v)); | |||
} | |||
@SuppressWarnings("serial") | |||
@Test | |||
public void map_error_mapperIsApplied() { | |||
Result<String> result = new SimpleResult<String>("foo", null) { | |||
@Override | |||
public <S> Result<S> flatMap(Function<String, Result<S>> mapper) { | |||
return new SimpleResult<S>(null, "bar"); | |||
} | |||
}; | |||
Result<String> mapResult = result.map(value -> { | |||
Assert.assertEquals("foo", value); | |||
return "somevalue"; | |||
}); | |||
Assert.assertTrue(mapResult instanceof SimpleResult); | |||
Assert.assertTrue(((SimpleResult<?>) mapResult).isError()); | |||
mapResult.ifError(msg -> Assert.assertEquals("bar", msg)); | |||
} | |||
} |
@@ -0,0 +1,93 @@ | |||
/* | |||
* Copyright 2000-2014 Vaadin Ltd. | |||
* | |||
* 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.vaadin.data; | |||
import java.util.Objects; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
/** | |||
* @author Vaadin Ltd | |||
* | |||
*/ | |||
public class ValidatorTest { | |||
@Test | |||
public void alwaysPass() { | |||
Validator<String> alwaysPass = Validator.alwaysPass(); | |||
Result<String> result = alwaysPass.apply("foo"); | |||
Assert.assertTrue(result instanceof SimpleResult); | |||
SimpleResult<String> implRes = (SimpleResult<String>) result; | |||
Assert.assertFalse(implRes.getMessage().isPresent()); | |||
} | |||
@Test | |||
public void chain_alwaysPassAndError() { | |||
Validator<String> alwaysPass = Validator.alwaysPass(); | |||
Validator<String> chain = alwaysPass | |||
.chain(value -> Result.error("foo")); | |||
Result<String> result = chain.apply("bar"); | |||
Assert.assertTrue(result.isError()); | |||
Assert.assertEquals("foo", result.getMessage().get()); | |||
} | |||
@SuppressWarnings("serial") | |||
@Test | |||
public void chain_mixture() { | |||
Validator<String> first = new Validator<String>() { | |||
@Override | |||
public Result<String> apply(String value) { | |||
if (value == null) { | |||
return Result.error("Cannot be null"); | |||
} | |||
return Result.ok(value); | |||
} | |||
}; | |||
Validator<String> second = new Validator<String>() { | |||
@Override | |||
public Result<String> apply(String value) { | |||
if (value != null && value.isEmpty()) { | |||
return Result.error("Cannot be empty"); | |||
} | |||
return Result.ok(value); | |||
} | |||
}; | |||
Validator<String> chain = first.chain(second); | |||
Result<String> result = chain.apply("bar"); | |||
Assert.assertFalse(result.isError()); | |||
result = chain.apply(null); | |||
Assert.assertTrue(result.isError()); | |||
result = chain.apply(""); | |||
Assert.assertTrue(result.isError()); | |||
} | |||
@Test | |||
public void from() { | |||
Validator<String> validator = Validator.from(Objects::nonNull, | |||
"Cannot be null"); | |||
Result<String> result = validator.apply(null); | |||
Assert.assertTrue(result.isError()); | |||
result = validator.apply(""); | |||
Assert.assertFalse(result.isError()); | |||
} | |||
} |