Skip to content

Commit

Permalink
Add error message provider to provide translations
Browse files Browse the repository at this point in the history
Change-Id: I657535d377c471369e8c77fa1db946c490023939
  • Loading branch information
Teemu Suo-Anttila committed Nov 1, 2016
1 parent 48c249a commit 855ec0f
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 7 deletions.
65 changes: 63 additions & 2 deletions server/src/main/java/com/vaadin/data/Binder.java
Expand Up @@ -149,6 +149,9 @@ public void bind(SerializableFunction<BEAN, TARGET> getter,
* property. If any validator returns a failure, the property value is
* not updated.
*
* @see #withValidator(SerializablePredicate, String)
* @see #withValidator(SerializablePredicate, ErrorMessageProvider)
*
* @param validator
* the validator to add, not null
* @return this binding, for chaining
Expand All @@ -167,6 +170,7 @@ public Binding<BEAN, FIELDVALUE, TARGET> withValidator(
* failure, the property value is not updated.
*
* @see #withValidator(Validator)
* @see #withValidator(SerializablePredicate, ErrorMessageProvider)
* @see Validator#from(SerializablePredicate, String)
*
* @param predicate
Expand All @@ -183,6 +187,34 @@ public default Binding<BEAN, FIELDVALUE, TARGET> withValidator(
return withValidator(Validator.from(predicate, message));
}

/**
* A convenience method to add a validator to this binding using the
* {@link Validator#from(SerializablePredicate, ErrorMessageProvider)}
* 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 Validator#from(SerializablePredicate, ErrorMessageProvider)
*
* @param predicate
* the predicate performing validation, not null
* @param errorMessageProvider
* the provider to generate error messages, not null
* @return this binding, for chaining
* @throws IllegalStateException
* if {@code bind} has already been called
*/
public default Binding<BEAN, FIELDVALUE, TARGET> withValidator(
SerializablePredicate<? super TARGET> predicate,
ErrorMessageProvider errorMessageProvider) {
return withValidator(
Validator.from(predicate, errorMessageProvider));
}

/**
* Maps the binding to another data type using the given
* {@link Converter}.
Expand Down Expand Up @@ -1062,6 +1094,8 @@ private BinderValidationStatus<BEAN> doWriteIfValid(BEAN bean) {
*
* @see #writeBean(Object)
* @see #writeBeanIfValid(Object)
* @see #withValidator(SerializablePredicate, String)
* @see #withValidator(SerializablePredicate, ErrorMessageProvider)
*
* @param validator
* the validator to add, not null
Expand All @@ -1081,8 +1115,10 @@ public Binder<BEAN> withValidator(Validator<? super BEAN> validator) {
* updated. If the validators fail, the bean instance is reverted to its
* previous state.
*
* @see #save(Object)
* @see #saveIfValid(Object)
* @see #writeBean(Object)
* @see #writeBeanIfValid(Object)
* @see #withValidator(Validator)
* @see #withValidator(SerializablePredicate, ErrorMessageProvider)
*
* @param predicate
* the predicate performing validation, not null
Expand All @@ -1095,6 +1131,31 @@ public Binder<BEAN> withValidator(SerializablePredicate<BEAN> predicate,
return withValidator(Validator.from(predicate, message));
}

/**
* A convenience method to add a validator to this binder using the
* {@link Validator#from(SerializablePredicate, ErrorMessageProvider)}
* factory method.
* <p>
* Bean level validators are applied on the bean instance after the bean is
* updated. If the validators fail, the bean instance is reverted to its
* previous state.
*
* @see #writeBean(Object)
* @see #writeBeanIfValid(Object)
* @see #withValidator(Validator)
* @see #withValidator(SerializablePredicate, String)
*
* @param predicate
* the predicate performing validation, not null
* @param errorMessageProvider
* the provider to generate error messages, not null
* @return this binder, for chaining
*/
public Binder<BEAN> withValidator(SerializablePredicate<BEAN> predicate,
ErrorMessageProvider errorMessageProvider) {
return withValidator(Validator.from(predicate, errorMessageProvider));
}

/**
* Validates the values of all bound fields and returns the validation
* status.
Expand Down
42 changes: 42 additions & 0 deletions server/src/main/java/com/vaadin/data/ErrorMessageProvider.java
@@ -0,0 +1,42 @@
/*
* 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 com.vaadin.data.util.converter.ValueContext;
import com.vaadin.server.SerializableFunction;

/**
* Provider interface for generating localizable error messages using
* {@link ValueContext}.
*
* @since
* @author Vaadin Ltd.
*/
@FunctionalInterface
public interface ErrorMessageProvider
extends SerializableFunction<ValueContext, String> {

/**
* Returns a generated error message for given {@code ValueContext}.
*
* @param context
* the value context
*
* @return generated error message
*/
@Override
public String apply(ValueContext context);
}
26 changes: 24 additions & 2 deletions server/src/main/java/com/vaadin/data/Validator.java
Expand Up @@ -101,15 +101,37 @@ 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
* 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
* @return the new validator using the function
*/
public static <T> Validator<T> from(SerializablePredicate<T> guard,
ErrorMessageProvider errorMessageProvider) {
Objects.requireNonNull(guard, "guard cannot be null");
Objects.requireNonNull(errorMessageProvider,
"errorMessageProvider cannot be null");
return (value, context) -> {
try {
if (guard.test(value)) {
return Result.ok(value);
} else {
return Result.error(errorMessage);
return Result.error(errorMessageProvider.apply(context));
}
} catch (Exception e) {
return Result.error(errorMessage);
return Result.error(errorMessageProvider.apply(context));
}
};
}
Expand Down
30 changes: 29 additions & 1 deletion server/src/test/java/com/vaadin/data/ValidatorTest.java
Expand Up @@ -15,18 +15,20 @@
*/
package com.vaadin.data;

import java.util.Locale;
import java.util.Objects;

import org.junit.Assert;
import org.junit.Test;

import com.vaadin.data.util.converter.ValueContext;
import com.vaadin.data.validator.ValidatorTestBase;

/**
* @author Vaadin Ltd
*
*/
public class ValidatorTest {
public class ValidatorTest extends ValidatorTestBase {

@Test
public void alwaysPass() {
Expand All @@ -47,4 +49,30 @@ public void from() {
result = validator.apply("", new ValueContext());
Assert.assertFalse(result.isError());
}

@Test
public void withValidator_customErrorMessageProvider() {
String finnishError = "Käyttäjän tulee olla täysi-ikäinen";
String englishError = "The user must be an adult";
String notTranslatableError = "NOT TRANSLATABLE";

Validator<Integer> ageValidator = Validator.from(age -> age >= 18,
ctx -> {
Locale locale = ctx.getLocale().orElse(Locale.ENGLISH);

if (locale.getLanguage().equals("fi")) {
return finnishError;
} else if (locale.getLanguage().equals("en")) {
return englishError;
}
return notTranslatableError;
});

setLocale(Locale.ENGLISH);
assertFails(17, englishError, ageValidator);
setLocale(new Locale("fi", "FI"));
assertFails(17, finnishError, ageValidator);
setLocale(Locale.GERMAN);
assertFails(17, notTranslatableError, ageValidator);
}
}
@@ -1,12 +1,23 @@
package com.vaadin.data.validator;

import java.util.Locale;

import org.junit.Assert;
import org.junit.Before;

import com.vaadin.data.Validator;
import com.vaadin.data.util.converter.ValueContext;
import com.vaadin.ui.Label;

public class ValidatorTestBase {

private Label localeContext;

@Before
public void setUp() {
localeContext = new Label();
}

protected <T> void assertPasses(T value, Validator<? super T> v) {
v.apply(value, new ValueContext())
.handle(val -> Assert.assertEquals(value, val), err -> Assert
Expand All @@ -15,12 +26,16 @@ protected <T> void assertPasses(T value, Validator<? super T> v) {

protected <T> void assertFails(T value, String errorMessage,
Validator<? super T> v) {
v.apply(value, new ValueContext()).handle(
v.apply(value, new ValueContext(localeContext)).handle(
val -> Assert.fail(value + " should fail " + v),
err -> Assert.assertEquals(errorMessage, err));
}

protected <T> void assertFails(T value, AbstractValidator<? super T> v) {
assertFails(value, v.getMessage(value), v);
}
}

protected void setLocale(Locale locale) {
localeContext.setLocale(locale);
}
}

0 comments on commit 855ec0f

Please sign in to comment.