LIVR — «независимые от языка правила валидации» или валидация данных без «проблем»

    Каждый программист неоднократно сталкивался с необходимостью проверки пользовательского ввода. Занимаясь веб-разработкой уже более 10 лет, я перепробовал массу библиотек, но так и не нашел той единственной, которая решала бы поставленные мною задачи.

    Основные проблемы, которые встречаются в библиотеках валидации данных

    Проблема №1. Многие валидаторы проверяют только те данные, для которых описаны правила проверки. Для меня важно, чтобы любой пользовательский ввод, который явно не разрешен, был проигнорирован. То есть, валидатор должен вырезать все данные для которых не описаны правила валидации. Это просто фундаментально требование.

    Проблема №2. Процедурное описание правил валидации. Я не хочу каждый раз думать про алгоритм валидации, я просто хочу описать декларативно, как должны выглядеть правильные данные. По сути, я хочу задать схему данных (почему не «JSON Schema» — в конце поста).

    Проблема №3. Описание правил валидации в виде кода. Казалось бы, это не так страшно, но это сразу сводит на нет все попытки сериализации правил валидации и использования одних и тех же правил валидации на бекенде и фронтенде.

    Проблема №4. Валидация останавливается на первом же поле с ошибкой. Такой подход не дает возможности подсветить сразу все ошибочные/обязательные поля в форме.

    Проблема №5. Нестандартизированные сообщения об ошибках. Например, «Field name is required». Такую ошибку я не могу показать пользователю по ряду причин:
    • поле в интерфейсе может называться совсем по другому
    • интерфейс может быть не на английском
    • нужно различать тип ошибки. Например, ошибки на пустое значение показывать специальным образом

    То есть, нужно возвращать не сообщение об ошибках, а стандартизированные коды ошибок.

    Проблема №6. Числовые коды ошибок. Это просто неудобно в использовании. Я хочу, чтобы коды ошибок были интуитивно понятны. Согласитесь, что код ошибки «REQUIRED» понятней, чем код «27». Логика аналогична работе с классами исключений.

    Проблема №7. Нет возможности проверять иерархические структуры данных. Сегодня, во времена разных JSON API, без этого просто не обойтись. Кроме самой валидации иерархических данных, нужно предусмотреть и возврат кодов ошибок для каждого поля.

    Проблема №8. Ограниченный набор правил. Стандартных правил всегда не хватает. Валидатор должен быть расширяемый и позволять добавлять в него правила любой сложности.

    Проблема №9. Слишком широкая сфера ответственности. Валидатор не должен генерировать формы, не должен генерировать код, не должен делать ничего, кроме валидации.

    Проблема №10. Невозможность провести дополнительную обработку данных. Практически всегда, где есть валидация, есть необходимость в какой-то дополнительной (часто предварительной) обработке данных: вырезать запрещенные символы, привести в нижний регистр, удалить лишние пробелы. Особенно актуально — это удаление пробелов в начале и в конце строки. В 99% случаев они там не нужны. Я знаю, что я до этого говорил, что валидатор не должен делать ничего кроме валидации.

    3 года назад, было решено написать валидатор, который не будет иметь всех вышеописанных проблем. Так появился LIVR (Language Independent Validation Rules). Есть реализации на Perl, PHP, JavaScript, Python (мы на python не пишем — фидбек по ней дать не могу). Валидатор используется в продакшене уже несколько лет практически в каждом проекте компании. Валидатор работает, как на сервере, так и на клиенте. Поиграться с валидатором можно тут — webbylab.github.io/livr-playground.

    Ключевой идеей было то, что ядро валидатора должно быть минимальным, а вся логика валидации находится в правилах (вернее в их реализации). То есть, для валидатора нет разницы между правилами «required» (проверяет наличие значения), «max_length» (проверяет максимальную длину), «to_lc» (приводит данные в нижний регистра), «list_of_objects» (помогает описать правила для поля, которое содержит массив объектов).

    Другими словами, валидатор ничего не знает ничего:
    • о кодах ошибок
    • о том, что он умеет валидировать иерархические объекты
    • о том, что он умеет преобразовывать/чистить данные
    • о многом другом

    Все это ответственность правил валидации.

    Спецификация LIVR

    Поскольку задача стояла сделать валидатор независимым от языка программирования, этакий себе mustache/handlebars, но только в мире валидации данных, то начали мы с написания спецификации.

    Цели спецификации:
    1. Стандартизировать формат описания данных.
    2. Описать минимальный набор правил валидации, которые должны поддерживаться каждой реализацией.
    3. Стандартизировать коды ошибок.
    4. Быть единой базовой документацией для всех реализаций.
    5. Иметь набор тестовых данных, которые позволяет проверить реализацию на соответствие спецификации

    Спецификация доступна по адресу livr-spec.org

    Основной идеей было то, что описание правил валидации должно выглядеть, как схема данных и максимально быть похожими на данные, только вместо значений правила.

    Пример описания правил валидации для формы авторизации (демо):
    {
        email: ['required', 'email'],
        password: 'required'
    }
    

    Пример правил валидации для регистрационной формы (демо):
    {
        name: 'required',
        email: ['required', 'email'],
        gender: { one_of: ['male', 'female'] },
        phone: {max_length: 10},
        password: ['required', {min_length: 10} ]
        password2: { equal_to_field: 'password' }
    }
    

    Пример валидации вложенного объекта (демо):
    {
        name: 'required',
        phone: {max_length: 10},
        address: { 'nested_object': {
            city: 'required',
            zip: ['required', 'positive_integer']
        }}
    }
    

    Правила валидации

    Как описываются правила валиции? Каждое правило состоит из имени и аргументов (практически, как вызов функции) и в общем случае описывается следующим образом {«RULE_NAME»: ARRAY_OF_ARGUMENTS}. Для каждого поля описывается массив правил, которые применяются в порядке следования.

    Например,
    {
        "login": [ { length_between: [ 5, 10 ] } ]
    }
    

    То есть, у нас есть поле «login» и правило «length_between», которое имеет 2 аргумента ( «5» и «10» ). Это наиболее полная форма, но разрешены следующие упрощения

    • Если правило к полю одно, то массив не обязателен
    • Если у правила один аргумент, то можно передавать только его (не обрамляя в массив)
    • Если у правила не аргументов, то можно записать просто название правила.

    Все 3 записи идентичны:
    "login": [ { required: [] } ]
    

    "login": [ "required" ]
    

    "login": "required"
    

    Более детально расписано в спецификации в разделе «How it works».

    Поддерживаемые правила

    Все правила можно разделить на 3 глобальных группы:
    • Правила, которые валидируют данные (числа, строки и тд). Например, «max_length».
    • Правила, которые позволяют составлять более сложные правила с более простых. Например, «nested_object».
    • Правила, которые преобразовывают данные. Например, «to_lc»

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

    Вот общий список правил, которые должны поддерживаться каждой реализаций валидатора:

    Базовые правила
    • required — поле обязательно и значение должно быть не пустым
    • not_empty — поле не обязательно, но если оно есть, то не может быть пустым
    • not_empty_list — значение должно содержать не пустой массив

    Правила для проверки строк
    • one_of
    • max_length
    • min_length
    • length_between
    • length_equal
    • like

    Правила для проверки чисел
    • integer
    • positive_integer
    • decimal
    • positive_decimal
    • max_number
    • min_number
    • number_between

    Правила для специальных форматов
    • email
    • url
    • iso_date
    • equal_to_field

    Правила для описания более сложных правил (метаправила)
    • nested_object — описывает правила для вложенного объекта
    • list_of — описывает правила, которым должен соответствовать каждый элемент списка
    • list_of_objects — значение должно быть массивом объектов нужном формате
    • list_of_different_objects — используйте, когда нужно проверить массив объектов разных типов.

    Правила для преобразования данных (названия начинаются с глагола)
    • trim — убирает пробелы в начале в конце
    • to_lc — приводит к нижнему регистру
    • to_uc — приводит к верхнему регистру
    • remove — удаляет указанные символы
    • leave_only — оставляет только указанные символы

    Метаправила

    Пример и коды ошибок для каждого правила можно найти в LIVR-спецификации. Немного детальней остановимся лишь на метаправилах. Метаправила — это правила, которые позволяет скомбинировать простые правила в более сложные для валидации сложных иерархических структур данных. Важно понимать, что валидатор не делает различия между простыми правилами и метаправилами. Метаправила ничем не отличаются от того же “required” (да, я повторяюсь).

    nested_object
    Позволяет описывать правила валидации для вложенных объектов. Этим правилом вы будете пользоваться постоянно.
    Код ошибки зависит от вложенных правил. Если вложенный объект не является хешом (словарем), то поле будет содержать ошибку: “FORMAT_ERROR”.
    Пример использования (демо):

    address: { 'nested_object': {
        city: 'required',
        zip: ['required', 'positive_integer']
    }}
    

    list_of
    Позволяет описать правила валидации для списка значений. Каждое правило будет применяться для каждого элемента списка.
    Код ошибки зависит от вложенных правил.
    Пример использования (демо):

    { product_ids: { 'list_of': [ 'required',  'positive_integer'] }}
    

    list_of_objects
    Позволяет описать правила валидации для массива хешей(словарей). Похоже на «nested_object», но ожидает массив объектов. Правила применяются для каждого элемента в массиве.
    Код ошибки зависит от вложенных правил. В случае если значение не является массивом, для поля будет возвращен код “FORMAT_ERROR”.
    Пример использования (демо):
    products: ['required', { 'list_of_objects': {
        product_id: ['required','positive_integer'],
        quantity: ['required', 'positive_integer']
    }}]
    

    list_of_different_objects
    Аналогичен «list_of_objects», но бывает, что массив, который нам приходит, содержит объекты разного типа. Тип объекта мы можем определить по какому-то полю, например, «type». «list_of_different_objects» позволяет описать правила для списка объектов разного вида.
    Код ошибки зависит от вложенных правил валидации. Если вложенных объект не является хешом, то поле будет содержать ошибку “FORMAT_ERROR”.
    Пример использования (демо):

    {
        products: ['required', { 'list_of_different_objects': [
            product_type, {
                material: {
                    product_type: 'required',
                    material_id: ['required', 'positive_integer'],
                    quantity: ['required', {'min_number': 1} ],
                    warehouse_id: 'positive_integer'
                },
                service: {
                    product_type: 'required',
                    name: ['required', {'max_length': 20} ]
                }
            }
        ]}]
    }
    

    В этом примере валидатор будут смотреть на “product_type” в каждом хеше и, в завимости от значения этого поля, будет использовать соответствующие правила валидации.

    Формат ошибок

    Как уже было сказано, правила возвращают строковые коды ошибок, которые понятны разработчику, например, «REQUIRED», «WRONG_EMAIL», «WRONG_DATE» и тд. Теперь разработчик может понять, в чем ошибка, осталось удобно донести в каких полях она возникла. Для этого валидатор возвращает структуру аналогичную переданной ему на валидацию, но она содержит только поля в которых возникли ошибки и вместо исходных значений в полях строковые коды ошибок.

    Например, есть правила:
    {
        name: 'required',
        phone: {max_length: 10},
        address: { 'nested_object': {
            city: 'required',
            zip: ['required', 'positive_integer']
        }}
    }
    

    и данные для валидации:
    {
        phone: 12345678901,
        address: {
           city: 'NYC' 
        }
    }
    

    на выходе получим следующую ошибку
    {
        "name": "REQUIRED",
        "phone": "TOO_LONG",
        "address": {
            "zip": "REQUIRED"
        }
    }
    

    демо валидации

    REST API и формат ошибок

    Возврат вменяемых ошибок всегда требует дополнительных усилий от разработчиков. И очень мало REST API, которые дают детальную информацию в ошибках. Часто это просто «Bad request» и все. Хочется, чтобы глядя на ошибку, к какому полю она относится и просто пути поля недостаточно, поскольку данные могут быть иерархическими и содержать массивы объектов… У нас в компании мы поступаем следующим образом — абсолютно для каждого запроса описываем правила валидации при помощи LIVR. В случае ошибки валидации, мы возвращаем объект ошибки клиенту. Объект ошибки содержит глобальный код ошибки и ошибку полученную от LIVR валидатора.

    Например, вы передаете данные на сервер:

    {
        "email": "user_at_mail_com",
        "age": 10,
        "address": {
            "country": "USQ"
        }
    }
    

    и в ответ получаете (демо валидации на livr playground):

    {"error": {
        "code": "FORMAT_ERROR",
        "fields": {
            "email": "WRONG_EMAIL",
            "age": "TOO_LOW",
            "fname": "REQUIRED",
            "lname": "REQUIRED",
            "address":  {
                "country": "NOT_ALLOWED_VALUE",
                "city": "REQUIRED",
                "zip": "REQUIRED"
            }
        }
    }}
    

    Это значительно информативнее, чем какой-то «Bad request».

    Работа с псевдонимами и регистрация собственных правил

    Спецификация содержит только наиболее используемые правила, но у каждого проекта своя специфика и постоянно возникают ситуации, когда каких-то правил не хватает. В связи с этим, одним из ключевых требований к валидатору была возможность его расширения собственными правилами любого типа. Изначально каждая реализация имела свой механизм описания правил, но начиная со спецификации версии 0.4 мы ввели стандартный способ создания правил на базе других правил (создание псевдонимов), это покрывает 70% ситуаций. Рассмотрим оба варианта.

    Создание псевдонима
    Способ, каким регистрируется псевдоним зависит от реализации, но то как псевдоним описывается — регламентировано спецификацией. Такой подход, например, позволяет сериализировать описания псевдонимов и использовать их с разными реализациями (например, на Perl-бекенде и JavaScript-фронтенде)

    // Регистрация псевдонима "valid_address"
    validator. registerAliasedRule({
        name: 'valid_address',
        rules: { nested_object: {
            country: 'required',
            city: 'required',
            zip: 'positive_integer'
        }}
    });
    
    // Регистрация псевдонима "adult_age"
    validator.registerAliasedRule( {
        name: 'adult_age',
        rules: [ 'positive_integer', { min_number: 18 } ]
    });
    
    // Теперь псевдонимы доступны, как обычные правила.
    {
        name: 'required',
        age: ['required', 'adult_age' ],
        address: ['required', 'valid_address']
    }
    
    

    Более того, можно устанавливать свои коды ошибок для правил.

    Например,
    validator.registerAliasedRule({
        name: 'valid_address',
        rules: { nested_object: {
            country: 'required',
            city: 'required',
            zip: 'positive_integer'
        }},
        error: 'WRONG_ADDRESS'
    });
    

    и в случае ошибки при валидации адреса, мы получим следующее:
    {
        address: 'WRONG_ADDRESS'
    }
    

    Регистрация полноценного правила на примере JavaScript реализации
    Для валидации используются функции обратного вызова, которые осуществляют проверку значений. Попробуем описать новое правило под названием “strong_password”. Будем проверять, что значение больше 8 символов и содержит цифры и буквы в верхнем и нижнем регистрах.

    var LIVR = require('livr');
    
    var rules = {password: ['required', 'strong_password']};
    
    var validator = new LIVR.Validator(rules);
    
    validator.registerRules({
        strong_password: function() {
            return function(val) {
                // пропускаем пустые значение. Для проверки на обязательность у нас и так есть правило "required"
                if (val === undefined || val === null || val === '' ) return;
                
                if ( length(val) < 8 || !val.match([0-9]) || !val.match([a-z] || !val.match([A-Z] ) ) {
                    return 'WEAK_PASSWORD';
                }
    
                return;
              }
        }
    });
    

    Теперь добавим возможность задавать минимальное количество символов в пароле и зарегистрируем это правило как глобальное (доступное во всех экземплярах валидатора).

    var LIVR = require('livr');
    
    var rules = {password: ['required', {'strong_password': 10}]};
    
    var validator = new LIVR.Validator(rules);
    
    var strongPassword = function(minLength) {
        if (!minLength) throw "[minLength] parameter required";
    
        return function(val) {
            // пропускаем пустые значение. Для проверки на обязательность у нас и так есть правило "required"
            if (val === undefined || val === null || val === '' ) return;
                
            if ( length(val) < minLength || !val.match([0-9]) || !val.match([a-z] || !val.match([A-Z] ) ) {
                return 'WEAK_PASSWORD';
            }
    
            return;
        }
    };
    
    LIVR.Validator.registerDefaultRules({ strong_password: strongPassword });
    


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

    Есть возможность регистрации правил, которые будут не только валидировать значение, но и изменять его. Например, приводить к верхнему регистру или удалять лишние пробелы.

    Своя реализация по спецификации

    Если есть желание сделать свою реализацию валидатора, то для облегчения задачи был создан набор тест-кейсов . Если ваша реализация проходит все тесты, то ее можно считать корректной. Комплект тестов состоит из 4-х групп:

    • «positive» — позитивные тесты для основных правил
    • «negative» — негативные тесты для основных правил
    • «aliases_positive» — позитивные тесты для псевдонимов правил
    • «aliases_negative» — негативные тесты для псевдонимов правил

    По сути, каждый тест содержит несколько файлов:

    • rules.json — описание правил валидации
    • input.json — структура, которая передается валидатору на проверку
    • output.json — очищенная структура, которая получается после валидации

    Каждый негативный тест вместо «output.json» содержит «errors.json» с описанием ошибки, которая должна возникнуть в результате валидации. В тестах псевдонимов есть файл «aliases.json» с псевдонимами, которые необходимо предварительно зарегистрировать.

    Почему не JSON Schema?

    Часто задаваемый вопрос. Если коротко, то причин несколько:
    • Сложный формат для правил. Хочется, чтобы структура с правилами была максимально близка к структуре с данными. Попробуйте описать этот пример на JSON Schema
    • Формат ошибок никак не специфицирован и разные реализации возвращают ошибки в разном формате.
    • Нет преобразования данных, например «to_lc».

    JSON Schema содержит и интересные вещи, как-то возможность задать максимальное количество элементов в списке, но в LIVR это реализуется просто добавлением еще одного правила.

    Ссылки по LIVR



    UPD:

    Вышел LIVR 2.0 (http://livr-spec.org/). С новых фич:
    • Согласованный подход к работе с типами.
    • «Base rules» переименовали в «Common rules».
    • «Filter rules» переименовали в «Modifiers». Поскольку они ничего не валидируют, а только модифицируют данные.
    • «Helper rules» переименовали to «Metarules»
    • Добавлено правило «any_object», проверяет, что это объект
    • Добавлено правило строка — «string», просто строка любого типа
    • Добавлено правило «eq» — проверяет на строковое равенство
    • Добавлено метаправило «variable_object». Динамически определяет, какую валидацию использвоать в зависимосте от полей в объекте.
    • Добавлено метаправило «or». Позволяет применять правила поочередно до первого совпадения.
    • Добавлен модификтор «default» для установки значений по-умолчинаю, если пользователь ничего не передал.
    • Значительно расширен комплект тестов.


    JavaScript реализация уже поддерживает все новые функции, остальные реализации в процессе обновления.
    Share post

    Comments 71

      +2
      Как альтернативу, подпадающие под все ваши вводные правила, могу порекомендовать Joi. Из плюсов — правил валидации намного больше, ну и чуть больше возможностей, например не увидел у вас, возможно ли задавать дефолтные значения переменных, если во входящий данных пусто и можно ли у вас задавать условия ветвления, если например я хочу допустить, что в одной переменной может быть строка или массив строк и соответственно задать разные правила для каждого из случаев.
        +5
        Спасибо, Joi выглядит неплохо, но ему присущи проблемы других валидаторов:
        1) Он только под JS. Нам нужно кроссплатформенное решение
        2) Правила валидации описываются в виде кода, а не структуры данных (Проблема №3)
        3) Останавливается на первой ошибке (Проблема №4)
        4) Не нашел механизма для регистрации собственных правил
        5) Сложно описывать иерархические правила (если это возможно)
        6) так далее… )

        Относительно большего количества правил, то мы старались в спеку LIVR включать необходимый минимум. LIVR легко расширяется и можно делать отдельные библиотеки правил.

        Дефолтовые значения переменных — это хорошая идея. В LIVR очень легко реализуема путем добавления правила «default». И можно будет описывать

        { age: [ 'positive_integer', {default: 18} ] }
        


        Если будет востребовано, то можно добавить правило и на уровень спеки
          +1
          1) Ну да, это только JS. У вас есть php и python это круто. Раньше, когда на этих языках писал, то испытывал те же проблемы что и вы — не было нормальной библиотеки для валидации. Теперь, видимо, есть, спасибо :)
          2) Можно описать в виде структуры (json), потом скомпилировать структуру в схему Joi.
          3) Есть опция, которая позволяет отобразить все ошибки, а не остановится только на первой.
          4) Если вы имеете ввиду, что для валидации данных необходимо использовать свою функцию, то для этого есть Joi.func
          5) Можно валидировать неограниченное количество вложенных объектов.

            0
            Кстати, вот наверно чего не хватает всем библиотекам — это конвертирующих функций, которые исполняются после успешной валидации. В Joi они примитивны, например там в число преобразовать или в строку. Не хватает чего-нить типа такого:

            convert: function (value) {
            return value.toString().split('\n');
            }

          +2
          Выглядит так, что вы взяли формат данных, не обладающий схемой, а теперь героически с этим боретесь.
            0
            Вначале мы перепробовали разные валидаторы, долго с ними боролись, писали обвязки всякие. А затем появился LIVR и мы перестали бороться, и начали получать удовольствие :)
              0
              Отличное решение, возьму на заметку и скорее всего буду использовать совместно с бэк/фронт частью (в этом плане вообще отлично получилось, что одни правила работают на разных языках/плфтформах и их стоит прописать один раз). Однозначно +
              +6
              Выглядит так, как будто вы не читали статью.
                0
                У нас, например, есть сканер, который собирает разную информацию. Сегодня в этом поле может быть число, завтра там строка, послезавтра там уже массив строк и т.п. То есть заранее не известно, что мы получим. При этом нам нужно обязательно хранить эту информацию, не важно какой бы она была. Понимаю, что это чисто наша спецефическая проблема, но всё же — заранее схему описать нельзя. Можно лишь попытаться подогнать пришедшие данные под допустимые схемы и сохранить их БД для последующей обработки.
                  +1
                  Можно создать свое правило, которое будет смотреть на тип пришедших данных и решать, как его проверять. Если же глядя на тип данных нельзя решить, как их валидировать, то значит тут никакой валидатор не поможет :).

                  Немного схожий кейс — github.com/koorchik/LIVR/issues/9
                +3
                И всё же «валидатор не должен делать ничего кроме валидации» (принцип единственной ответственности).
                Пример, показывающий зависимость результата валидации от порядка следования правил, «хорошие валидаторы» так не делают.
                  0
                  Я бесспорно понял что Вы хотели сказать, но вы же сами задали правила валидации в таком порядке? И одно правило можно указать несколько раз… Я бы наверно даже остался в таком варианте и доверил регуляцию этих правил вышестоящему инструменту(защита от дурака)
                    +3
                    На это мы пошли умышлено. Такой подход дает возможность организовать pipe. Вот пример.

                    Кроме того, это делает валидатор более предсказуемым, поскольку есть приоритет правил. На практике такой подход показал себя очень удобным.
                    +1
                    Автору LIVR: посмотрите библиотеку dklab.ru/lib/HTML_MetaForm
                    Эта библиотека, конечно, не блещет новизной, да и способ реализации в ней сомнителен (не говоря уж о публичном интерфейсе класса), однако я не для того ссылку на нее привожу, чтобы сравнивать, а только потому, что, может быть, вы в ней найдете какие-то интересные идеи для своего проекта. Например, одна из идей — в том, что HTML-разметка формы сама по себе содержит уже много информации для валидаторов (например, что из селектбокса не может быть выбран несуществующий элемент, или что в hidden-поле или в атрибуте action формы значение не может меняться волшебным образом). В HTML есть полный список полей формы, MetaForm позволяет навешивать на них мета-атрибуты, одни из них — meta:validator — очень похож по смыслу на валидаторы, описаные в статье.
                      0
                      Спасибо. В целом концепция такая, что есть LIVR-валидатор, как ядро, и его можно использовать в более высокоуровневых библиотеках. Например, можно написать LIVR-Forms, который, будет смотреть на поля с атрибутом data-rules и использовать LIVR для их валидации. Или же тянуть метаинформацию с самой HTML разметки (селекты и тд)
                      0
                      А как вы храните все ваши схемы для валидации? В каждом проекте лежит свой набор схем или как-то централизованно?
                        +1
                        В каждом проекте свой набор схем, централизированного хранилища нет. Кроме того каждый проект имеет набор каких-то специфических дополнительных правил, например, «company_id», или «query».

                        Относительно хранения схем в пределах проекта. Иногда мы описываем схемы в отдельных файлах. У нас есть инструмент для тестирования всяких REST API и там мы всегда описываем схему ожидаемого ответа в отдельных JSON файлах. И есть разные RESTful сервисы, где чаще просто непосредственно в коде и пишем правила. Если возникает необходимость переиспользовать правила, тогда можно выносить в отдельные файлы.
                        0
                        Объясните, пожалуйста, как вы отличаете такие два примера:
                        rules:
                        {
                        name: 'required',
                        }

                        data:
                        {
                        name: 'REQUIRED',
                        }

                        result:
                        {
                        name: 'REQUIRED',
                        }



                        и
                        rules:
                        {
                        name: 'required',
                        }

                        data:
                        {
                        name: '',
                        }

                        result:
                        {
                        name: 'REQUIRED',
                        }

                        ?
                          0
                          В livr playground по цвету (зеленый — успешная валидация, красный — ошибка) — Пример 1 и Пример 2 :)
                            0
                            Какую это играет роль, если, согласно статье, валидатор вернет только невалидные поля, т.е. result из второго примера? (в песочнице на сайте — на то она и песочница — будут отображены оба, различие заключается в подсветке)
                            –1
                            написал в свое время аналогичный велосипед (для ноды), добавлю пару идей — не увидел у Вас в описании рекурсивности, то есть если роверяемый параметр — объект, со своей структурой, то как его проверять? Вроде вот этого(извините что без тегов):
                            var rules = {
                            «contracts»: {
                            «flags»: «required»,
                            «type»: «object»,
                            «rules»: {
                            //вложенные правила
                            }
                            },
                            «lots»: {
                            «flags»: «required»,
                            «type»: «object»,
                            «rules»: {
                            «id»: {
                            «flags»: «required»,
                            «type»: «number»
                            },
                            «contract_id»: {
                            «flags»: «required»,
                            «type»: «number»
                            },
                            «filter_id»: {
                            «flags»: «required»,
                            «type»: «number»
                            }

                            А, нет, вижу. nested_object — это оно, да?
                            И второй момент, вы почему то продолжаете традиции предыдущих валидаторов, создавая сто пятсот правил вместо простого match:
                            var rules = {
                            «mandate»: {
                            «flags»: «required»,
                            «type»: «string»,
                            «match»: "^[0-9a-f]{16}$",
                            }
                            };
                            Согласитесь, добрая часть правил спокойно реализуется через регулярки, зачем для этого лепить дестки/сотни сущностей?
                            И еще, по опыту. Для валидатора неплохо иметь возможность (по крайней мере мне это удобно в работе) задать политику — strict — первое же несовпадение с правилом — всех в лес, soft — все что не проходит по правилам — помечать либо возвращать в отдельном объекте.
                            Дело в том, что валидатор может использоваться немного шире чем простая проверка параметров GET запроса. Это и проверка параметров функции и соответствие структуры объекта шаблону и полученные с базы данные.
                            А так — целиком поддерживаю идею декларативной валидации, функциональщина в этом деле, на мой взгляд — зло.
                              0
                              По порядку:
                              1) Все верно, вложенные объекты описываются через «nested_object». В посте раздел «Метаправила» посвящен таким правилам. Кроме того, можно добавить свои дополнительные (как в этом примере github.com/koorchik/LIVR/issues/9)
                              2) Относительно регулярок. Это поддерживается, только называется «like». Но регулярки не всегда удобно читать, например легче зарегистрировать алиас «ip_address» для проверки айпишки и использовать его везде ( и возвращать какой-то понятный код ошибки ), чем везде вставлять регулярку.
                              3) strict режим идея неплохая. Например, у нас один из заказчиков хотел, что если придет любой лишний параметр в REST API, например, человек опечатался, то выдавать ему сразу ошибку про неподдерживаемый параметр. Подумает над этим.
                                0
                                >strict режим идея неплохая. Подумает над этим.
                                так, на всякий случай. У себя сделал это через исключения, то есть в стрикт режиме вообще ничего не возвращается, бросает ексепшн. Удобно, не надо дополнительно проверять результаты проверки (не навязываю, просто делюсь опытом)
                                  0
                                  Исключения добавляются достаточно просто, нужно просто обернуть все правила (есть в SYNOPSYS разделе для Perl реализации — metacpan.org/pod/distribution/Validator-LIVR/README.pod ). В Perl реализации выглядит так:
                                  my $default_rules = Validator::LIVR->ger_default_rules();
                                   
                                  while ( my ($rule_name, $rule_builder) = each %$default_rules ) {
                                      Validator::LIVR->register_default_rules($rule_name => sub {
                                          my $rule_validator = $rule_builder->(@_);
                                   
                                          return sub {
                                              my $error = $rule_validator->(@_);
                                              die $error if $error;
                                              return;
                                          }
                                      });
                                  }
                                  


                                  для других реализаций должно работать аналогично
                                +1
                                целиком поддерживаю идею декларативной валидации, функциональщина в этом деле, на мой взгляд — зло.

                                [zanuda_mode]
                                Функциональное программирование — подмножество декларативного программирования.
                                [/zanuda_mode]
                                0
                                Возможно дико туплю, но где регэкспы и как их использовать?
                                  0
                                  См. правило «like»
                                  0
                                  Из спеки:
                                  like

                                  Be aware that regular expressions can be language dependent. Try to use most common syntax.
                                  Вам не кажется, что в таком виде это просто не имеет смысла? Декларируется независимость от языка, но регулярки везде разные, никакого «common syntax» по сути нет, даже в самых тривиальных вещах где-то пишут «a+» а где-то «a\+». Не лучше ли в спеке однозначно объявить что синтаксис регулярок должен быть, например, PCRE?

                                  Ещё вопрос по playground — я не разбирался с чем это связано, но в Firefox/Linux при попытке изменять значения полей в «Data for validation» после нажатия каждой кнопки заметно шуршит винт и firefox грузит CPU. Например, если в поле zip: непрерывно набирать и удалять цифры то винт трещит непрерывно и firefox ест 100% одного ядра CPU. Вроде бы проверка структуры на 5 полей, пусть даже выполняемая после ввода каждого символа, не должна так сильно грузить машину (причём не офисную, а разогнанный на 4.5GHz i7-2600K). Что и зачем при этом пишется на винт вообще не понятно — Вы что, каждое изменение по keyUp сохраняете куда-нить в localstorage?
                                    0
                                    Ответил ниже
                                    0
                                    Относительно регулярок. JS регулярки могут быть несовместимы с PCRE. Кроме того сами регулярки никак спецификацией не регламентированы. То есть, используется движок регулярных выражений, встроенный в язык. Единственное, что можно сделать в спеке — это рекомендовать не использовать очень хитроумные регулярки при описании правил, если хотите сохранить переносимость. Если переносимость не важна, тогда это не проблема.

                                    Относительно подтормаживаний, livr-playground в localstorage ничего не пишет. Можно поиграться с исходниками, доступны на github -https://github.com/WebbyLab/livr-playground.
                                      0
                                      Ну, Вы-ж хотите чтобы один и тот же набор правил идентично выполнялся разными языками. Если за идентичность реализации кастомных проверок отвечают их разработчики, то вот за идентичность регулярок в данный момент отвечает спека. И по текущей спеке получится так, что из-за мелкого отличия в синтаксисе регулярки возникнет необходимость держать две копии правил — для фронта и для сервера… или для RPC сервиса написанного на одном языке и клиента к нему написанном на другом.
                                        0
                                        В теории, если стремиться к полной идентичности, тогда спецификация должна описывать полный синтаксис регулярок (и предоставлять набор тестов для движков регулярных выражений), а каждая реализация должна реализовать свой движок по спецификации. На практике же, использования движка регулярных выражений, встроенного в язык, не создает проблем. Но, на всякий случай, в спеку добавили ремрку «Try to use most common syntax.». Если следовать этому совету, то не нужно будет держать две копии правил.

                                        Если же, на практике есть необходимость в полной идентичности, то всегда можно переопределить правило like, и разрешить в нем только определенный набор символов, например.
                                        0
                                        Может добавить для таких платформозависимых вещей опционально возможность указывать разные версии для разных платформ? Потому что вот только загорелся попробовать, но после замечания выше уже не буду, поскольку точно знаю, что на мои правила приходится писать похожие, но не идентичные регулярки на php и js.
                                          0
                                          Проблема высосана из пальца:
                                          1) Регулярки в PHP, JS, Python и других языках очень близки. Очень редко нужны фичи регулярок, которые доступны только на одной платформе. Ок, в JS регулярках не поддерживается lookbehind, но много ли у вас таких регулярок? За такой совместимостью следить не большая проблема.
                                          2) Просмотрел несколько наших проектов — «like» практически нигде не нужен. Намного удобней добавить свое правило, чтобы не копипастить регулярку по 10 модулям. Например, для проверки суммы, мы не используем «like», а создаем свое правило «positive_amount». Это как вынести повторяющийся код в функцию и дать ей нормальное название. Например, у нас есть такие правила «valid_phone», «valid_url», «alphanumeric», «company_id», «uppercase_latin_symbols». Вместо всех них можно было везде писать было бы «like», но это значительно хуже в поддержке.
                                          3) Определение своих правил позволяет выдавать более информативные ошибки. Например, «positive_amount» у нас возвращает WRONG_AMOUNT_FORMAT, AMOUNT_TOO_SMALL, AMOUNT_TOO_LARGE. Эти ошибки затем локализируются и показываются в виде сообщений пользователю. Это намного информативней, чем просто WRONG_FORMAT, который возвращает «like».
                                          4) Можно написать пару тестов для кастомных правил (да и для бизнес-логики тоже), что будет полезно в любом случае.
                                          5) Можно переопределить «like» (или добавить какое-то свое правило, типа «match») и разрешить принимать регулярки (или просто wildcards) только в определенном формате.
                                          6) Указывать версии для разных платформ не даст особого выигрыша, поскольку очень низка вероятность, что в каждой версии вы решите использовать фичи доступные только на конкретной платформе. Одна из версий у вас будет работать на всех платформах, ее и оставляйте.
                                          7) Мир не идеален, абстракции текут, но здравый смысл всех спасет :). То есть, если не вдаваться в крайности и решать практические задачи, то все работает.
                                          8) Спецификация не является чем-то закрытым — она развивается, принимаются пожелания и замечания. Если при решении практических задач вы упретесь в ограничения LIVR, это будет отличным поводом для развития спецификации.
                                          9) У нас в компании LIVR круто себя показал, даже в тех проектах, где он используется только для бекенда. По принципу «Learn once, use everywhere».
                                          10) Инструмент выбирается из задач и я верю, что есть ситуации, когда LIVR может и не подойти )
                                        0
                                        А это нормально, что до и после email можно вводить любые символы?
                                        пример
                                          0
                                          В целом, да. В email разрешен юникод.
                                            0
                                              0
                                              В JS реализации не идеальная проверка. Хотя Perl-реализации полностью по RFC. Спасибо, добавим пару доп. тестов в тестовый комплект спецификации и попросим авторов реализаций обновить тесты.  
                                                0
                                                Обновили тесты, исправили, проявлялось только в JS реализации.
                                          0
                                          Числа между нулём и единицей почему-то не положительные
                                            0
                                            Вы правы, в случае с десятичными положительные числа начинаются не с единицы :). Спасибо — исправим.
                                              0
                                              Исправили, проявлялось только в JS реализации. Расширили тестовый комлпект для спеки.
                                            0
                                            Иногда встречается ситуация, когда правило валидации нельзя однозначно отнести к конкретному элементу формы. Например, в форме есть email и номер телефона, а для продолжения нужно заполнить хотя бы одно из этих двух полей. Позволяет ли LIVR каким-то образом описать такое правило, и если да, то к какому из полей оно относится?
                                              +1
                                              Да, это возможно и делается достаточно просто:

                                              var LIVR = require('livr');
                                              
                                              var requiredIfFieldEmpty = function(field) {
                                                  return function(value, fields) {
                                                      var isDependentFieldEmpty = fields[field] === null || fields[field] === undefined || fields[field] === '';
                                                      var isTargetFieldEmpty = value === null || value === undefined || value === '';
                                              
                                                      if (isDependentFieldEmpty && isTargetFieldEmpty) {
                                                          return 'REQUIRED'
                                                      }
                                                  }
                                              };
                                              
                                              LIVR.Validator.registerDefaultRules({ required_if_field_empty: requiredIfFieldEmpty});
                                              
                                              var validator = new LIVR.Validator({
                                                   phone: {required_if_field_empty: 'email'},
                                                   email: {required_if_field_empty: 'phone'},
                                              });
                                              
                                                +1
                                                Спасибо, очень интересно.
                                              0
                                              Сейчас согласовываем апдейт к LIVR v0.5 — github.com/koorchik/LIVR/labels/proposal. Оставляйте свои комментарии/замечания.

                                              Из нового, что уже решили добавлять в спеку — правило «any» — github.com/koorchik/LIVR/compare/v0.5.
                                                0
                                                Попробуйте описать этот пример на JSON Schema


                                                Могу ошибаться, но разве такая схема не подходит?
                                                {
                                                    "$schema": "http://json-schema.org/draft-04/schema#",
                                                    "type": "object",
                                                    "properties": {
                                                        "order_id": {
                                                            "type": "integer",
                                                            "minimum": 0,
                                                            "exclusiveMinimum": true
                                                        },
                                                        "product_ids": {
                                                            "type": "array",
                                                            "items": {
                                                                "type": "integer",
                                                                "minimum": 0,
                                                                "exclusiveMinimum": true
                                                            }
                                                        }
                                                    },
                                                    "required": ["order_id", "product_ids"]
                                                }
                                                


                                                При этом, свой тип можно определить в definitions:
                                                {
                                                    "$schema": "http://json-schema.org/draft-04/schema#",
                                                    "type": "object",
                                                    "definitions": {
                                                        "positiveInteger": {
                                                            "type": "integer",
                                                            "minimum": 0,
                                                            "exclusiveMinimum": true
                                                        }
                                                    },
                                                    "properties": {
                                                        "order_id": { "$ref": "#/definitions/positiveInteger" },
                                                        "product_ids": {
                                                            "type": "array",
                                                            "items": { "$ref": "#/definitions/positiveInteger" }
                                                        }
                                                    },
                                                    "required": ["order_id", "product_ids"]
                                                }
                                                


                                                Софта для json-schema больше и он есть для большего количества языков. Да и это более известная и популярная штука, как мне кажется.
                                                  0
                                                  Забыл дописать, а как будет выглядеть, например, подобная схема на LIVR?
                                                  Пример с оф. сайта json-schema
                                                  {
                                                      "id": "http://some.site.somewhere/entry-schema#",
                                                      "$schema": "http://json-schema.org/draft-04/schema#",
                                                      "description": "schema for an fstab entry",
                                                      "type": "object",
                                                      "required": [ "storage" ],
                                                      "properties": {
                                                          "storage": {
                                                              "type": "object",
                                                              "oneOf": [
                                                                  { "$ref": "#/definitions/diskDevice" },
                                                                  { "$ref": "#/definitions/diskUUID" },
                                                                  { "$ref": "#/definitions/nfs" },
                                                                  { "$ref": "#/definitions/tmpfs" }
                                                              ]
                                                          },
                                                          "fstype": {
                                                              "enum": [ "ext3", "ext4", "btrfs" ]
                                                          },
                                                          "options": {
                                                              "type": "array",
                                                              "minItems": 1,
                                                              "items": { "type": "string" },
                                                              "uniqueItems": true
                                                          },
                                                          "readonly": { "type": "boolean" }
                                                      },
                                                      "definitions": {
                                                          "diskDevice": {
                                                              "properties": {
                                                                  "type": { "enum": [ "disk" ] },
                                                                  "device": {
                                                                      "type": "string",
                                                                      "pattern": "^/dev/[^/]+(/[^/]+)*$"
                                                                  }
                                                              },
                                                              "required": [ "type", "device" ],
                                                              "additionalProperties": false
                                                          },
                                                          "diskUUID": {
                                                              "properties": {
                                                                  "type": { "enum": [ "disk" ] },
                                                                  "label": {
                                                                      "type": "string",
                                                                      "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
                                                                  }
                                                              },
                                                              "required": [ "type", "label" ],
                                                              "additionalProperties": false
                                                          },
                                                          "nfs": {
                                                              "properties": {
                                                                  "type": { "enum": [ "nfs" ] },
                                                                  "remotePath": {
                                                                      "type": "string",
                                                                      "pattern": "^(/[^/]+)+$"
                                                                  },
                                                                  "server": {
                                                                      "type": "string",
                                                                      "oneOf": [
                                                                          { "format": "host-name" },
                                                                          { "format": "ipv4" },
                                                                          { "format": "ipv6" }
                                                                      ]
                                                                  }
                                                              },
                                                              "required": [ "type", "server", "remotePath" ],
                                                              "additionalProperties": false
                                                          },
                                                          "tmpfs": {
                                                              "properties": {
                                                                  "type": { "enum": [ "tmpfs" ] },
                                                                  "sizeInMB": {
                                                                      "type": "integer",
                                                                      "minimum": 16,
                                                                      "maximum": 512
                                                                  }
                                                              },
                                                              "required": [ "type", "sizeInMB" ],
                                                              "additionalProperties": false
                                                          }
                                                      }
                                                  }
                                                  

                                                    0
                                                    Относительно этой схемы, то тут нужно будет описать некоторые правила/алиасы (аналог oneOf). Пример хороший, создал тикет github.com/koorchik/LIVR/issues/14, воможно добавим пару правил на уровень спецификации в LIVR 0.5
                                                    0
                                                    Автор написал:
                                                    > Сложный формат для правил. Хочется, чтобы структура с правилами была максимально близка к структуре с данными. Попробуйте описать этот пример на JSON Schema

                                                    Он не ставил под сомнение возможность описать это в JSON Schema, он говорил что получится структура слабо похожая на структуру данных. Что собственно, и было вами доказано. Да и вообще простыня знатная получилась, раза в три длинней чем
                                                    {
                                                        order_id: ['required', 'positive_integer'],
                                                        product_ids: [{
                                                           'list_of': [ 'required',  'positive_integer' ]
                                                        }]
                                                    }
                                                    
                                                      0
                                                      Да, схема многословная, но почему слабо похожая? Здесь поле описывается массивом, там — объектом. При этом в схеме сразу видно, какого типа должно быть поле. В схеме поля объекта находятся по ключу properties, что позволяет добавить дополнительные условия на весь описываемый объект, вроде additionalProperties. Как тут запретить передавать поля, не описанные в схеме. Я увидел только что они просто игнорируются. А как быть, если эти поля нужно сохранять?

                                                      В этом примере мне не понятно, например, описание product_ids. Написано, что это список, состоящий из… А из чего он состоит? Из типа required и типа положительное число? Но required — это же не тип. Т.е. поле products_ids само по себе необязательное, но представляет из себя список, элементы которого обязательные и положительные числа. При этом, пустой список проходит валидацию. Зачем вообще тут нужен required?
                                                      Для меня более понятно было бы, если бы в списке list_of перечислялись допустимые типы в этом списке. Но для списка из разных элементов зачем-то сделан отдельный тип list_of_different_objects. Странно как-то.
                                                        0
                                                        Вам правда соит прочитать статью, такие вопросы возникают по тому что вы не разобравшись пытаетесь применить принципы своего любимого валидатора на livr, а он работает не так.

                                                        Цитирую автора (почти начало статьи):
                                                        Как описываются правила валиции? Каждое правило состоит из имени и аргументов (практически, как вызов функции) и в общем случае описывается следующим образом {«RULE_NAME»: ARRAY_OF_ARGUMENTS}. Для каждого поля описывается массив правил, которые применяются в порядке следования.
                                                          0
                                                          Все зависит от задач. JSON Schema имеет свои сисльные стороны и уникальные возможности. JSON Schema прекрасно подойдет в случаях, когда:
                                                          • Вас не пугает многословный синтаксис
                                                          • Нет потребовности в кастомных правилах со сложной логикой
                                                          • Нет необходимости добавлять филтры, типа trim, to_lc и так далее
                                                          • Нет потребоности в стандартизации ошибок
                                                          • Нет нужно получать ошибки сразу для всех полей, включая вложенные структуры данных.


                                                          Но нужно, например, разрешить неописанные поля.

                                                          Относительно «additionalProperties», то в JSON Schema, по-умолчанию, разрешены все дополнительные поля. Что, я считаю, тоже недостатком:

                                                          Проблема №1. Многие валидаторы проверяют только те данные, для которых описаны правила проверки. Для меня важно, чтобы любой пользовательский ввод, который явно не разрешен, был проигнорирован. То есть, валидатор должен вырезать все данные для которых не описаны правила валидации. Это просто фундаментально требование.


                                                          По поводу правила «list_of», то это не так особенность валидатора, как правила. Валидатор очень гибкий и позволяет реализовать любые правила.

                                                          В общем, основной мой посыл в том, что инструмент подбирается под задачи и есть задачи, где JSON Schema удобней, а есть, где удобней будет LIVR
                                                        0
                                                        Да, все верно. Это совсем простой кейс, по которому можно сравнить описание на LIVR и JSON Schema. На LIVR получается явно лаконичние:

                                                        {
                                                            order_id: ['required', 'positive_integer'],
                                                            product_ids: [{
                                                               'list_of': [ 'required',  'positive_integer' ]
                                                            }]
                                                        }
                                                        


                                                        Кроме того, остается остальные вышеупомянутые проблемы. Относительно софта — тоже верно. Например, пока нет LIVR для Ruby, но я думаю, если будет необходимость, найдутся желающие портировать.
                                                          0
                                                          Да, понятное дело, что лаконичнее. Это несомненно плюс, но мне кажется, из-за такой лаконичности какие-то сложные схемы будет трудно или невозможно описать на LIVR. Например, в json-schema для чисел есть проверка на делимость (multipleOf), для строк — regexp и т.д. И в схеме мне нравится то, что она самодостаточна и набор условий минимален, но из него можно слепить что-то сложное. Можно также вынести все свои типы в какой-то отдельный файл, а и через $ref их использовать.
                                                          В LIVR для сокращения строк кода введены некие готовые правила (например, positive_integer), хотя мы и сами могли бы их описать. А вот, например, negative_integer почему-то нет, это странно. Ещё в схеме есть patternProperties, позволяющий проверять имя ключа по регулярке, это порой бывает нужно.
                                                          В целом, для простых схем LIVR хорош, но сложные — пока что не его конёк.

                                                          Вот про софт ещё такой вопрос: из json-схемы можно генерировать html-формы (есть пара js-библиотек). Есть ли (и возможно ли) что-то подобное для livr?
                                                            0
                                                            «multipleOf» без проблем реализуется, но не хочется добавлять вещи в спеку, которые никто не будет использовать. Если уж возникнет такой случай, то в проекте всегда можно описать свое правило. LIVR легко расширить практически любой функциональностью. Не знаю возможно ли такое в JSON Schema. Относительно описания своих типов и сохранения их в отельный файл, то для этого отлично походят алиасы.

                                                            Относительно генирации html-форм, то можно было бы попробовать написать библиотеку, но на моей практике от таких инструментов для меня никакой пользы не было.
                                                              0
                                                              мне кажется, из-за такой лаконичности какие-то сложные схемы будет трудно или невозможно описать на LIVR

                                                              LIVR это больше чем просто набор стандартных правил, одна из основных идей в том чтоб можно было легко добавлять свои. В стандартной поставке идут только те, которые будут нужны почти всем.

                                                              Например, в json-schema для чисел есть проверка на делимость (multipleOf)
                                                              Ещё в схеме есть patternProperties, позволяющий проверять имя ключа по регулярке, это порой бывает нужно

                                                              Вот примеры правил которые редко бывает нужны. Их можно легко описать самому. При этом правило пишется на обычном языке программирования, и соответственно можно реализовать что угодно (например валидацию, которая зависит от значения получаемого из БД) в отличии от.

                                                              для строк — regexp

                                                              см. правило like.

                                                              Можно также вынести все свои типы в какой-то отдельный файл, а и через $ref их использовать

                                                              Регистрируете свои правила, потом их используете.

                                                              В целом, для простых схем LIVR хорош, но сложные — пока что не его конёк.

                                                              Ну и далее в том же духе. Вы просто не поняли основную идею, возможно стоит еще раз прочитать статью?
                                                                0
                                                                А если зарегистрировать правило на JS, его можно будет использовать в python-коде. Например, есть библиотека с кучей классных правил, но на js, а мой проект на питоне. Смогу ли я использовать эту библиотеку? Как я понял — нет. Тогда где здесь независимость от языка? Или как-то можно взять реализацию того же strongPassword из статьи и каким-то образом использовать её и в программе на других языках (без установки какого либо интерпретатора JS).
                                                                Schema же декларативна, все правила описываются таким же json-кодом, поэтому она реально независима. Я могу взять схему и данные и всегда буду уверен, что на любой реализации результат будет одинаковым.
                                                                  0
                                                                  Да, нужно будет портировать правило на другой язык. На деле редко бывает нужно, да и правила это не огромные простыни кода, портирование которых может вызвать проблемы. Но сама схема при этом останется прежней. В этом и есть независимость. В JsonSchema это достигается по сути изобретением своего языка для описания правил, который во первых очень ограничен, во вторых его еще как-никак нужно учить.
                                                                    0
                                                                    Для такой задачи в LIVR 0.4 были добавлены алиасы. Когда правило можно скомбинировать на базе других, дать ему название и дать свой код ошибки (опционально). Естественно, это не покрывает все ситуации, но многие проблемы решает.
                                                            +1
                                                            Вышел LIVR 2.0 (http://livr-spec.org/) С новых фич:

                                                            Согласованный подход к работе с типами.
                                                            «Base rules» переименовали в «Common rules».
                                                            «Filter rules» переименовали в «Modifiers». Поскольку они ничего не валидируют, а только модифицируют данные.
                                                            «Helper rules» переименовали to «Metarules»
                                                            Добавлено правило «any_object», проверяет, что это объект
                                                            Добавлено правило строка — «string», просто строка любого типа
                                                            Добавлено правило «eq» — проверяет на строковое равенство
                                                            Добавлено метаправило «variable_object». Динамически определяет, какую валидацию использвоать в зависимосте от полей в объекте.
                                                            Добавлено метаправило «or». Позволяет применять правила поочередно до первого совпадения.
                                                            Добавлен модификтор «default» для установки значений по-умолчинаю, если пользователь ничего не передал.
                                                            Значительно расширен комплект тестов.

                                                            JavaScript реализация уже поддерживает все новые функции, остальные реализации в процессе обновления.
                                                              0
                                                              1. Можно ли для поля X задать два и более набора правил?

                                                              2. При этом (простой случай) считаем, что X валидно, если соответствует любому из наборов.

                                                              3. Можно ли задать логику «если поле X соответствует набору правил X1, то поле Y обязано соответствовать набору Y1, если же X соответствует набору X2, то Y — Y2 и.т.д.

                                                              4. При этом (сложный случай) считаем, что X валидно, только если Y тоже валидно, при том, что сам по себе X может быть валидным без учета Y.
                                                                0
                                                                1-2. Да. См. комментарий выше
                                                                > Добавлено метаправило «or». Позволяет применять правила поочередно до первого совпадения.

                                                                3-4. Да. на больших проектах возникает и в более сложных кейсах, это все и многое другое можно реализовать зарегистрировав свое правило.
                                                                0
                                                                Можно ли задать набор одинаковых правил сразу для списка полей? Чтобы не повторять одинаковые правила для каждого поля. При этом для каждого поля по отдельности могут быть заданы еще и уникальные правила.
                                                                  0
                                                                  Да. Одним из способов это сделать является алиасы правил. Регистрируешь свое правило для того что будет одинаково, используешь как обычно, там где надо добавляешь дополнительные правила. См пункт «Работа с псевдонимами и регистрация собственных правил» выше.
                                                                    0
                                                                    Но это означает, что правило/алиас нужно будет всё-таки вписывать в каждое поле?

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

                                                                    правило: [поле, поле, поле...]

                                                                    Т.е. вроде как обратный порядок записи.
                                                                      0

                                                                      Это похоже на "Выстрел дробью". Поддерживать потом такой набор правил...

                                                                        0
                                                                        Какая-то незнакомая мне идиома:)

                                                                        Что не так? При большом количестве полей и небольшом количестве правил так удобнее. Особенно, если поля генерируются на лету, добавить название поля в список проще, чем в поле добавлять правила.
                                                                          0

                                                                          Найти поле и добавить в него несколько правил, проще, чем найти несколько списков и добавить в каждый имя поля.

                                                                        0
                                                                        Нет, так нельзя. Но можно до передачи в валидатор преобразовать эту структуру в то что он понимает.
                                                                          0

                                                                          С таким доходом есть ряд проблем:


                                                                          1. Не ясно, как адресовать вложенные структуры. Можно конечно писать {required: ['address.city']} или {required: ['address/city'], но нужно помнить про массивы и тд.
                                                                          2. Не ясно в каком порядке применяются правила. Например, {required: ['email'], trim: ['email'] }. Конечно можно записывать [{required: ['email']}, {trim: ['email'] }], но все равно остается вопрос приоритета, если у меня уже есть уточняющие правила в основной структуре, типа {email: 'to_lc'}. В каком порядке применятся дополнительные правила? Конечно можно решить, что приоритет такой вот, но это не всем подойдет, во-вторых, нужно всегда помнить про этот приоритет.
                                                                          3. Вопрос безопасности. Если сейчас я удалю валидацию для поля, то поля будет вырезано валидатором и могу быть уверен, что никто ничего в нем не передаст. Если же описывать в перевернутом виде, то нужно будет постоянно помнить, что где-то это поле могло быть упомянуто.

                                                                          Кроме того, можно сделать функцию типа applyCommonRules(livr, commonRules) и просто преобразовывать один формат в другой. Мощь LIVR в том, что это просто структура данных и можно делать с этими данными любые операции. Написать такую функцию займет полчаса времени и позволит увидеть в реальном проекте нужно ли это на уровне спецификации или может быть просто сторонней библиотекой (ну и ее можно зарелизить в opensource)

                                                                    Only users with full accounts can post comments. Log in, please.