Оптимизация проверки формы с помощью схемы JSON для внешнего и внутреннего интерфейса

Оптимизация проверки формы с помощью схемы JSON для внешнего и внутреннего интерфейса

17 марта 2023 г.

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

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

Эти обстоятельства привели нас к поиску решений, удовлетворяющих следующим требованиям:

  1. Единый источник правды. Другими словами, один выделенный файл с правилами проверки для всех потребителей: сервисов, веб-приложений, мобильных приложений и т. д. Потому что на противоположной стороне после успешной проверки внешнего интерфейса сервис может отклонить запрос из-за некорректных входящих данных.
  2. Поддержка условной проверки: например, уникальные правила для полей юридического лица для каждой страны.
  3. Понятный язык продуктовой аналитики. Чтобы иметь возможность изменять правила без инженеров.
  4. Возможность показывать понятные пользователям сообщения об ошибках.

n Описание изображения

Решение

Мы решили использовать схему JSON (проект 7). Это удовлетворяло наши потребности. В двух словах, стандартно он представлен как JSON, который содержит набор правил для некоторых объектов JSON. Теперь мы рассмотрим наиболее распространенные и полезные шаблоны проверки.

Базовый

Начнем с простого примера. Нам нужно проверить только одно поле: оно должно быть обязательным и соответствовать регулярному выражению электронной почты.

n Описание изображения

Наша модель: n

{
   "email": "Steve"
}

И наша схема проверки следующая:

{
   "type": "object",
   "properties": {
       "email": {
           "type": "string",
           "pattern": "(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[x01-x08x0bx0cx0e-x1fx21x23-x5bx5d-x7f]|[x01-x09x0bx0cx0e-x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])).){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[x01-x08x0bx0cx0e-x1fx21-x5ax53-x7f]|[x01-x09x0bx0cx0e-x7f])+)])",
           "errorMessage": "Can be only in name@domain.com"
       }
   },
   "required": ["email"]
}

Условные поля

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

n Описание изображения

Давайте рассмотрим конкретный случай.

Здесь каждая страна должна применять уникальную проверку для номера плательщика НДС.

  1. Для Великобритании это может быть только: GB000000000(000), GBGD000 или GBHA000.
  2. Для России: ровно 9 цифр и ничего больше
  3. Для других стран мы пока не применяем проверки. (поскольку мы собираемся расширять это по частям)

Модель немного сложнее.

Теперь у нас есть страна:

{
   "name": "Samsung Ltd.",
   "country": {
       "id": "GB",
       "name": "United Kingdom"
   },
   "vatNumber": "314685"
}

Для выполнения условной проверки мы будем использовать конструкцию allOf, а также блоки if и then. Обратите внимание на поле обязательное в блоке if. Это должно быть здесь. В противном случае это не сработает. п

{
   "type": "object",
   "properties": {
       "name": {
           "type": "string"
       },
       "vatNumber": {
           "type": "string"
       }
   },
   "required": [
       "vatNumber",
       "name"
   ],
   "allOf": [
       {
           "if": {
               "properties": {
                   "country": {
                       "properties": {
                         "id": {"const": "GB"}
                       }
                   }
               },
               "required": ["country"]
           },
           "then": {
               "properties": {
                   "vatNumber": {
                       "pattern": "^GB([d]{9}|[d]{12}|GD[d]{3}|HA[d]{3})$",
                       "errorMessage": "Can be GB000000000(000), GBGD000 or GBHA000"
                   }
               }
           }
       },
       {
           "if": {
               "properties": {
                   "country": {
                       "properties": {
                           "id": {"const": "RU"}
                       }
                   }
               },
               "required": ["country"]
           },
           "then": {
               "properties": {
                   "vatNumber": {
                       "pattern": "^[0-9]{9}$",
                       "errorMessage": "Can be only 9 digits"
                   }
               }
           }
       }
   ]
}

Один или все

Иногда нам нужно заполнить хотя бы одно поле. В качестве примера из реальной жизни, чтобы осуществлять платежи в Великобритании, вы должны знать BIC/SWIFT или номера кодов сортировки банка. Если вы знаете и то, и другое — отлично! Но по крайней мере один является обязательным.

n Описание изображения

Для этого воспользуемся конструкцией anyOf. Как вы заметили, это второе ключевое слово после allOf.

Просто чтобы прояснить их все:

  1. allOf — ВСЕ операторы должны быть действительными
  2. oneOf — допустимо ТОЛЬКО ОДНО утверждение. Если больше или ничего не получается
  3. anyOf – допустимо ОДНО ИЛИ НЕСКОЛЬКО утверждений

Наша модель следующая:

{
   "swiftBic": "",
   "sortCode": "402030"
}

И схема проверки:

{
   "type": "object",
   "anyOf": [
       {
           "required": ["swiftBic"]
       },
       {
           "required": ["sortCode"]
       }
   ]
}

Реализация на JavaScript

Схема JSON поддерживается многими языками. Однако наиболее исследованной мной была версия JavaScript.

Мы взяли библиотеку ajv как самую быструю. Он не зависит от платформы. Другими словами, вы можете использовать его во внешних приложениях с любым фреймворком и в Node.JS. Кроме того, ajv позволяет использовать собственные сообщения об ошибках. Потому что, к сожалению, они не поддерживаются стандартами.

Прежде чем мы начнем, нам нужно добавить 2 зависимости: ajv и ajv-errors.

import Ajv from 'ajv';
import connectWithErrorsLibrary from 'ajv-errors';

const ajv = new Ajv({
   // 1. The error message is custom property, we have to disable strict mode firstly
   strict: false,
   // 2. This property enables custom error messages
   allErrors: true
});
// 3. We have to connect an additional library for this
connectWithErrorsLibrary(ajv);

// 4. Our model
const dto = { dunsNumber: 'abc' };

// 5. Validation schema
const schema = {
   type: 'object',
   properties: {
       dunsNumber: {
           type: 'string',
           pattern: '^[0-9]{9}$',
           errorMessage: 'Can be only 9 digits'
       }
   },
   required: ['dunsNumber']
};

// 6. Set up validation container
const validate = ajv.compile(schema);

// 7. Perform validation.
// ... It's not straightforward, but the result will be inside the "error" property
validate(dto);

console.log('field error:', validate.errors);

В результате мы получим:

[
    {
        "instancePath": "/dunsNumber",
        "schemaPath": "#/properties/dunsNumber/errorMessage",
        "keyword": "errorMessage",
        "params": {
            "errors": [
                {
                    "instancePath": "/dunsNumber",
                    "schemaPath": "#/properties/dunsNumber/pattern",
                    "keyword": "pattern",
                    "params": {
                        "pattern": "^[0-9]{9}$"
                    },
                    "message": "must match pattern "^[0-9]{9}$"",
                    "emUsed": true
                }
            ]
        },
        "message": "Can be only 9 digits"
    }
]

И в зависимости от нашей реализации формы мы можем получить ошибку и поместить ее в недопустимые поля.

Заключение

Для выполнения проверки, которая описана в одном месте, мы использовали схему JSON. Более того, мы столкнулись с такими случаями, как условная проверка, выборочная проверка и базовая проверка.

Спасибо за прочтение! ✨


Также опубликовано здесь


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE