Проверка данных в приложении введённых пользователем или полученных другим путём в классическом понимании подразумевает использование всего лишь двух выражений в коде: 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, но подход который там предлагается может использоваться далеко не во всех случаях и основан на аннотациях и исключениях.
В окончание статьи пару полезных ссылок про тестирование:
