diff options
author | Johannes Dahlström <johannesd@vaadin.com> | 2016-07-29 00:51:43 +0300 |
---|---|---|
committer | Ilia Motornyi <elmot@vaadin.com> | 2016-08-03 13:13:00 +0000 |
commit | 3b4753a85b8489ee4d86ec1ecb947bf836db246d (patch) | |
tree | 5aa656ce0500105e284e8ef2557c5bcb842c3403 | |
parent | 895da2253630a9b44dc151400afe034c8589215d (diff) | |
download | vaadin-framework-3b4753a85b8489ee4d86ec1ecb947bf836db246d.tar.gz vaadin-framework-3b4753a85b8489ee4d86ec1ecb947bf836db246d.zip |
Implement new String validators
Change-Id: I893af1f426d04674269860871fc42fb71e5a9188
11 files changed, 523 insertions, 34 deletions
diff --git a/server/src/main/java/com/vaadin/tokka/data/Validator.java b/server/src/main/java/com/vaadin/tokka/data/Validator.java index 3c6fc522dd..884737de11 100644 --- a/server/src/main/java/com/vaadin/tokka/data/Validator.java +++ b/server/src/main/java/com/vaadin/tokka/data/Validator.java @@ -93,7 +93,9 @@ public interface Validator<T> extends Function<T, Result<T>>, Serializable { /** * Validates the given value. Returns a {@code Result} instance representing - * the outcome of the validation. + * the outcome of the validation: either {@link Result#ok(Object) Result.ok} + * if the value passed validation or {@link Result#error(String) + * Result.error} otherwise. * * @param value * the input value to validate diff --git a/server/src/main/java/com/vaadin/tokka/data/util/Result.java b/server/src/main/java/com/vaadin/tokka/data/util/Result.java index 7792c447e5..4e6b880072 100644 --- a/server/src/main/java/com/vaadin/tokka/data/util/Result.java +++ b/server/src/main/java/com/vaadin/tokka/data/util/Result.java @@ -62,7 +62,7 @@ public interface Result<R> extends Serializable { */ public static <R> Result<R> error(String message) { Objects.requireNonNull(message, "message cannot be null"); - return new ResultImpl<R>(null, message); + return new ResultImpl<>(null, message); } /** @@ -141,16 +141,38 @@ public interface Result<R> extends Serializable { */ public void handle(Consumer<R> ifOk, Consumer<String> ifError); + /** + * If this result denotes a success, invokes the given consumer with the + * carried value, otherwise does nothing. + * + * @param consumer + * the function to call if success + */ public default void ifOk(Consumer<R> consumer) { handle(consumer, error -> { }); } + /** + * If this result denotes a failure, invokes the given consumer with the + * carried error message, otherwise does nothing. + * + * @param consumer + * the function to call if failure + */ public default void ifError(Consumer<String> consumer) { handle(value -> { }, consumer); } + /** + * Returns whether this result represents a successful outcome. + * + * @return true if this result is successful, false otherwise + * + * @see #ok(Object) + * @see #ifOk(Consumer) + */ public boolean isOk(); /** diff --git a/server/src/main/java/com/vaadin/tokka/data/validators/EmailValidator.java b/server/src/main/java/com/vaadin/tokka/data/validators/EmailValidator.java new file mode 100644 index 0000000000..ffd6fb3a2e --- /dev/null +++ b/server/src/main/java/com/vaadin/tokka/data/validators/EmailValidator.java @@ -0,0 +1,47 @@ +/* + * 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.tokka.data.validators; + +/** + * A string validator for e-mail addresses. The e-mail address syntax is not + * complete according to RFC 822 but handles the vast majority of valid e-mail + * addresses correctly. + * + * @author Vaadin Ltd. + * @since + */ +@SuppressWarnings("serial") +public class EmailValidator extends RegexpValidator { + + private static final String PATTERN = "^" + + "([a-zA-Z0-9_\\.\\-+])+" // local + + "@" + + "[a-zA-Z0-9-.]+" // domain + + "\\." + + "[a-zA-Z0-9-]{2,}" // tld + + "$"; + + /** + * Creates a validator for checking that a string is a syntactically valid + * e-mail address. + * + * @param errorMessage + * the message to display in case the value does not validate. + */ + public EmailValidator(String errorMessage) { + super(errorMessage, PATTERN, true); + } +} diff --git a/server/src/main/java/com/vaadin/tokka/data/validators/RegexpValidator.java b/server/src/main/java/com/vaadin/tokka/data/validators/RegexpValidator.java new file mode 100644 index 0000000000..0350806720 --- /dev/null +++ b/server/src/main/java/com/vaadin/tokka/data/validators/RegexpValidator.java @@ -0,0 +1,111 @@ +/* + * 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.tokka.data.validators; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.vaadin.tokka.data.util.Result; + +/** + * A string validator comparing the string against a Java regular expression. + * Both complete matches and substring matches are supported. + * <p> + * For the Java regular expression syntax, see {@link java.util.regex.Pattern}. + * + * @author Vaadin Ltd. + * @since + */ +@SuppressWarnings("serial") +public class RegexpValidator extends AbstractValidator<String> { + + private Pattern pattern; + private boolean complete; + private transient Matcher matcher = null; + + /** + * Creates a validator for checking that the regular expression matches the + * complete string to validate. + * + * @param errorMessage + * the message to display in case the value does not validate. + * @param regexp + * a Java regular expression + */ + public RegexpValidator(String errorMessage, String regexp) { + this(errorMessage, regexp, true); + } + + /** + * Creates a validator for checking that the regular expression matches the + * string to validate. + * + * @param regexp + * a Java regular expression + * @param complete + * true to use check for a complete match, false to look for a + * matching substring + * @param errorMessage + * the message to display in case the value does not validate. + */ + public RegexpValidator(String errorMessage, String regexp, + boolean complete) { + super(errorMessage); + pattern = Pattern.compile(regexp); + this.complete = complete; + } + + @Override + public Result<String> apply(String value) { + return toResult(value, isValid(value)); + } + + @Override + public String toString() { + return "RegexpValidator[" + pattern + "]"; + } + + /** + * Returns whether the given string matches the regular expression. + * + * @param value + * the string to match + * @return true if the string matched, false otherwise + */ + protected boolean isValid(String value) { + if (complete) { + return getMatcher(value).matches(); + } else { + return getMatcher(value).find(); + } + } + + /** + * Returns a new or reused matcher for the pattern. + * + * @param value + * the string to find matches in + * @return a matcher for the string + */ + private Matcher getMatcher(String value) { + if (matcher == null) { + matcher = pattern.matcher(value); + } else { + matcher.reset(value); + } + return matcher; + } +} diff --git a/server/src/main/java/com/vaadin/tokka/data/validators/StringLengthValidator.java b/server/src/main/java/com/vaadin/tokka/data/validators/StringLengthValidator.java new file mode 100644 index 0000000000..affb71fff8 --- /dev/null +++ b/server/src/main/java/com/vaadin/tokka/data/validators/StringLengthValidator.java @@ -0,0 +1,101 @@ +/* + * 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.tokka.data.validators; + +import com.vaadin.tokka.data.util.Result; + +/** + * Verifies that the length of a string is within the given range. + * + * @author Vaadin Ltd. + * @since + */ +@SuppressWarnings("serial") +public class StringLengthValidator extends AbstractValidator<String> { + + private RangeValidator<Integer> validator; + + /** + * Creates a new StringLengthValidator with a given error message and + * minimum and maximum length limits. + * + * @param errorMessage + * the error message to return if validation fails + * @param minLength + * the minimum permissible length of the string or null for no + * limit. + * @param maxLength + * the maximum permissible length of the string or null for no + * limit. + */ + public StringLengthValidator(String errorMessage, Integer minLength, + Integer maxLength) { + super(errorMessage); + validator = RangeValidator.of(errorMessage, minLength, maxLength); + } + + @Override + public Result<String> apply(String value) { + Result<?> lengthCheck = validator.apply(value.length()); + return toResult(value, lengthCheck.isOk()); + } + + /** + * Gets the maximum permissible length of the string. + * + * @return the maximum length of the string or null if there is no limit + */ + public Integer getMaxLength() { + return validator.getMaxValue(); + } + + /** + * Gets the minimum permissible length of the string. + * + * @return the minimum length of the string or null if there is no limit + */ + public Integer getMinLength() { + return validator.getMinValue(); + } + + /** + * Sets the maximum permissible length of the string. + * + * @param maxLength + * the maximum length to accept or null for no limit + */ + public void setMaxLength(Integer maxLength) { + validator.setMaxValue(maxLength); + } + + /** + * Sets the minimum permissible length. + * + * @param minLength + * the minimum length to accept or null for no limit + */ + public void setMinLength(Integer minLength) { + validator.setMaxValue(minLength); + } + + @Override + public String toString() { + return String.format("%s[%d, %d]", getClass().getSimpleName(), + getMinLength(), getMaxLength()); + } + +} diff --git a/server/src/test/java/com/vaadin/tokka/data/validators/BeanValidatorTest.java b/server/src/test/java/com/vaadin/tokka/data/validators/BeanValidatorTest.java index 0e144102b9..48931c3207 100644 --- a/server/src/test/java/com/vaadin/tokka/data/validators/BeanValidatorTest.java +++ b/server/src/test/java/com/vaadin/tokka/data/validators/BeanValidatorTest.java @@ -1,8 +1,5 @@ package com.vaadin.tokka.data.validators; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - import java.util.Calendar; import java.util.Locale; @@ -11,7 +8,7 @@ import org.junit.Test; import com.vaadin.tests.data.bean.Address; import com.vaadin.tests.data.bean.BeanToValidate; -public class BeanValidatorTest { +public class BeanValidatorTest extends ValidatorTestBase { @Test public void testFirstNameNullFails() { @@ -74,16 +71,4 @@ public class BeanValidatorTest { private BeanValidator validator(String propertyName) { return new BeanValidator(BeanToValidate.class, propertyName); } - - private <T> void assertPasses(T value, BeanValidator v) { - v.apply(value).handle( - val -> assertEquals(value, val), - err -> fail(value + " should pass " + v + " but got " + err)); - } - - private <T> void assertFails(T value, String error, BeanValidator v) { - v.apply(value).handle( - val -> fail(value + " should fail " + v), - err -> assertEquals(error, err)); - } } diff --git a/server/src/test/java/com/vaadin/tokka/data/validators/EmailValidatorTest.java b/server/src/test/java/com/vaadin/tokka/data/validators/EmailValidatorTest.java new file mode 100644 index 0000000000..e98b966cc8 --- /dev/null +++ b/server/src/test/java/com/vaadin/tokka/data/validators/EmailValidatorTest.java @@ -0,0 +1,98 @@ +package com.vaadin.tokka.data.validators; + +import org.junit.Test; + +public class EmailValidatorTest extends ValidatorTestBase { + + @Test(expected = NullPointerException.class) + public void testNullStringFails() { + validator("null should throw").apply(null); + } + + @Test + public void testEmptyStringFails() { + assertFails("", validator("empty string not allowed")); + } + + @Test + public void testStringWithoutAtSignFails() { + assertFails("johannesd.vaadin", validator("@ is required")); + } + + @Test + public void testMissingLocalPartFails() { + RegexpValidator v = validator("local part is required"); + assertFails("@localhost", v); + assertFails(" @localhost", v); + } + + @Test + public void testNonAsciiEmailFails() { + RegexpValidator v = validator("accented letters not allowed"); + assertFails("jöhännes@vaadin.com", v); + assertFails("johannes@váádìn.com", v); + assertFails("johannes@vaadin.cõm", v); + } + + @Test + public void testLocalPartWithPunctuationPasses() { + RegexpValidator v = shouldNotFail(); + assertPasses("johannesd+test@vaadin.com", v); + assertPasses("johannes.dahlstrom@vaadin.com", v); + assertPasses("johannes_d@vaadin.com", v); + } + + @Test + public void testEmailWithoutDomainPartFails() { + assertFails("johannesd@", validator("domain part is required")); + } + + @Test + public void testComplexDomainPasses() { + assertPasses("johannesd@foo.bar.baz.vaadin.com", shouldNotFail()); + } + + @Test + public void testDomainWithPunctuationPasses() { + assertPasses("johannesd@vaadin-dev.com", shouldNotFail()); + } + + @Test + public void testMissingTldFails() { + assertFails("johannesd@localhost", validator("tld is required")); + } + + @Test + public void testOneLetterTldFails() { + assertFails("johannesd@vaadin.f", + validator("one-letter tld not allowed")); + } + + @Test + public void testLongTldPasses() { + assertPasses("joonas@vaadin.management", shouldNotFail()); + } + + @Test + public void testIdnTldPasses() { + assertPasses("leif@vaadin.XN--VERMGENSBERATER-CTB", shouldNotFail()); + } + + @Test + public void testYelledEmailPasses() { + assertPasses("JOHANNESD@VAADIN.COM", shouldNotFail()); + } + + @Test + public void testEmailWithDigitsPasses() { + assertPasses("johannes84@v44d1n.com", shouldNotFail()); + } + + private EmailValidator validator(String errorMessage) { + return new EmailValidator(errorMessage); + } + + private EmailValidator shouldNotFail() { + return validator("this should not fail"); + } +} diff --git a/server/src/test/java/com/vaadin/tokka/data/validators/RangeValidatorTest.java b/server/src/test/java/com/vaadin/tokka/data/validators/RangeValidatorTest.java index f5ac670472..02a1b1859c 100644 --- a/server/src/test/java/com/vaadin/tokka/data/validators/RangeValidatorTest.java +++ b/server/src/test/java/com/vaadin/tokka/data/validators/RangeValidatorTest.java @@ -1,13 +1,10 @@ package com.vaadin.tokka.data.validators; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - import java.time.LocalDate; import org.junit.Test; -public class RangeValidatorTest { +public class RangeValidatorTest extends ValidatorTestBase { @Test public void testIntegerRangeValidIntPasses() { @@ -104,16 +101,4 @@ public class RangeValidatorTest { assertPasses(LocalDate.of(2016, 7, 31), v); assertFails(LocalDate.ofEpochDay(1_000_000_000), v); } - - private <T> void assertPasses(T value, RangeValidator<T> v) { - v.apply(value).handle( - val -> assertEquals(value, val), - err -> fail(value + " should pass " + v + " but got " + err)); - } - - private <T> void assertFails(T value, RangeValidator<T> v) { - v.apply(value).handle( - val -> fail(value + " should fail " + v), - err -> assertEquals(v.getMessage(value), err)); - } } diff --git a/server/src/test/java/com/vaadin/tokka/data/validators/RegexpValidatorTest.java b/server/src/test/java/com/vaadin/tokka/data/validators/RegexpValidatorTest.java new file mode 100644 index 0000000000..1deca3f2f2 --- /dev/null +++ b/server/src/test/java/com/vaadin/tokka/data/validators/RegexpValidatorTest.java @@ -0,0 +1,71 @@ +package com.vaadin.tokka.data.validators; + +import org.junit.Test; + +public class RegexpValidatorTest extends ValidatorTestBase { + + @Test(expected = NullPointerException.class) + public void testNullStringFails() { + new RegexpValidator("Should be 'abc'", "abc").apply(null); + } + + @Test + public void testEmptyPatternMatchesEmptyString() { + assertPasses("", new RegexpValidator("Should be empty", "", true)); + } + + @Test + public void testEmptyPatternDoesNotMatchNonEmptyString() { + assertFails("x", new RegexpValidator("Should be empty", "", true)); + } + + @Test + public void testPatternMatchesString() { + RegexpValidator v = new RegexpValidator( + "Should be foo and bar repeating", "(foo|bar)+", true); + + assertPasses("foo", v); + assertPasses("barfoo", v); + assertPasses("foobarbarbarfoobarfoofoobarbarfoofoofoobar", v); + } + + @Test + public void testPatternDoesNotMatchString() { + RegexpValidator v = new RegexpValidator( + "Should be foo and bar repeating", "(foo|bar)+", true); + + assertFails("", v); + assertFails("barf", v); + assertFails(" bar", v); + assertFails("foobarbarbarfoobar.foofoobarbarfoofoofoobar", v); + } + + @Test + public void testEmptyPatternFoundInAnyString() { + RegexpValidator v = new RegexpValidator("Should always pass", "", + false); + + assertPasses("", v); + assertPasses(" ", v); + assertPasses("qwertyuiopasdfghjklzxcvbnm", v); + } + + @Test + public void testPatternFoundInString() { + RegexpValidator v = new RegexpValidator("Should contain a number", + "\\d+", false); + + assertPasses("0", v); + assertPasses(" 123 ", v); + assertPasses("qwerty9iop", v); + } + + @Test + public void testPatternNotFoundInString() { + RegexpValidator v = new RegexpValidator("Should contain a number", + "\\d+", false); + + assertFails("", v); + assertFails("qwertyiop", v); + } +} diff --git a/server/src/test/java/com/vaadin/tokka/data/validators/StringLengthValidatorTest.java b/server/src/test/java/com/vaadin/tokka/data/validators/StringLengthValidatorTest.java new file mode 100644 index 0000000000..048277bbd7 --- /dev/null +++ b/server/src/test/java/com/vaadin/tokka/data/validators/StringLengthValidatorTest.java @@ -0,0 +1,41 @@ +package com.vaadin.tokka.data.validators; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.Test; + +public class StringLengthValidatorTest extends ValidatorTestBase { + + private static String LONG_STRING = Stream.generate(() -> "x").limit(1000) + .collect(Collectors.joining()); + + @Test(expected = NullPointerException.class) + public void testNullStringFails() { + new StringLengthValidator("", 0, 10).apply(null); + } + + @Test + public void testMaxLengthTooLongStringFails() { + assertFails(LONG_STRING, new StringLengthValidator( + "Should be at most 10", null, 10)); + } + + @Test + public void testMaxLengthStringPasses() { + assertPasses(LONG_STRING, new StringLengthValidator( + "Should be at most 1000", null, 1000)); + } + + @Test + public void testMinLengthEmptyStringFails() { + assertFails("", new StringLengthValidator("Should be at least 1", 1, + null)); + } + + @Test + public void testMinLengthStringPasses() { + assertPasses("å", new StringLengthValidator("Should be at least 1", 1, + null)); + } +} diff --git a/server/src/test/java/com/vaadin/tokka/data/validators/ValidatorTestBase.java b/server/src/test/java/com/vaadin/tokka/data/validators/ValidatorTestBase.java new file mode 100644 index 0000000000..6546dad76f --- /dev/null +++ b/server/src/test/java/com/vaadin/tokka/data/validators/ValidatorTestBase.java @@ -0,0 +1,26 @@ +package com.vaadin.tokka.data.validators; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import com.vaadin.tokka.data.Validator; + +public class ValidatorTestBase { + + protected <T> void assertPasses(T value, Validator<? super T> v) { + v.apply(value).handle( + val -> assertEquals(value, val), + err -> fail(value + " should pass " + v + " but got " + err)); + } + + protected <T> void assertFails(T value, String errorMessage, + Validator<? super T> v) { + v.apply(value).handle( + val -> fail(value + " should fail " + v), + err -> assertEquals(errorMessage, err)); + } + + protected <T> void assertFails(T value, AbstractValidator<? super T> v) { + assertFails(value, v.getMessage(value), v); + } +} |