Git Product home page Git Product logo

meta-validate's Introduction

meta-validate

Декораторы для валидации классов.

Общие принципы:

1 - валидаторы складываются в цепочку.

Цепочка всегда начинается с @MetaValidate. и заканчивается на .make()

2 - ошибки складываются в объект следующего вида:

{
    errors: {
        %ПОЛЕ%: {
            %КЛЮЧ_ОШИБКИ_1%: boolean,
            %КЛЮЧ_ОШИБКИ_2%: boolean,
        },
        %ПОЛЕ_КОМПОЗИЦИЯ%: {
            %ПОЛЕ_КЛАССА_КОМПОЗИЦИИ_1%: {
                %КЛЮЧ_ОШИБКИ_1%: boolean
                ...
            }
        }
    }
}

3 - есть пренастроенные классы валидаторов и можно будет регистрировать свои(скоро)

Это значит, что сам по себе @MetaValidate просто хранилище всяких классов с валидаторами. Сейчас их несколько:

  • Number<T> - класс с частыми валидаторами чисел
  • String<T> - класс с частыми валидаторами строк
  • Trigger - класс-триггер, нужен для связывания полей
  • Nested - класс, обозначающий что поле содержит сложный экземпляр, который тоже умеет валидироваться
  • Base - класс с базовыми методами, предполагающий использование только кастомных валидаторов Дженерики принимают класс, поля которого вы валидируете.

4 - ваши классы должны реализовывать интерфейс ReceiveValidity

В вашем классе обязательно должно быть поле validity$: Subject<Validity>, в который будут валиться ошибки.

5 - семантика валидаторов.

Валидаторы возвращают false, если значение валидно. true - если есть ошибка. Т.е. валидатор отвечает на вопрос - есть-ли ошибка?

Это сделано для того, чтобы использовать объект с ошибками в шаблонах без всяких плясок вокруг ng-if. Пишем <span class="error" ng-if="validity.field.min">Ошибочка min, маловато ввели</span>

6 - преднастроенные валидаторы умеют принимать как значение, так и функцию от экземпляра

7 - валидаторы запускаются на каждый чих над полем или связанными полями

Базовое использование

Пример

class Foo {
    validity$: BehaviorSubject<Validity> = new BehaviorSubject();

    @MetaValidate.MVString<Foo>().required().make()
    bar: number = null;
}

Здесь

  • @MetaValidate - тот самый базовый декоратор
  • MVString - один из преднастроенных классов с валидаторами
  • .make() - метод, делающий декорацию

Базовый класс валидаторов и его методы

Базовый класс валидаторов - от него наследуются все конкретные классы с валидаторами.

Содержит общие методы, которые нужны всем.

Список методов

  • required() - делает поле обязательным к проверке. Ключ ошибки: required
  • skipIf(condition: (instance) => boolean) - метод, который можно повесить после валидатора, он запустит переданную функцию-условие с экземпляром вашего класса в качестве аргумента, и в зависимости от этого запустит или нет валидатор для предыдущего поля. В случае, если функция-условие вернет true - валидатор не будет запущен, ошибка будет false для него.
  • skip(condition: (instance) => boolean) - метод, который обычно стоит вешать первым в цепочке, по условию определяет - запускать все валидаторы на поле или игнорировать его. В случае, если функция-условие вернет true - ни один валидатор для поля не будет запущен, во всех ключах ошибок будет false.
  • with(fields: Array<string>) - список полей, с изменением которых будут вызваны проверки данного поля. Эти зависимые поля должны быть либо так же валидируемы, либо если они не требуют специальной валидации - декорированы специальным классом Trigger
  • custom(name: string, validator: (value: any, instance: any) => boolean) - кастомный валидатор, который можно повесть на поле. Обязательно должен возвращать boolean, в любом случае.
  • make() - конечный метод цепочки, декорирует свойство

Класс String

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

@MetaValidate.String<T>()...make()
field: string = null;

Валидаторы:

  • minLength(len: number | (instance) => number) - минимальная длина значения должна быть len. Ключ ошибки - minLength
  • maxLength(len: number | (instance) => number) - максимальная длина значения должна быть len. Ключ ошибки - maxLength
  • length(len: number | (instance) => number) - Длина значения должна быть строго len. Ключ ошибки - length
  • regex(pattern: RegExp | (instance) => RegExp, name) - Значение должно соответствовать регулярке. Рекомендуется не забывать в регулярке ^$. Ключ ошибки - переданный name
  • alphanum - Поле должно содержать только цифры и буквы. Ключ ошибки - alphanum
  • token - Поле должно содержать только цифры, буквы, тире и подчеркивание. Ключ ошибки - token

Класс Number

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

@MetaValidate.Number<T>()...make()

Преднастроенные валидаторы пытаются привести значение к числу. Т.е. например ng-model инпута может быть забито на поле с этими валидаторами, и они будут приовдить значение к числу. Но только для проверки. Приведение для хранения - пока ваша задача. Скоро будет метод convert в цепочке валидаторов, он будет отвечать за приведение

Валидаторы:

  • min(len: number | (instance) => number) - число должно быть строго больше переданного значения
  • greater(len: number | (instance) => number) - число должно быть больше или равно переданного значения
  • max(len: number | (instance) => number) - число должно быть строго меньше переданныого значения
  • less(len: number | (instance) => number) - число должно быть меньше или равно переданного значения
  • integer() - число должно быть без знаков после запятой
  • negative() - число должно быть отрицательным
  • positive() - число должно быть положительным
  • divideBy(arg: number | (instance) => number) - число делится на переданный аргумент

Объект с ошибками Validity

Вид объекта представлен выше в документации. У него есть дополнительный метод .isFullValid(ignoreFields: Array<string>) - вернет true, если все поля валидны. Исключает поля из списка ignoreFields. Они могут быть вложенными, например: document.number

###Пример использования

import { MetaValidate, ReceiveValidity, Validity } from 'meta-validate';

class Document implements ReceiveValidity {
    validity$: BehaviorSubject<Validity> = new BehaviorSubject(null);

    @MetaValidate.Trigger().make()
    docType: string = null;

    @MetaValidate.String<Document>()
    .with(['docType'])
    .required().skipIf(instance => instance.docType === 'dumb')
    .alphanum()
    .custom('length', (value, instance) => instance.docType === 'pass_rf' ? !value || value.length !== 4 : !value || value.length === 0)
    .make()
    docNumber: string = null;

    @MetaValidate.String<Document>()
    .with(['docType'])
    .required()
    .minLength(2)
    .maxLength(4)
    .make()
    docSeries: string = null;

    @MetaValidate.String<Document>()
    .skip(instance => instance.docType !== 'zagranpasport')
    .length(9)
    .make()
    additionalDocumentNumber: string = null;
}

class Customer implements ReceiveValidity {
    validity$: BehaviorSubject<Validity> = new BehaviorSubject();

    @MetaValidate.Nested().make()
    document: Document = new Document();

    @MetaValidate.String<Customer>().required().make()
    name: string = null;
}

Объект ошибок будет следующим:

customerErrors = {
    errors: {
        document: {
            docNumber: {
                required: true,
                alphanum: true,
                length: true
            },
            docSeried: {
                required: true,
                minLength: true,
                maxLength: true
            },
            additionalDocumentNumber: {
                length: true
            }
        }
        name: {
            required: true
        }
    }
}

Пользоваться следующим образом:

const customer = new Customer();
customer.validity$.subscribe((validity: Validity) => {
    this.errors = validity.errors;
    this.isFullValid = validity.isFullValid();
});

meta-validate's People

Contributors

soeasy avatar

Stargazers

 avatar

Watchers

 avatar  avatar  avatar

meta-validate's Issues

registerPartialValidity

Как-нибудь реализовать добавление частичных валидностей.
Зачем и как это будет использоваться?
Пусть некая форма содержит пофдормы и в пользовательстком интерфейсе реализована шагами. Например первый шаг - ФИО, второй - данные паспорта, третий шаг - данные банковского счета. Интерфейс не должен допускать переход к следующему шагу, если на текущем шаге есть ошибки. В этом случае можно добавлять частичные валидности, включающие в себя только определенные поля.

Другой пример: в форме в определенных условиях значимым является телефон, а в других - email. Соответственно, видно всегда только одно поле из двух, и полная валидность формы зависит от того, какое поле сейчас значимо.
Сейчас это решается путем передачи в метод .isFullValid аргументов исключаемых полей:

// if email is required
const submitAvailable = validity.isFullValid(['phone']);
// if phone is required
const submitAvailable = validity.isFullValid(['email'])

Предлагаемое решение - добавить две частичных валидности, исключающие определенные поля.

Какой интерфейс будет у этого решения?
В validity$ помимо полей errors и .isFullValid() добавляется partialValidity, которое в сущности должно заменить isFullValid.

Для его конфигурации должно быть реализовано что-то типа такого:

PartialValidity('validityOne').fromFull().exclude('f1').exclude('f2')...make()
PartialValidity('validityTwo').fromNull().include('f1').include('f2')...make()

fromFull(validityName: string) - метод, создающий частичную валидность validityName, включающую все поля формы. Возвращает объект с методом exclude(fieldName), который будет исключать некоторые поля из формы.
fromNull(validityName: string) - метод, создающий частичную валидность validityName, изначально совсем пустую. Возвращает объект с методом include(fieldName), который будет включать некоторые поля формы.

Для приведенных выше примеров форм это будет работать так:

// Форма с шагами
PartialValidity('fioValidity').fromNull().include('firstName').include('lastName').include('middleName').make();
PartialValidity('documentValidity').fromNull().include('docType').include('series').include('number').make();
PartialValidity('bankValidity').fromNull().inclide('bic').include('account').make();

// Форма с меняющимся значимым полем
PartialValidity('whenEmailValidity').fromFull().exclude('phone').make();
PartialValidity('whenPhoneValidity').fromFull().exclude('email').make();

Не делать дескриптор на прототип

function ClassDecoratorImpl(...decoratorArgs: Array): ClassDecorator {
console.log('injectable works fine');
return function(target) {
// common code для переопределения конструктора
const originalConstructor = target;

function construct(constructor: any, args: Array<any>): any {
  // tslint:disable-next-line
  const c: any = function () {
    // tslint:disable-next-line
    return constructor.apply(this, args);
  };

  c.prototype = constructor.prototype;
  return new c();
}

// tslint:disable-next-line
const newConstructor = function (...args) {
  const newInstance = construct(originalConstructor, args);
  for (let key of Object.keys(newInstance)) {
    let keyValue = newInstance[key];
    Object.defineProperty(newInstance, key, {
      enumerable: true,
      configurable: true,
      get() {
        return keyValue;
      },
      set(value) {
        keyValue = value;
      }
    });
  }
  return newInstance;
};

newConstructor.prototype = originalConstructor.prototype;

return newConstructor;

}
}

@ClassDecoratorImpl()
class A {
foo: number = 1;
}

const a = new A();
console.log(a);

fullValid изменить проверку

Сейчас проверка isFullValid в качестве условия входа вглубь использует "instanceof boolean" - изменить, потому что иногда невнимательно написанные валидаторы могут вернуть не boolean

MetaValidate drop object properties

When we decorate some property - them drop from Object.keys.
Actually here exists in prototype, not in instance. This is interrupt to copy objects

add to builder-chain param 'async'

Add it to run validators synchronously or asynchronously with setter.
async: true
set value
return eventloop
run validators

async: false
set value
run validators
return eventloop

pristine/dirty

Возможно стоит добавить pristine/dirty для полей

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.