Browse Source

Add ValueContext to Validators

Change-Id: I2ac99adf1fdb60dc0638e6fe98c4542ebd5f21a5
tags/8.0.0.alpha6
Teemu Suo-Anttila 7 years ago
parent
commit
524a69fd2d

+ 28
- 4
server/src/main/java/com/vaadin/data/Binder.java View File



@Override @Override
public Result<T> convertToModel(T value, ValueContext context) { public Result<T> convertToModel(T value, ValueContext context) {
Result<? super T> validationResult = validator.apply(value);
Result<? super T> validationResult = validator.apply(value,
context);
if (validationResult.isError()) { if (validationResult.isError()) {
return Result.error(validationResult.getMessage().get()); return Result.error(validationResult.getMessage().get());
} else { } else {
return this; return this;
} }


/**
* A convenience method to add a validator to this binder using the
* {@link Validator#from(SerializablePredicate, String)} 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 #save(Object)
* @see #saveIfValid(Object)
*
* @param predicate
* the predicate performing validation, not null
* @param message
* the error message to report in case validation failure
* @return this binder, for chaining
*/
public Binder<BEAN> withValidator(SerializablePredicate<BEAN> predicate,
String message) {
return withValidator(Validator.from(predicate, message));
}

/** /**
* Validates the values of all bound fields and returns the validation * Validates the values of all bound fields and returns the validation
* status. * status.
*/ */
private List<Result<?>> validateBean(BEAN bean) { private List<Result<?>> validateBean(BEAN bean) {
Objects.requireNonNull(bean, "bean cannot be null"); Objects.requireNonNull(bean, "bean cannot be null");
List<Result<?>> results = Collections.unmodifiableList(
validators.stream().map(validator -> validator.apply(bean))
.collect(Collectors.toList()));
List<Result<?>> results = Collections.unmodifiableList(validators
.stream()
.map(validator -> validator.apply(bean, new ValueContext()))
.collect(Collectors.toList()));
return results; return results;
} }



+ 9
- 31
server/src/main/java/com/vaadin/data/Validator.java View File



import java.io.Serializable; import java.io.Serializable;
import java.util.Objects; import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.BiFunction;


import com.vaadin.data.util.converter.ValueContext;
import com.vaadin.server.SerializablePredicate; import com.vaadin.server.SerializablePredicate;


/** /**
* @see Result * @see Result
*/ */
@FunctionalInterface @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&lt;Integer&gt; 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);
}
public interface Validator<T>
extends BiFunction<T, ValueContext, Result<T>>, Serializable {


/** /**
* Validates the given value. Returns a {@code Result} instance representing * Validates the given value. Returns a {@code Result} instance representing
* *
* @param value * @param value
* the input value to validate * the input value to validate
* @param context
* the value context for validation
* @return the validation result * @return the validation result
*/ */
@Override @Override
public Result<T> apply(T value);
public Result<T> apply(T value, ValueContext context);


/** /**
* Returns a validator that passes any value. * Returns a validator that passes any value.
* @return an always-passing validator * @return an always-passing validator
*/ */
public static <T> Validator<T> alwaysPass() { public static <T> Validator<T> alwaysPass() {
return v -> Result.ok(v);
return (v, ctx) -> Result.ok(v);
} }


/** /**
String errorMessage) { String errorMessage) {
Objects.requireNonNull(guard, "guard cannot be null"); Objects.requireNonNull(guard, "guard cannot be null");
Objects.requireNonNull(errorMessage, "errorMessage cannot be null"); Objects.requireNonNull(errorMessage, "errorMessage cannot be null");
return value -> {
return (value, context) -> {
try { try {
if (guard.test(value)) { if (guard.test(value)) {
return Result.ok(value); return Result.ok(value);

+ 1
- 1
server/src/main/java/com/vaadin/data/util/converter/ValueContext.java View File

* Constructor for {@code ValueContext} without a {@code Locale}. * Constructor for {@code ValueContext} without a {@code Locale}.
*/ */
public ValueContext() { public ValueContext() {
this.locale = null;
this.component = null; this.component = null;
this.locale = findLocale();
} }


/** /**

+ 2
- 1
server/src/main/java/com/vaadin/data/validator/BeanValidator.java View File

import com.vaadin.data.Result; import com.vaadin.data.Result;
import com.vaadin.data.Validator; import com.vaadin.data.Validator;
import com.vaadin.data.util.BeanUtil; import com.vaadin.data.util.BeanUtil;
import com.vaadin.data.util.converter.ValueContext;


/** /**
* A {@code Validator} using the JSR-303 (javax.validation) annotation-based * A {@code Validator} using the JSR-303 (javax.validation) annotation-based
* annotation or equivalent. * annotation or equivalent.
*/ */
@Override @Override
public Result<Object> apply(final Object value) {
public Result<Object> apply(final Object value, ValueContext context) {
Set<? extends ConstraintViolation<?>> violations = getJavaxBeanValidator() Set<? extends ConstraintViolation<?>> violations = getJavaxBeanValidator()
.validateValue(beanType, propertyName, value); .validateValue(beanType, propertyName, value);



+ 2
- 1
server/src/main/java/com/vaadin/data/validator/NotEmptyValidator.java View File

import com.vaadin.data.HasRequired; import com.vaadin.data.HasRequired;
import com.vaadin.data.Result; import com.vaadin.data.Result;
import com.vaadin.data.Validator; import com.vaadin.data.Validator;
import com.vaadin.data.util.converter.ValueContext;


/** /**
* Simple validator to check against {@code null} value and empty {@link String} * Simple validator to check against {@code null} value and empty {@link String}
} }


@Override @Override
public Result<T> apply(T value) {
public Result<T> apply(T value, ValueContext context) {
if (Objects.isNull(value) || Objects.equals(value, "")) { if (Objects.isNull(value) || Objects.equals(value, "")) {
return Result.error(message); return Result.error(message);
} else { } else {

+ 2
- 1
server/src/main/java/com/vaadin/data/validator/NotNullValidator.java View File

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


import com.vaadin.data.Result; import com.vaadin.data.Result;
import com.vaadin.data.util.converter.ValueContext;


/** /**
* This validator is used for validating properties that do not allow null * This validator is used for validating properties that do not allow null
} }


@Override @Override
public Result<String> apply(String value) {
public Result<String> apply(String value, ValueContext context) {
return Objects.isNull(value) ? Result.error(getMessage(value)) return Objects.isNull(value) ? Result.error(getMessage(value))
: Result.ok(value); : Result.ok(value);
} }

+ 2
- 1
server/src/main/java/com/vaadin/data/validator/RangeValidator.java View File

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


import com.vaadin.data.Result; import com.vaadin.data.Result;
import com.vaadin.data.util.converter.ValueContext;


/** /**
* Verifies that a value is within the given range. * Verifies that a value is within the given range.
* behavior depends on the used comparator. * behavior depends on the used comparator.
*/ */
@Override @Override
public Result<T> apply(T value) {
public Result<T> apply(T value, ValueContext context) {
return toResult(value, isValid(value)); return toResult(value, isValid(value));
} }



+ 2
- 1
server/src/main/java/com/vaadin/data/validator/RegexpValidator.java View File

import java.util.regex.Pattern; import java.util.regex.Pattern;


import com.vaadin.data.Result; import com.vaadin.data.Result;
import com.vaadin.data.util.converter.ValueContext;


/** /**
* A string validator comparing the string against a Java regular expression. * A string validator comparing the string against a Java regular expression.
} }


@Override @Override
public Result<String> apply(String value) {
public Result<String> apply(String value, ValueContext context) {
return toResult(value, isValid(value)); return toResult(value, isValid(value));
} }



+ 3
- 2
server/src/main/java/com/vaadin/data/validator/StringLengthValidator.java View File

package com.vaadin.data.validator; package com.vaadin.data.validator;


import com.vaadin.data.Result; import com.vaadin.data.Result;
import com.vaadin.data.util.converter.ValueContext;


/** /**
* Verifies that the length of a string is within the given range. * Verifies that the length of a string is within the given range.
} }


@Override @Override
public Result<String> apply(String value) {
public Result<String> apply(String value, ValueContext context) {
if (value == null) { if (value == null) {
return toResult(value, true); return toResult(value, true);
} }
Result<?> lengthCheck = validator.apply(value.length());
Result<?> lengthCheck = validator.apply(value.length(), context);
return toResult(value, !lengthCheck.isError()); return toResult(value, !lengthCheck.isError());
} }



+ 3
- 1
server/src/main/java/com/vaadin/ui/AbstractDateField.java View File

import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;


import com.vaadin.data.Result; import com.vaadin.data.Result;
import com.vaadin.data.util.converter.ValueContext;
import com.vaadin.data.validator.DateRangeValidator; import com.vaadin.data.validator.DateRangeValidator;
import com.vaadin.event.FieldEvents.BlurEvent; import com.vaadin.event.FieldEvents.BlurEvent;
import com.vaadin.event.FieldEvents.BlurListener; import com.vaadin.event.FieldEvents.BlurListener;
getDateOutOfRangeMessage(), getDateOutOfRangeMessage(),
getDate(getRangeStart(), getResolution()), getDate(getRangeStart(), getResolution()),
getDate(getRangeEnd(), getResolution())); getDate(getRangeEnd(), getResolution()));
Result<LocalDate> result = validator.apply(value);
Result<LocalDate> result = validator.apply(value,
new ValueContext(this));
if (result.isError()) { if (result.isError()) {
setComponentError(new UserError(getDateOutOfRangeMessage())); setComponentError(new UserError(getDateOutOfRangeMessage()));
} }

+ 5
- 10
server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java View File

binder.forField(yearOfBirth) binder.forField(yearOfBirth)
.withConverter(new StringToIntegerConverter("err")) .withConverter(new StringToIntegerConverter("err"))
.bind(BookPerson::getYearOfBirth, BookPerson::setYearOfBirth); .bind(BookPerson::getYearOfBirth, BookPerson::setYearOfBirth);
binder.withValidator(bean -> bean.yearOfBirth < 2000 ? Result.ok(bean)
: Result.error(message))
.withValidator(bean -> bean.yearOfBirth == 2000
? Result.error(message2) : Result.ok(bean));
binder.withValidator(bean -> bean.yearOfBirth < 2000, message)
.withValidator(bean -> bean.yearOfBirth != 2000, message2);


binder.setBean(p); binder.setBean(p);


BookPerson p = new BookPerson(1500, 12); BookPerson p = new BookPerson(1500, 12);
binder.forField(yearOfBirth) binder.forField(yearOfBirth)
.withConverter(new StringToIntegerConverter("err")) .withConverter(new StringToIntegerConverter("err"))
.withValidator(value -> value % 2 == 0 ? Result.ok(value)
: Result.error(bindingMessage))
.withValidator(value -> value % 2 == 0, bindingMessage)
.bind(BookPerson::getYearOfBirth, BookPerson::setYearOfBirth); .bind(BookPerson::getYearOfBirth, BookPerson::setYearOfBirth);
binder.withValidator(bean -> bean.yearOfBirth < 2000 ? Result.ok(bean)
: Result.error(message))
.withValidator(bean -> bean.yearOfBirth == 2000
? Result.error(message2) : Result.ok(bean));
binder.withValidator(bean -> bean.yearOfBirth < 2000, message)
.withValidator(bean -> bean.yearOfBirth != 2000, message2);


binder.setBean(p); binder.setBean(p);



+ 5
- 4
server/src/test/java/com/vaadin/data/BinderConverterValidatorTest.java View File



import com.vaadin.data.Binder.Binding; import com.vaadin.data.Binder.Binding;
import com.vaadin.data.util.converter.StringToIntegerConverter; import com.vaadin.data.util.converter.StringToIntegerConverter;
import com.vaadin.data.util.converter.ValueContext;
import com.vaadin.data.validator.NotEmptyValidator; import com.vaadin.data.validator.NotEmptyValidator;
import com.vaadin.server.AbstractErrorMessage; import com.vaadin.server.AbstractErrorMessage;
import com.vaadin.server.ErrorMessage; import com.vaadin.server.ErrorMessage;
String msg2 = "bar"; String msg2 = "bar";
binding.withValidator(new Validator<String>() { binding.withValidator(new Validator<String>() {
@Override @Override
public Result<String> apply(String value) {
public Result<String> apply(String value, ValueContext context) {
return new SimpleResult<>(null, msg1); return new SimpleResult<>(null, msg1);
} }
}); });
// validator for Number can be used on a Double // validator for Number can be used on a Double


TextField salaryField = new TextField(); TextField salaryField = new TextField();
Validator<Number> positiveNumberValidator = value -> {
Validator<Number> positiveNumberValidator = (value, context) -> {
if (value.doubleValue() >= 0) { if (value.doubleValue() >= 0) {
return Result.ok(value); return Result.ok(value);
} else { } else {
Binding<Person, String, String> binding = binder.forField(nameField) Binding<Person, String, String> binding = binder.forField(nameField)
.withValidator(notEmpty); .withValidator(notEmpty);
binding.bind(Person::getFirstName, Person::setFirstName); binding.bind(Person::getFirstName, Person::setFirstName);
binder.withValidator(bean -> bean.getFirstName().contains("error")
? Result.error("error") : Result.ok(bean));
binder.withValidator(bean -> !bean.getFirstName().contains("error"),
"error");
Person person = new Person(); Person person = new Person();
person.setFirstName(""); person.setFirstName("");
binder.setBean(person); binder.setBean(person);

+ 7
- 8
server/src/test/java/com/vaadin/data/BinderValidationStatusTest.java View File

/* /*
* Copyright 2000-2016 Vaadin Ltd. * Copyright 2000-2016 Vaadin Ltd.
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not * 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 * use this file except in compliance with the License. You may obtain a copy of
* the License at * the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
import com.vaadin.tests.data.bean.Person; import com.vaadin.tests.data.bean.Person;
import com.vaadin.ui.Label; import com.vaadin.ui.Label;


public class BinderValidationStatusTest extends
BinderTestBase<Binder<Person>, Person> {
public class BinderValidationStatusTest
extends BinderTestBase<Binder<Person>, Person> {


protected final static ValidationStatusHandler NOOP = event -> { protected final static ValidationStatusHandler NOOP = event -> {
}; };
"Using a custom status change handler so no change should end up here"); "Using a custom status change handler so no change should end up here");
}).bind(Person::getAge, Person::setAge); }).bind(Person::getAge, Person::setAge);
binder.withValidator( binder.withValidator(
bean -> !bean.getFirstName().isEmpty() && bean.getAge() > 0
? Result.ok(bean)
: Result.error("Need first name and age"));
bean -> !bean.getFirstName().isEmpty() && bean.getAge() > 0,
"Need first name and age");


binder.setValidationStatusHandler(r -> { binder.setValidationStatusHandler(r -> {
statusCapture.set(r); statusCapture.set(r);

+ 5
- 48
server/src/test/java/com/vaadin/data/ValidatorTest.java View File

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


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

/** /**
* @author Vaadin Ltd * @author Vaadin Ltd
* *
@Test @Test
public void alwaysPass() { public void alwaysPass() {
Validator<String> alwaysPass = Validator.alwaysPass(); Validator<String> alwaysPass = Validator.alwaysPass();
Result<String> result = alwaysPass.apply("foo");
Result<String> result = alwaysPass.apply("foo", new ValueContext());
Assert.assertTrue(result instanceof SimpleResult); Assert.assertTrue(result instanceof SimpleResult);
SimpleResult<String> implRes = (SimpleResult<String>) result; SimpleResult<String> implRes = (SimpleResult<String>) result;
Assert.assertFalse(implRes.getMessage().isPresent()); 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 @Test
public void from() { public void from() {
Validator<String> validator = Validator.from(Objects::nonNull, Validator<String> validator = Validator.from(Objects::nonNull,
"Cannot be null"); "Cannot be null");
Result<String> result = validator.apply(null);
Result<String> result = validator.apply(null, new ValueContext());
Assert.assertTrue(result.isError()); Assert.assertTrue(result.isError());


result = validator.apply("");
result = validator.apply("", new ValueContext());
Assert.assertFalse(result.isError()); Assert.assertFalse(result.isError());
} }
} }

+ 4
- 3
server/src/test/java/com/vaadin/data/validator/NotEmptyValidatorTest.java View File

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


import com.vaadin.data.Result; import com.vaadin.data.Result;
import com.vaadin.data.util.converter.ValueContext;


/** /**
* @author Vaadin Ltd * @author Vaadin Ltd
@Test @Test
public void nullValueIsDisallowed() { public void nullValueIsDisallowed() {
NotEmptyValidator<String> validator = new NotEmptyValidator<>("foo"); NotEmptyValidator<String> validator = new NotEmptyValidator<>("foo");
Result<String> result = validator.apply(null);
Result<String> result = validator.apply(null, new ValueContext());
Assert.assertTrue(result.isError()); Assert.assertTrue(result.isError());
Assert.assertEquals("foo", result.getMessage().get()); Assert.assertEquals("foo", result.getMessage().get());
} }
@Test @Test
public void emptyValueIsDisallowed() { public void emptyValueIsDisallowed() {
NotEmptyValidator<String> validator = new NotEmptyValidator<>("foo"); NotEmptyValidator<String> validator = new NotEmptyValidator<>("foo");
Result<String> result = validator.apply("");
Result<String> result = validator.apply("", new ValueContext());
Assert.assertTrue(result.isError()); Assert.assertTrue(result.isError());
Assert.assertEquals("foo", result.getMessage().get()); Assert.assertEquals("foo", result.getMessage().get());
} }
public void nonNullValueIsAllowed() { public void nonNullValueIsAllowed() {
NotEmptyValidator<Object> validator = new NotEmptyValidator<>("foo"); NotEmptyValidator<Object> validator = new NotEmptyValidator<>("foo");
Object value = new Object(); Object value = new Object();
Result<Object> result = validator.apply(value);
Result<Object> result = validator.apply(value, new ValueContext());
Assert.assertFalse(result.isError()); Assert.assertFalse(result.isError());
result.ifOk(val -> Assert.assertEquals(value, val)); result.ifOk(val -> Assert.assertEquals(value, val));
result.ifError(msg -> Assert.fail()); result.ifError(msg -> Assert.fail());

+ 3
- 2
server/src/test/java/com/vaadin/data/validator/NotNullValidatorTest.java View File

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


import com.vaadin.data.Result; import com.vaadin.data.Result;
import com.vaadin.data.util.converter.ValueContext;


public class NotNullValidatorTest { public class NotNullValidatorTest {


@Test @Test
public void nullValueIsDisallowed() { public void nullValueIsDisallowed() {
NotNullValidator validator = new NotNullValidator("foo"); NotNullValidator validator = new NotNullValidator("foo");
Result<String> result = validator.apply(null);
Result<String> result = validator.apply(null, new ValueContext());
Assert.assertTrue(result.isError()); Assert.assertTrue(result.isError());
Assert.assertEquals("foo", result.getMessage().get()); Assert.assertEquals("foo", result.getMessage().get());
} }
@Test @Test
public void nonNullValueIsAllowed() { public void nonNullValueIsAllowed() {
NotNullValidator validator = new NotNullValidator("foo"); NotNullValidator validator = new NotNullValidator("foo");
Result<String> result = validator.apply("bar");
Result<String> result = validator.apply("bar", new ValueContext());
Assert.assertFalse(result.isError()); Assert.assertFalse(result.isError());
result.ifOk(value -> Assert.assertEquals("bar", value)); result.ifOk(value -> Assert.assertEquals("bar", value));
result.ifError(msg -> Assert.fail()); result.ifError(msg -> Assert.fail());

+ 5
- 3
server/src/test/java/com/vaadin/data/validator/ValidatorTestBase.java View File

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


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


public class ValidatorTestBase { public class ValidatorTestBase {


protected <T> void assertPasses(T value, Validator<? super T> v) { protected <T> void assertPasses(T value, Validator<? super T> v) {
v.apply(value).handle(val -> Assert.assertEquals(value, val),
err -> Assert
v.apply(value, new ValueContext())
.handle(val -> Assert.assertEquals(value, val), err -> Assert
.fail(value + " should pass " + v + " but got " + err)); .fail(value + " should pass " + v + " but got " + err));
} }


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



Loading…
Cancel
Save