Русский
Русский
English
Статистика
Реклама

Из песочницы Валидация данных в C с использованием библиотеки cpp-validator


Казалось бы, валидация данных это одна из базовых задач в программировании, которая встретится и в начале изучения языка вместе с "Hello world!", и в том или ином виде будет присутствовать в множестве зрелых проектов. Тем не менее, Google до сих пор выдает ноль релевантных результатов при попытке найти универсальную библиотеку валидации данных с открытым исходным кодом на C++.


В лучшем случае, там найдутся или инструменты проверки самого кода C++, или библиотеки валидации определенных форматов, например, таких как JSON или XML. Похоже на то, что либо разработчики для каждого случая реализуют валидацию данных вручную, либо инструменты валидации создаются под конкретный проект и плохо приспособлены для использования в качестве универсальных библиотек.


Если в комментариях кто-то сможет привести примеры открытых библиотек валидации данных на C++ помимо отдельных GUI-форм, то буду очень признателен и добавлю соответствующий список в статью.


Содержание



Мотивация


Стоить отметить, что мотивом к разработке валидатора данных для C++ послужило не столько отсутствие подобной библиотеки, сколько желание получить инструмент, при помощи которого можно было бы единообразно описывать:


  • условия выборки записей в базе данных;
  • условия проверки на сервере содержимого команд, поступающих от клиентов;
  • условия проверки данных, вводимых через пользовательский интерфейс на клиенте, перед их записью в объект и отправкой на сервер.

Т.е., если с прикладной точки зрения во всех трех случаях речь идет о разных представлениях одной и той же сущности, то почему бы не сделать так, чтобы правила проверки характеристик этой сущности описывались бы одинаковым образом? И уже потом эти правила обрабатывать разными способами применительно к каждому конкретному случаю например, транслировать в строку запроса к базе данных или в проверку набора содержимого ранее заполненного объекта или в проверку отдельных переменных перед записью в объект.


В итоге, библиотека валидации разрабатывалась с учетом основного требования, чтобы было четкое разделение между:


  • описанием правил валидации;
  • реализацией обработчиков правил валидации;
  • обработкой конкретных правил валидации конкретным обработчиком.

То есть, правила валидации должны предварительно описываться в отдельном месте, желательно с применением синтаксиса, похожего на декларативный. В другом месте должны быть реализованы обработчики правил валидации. Причем, разные обработчики могут транслировать те же самые правила в разные операции например, один обработчик использует правила для фактической проверки данных, а другой обработчик транслирует правила в запросы SQL. И, собственно, третий участок это непосредственно применение правил валидации конкретным обработчиком в момент вызова процедуры валидации.


Возможности библиотеки


cpp-validator является header-only библиотекой для современного C++ с поддержкой стандартов C++14/C++17. В коде cpp-validator активно используется метапрограммирование на шаблонах и библиотека Boost.Hana.


Основные возможности библиотеки cpp-validator перечислены ниже.


  • Валидация данных для различных конструкций языка:
    • простых переменных;
    • свойств объектов, включая:
      • переменные классов;
      • методы классов вида getter;
    • содержимого и свойств контейнеров;
    • иерархических типов данных, таких как вложенные объекты и контейнеры.
  • Пост-валидация объектов, когда проверяется содержимое уже заполненного объекта на соответствие сразу всем правилам.
  • Пре-валидация данных, когда перед записью в объект проверяются только те свойства, которые планируется изменить.
  • Комбинация правил с использованием логических связок AND, OR и NOT.
  • Массовая проверка элементов контейнеров с условиями ALL или ANY.
  • Частично подготовленные правила валидации с отложенной подстановкой аргументов (lazy operands).
  • Сравнение друг с другом разных свойств одного и того же объекта.
  • Автоматическая генерация описания ошибок валидации:
    • широкие возможности по настройке генерации текста ошибок;
    • перевод текста ошибок на различные языки с учетом грамматических атрибутов слов, например, числа, рода и т.д.
  • Расширяемость:
    • регистрация новых свойств объектов, доступных для валидации;
    • добавление новых операторов правил валидации;
    • добавление новых обработчиков правил валидации (адаптеров).
  • Операторы, уже встроенные в библиотеку:
    • сравнения;
    • лексикографические, с учетом и без учета регистра;
    • существования элементов;
    • проверки вхождения в интервал или набор;
    • регулярные выражения.
  • Широкая поддержка платформ и компиляторов, включая компиляторы Clang, GCC, MSVC и операционные системы Windows, Linux, macOS, iOS, Android.

Использование библиотеки


Базовая валидация данных с использованием cpp-validator выполняется в три шага:


  1. сперва создается валидатор, содержащий правила валидации, описанные с использованием почти-декларативного языка;
  2. затем валидатор применяется к объекту валидации;
  3. в конце проверяется результат валидации, для работы с которым может использоваться либо специальный объект ошибки, либо исключение.

// определение валидатораauto container_validator=validator(   _[size](eq,1), // размер контейнера должен быть равен 1   _["field1"](exists,true), // поле "field1" должно существовать в контейнере   _["field1"](ne,"undefined") // поле "field1" должно быть не равно "undefined");// успешная валидацияstd::map<std::string,std::string> map1={{"field1","value1"}};validate(map1,container_validator);// неуспешная валидация, с объектом ошибкиerror_report err;std::map<std::string,std::string> map2={{"field2","value2"}};validate(map2,container_validator,err);if (err){    std::cerr<<err.message()<<std::endl;    /* напечатает:    field1 must exist    */}// неуспешная валидация, с исключениемtry{    std::map<std::string,std::string> map3={{"field1","undefined"}};    validate(map3,container_validator);}catch(const validation_error& ex){    std::cerr<<ex.what()<<std::endl;    /* напечатает:    field1 must be not equal to undefined    */}

При расширенном использовании cpp-validator можно регистрировать новые валидируемые свойства объектов, настраивать генерацию текстовых описаний ошибок, добавлять новые операторы или создавать полностью новые адаптеры-обработчики правил валидаций, которые, вообще говоря, могут использоваться даже не для валидации как таковой, а для выполнения других специфических задач.


Текущий статус библиотеки


Библиотека cpp-validator доступна на GitHub по адресу https://github.com/evgeniums/cpp-validator и готова к использованию на момент написания статьи номер стабильной версии 1.0.2. Библиотека распространяется под лицензией Boost 1.0.


Приветствуются замечания, пожелания и дополнения.


Примеры


Тривиальная валидация числа


// определение валидатораauto v=validator(gt,100); // больше чем 100// объект ошибкиerror err;// условия не выполненыvalidate(90,v,err);if (err){  // валидация неуспешна}// условия выполненыvalidate(200,v,err);if (!err){  // валидация успешна}

Валидация с исключением


// определение валидатораauto v=validator(gt,100); // больше чем 100try{    validate(200,v); // успешно    validate(90,v); // генерирует исключение}catch (const validation_error& err){    std::cerr << err.what() << std::endl;    /* напечатает:    must be greater than 100    */}

Явное применение валидатора к переменной


// определение валидатораauto v=validator(gt,100); // больше чем 100// применить валидатор к переменнымint value1=90;if (!v.apply(value1)){  // валидация неуспешна}int value2=200;if (v.apply(value2)){  // валидация успешна}

Составной валидатор


// валидатор: размер меньше 15 и значение бинарно больше или равно "sample string"auto v=validator(  length(lt,15),  value(gte,"sample string"));// явное применение валидатора к переменнымstd::string str1="sample";if (!v.apply(str1)){  // валидация неупешна потому что sample бинарно меньше, чем sample string}std::string str2="sample string+";if (v.apply(str2)){  // валидация успешна}std::string str3="too long sample string";if (!v.apply(str3)){  // валидация неуспешна, потому что длина строки больше 15 символов}

Проверить, что число входит в интервал, и напечатать описание ошибки


