Проверка данных в приложении введённых пользователем или полученных другим путём в классическом понимании подразумевает использование всего лишь двух выражений в коде: TRUE и FALSE. В другом варианте используют исключения которые явно не предназначены для этого. Есть ли вариант получше?
Проверкой занимаются так называемые Валидаторы(которые являются лишь частью всего процесса проверки данных). В статье Серверная валидация пользовательских данных приводится интересный вариант реализации валидатора, но есть несколько нюансов в виде локализации сообщений и самого формата ошибок.
Рассмотрим сначала формат ошибок.
Предлагаемый подход состоит в том, чтобы метод валидатора, проверяющий данные, возвращал коллекцию(массив, список и т.д.) строк вместо булевых значений или бросания исключений. Такой формат будет более гибким и информативным.
Приведу пример на Java:
Как здесь видно все методы возвращают коллекцию строк.
Пример Unit теста:
А теперь рассмотрим локализацию сообщений ошибок.
Пример снова на Java:
Файлы локализаций лежат в src/main/resources.
errors_ru.properties:
errors.properties:
Надеюсь, что и другие программисты, пишушие на своих языках, найдут такой подход практичным и удобным и смогут его применить у себя.
P.S.
Вариант метода аутентификации пользователя, который возвращает реализацию интерфейса java.util.Map.Entry<K, V>, и может содержать в себе как объект пользователя так и данные об ошибках в виде строки(такой подход также может использоваться для избежания возврата Null из метода когда ожидается объект пользователя):
Есть, конечно, замечательная статья на хабре про валидацию в Java, но подход который там предлагается может использоваться далеко не во всех случаях и основан на аннотациях и исключениях.
В окончание статьи пару полезных ссылок про тестирование:
Проверкой занимаются так называемые Валидаторы(которые являются лишь частью всего процесса проверки данных). В статье Серверная валидация пользовательских данных приводится интересный вариант реализации валидатора, но есть несколько нюансов в виде локализации сообщений и самого формата ошибок.
Рассмотрим сначала формат ошибок.
Предлагаемый подход состоит в том, чтобы метод валидатора, проверяющий данные, возвращал коллекцию(массив, список и т.д.) строк вместо булевых значений или бросания исключений. Такой формат будет более гибким и информативным.
Приведу пример на Java:
import java.util.ArrayList;
import java.util.Collection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public interface ValidateUser {
String OK = "OK:";
String FAIL= "FAIL:";
Collection<String> apply(String username, String password, String email);
default Collection<String> passwordValidate(String password){
var result = new ArrayList<String>(1);
int size = password.trim().length();
if(size < 3 || size > 20) result.add("Password error:too short value or too long name. Password must be greater or 3 characters and smaller then 20 simbols.");
return result;
}
default Collection<String> usernameValidate(String name){
var result = new ArrayList<String>(1);
int size = name.trim().length();
if(size < 3 || size > 30) result.add("Username error:too short or too long name. Name must be greater or 3 characters and smaller then 30 simbols.");
return result;
}
default Collection<String> emailValidate(String email){
var result = new ArrayList<String>(1);
String regex = "^[\\w!#$%&'*+/=?`{|}~^-]+(?:\\.[\\w!#$%&'*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(email);
if (!matcher.find()) result.add("Email error:" + email + " is not valid");
return result;
}
class Default implements ValidateUser{
@Override
public Collection<String> apply(String username, String password,
String email) {
var errors = passwordValidate(password.trim());
errors.addAll(usernameValidate(username.trim()));
errors.addAll(emailValidate(email.trim()));
return errors;
}
}
}
Как здесь видно все методы возвращают коллекцию строк.
Пример Unit теста:
@Test
void validateUserTest() {
var validate = new ValidateUser2.Default();
var result = validate.apply("aaa", "qwe", "aaa@mail.ru");
assertTrue(result.isEmpty());
result = validate.apply("aaa", "qwe", "");
assertFalse(result.isEmpty());
assertEquals(1, result.size());
result = validate.apply("aa", "qwe", "aaa@mail.ru");
assertFalse(result.isEmpty());
assertEquals(1, result.size());
result = validate.apply("aaa", "qwe", "@mail.qweqwe");
assertFalse(result.isEmpty());
assertEquals(1, result.size());
result = validate.apply("aa", "qw", "");
assertFalse(result.isEmpty());
assertEquals(3, result.size());
}
А теперь рассмотрим локализацию сообщений ошибок.
Пример снова на Java:
public interface LocalizedValidation {
String OK = "OK:";
String FAIL= "FAIL:";
Collection<String> apply(String username, String password, String email, Locale locale);
default Collection<String> passwordValidate(String password, ResourceBundle bundle){
var result = new ArrayList<String>(1);
int size = password.trim().length();
if(size < 3 || size > 20) result.add(bundle.getString("password"));
return result;
}
default Collection<String> usernameValidate(String name, ResourceBundle bundle){
var result = new ArrayList<String>(1);
int size = name.trim().length();
if(size < 3 || size > 30) result.add(bundle.getString("username"));
return result;
}
default Collection<String> emailValidate(String email, ResourceBundle bundle){
var result = new ArrayList<String>(1);
String regex = "^[\\w!#$%&'*+/=?`{|}~^-]+(?:\\.[\\w!#$%&'*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(email);
if (!matcher.find()) result.add(bundle.getString("username")+email);
return result;
}
class Default implements LocalizedValidation{
@Override
public Collection<String> apply(String username, String password,
String email, Locale locale) {
ResourceBundle bundle = ResourceBundle.getBundle("errors", locale);
var errors = passwordValidate(password.trim(), bundle);
errors.addAll(usernameValidate(username.trim(), bundle));
errors.addAll(emailValidate(email.trim(), bundle));
return errors;
}
}
}
@Test
void localizedUserTest() {
var validate = new LocalizedValidation.Default();
var result = validate.apply("aaa", "qwe", "aaa@mail.ru", Locale.ENGLISH);
assertTrue(result.isEmpty());
result = validate.apply("aaa", "qwe", "", Locale.ENGLISH);
assertFalse(result.isEmpty());
assertEquals(1, result.size());
System.out.println(result.iterator().next());
result = validate.apply("aaa", "qwe", "", new Locale("ru"));
assertFalse(result.isEmpty());
assertEquals(1, result.size());
System.out.println(result.iterator().next());
}
Файлы локализаций лежат в src/main/resources.
errors_ru.properties:
mail=Email ошибка: не верный Email:
username=Ошибка имени пользователя: слишком короткое или длинное имя.
password=Ошибка в длине пароля: пароль слишком длинный.
errors.properties:
mail=Email error: is not valid:
username=Username error:too short or too long name. Name must be greater or 3 characters and smaller then 30 simbols.
password=Password error:too short value or too long name. Password must be greater or 3 characters and smaller then 20 simbols.
Надеюсь, что и другие программисты, пишушие на своих языках, найдут такой подход практичным и удобным и смогут его применить у себя.
P.S.
Вариант метода аутентификации пользователя, который возвращает реализацию интерфейса java.util.Map.Entry<K, V>, и может содержать в себе как объект пользователя так и данные об ошибках в виде строки(такой подход также может использоваться для избежания возврата Null из метода когда ожидается объект пользователя):
@Override
public Entry<String, User> auth(String username, String password, String email) {
var errors = passwordValidation.apply(password.trim());
errors.addAll(userNameValidation.apply(username.trim()));
errors.addAll(emailValidation.apply(email.trim()));
if(!errors.isEmpty()) {
return REntry.ofI(null, errors.stream().collect(Collectors.joining(";")));
}else {
try {
var passwordEncrypted = new Encrypt().apply(password.trim());
var user = new User.Default(0L, username.trim(), email.trim(), passwordEncrypted, "").create(dataSource);
return REntry.ofI(user, "user is null!");
} catch (RuntimeException e) {
return REntry.ofI(null, e.getMessage());
}
}
}
Есть, конечно, замечательная статья на хабре про валидацию в Java, но подход который там предлагается может использоваться далеко не во всех случаях и основан на аннотациях и исключениях.
В окончание статьи пару полезных ссылок про тестирование: