aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--all/src/main/templates/release-notes.html1
-rw-r--r--server/src/main/java/com/vaadin/data/Binder.java121
-rw-r--r--server/src/main/java/com/vaadin/data/BindingValidationStatus.java80
-rw-r--r--server/src/main/java/com/vaadin/data/ValidationResult.java77
-rw-r--r--server/src/main/java/com/vaadin/data/ValidationResultWrap.java102
-rw-r--r--server/src/main/java/com/vaadin/data/Validator.java71
-rw-r--r--server/src/test/java/com/vaadin/data/BinderTest.java20
m---------tests/screenshots0
-rw-r--r--uitest/src/main/java/com/vaadin/tests/binder/BinderValidatorErrorLevel.java29
-rw-r--r--uitest/src/test/java/com/vaadin/tests/binder/BinderValidatorErrorLevelTest.java41
10 files changed, 491 insertions, 51 deletions
diff --git a/all/src/main/templates/release-notes.html b/all/src/main/templates/release-notes.html
index 20a7346182..f2c9a6523e 100644
--- a/all/src/main/templates/release-notes.html
+++ b/all/src/main/templates/release-notes.html
@@ -115,6 +115,7 @@
<li><tt>Button</tt> has a new constructor that may cause constructor calls with null as first parameter to be ambiguous.</li>
<li><tt>DataCommunicator</tt> methods <tt>getDataProviderSize</tt> and <tt>fetchItemsWithRange</tt> are now <tt>public</tt>, not <tt>protected</tt>.</li>
<li><tt>Binder</tt> method <tt>getBindings</tt> now returns a Collection, not a Set.</li>
+ <li><tt>Binder</tt> protected method <tt>handleError</tt> now takes a <tt>ValidationResult</tt>, not <tt>String</tt>.</li>
<li><tt>BindingBuilder</tt> now works like a proper builder. Adding a converter will not mark Binding as <tt>bound</tt> allowing chaining to the same object.</li>
<li><tt>ErrorLevel</tt> is removed from <tt>ErrorMessage</tt> and now <tt>com.vaadin.shared.ui.ErrorLevel</tt> should be used.</li>
<li>Error indicators are now <tt>&lt;span class="v-errorindicator"&gt;&lt;/span&gt;</tt> elements.</li>
diff --git a/server/src/main/java/com/vaadin/data/Binder.java b/server/src/main/java/com/vaadin/data/Binder.java
index e28c191018..828130bd81 100644
--- a/server/src/main/java/com/vaadin/data/Binder.java
+++ b/server/src/main/java/com/vaadin/data/Binder.java
@@ -43,12 +43,14 @@ import com.vaadin.data.HasValue.ValueChangeListener;
import com.vaadin.data.converter.StringToIntegerConverter;
import com.vaadin.data.validator.BeanValidator;
import com.vaadin.event.EventRouter;
+import com.vaadin.server.AbstractErrorMessage.ContentMode;
import com.vaadin.server.ErrorMessage;
import com.vaadin.server.SerializableFunction;
import com.vaadin.server.SerializablePredicate;
import com.vaadin.server.Setter;
import com.vaadin.server.UserError;
import com.vaadin.shared.Registration;
+import com.vaadin.shared.ui.ErrorLevel;
import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.Component;
import com.vaadin.ui.Label;
@@ -286,6 +288,7 @@ public class Binder<BEAN> implements Serializable {
* failure, the property value is not updated.
*
* @see #withValidator(Validator)
+ * @see #withValidator(SerializablePredicate, String, ErrorLevel)
* @see #withValidator(SerializablePredicate, ErrorMessageProvider)
* @see Validator#from(SerializablePredicate, String)
*
@@ -305,6 +308,40 @@ public class Binder<BEAN> implements Serializable {
/**
* A convenience method to add a validator to this binding using the
+ * {@link Validator#from(SerializablePredicate, String, ErrorLevel)}
+ * factory method.
+ * <p>
+ * Validators are applied, in registration order, when the field value
+ * is written to the backing property. If any validator returns a
+ * failure, the property value is not updated.
+ *
+ * @see #withValidator(Validator)
+ * @see #withValidator(SerializablePredicate, String)
+ * @see #withValidator(SerializablePredicate, ErrorMessageProvider,
+ * ErrorLevel)
+ * @see Validator#from(SerializablePredicate, String)
+ *
+ * @param predicate
+ * the predicate performing validation, not null
+ * @param message
+ * the error message to report in case validation failure
+ * @param errorLevel
+ * the error level for failures from this validator, not null
+ * @return this binding, for chaining
+ * @throws IllegalStateException
+ * if {@code bind} has already been called
+ *
+ * @since 8.2
+ */
+ public default BindingBuilder<BEAN, TARGET> withValidator(
+ SerializablePredicate<? super TARGET> predicate, String message,
+ ErrorLevel errorLevel) {
+ return withValidator(
+ Validator.from(predicate, message, errorLevel));
+ }
+
+ /**
+ * A convenience method to add a validator to this binding using the
* {@link Validator#from(SerializablePredicate, ErrorMessageProvider)}
* factory method.
* <p>
@@ -314,6 +351,8 @@ public class Binder<BEAN> implements Serializable {
*
* @see #withValidator(Validator)
* @see #withValidator(SerializablePredicate, String)
+ * @see #withValidator(SerializablePredicate, ErrorMessageProvider,
+ * ErrorLevel)
* @see Validator#from(SerializablePredicate, ErrorMessageProvider)
*
* @param predicate
@@ -332,6 +371,41 @@ public class Binder<BEAN> implements Serializable {
}
/**
+ * A convenience method to add a validator to this binding using the
+ * {@link Validator#from(SerializablePredicate, ErrorMessageProvider, ErrorLevel)}
+ * factory method.
+ * <p>
+ * Validators are applied, in registration order, when the field value
+ * is written to the backing property. If any validator returns a
+ * failure, the property value is not updated.
+ *
+ * @see #withValidator(Validator)
+ * @see #withValidator(SerializablePredicate, String, ErrorLevel)
+ * @see #withValidator(SerializablePredicate, ErrorMessageProvider)
+ * @see Validator#from(SerializablePredicate, ErrorMessageProvider,
+ * ErrorLevel)
+ *
+ * @param predicate
+ * the predicate performing validation, not null
+ * @param errorMessageProvider
+ * the provider to generate error messages, not null
+ * @param errorLevel
+ * the error level for failures from this validator, not null
+ * @return this binding, for chaining
+ * @throws IllegalStateException
+ * if {@code bind} has already been called
+ *
+ * @since 8.2
+ */
+ public default BindingBuilder<BEAN, TARGET> withValidator(
+ SerializablePredicate<? super TARGET> predicate,
+ ErrorMessageProvider errorMessageProvider,
+ ErrorLevel errorLevel) {
+ return withValidator(Validator.from(predicate, errorMessageProvider,
+ errorLevel));
+ }
+
+ /**
* Maps the binding to another data type using the given
* {@link Converter}.
* <p>
@@ -905,10 +979,7 @@ public class Binder<BEAN> implements Serializable {
private BindingValidationStatus<TARGET> toValidationStatus(
Result<TARGET> result) {
- return new BindingValidationStatus<>(this,
- result.isError()
- ? ValidationResult.error(result.getMessage().get())
- : ValidationResult.ok());
+ return new BindingValidationStatus<>(result, this);
}
/**
@@ -1031,11 +1102,7 @@ public class Binder<BEAN> implements Serializable {
@Override
public Result<T> convertToModel(T value, ValueContext context) {
ValidationResult validationResult = validator.apply(value, context);
- if (validationResult.isError()) {
- return Result.error(validationResult.getErrorMessage());
- } else {
- return Result.ok(value);
- }
+ return new ValidationResultWrap<>(value, validationResult);
}
@Override
@@ -2012,14 +2079,18 @@ public class Binder<BEAN> implements Serializable {
*
* @param field
* the field with the invalid value
- * @param error
- * the error message to set
+ * @param result
+ * the validation error result
+ *
+ * @since 8.2
*/
- protected void handleError(HasValue<?> field, String error) {
- if (field instanceof AbstractComponent) {
- ((AbstractComponent) field).setComponentError(new UserError(error));
- }
-
+ protected void handleError(HasValue<?> field, ValidationResult result) {
+ result.getErrorLevel().ifPresent(level -> {
+ if (field instanceof AbstractComponent) {
+ ((AbstractComponent) field).setComponentError(new UserError(
+ result.getErrorMessage(), ContentMode.TEXT, level));
+ }
+ });
}
/**
@@ -2033,7 +2104,23 @@ public class Binder<BEAN> implements Serializable {
HasValue<?> source = status.getField();
clearError(source);
if (status.isError()) {
- handleError(source, status.getMessage().get());
+ Optional<ValidationResult> firstError = status
+ .getValidationResults().stream()
+ .filter(ValidationResult::isError).findFirst();
+ if (firstError.isPresent()) {
+ // Failed with a Validation error
+ handleError(source, firstError.get());
+ } else {
+ // Conversion error
+ status.getResult()
+ .ifPresent(result -> handleError(source, result));
+ }
+ } else {
+ // Show first non-error ValidationResult message.
+ status.getValidationResults().stream()
+ .filter(result -> result.getErrorLevel().isPresent())
+ .findFirst()
+ .ifPresent(result -> handleError(source, result));
}
}
diff --git a/server/src/main/java/com/vaadin/data/BindingValidationStatus.java b/server/src/main/java/com/vaadin/data/BindingValidationStatus.java
index 5b5a066418..f6a7809970 100644
--- a/server/src/main/java/com/vaadin/data/BindingValidationStatus.java
+++ b/server/src/main/java/com/vaadin/data/BindingValidationStatus.java
@@ -16,6 +16,8 @@
package com.vaadin.data;
import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -24,8 +26,8 @@ import com.vaadin.data.Binder.BindingBuilder;
/**
* Represents the status of field validation. Status can be {@code Status.OK},
- * {@code Status.ERROR} or {@code Status.RESET}. Status OK and ERROR are always
- * associated with a ValidationResult {@link #getResult}.
+ * {@code Status.ERROR} or {@code Status.UNRESOLVED}. Status OK and ERROR are
+ * always associated with a ValidationResult {@link #getResult}.
* <p>
* Use
* {@link BindingBuilder#withValidationStatusHandler(BindingValidationStatusHandler)}
@@ -70,8 +72,9 @@ public class BindingValidationStatus<TARGET> implements Serializable {
}
private final Status status;
- private final ValidationResult result;
+ private final List<ValidationResult> results;
private final Binding<?, TARGET> binding;
+ private Result<TARGET> result;
/**
* Convenience method for creating a {@link Status#UNRESOLVED} validation
@@ -86,7 +89,7 @@ public class BindingValidationStatus<TARGET> implements Serializable {
*/
public static <TARGET> BindingValidationStatus<TARGET> createUnresolvedStatus(
Binding<?, TARGET> source) {
- return new BindingValidationStatus<>(source, Status.UNRESOLVED, null);
+ return new BindingValidationStatus<TARGET>(null, source);
}
/**
@@ -98,6 +101,7 @@ public class BindingValidationStatus<TARGET> implements Serializable {
* @param result
* the result of the validation
*/
+ @Deprecated
public BindingValidationStatus(Binding<?, TARGET> source,
ValidationResult result) {
this(source, result.isError() ? Status.ERROR : Status.OK, result);
@@ -116,20 +120,43 @@ public class BindingValidationStatus<TARGET> implements Serializable {
* @param result
* the related result, may be {@code null}
*/
+ @Deprecated
public BindingValidationStatus(Binding<?, TARGET> source, Status status,
ValidationResult result) {
+ this(result.isError() ? Result.error(result.getErrorMessage())
+ : Result.ok(null), source);
+ }
+
+ /**
+ * Creates a new status change event.
+ * <p>
+ * If {@code result} is {@code null}, the {@code status} is
+ * {@link Status#UNRESOLVED}.
+ *
+ * @param result
+ * the related result object, may be {@code null}
+ * @param source
+ * field whose status has changed, not {@code null}
+ *
+ * @since 8.2
+ */
+ public BindingValidationStatus(Result<TARGET> result,
+ Binding<?, TARGET> source) {
Objects.requireNonNull(source, "Event source may not be null");
- Objects.requireNonNull(status, "Status may not be null");
- if (Objects.equals(status, Status.OK) && result.isError()
- || Objects.equals(status, Status.ERROR) && !result.isError()
- || Objects.equals(status, Status.UNRESOLVED)
- && result != null) {
- throw new IllegalStateException(
- "Invalid validation status " + status + " for given result "
- + (result == null ? "null" : result.toString()));
- }
+
binding = source;
- this.status = status;
+ if (result != null) {
+ this.status = result.isError() ? Status.ERROR : Status.OK;
+ if (result instanceof ValidationResultWrap) {
+ results = ((ValidationResultWrap<TARGET>) result)
+ .getValidationResults();
+ } else {
+ results = Collections.emptyList();
+ }
+ } else {
+ this.status = Status.UNRESOLVED;
+ results = Collections.emptyList();
+ }
this.result = result;
}
@@ -159,8 +186,10 @@ public class BindingValidationStatus<TARGET> implements Serializable {
* status is not an error
*/
public Optional<String> getMessage() {
- return Optional.ofNullable(result).filter(ValidationResult::isError)
- .map(ValidationResult::getErrorMessage);
+ if (getStatus() == Status.OK || result == null) {
+ return Optional.empty();
+ }
+ return result.getMessage();
}
/**
@@ -171,7 +200,24 @@ public class BindingValidationStatus<TARGET> implements Serializable {
* @return the validation result
*/
public Optional<ValidationResult> getResult() {
- return Optional.ofNullable(result);
+ if (result == null) {
+ return Optional.empty();
+ }
+ return Optional.of(result.isError()
+ ? ValidationResult.error(result.getMessage().orElse(""))
+ : ValidationResult.ok());
+ }
+
+ /**
+ * Gets all the validation results related to this binding validation
+ * status.
+ *
+ * @return list of validation results
+ *
+ * @since 8.2
+ */
+ public List<ValidationResult> getValidationResults() {
+ return Collections.unmodifiableList(results);
}
/**
diff --git a/server/src/main/java/com/vaadin/data/ValidationResult.java b/server/src/main/java/com/vaadin/data/ValidationResult.java
index d458911ba1..33e10d6db2 100644
--- a/server/src/main/java/com/vaadin/data/ValidationResult.java
+++ b/server/src/main/java/com/vaadin/data/ValidationResult.java
@@ -17,6 +17,9 @@ package com.vaadin.data;
import java.io.Serializable;
import java.util.Objects;
+import java.util.Optional;
+
+import com.vaadin.shared.ui.ErrorLevel;
/**
* Represents the result of a validation. A result may be either successful or
@@ -35,26 +38,30 @@ public interface ValidationResult extends Serializable {
class SimpleValidationResult implements ValidationResult {
private final String error;
+ private final ErrorLevel errorLevel;
- SimpleValidationResult(String error) {
+ SimpleValidationResult(String error, ErrorLevel errorLevel) {
+ if (error != null && errorLevel == null) {
+ throw new IllegalStateException("ValidationResult has an "
+ + "error message, but no ErrorLevel is provided.");
+ }
this.error = error;
+ this.errorLevel = errorLevel;
}
@Override
public String getErrorMessage() {
- if (error == null) {
+ if (!getErrorLevel().isPresent()) {
throw new IllegalStateException("The result is not an error. "
+ "It cannot contain error message");
} else {
- return error;
+ return error != null ? error : "";
}
}
- @Override
- public boolean isError() {
- return error != null;
+ public Optional<ErrorLevel> getErrorLevel() {
+ return Optional.ofNullable(errorLevel);
}
-
}
/**
@@ -69,12 +76,36 @@ public interface ValidationResult extends Serializable {
String getErrorMessage();
/**
+ * Returns optional error level for this validation result. Error level is
+ * not present for successful validation results.
+ * <p>
+ * <strong>Note:</strong> By default {@link ErrorLevel#INFO} and
+ * {@link ErrorLevel#WARNING} are not considered to be blocking the
+ * validation and conversion chain.
+ *
+ * @see #isError()
+ *
+ * @return optional error level; error level is present for validation
+ * results that have not passed validation
+ *
+ * @since 8.2
+ */
+ Optional<ErrorLevel> getErrorLevel();
+
+ /**
* Checks if the result denotes an error.
+ * <p>
+ * <strong>Note:</strong> By default {@link ErrorLevel#INFO} and
+ * {@link ErrorLevel#WARNING} are not considered to be errors.
*
* @return <code>true</code> if the result denotes an error,
* <code>false</code> otherwise
*/
- boolean isError();
+ default boolean isError() {
+ ErrorLevel errorLevel = getErrorLevel().orElse(null);
+ return errorLevel != null && errorLevel != ErrorLevel.INFO
+ && errorLevel != ErrorLevel.WARNING;
+ }
/**
* Returns a successful result.
@@ -82,7 +113,7 @@ public interface ValidationResult extends Serializable {
* @return the successful result
*/
public static ValidationResult ok() {
- return new SimpleValidationResult(null);
+ return new SimpleValidationResult(null, null);
}
/**
@@ -98,6 +129,32 @@ public interface ValidationResult extends Serializable {
*/
public static ValidationResult error(String errorMessage) {
Objects.requireNonNull(errorMessage);
- return new SimpleValidationResult(errorMessage);
+ return create(errorMessage, ErrorLevel.ERROR);
+ }
+
+ /**
+ * Creates the validation result with the given {@code errorMessage} and
+ * {@code errorLevel}. Results with {@link ErrorLevel} of {@code INFO} or
+ * {@code WARNING} are not errors by default.
+ *
+ * @see #ok()
+ * @see #error(String)
+ *
+ * @param errorMessage
+ * error message, not {@code null}
+ * @param errorLevel
+ * error level, not {@code null}
+ * @return validation result with the given {@code errorMessage} and
+ * {@code errorLevel}
+ * @throws NullPointerException
+ * if {@code errorMessage} or {@code errorLevel} is {@code null}
+ *
+ * @since 8.2
+ */
+ public static ValidationResult create(String errorMessage,
+ ErrorLevel errorLevel) {
+ Objects.requireNonNull(errorMessage);
+ Objects.requireNonNull(errorLevel);
+ return new SimpleValidationResult(errorMessage, errorLevel);
}
}
diff --git a/server/src/main/java/com/vaadin/data/ValidationResultWrap.java b/server/src/main/java/com/vaadin/data/ValidationResultWrap.java
new file mode 100644
index 0000000000..71bb475686
--- /dev/null
+++ b/server/src/main/java/com/vaadin/data/ValidationResultWrap.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2000-2016 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.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import com.vaadin.server.SerializableConsumer;
+import com.vaadin.server.SerializableFunction;
+
+/**
+ * Internal implementation of a {@code Result} that collects all possible
+ * ValidationResults into one list. This class intercepts the normal chaining of
+ * Converters and Validators, catching and collecting results.
+ *
+ * @param <R>
+ * the result data type
+ *
+ * @since 8.2
+ */
+class ValidationResultWrap<R> implements Result<R> {
+
+ private final List<ValidationResult> resultList;
+ private final Result<R> wrappedResult;
+
+ ValidationResultWrap(Result<R> result, List<ValidationResult> resultList) {
+ this.resultList = resultList;
+ this.wrappedResult = result;
+ }
+
+ ValidationResultWrap(R value, ValidationResult result) {
+ if (result.isError()) {
+ wrappedResult = new SimpleResult<>(null, result.getErrorMessage());
+ } else {
+ wrappedResult = new SimpleResult<>(value, null);
+ }
+ this.resultList = new ArrayList<>();
+ this.resultList.add(result);
+ }
+
+ List<ValidationResult> getValidationResults() {
+ return Collections.unmodifiableList(resultList);
+ }
+
+ Result<R> getWrappedResult() {
+ return wrappedResult;
+ }
+
+ @Override
+ public <S> Result<S> flatMap(SerializableFunction<R, Result<S>> mapper) {
+ Result<S> result = wrappedResult.flatMap(mapper);
+ if (!(result instanceof ValidationResultWrap)) {
+ return new ValidationResultWrap<S>(result, resultList);
+ }
+
+ List<ValidationResult> currentResults = new ArrayList<>(resultList);
+ ValidationResultWrap<S> resultWrap = (ValidationResultWrap<S>) result;
+ currentResults.addAll(resultWrap.getValidationResults());
+
+ return new ValidationResultWrap<>(resultWrap.getWrappedResult(),
+ currentResults);
+ }
+
+ @Override
+ public void handle(SerializableConsumer<R> ifOk,
+ SerializableConsumer<String> ifError) {
+ wrappedResult.handle(ifOk, ifError);
+ }
+
+ @Override
+ public boolean isError() {
+ return wrappedResult.isError();
+ }
+
+ @Override
+ public Optional<String> getMessage() {
+ return wrappedResult.getMessage();
+ }
+
+ @Override
+ public <X extends Throwable> R getOrThrow(
+ SerializableFunction<String, ? extends X> exceptionProvider)
+ throws X {
+ return wrappedResult.getOrThrow(exceptionProvider);
+ }
+
+}
diff --git a/server/src/main/java/com/vaadin/data/Validator.java b/server/src/main/java/com/vaadin/data/Validator.java
index 30d6976bde..4f8f4dc8ca 100644
--- a/server/src/main/java/com/vaadin/data/Validator.java
+++ b/server/src/main/java/com/vaadin/data/Validator.java
@@ -21,6 +21,7 @@ import java.util.Objects;
import java.util.function.BiFunction;
import com.vaadin.server.SerializablePredicate;
+import com.vaadin.shared.ui.ErrorLevel;
/**
* A functional interface for validating user input or other potentially invalid
@@ -80,8 +81,8 @@ public interface Validator<T>
* 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,
- * {@link ValidationResult#error(String)} is returned with the given
- * message.
+ * {@link ValidationResult#error(String)} is returned with the given message
+ * and error level {@link ErrorLevel#ERROR}.
* <p>
* For instance, the following validator checks if a number is between 0 and
* 10, inclusive:
@@ -101,12 +102,44 @@ public interface Validator<T>
*/
public static <T> Validator<T> from(SerializablePredicate<T> guard,
String errorMessage) {
- Objects.requireNonNull(guard, "guard cannot be null");
Objects.requireNonNull(errorMessage, "errorMessage cannot be null");
return from(guard, ctx -> errorMessage);
}
/**
+ * 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,
+ * {@link ValidationResult#error(String)} is returned with the given message
+ * and error level.
+ * <p>
+ * For instance, the following validator checks if a number is between 0 and
+ * 10, inclusive:
+ *
+ * <pre>
+ * Validator&lt;Integer&gt; v = Validator.from(num -&gt; num &gt;= 0 && num &lt;= 10,
+ * "number must be between 0 and 10", ErrorLevel.ERROR);
+ * </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
+ * @param errorLevel
+ * the error level for failures from this validator, not null
+ * @return the new validator using the function
+ *
+ * @since 8.2
+ */
+ public static <T> Validator<T> from(SerializablePredicate<T> guard,
+ String errorMessage, ErrorLevel errorLevel) {
+ Objects.requireNonNull(errorMessage, "errorMessage cannot be null");
+ return from(guard, ctx -> errorMessage, errorLevel);
+ }
+
+ /**
* Builds a validator out of a conditional function and an error message
* provider. If the function returns true, the validator returns
* {@code Result.ok()}; if it returns false or throws an exception,
@@ -122,20 +155,44 @@ public interface Validator<T>
*/
public static <T> Validator<T> from(SerializablePredicate<T> guard,
ErrorMessageProvider errorMessageProvider) {
+ return from(guard, errorMessageProvider, ErrorLevel.ERROR);
+ }
+
+ /**
+ * Builds a validator out of a conditional function and an error message
+ * provider. 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 message from the provider.
+ *
+ * @param <T>
+ * the value type
+ * @param guard
+ * the function used to validate, not null
+ * @param errorMessageProvider
+ * the provider to generate error messages, not null
+ * @param errorLevel
+ * the error level for failures from this validator, not null
+ * @return the new validator using the function
+ *
+ * @since 8.2
+ */
+ public static <T> Validator<T> from(SerializablePredicate<T> guard,
+ ErrorMessageProvider errorMessageProvider, ErrorLevel errorLevel) {
Objects.requireNonNull(guard, "guard cannot be null");
Objects.requireNonNull(errorMessageProvider,
"errorMessageProvider cannot be null");
+ Objects.requireNonNull(errorLevel, "errorLevel cannot be null");
return (value, context) -> {
try {
if (guard.test(value)) {
return ValidationResult.ok();
} else {
- return ValidationResult
- .error(errorMessageProvider.apply(context));
+ return ValidationResult.create(
+ errorMessageProvider.apply(context), errorLevel);
}
} catch (Exception e) {
- return ValidationResult
- .error(errorMessageProvider.apply(context));
+ return ValidationResult.create(
+ errorMessageProvider.apply(context), errorLevel);
}
};
}
diff --git a/server/src/test/java/com/vaadin/data/BinderTest.java b/server/src/test/java/com/vaadin/data/BinderTest.java
index a44e3b3e06..032ce99580 100644
--- a/server/src/test/java/com/vaadin/data/BinderTest.java
+++ b/server/src/test/java/com/vaadin/data/BinderTest.java
@@ -14,6 +14,7 @@ import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -24,6 +25,7 @@ import com.vaadin.data.validator.IntegerRangeValidator;
import com.vaadin.data.validator.NotEmptyValidator;
import com.vaadin.data.validator.StringLengthValidator;
import com.vaadin.server.ErrorMessage;
+import com.vaadin.shared.ui.ErrorLevel;
import com.vaadin.tests.data.bean.Person;
import com.vaadin.tests.data.bean.Sex;
import com.vaadin.ui.TextField;
@@ -943,6 +945,24 @@ public class BinderTest extends BinderTestBase<Binder<Person>, Person> {
}
@Test
+ public void info_validator_not_considered_error() {
+ String infoMessage = "Young";
+ binder.forField(ageField)
+ .withConverter(new StringToIntegerConverter("Can't convert"))
+ .withValidator(i -> i > 5, infoMessage, ErrorLevel.INFO)
+ .bind(Person::getAge, Person::setAge);
+
+ binder.setBean(item);
+ ageField.setValue("3");
+ Assert.assertEquals(infoMessage,
+ ageField.getComponentError().getFormattedHtmlMessage());
+ Assert.assertEquals(ErrorLevel.INFO,
+ ageField.getComponentError().getErrorLevel());
+
+ Assert.assertEquals(3, item.getAge());
+ }
+
+ @Test
public void two_asRequired_fields_without_initial_values() {
binder.forField(nameField).asRequired("Empty name").bind(p -> "",
(p, s) -> {
diff --git a/tests/screenshots b/tests/screenshots
-Subproject 93a7b5d5a00209767b54a69395b1a7cc30ee970
+Subproject a20d0108ad315b1b5a4626619cd157e03cfa7b7
diff --git a/uitest/src/main/java/com/vaadin/tests/binder/BinderValidatorErrorLevel.java b/uitest/src/main/java/com/vaadin/tests/binder/BinderValidatorErrorLevel.java
new file mode 100644
index 0000000000..03a20bbe1f
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/tests/binder/BinderValidatorErrorLevel.java
@@ -0,0 +1,29 @@
+package com.vaadin.tests.binder;
+
+import com.vaadin.annotations.Widgetset;
+import com.vaadin.data.Binder;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.shared.ui.ErrorLevel;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.ui.TextField;
+
+@Widgetset("com.vaadin.DefaultWidgetSet")
+public class BinderValidatorErrorLevel extends AbstractTestUI {
+
+ @Override
+ protected void setup(VaadinRequest request) {
+ Binder<Object> binder = new Binder<>();
+
+ for (ErrorLevel l : ErrorLevel.values()) {
+ TextField field = new TextField(l.name());
+ binder.forField(field)
+ .withValidator(s -> s.length() > 3,
+ "ErrorLevel: " + l.name(), l)
+ .bind(t -> "", (t, s) -> {
+ });
+ addComponent(field);
+ }
+ binder.validate();
+ }
+
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/binder/BinderValidatorErrorLevelTest.java b/uitest/src/test/java/com/vaadin/tests/binder/BinderValidatorErrorLevelTest.java
new file mode 100644
index 0000000000..4be9f9a49e
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/tests/binder/BinderValidatorErrorLevelTest.java
@@ -0,0 +1,41 @@
+package com.vaadin.tests.binder;
+
+import java.io.IOException;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.shared.ui.ErrorLevel;
+import com.vaadin.testbench.By;
+import com.vaadin.testbench.elements.TextFieldElement;
+import com.vaadin.tests.tb3.SingleBrowserTest;
+
+public class BinderValidatorErrorLevelTest extends SingleBrowserTest {
+
+ @Test
+ public void testErrorLevelStyleNames() throws IOException {
+ openTestURL();
+
+ for (ErrorLevel l : ErrorLevel.values()) {
+ TextFieldElement textField = $(TextFieldElement.class)
+ .caption(l.name()).first();
+
+ // Screenshot the whole slot
+ compareScreen(textField.findElement(By.xpath("..")),
+ l.name().toLowerCase());
+
+ Assert.assertTrue("Error style for " + l.name() + " not present",
+ textField.getAttribute("class").contains(
+ "v-textfield-error-" + l.name().toLowerCase()));
+ textField.setValue("long enough text");
+ Assert.assertFalse("Error style for " + l.name() + " still present",
+ textField.getAttribute("class").contains(
+ "v-textfield-error-" + l.name().toLowerCase()));
+ textField.setValue("foo");
+ Assert.assertTrue(
+ "Error style for " + l.name() + " should be present again.",
+ textField.getAttribute("class").contains(
+ "v-textfield-error-" + l.name().toLowerCase()));
+ }
+ }
+}