// валидатор: входит в интервал [95,100]auto v=validator(in,interval(95,100));// объект ошибкиerror_report err;// проверить значениеsize_t val=90;validate(val,v,err);if (err){    std::cerr << err.message() << std::endl;     /* напечатает:    must be in interval [95,100]    */}

Составной валидатор для проверки элемента контейнера


// составной валидаторauto v=validator(                _["field1"](gte,"xxxxxx")                 ^OR^                _["field1"](size(gte,100) ^OR^ value(gte,"zzzzzzzzzzzz"))            );// валидация контейнера и печать ошибкиerror_report err;std::map<std::string,std::string> test_map={{"field1","value1"}};validate(test_map,v,err);if (err){    std::cerr << err.message() << std::endl;    /* напечатает:    field1 must be greater than or equal to xxxxxx OR size of field1 must be greater than or equal to 100 OR field1 must be greater than or equal to zzzzzzzzzzzz    */}

Проверить элементы вложенных контейнеров


// составной валидатор элементов вложенных контейнеровauto v=validator(                _["field1"][1](in,range({10,20,30,40,50})),                _["field1"][2](lt,100),                _["field2"](exists,false),                _["field3"](empty(flag,true))            );// валидация вложенного контейнера и печать ошибкиerror_report err;std::map<std::string,std::map<size_t,size_t>> nested_map={            {"field1",{{1,5},{2,50}}},            {"field3",{}}        };validate(nested_map,v,err);if (err){    std::cerr << err.message() << std::endl;    /* напечатает:    element #1 of field1 must be in range [10, 20, 30, 40, 50]    */}

Провести валидацию кастомного свойства объекта


// структура с getter методомstruct Foo{    bool red_color() const    {        return true;    }};// зарегистрировать новое свойство red_colorDRACOSHA_VALIDATOR_PROPERTY_FLAG(red_color,"Must be red","Must be not red");// валидатор зарегистрированного свойства red_colorauto v=validator(    _[red_color](flag,false));// провести валидацию кастомного свойства и напечатать ошибкуerror_report err;Foo foo_instance;validate(foo_instance,v,err);if (err){    std::cerr << err.message() << std::endl;    /* напечатает:    "Must be not red"    */}

Пре-валидация данных перед записью


// структура с переменными и методом вида setterstruct Foo{    std::string bar_value;    uint32_t other_value;    size_t some_size;    void set_bar_value(std::string val)    {        bar_value=std::move(val);    }};using namespace DRACOSHA_VALIDATOR_NAMESPACE;// зарегистрировать кастомные свойстваDRACOSHA_VALIDATOR_PROPERTY(bar_value);DRACOSHA_VALIDATOR_PROPERTY(other_value);// специализация шаблона класса set_member_t для записи свойства bar_value структуры FooDRACOSHA_VALIDATOR_NAMESPACE_BEGINtemplate <>struct set_member_t<Foo,DRACOSHA_VALIDATOR_PROPERTY_TYPE(bar_value)>{    template <typename ObjectT, typename MemberT, typename ValueT>    void operator() (            ObjectT& obj,            MemberT&&,            ValueT&& val        ) const    {        obj.set_bar_value(std::forward<ValueT>(val));    }};DRACOSHA_VALIDATOR_NAMESPACE_END// валидатор с кастомными свойствамиauto v=validator(    _[bar_value](ilex_ne,"UNKNOWN"), // лексикографическое "не равно" без учета регистра    _[other_value](gte,1000) // больше или равно 1000);Foo foo_instance;error_report err;// запись валидного значение в свойство bar_value объекта foo_instanceset_validated(foo_instance,bar_value,"Hello world",v,err);if (!err){    // свойство bar_value объекта foo_instance успешно записано}// попытка записи невалидного значение в свойство bar_value объекта foo_instanceset_validated(foo_instance,bar_value,"unknown",v,err);if (err){    // запись не удалась    std::cerr << err.message() << std::endl;    /* напечатает:     bar_value must be not equal to UNKNOWN     */}

Один и тот же валидатор для пост-валидации и пре-валидации


#include <iostream>#include <dracosha/validator/validator.hpp>#include <dracosha/validator/validate.hpp>using namespace DRACOSHA_VALIDATOR_NAMESPACE;namespace validator_ns {// зарегистрировать getter свойства "x"DRACOSHA_VALIDATOR_PROPERTY(GetX);// валидатор GetXauto MyClassValidator=validator(   /*    "x" в кавычках - это имя поля, которое писать в отчете вместо GetX;   interval.open() - модификатор открытого интервала без учета граничных точек   */   _[GetX]("x")(in,interval(0,500,interval.open())) );}using namespace validator_ns;// определение тестового класса  class MyClass {  double x;public:  // Конструктор с пост-валидацией  MyClass(double _x) : x(_x) {      validate(*this,MyClassValidator);  }  // Getter  double GetX() const noexcept  {     return _x;  }  // Setter с пре-валидацией  void SetX(double _x) {    validate(_[validator_ns::GetX],_x,MyClassValidator);    x = _x;  }};int main(){// конструктор с валидным аргументомtry {    MyClass obj1{100.0}; // ok}catch (const validation_error& err){}// конструктор с невалидным аргументомtry {    MyClass obj2{1000.0}; // значение вне интервала}catch (const validation_error& err){    std::cerr << err.what() << std::endl;    /*     напечатает:     x must be in interval(0,500)    */}MyClass obj3{100.0};// запись с валидным аргументомtry {    obj3.SetX(200.0); // ok}catch (const validation_error& err){}// попытка записи с невалидным аргументомtry {    obj3.SetX(1000.0); // значение вне интервала}catch (const validation_error& err){    std::cerr << err.what() << std::endl;    /*     напечатает:     x must be in interval (0,500)    */}return 0;}

Перевод ошибок валидации на русский язык


// переводчик ключей контейнера на русский язык с учетом рода, падежа и числаphrase_translator tr;tr["password"]={                    {"пароль"},                    {"пароля",grammar_ru::roditelny_padezh}               };tr["hyperlink"]={                    {{"гиперссылка",grammar_ru::zhensky_rod}},                    {{"гиперссылки",grammar_ru::zhensky_rod},grammar_ru::roditelny_padezh}                };tr["words"]={                {{"слова",grammar_ru::mn_chislo}}            };/* финальный переводчик включает в себя встроенный переводчик на русскийvalidator_translator_ru() и переводчик tr для имен элементов*/auto tr1=extend_translator(validator_translator_ru(),tr);// контейнер для валидацииstd::map<std::string,std::string> m1={    {"password","123456"},    {"hyperlink","zzzzzzzzz"}};// адаптер с генерацией отчета об ошибке на русском языкеstd::string rep;auto ra1=make_reporting_adapter(m1,make_reporter(rep,make_formatter(tr1)));// различные валидаторы и печать ошибок на русском языкеauto v1=validator(    _["words"](exists,true) );if (!v1.apply(ra1)){    std::cerr<<rep<<std::endl;    /*    напечатает:    слова должны существовать    */}rep.clear();auto v2=validator(    _["hyperlink"](eq,"https://www.boost.org") );if (!v2.apply(ra1)){    std::cerr<<rep<<std::endl;    /*    напечатает:    гиперссылка должна быть равна https://www.boost.org    */}rep.clear();auto v3=validator(    _["password"](length(gt,7)) );if (!v3.apply(ra1)){    std::cerr<<rep<<std::endl;    /*    напечатает:    длина пароля должна быть больше 7    */}rep.clear();auto v4=validator(    _["hyperlink"](length(lte,7)) );if (!v4.apply(ra1)){    std::cerr<<rep<<std::endl;    /*    напечатает:    длина гиперссылки должна быть меньше или равна 7    */}rep.clear();
Источник: habr.com
К списку статей
Опубликовано: 27.10.2020 12:09:43
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Программирование

Совершенный код

C++

Проектирование и рефакторинг

Валидация данных

Валидатор

Validation

Метапрограммирование

C++14

C++17

C++ библиотеки

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru