Куперс

Бухучет и анализ

Что значит ошибка валидации?

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

К часто встречающимся ошибкам валидации относятся:

  • отсутствие тега Doctype, который нужен для того, чтобы браузер мог корректно определить тип загружаемого им документа;
  • проблемы с конвертацией специальных символов;
  • использование блочных элементов внутри строчных;
  • незакрытые элементы (нарушение вложенности), неверное количество кавычек;
  • отсутствие атрибута alt у присутствующих на странице изображений;
  • HTML-элемент в неправильном разделе;
  • отсутствие обязательных тегов в структуре элементов;
  • теги, которые должны быть в единичном виде в пределах отдельной веб-страницы, повторяются несколько раз.

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

Спасибо. Но дело в чем то другом…
Открыла «Все функции — Регистры сведений — Фамилия,имя, отчество физического лица», там заполнены все Фамилии, Имена и Отчества.
Вот так выглядит ошибка полностью:
Протокол ФЛК 1 Ошибка валидации файла, проверьте правильность элементов в файле. Атрибут «Фамилия» недействителен: значение «» недействительно с зрения его типа данных «String» — Фактическая длина меньше значения MinLength.
2 Ошибка валидации файла, проверьте правильность элементов в файле. Атрибут «Имя» недействителен: значение «» недействительно с зрения его типа данных «String» — Фактическая длина меньше значения MinLength.
3 Ошибка валидации файла, проверьте правильность элементов в файле. Атрибут «Фамилия» недействителен: значение «» недействительно с зрения его типа данных «String» — Фактическая длина меньше значения MinLength.
4 Ошибка валидации файла, проверьте правильность элементов в файле. Атрибут «Имя» недействителен: значение «» недействительно с зрения его типа данных «String» — Фактическая длина меньше значения MinLength.
5 Ошибка валидации файла, проверьте правильность элементов в файле. Атрибут «Фамилия» недействителен: значение «» недействительно с зрения его типа данных «String» — Фактическая длина меньше значения MinLength.
6 Ошибка валидации файла, проверьте правильность элементов в файле. Атрибут «Имя» недействителен: значение «» недействительно с зрения его типа данных «String» — Фактическая длина меньше значения MinLength.
7 Ошибка валидации файла, проверьте правильность элементов в файле. Атрибут «Фамилия» недействителен: значение «» недействительно с зрения его типа данных «String» — Фактическая длина меньше значения MinLength.
8 Ошибка валидации файла, проверьте правильность элементов в файле. Атрибут «Имя» недействителен: значение «» недействительно с зрения его типа данных «String» — Фактическая длина меньше значения MinLength.
9 Ошибка валидации файла, проверьте правильность элементов в файле. Атрибут «Фамилия» недействителен: значение «» недействительно с зрения его типа данных «String» — Фактическая длина меньше значения MinLength.
10 Ошибка валидации файла, проверьте правильность элементов в файле. Атрибут «Имя» недействителен: значение «» недействительно с зрения его типа данных «String» — Фактическая длина меньше значения MinLength.
11 Атрибут «Фамилия» недействителен: значение «» недействительно с зрения его типа данных «String» — Фактическая длина меньше значения MinLength.
12 Атрибут «Имя» недействителен: значение «» недействительно с зрения его типа данных «String» — Фактическая длина меньше значения MinLength.
13 Атрибут «Фамилия» недействителен: значение «» недействительно с зрения его типа данных «String» — Фактическая длина меньше значения MinLength.
14 Атрибут «Имя» недействителен: значение «» недействительно с зрения его типа данных «String» — Фактическая длина меньше значения MinLength.
15 Атрибут «Фамилия» недействителен: значение «» недействительно с зрения его типа данных «String» — Фактическая длина меньше значения MinLength.
16 Атрибут «Имя» недействителен: значение «» недействительно с зрения его типа данных «String» — Фактическая длина меньше значения MinLength.
17 Атрибут «Фамилия» недействителен: значение «» недействительно с зрения его типа данных «String» — Фактическая длина меньше значения MinLength.
18 Атрибут «Имя» недействителен: значение «» недействительно с зрения его типа данных «String» — Фактическая длина меньше значения MinLength.
19 Атрибут «Фамилия» недействителен: значение «» недействительно с зрения его типа данных «String» — Фактическая длина меньше значения MinLength.
20 Атрибут «Имя» недействителен: значение «» недействительно с зрения его типа данных «String» — Фактическая длина меньше значения MinLength.

История о том, как потратить два дня на многократное переписывание одного и того же кода.

Вступление

Немного предыстории

Сейчас я единственный backend разработчик (именно, пишущий код) на проекте. Функциональность — не суть, но ключевая сущность — это довольно длинная анкета с личными данными. Скорость работы и качество кода завязано на моём малом опыте самостоятельной работы над проектами с нуля, ещё более малом опыте работы с JS (всего 4й месяц) и попутно, очень криво-косо, пишу на TypeScript (далее — TS). Сроки сжаты, булки сжаты, постоянно прилетают правки и получается сначала писать код бизнес-логики, а потом сверху интерфейсы. Тем не менее, технический долг способен догнать и настучать по шапке, что, примерно, с нами и случилось.

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

Проблема

В качестве абстрактного примера выступит простая анкета пользователя.

  • Первый Нулевой шаг хорошего разработчика: описать данные написать тесты;
  • Первый шаг: написать тесты описать данные;
  • ну и так далее.

Допустим, на этот код уже написаны тесты, осталось описать данные:

interface IUser { name: string; age: number; phone: string | number; } const aleg: IUser = { name: ‘Aleg’, age: 45, phone: ‘79001231212’ };

Чтож, тут всё понятно и предельно просто. Весь этот код, как мы помним, на бэкэнде, а точнее, в api, то есть пользователь создаётся на основе данных, которые пришли по сети. Таким образом, нам нужно сделать валидацию входящих данных и поможет в этом Joi:

const joiUserValidator = { name: Joi.string(), age: Joi.number(), phone: Joi.alternatives() };

Решение «в лоб» готово. Очевидный минус такого подхода — валидатор полностью оторван от интерфейса. Если в процессе жизни приложения изменятся / добавятся поля или поменяется их тип, то данное изменение надо будет вручную отследить и указать в валидаторе. Думаю, таких ответственных разработчиков не будет до тех пор, пока что-то не упадёт. Кроме того, в нашем проекте, анкета состоит из 50+ полей на трёх уровнях вложенности и разбираться в этом крайне сложно, даже зная всё наизусть.

Просто указать const joiUserValidator: IUser мы не можем, потому что Joi использует свои типы данных, что порождает при компиляции ошибки вида Type ‘NumberSchema’ is not assignable to type ‘number’. Но ведь должен быть способ выполнить валидацию по интерфейсу?

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

type ValidatedValueType<T extends joi.Schema> = T extends joi.StringSchema ? string : T extends joi.NumberSchema ? number : T extends joi.BooleanSchema ? boolean : T extends joi.ObjectSchema ? ValidatedObjectType<T> : /* … more schemata … */ never;

Решение

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

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

Получить все свойства

Поскольку со статикой ранее дел я не имел, вышеуказанный код открыл Америку в плане применения тернарных операторов в типах. К счастью, применить его в проекте не удалось. Зато нашёл другой интересный велосипед:

interface IUser { name: string; age: number; phone: string | number; } type UserKeys<T> = { ; } const evan: UserKeys<IUser> = { name: ‘Evan’, age: 32, phone: 791234567890 }; const joiUser: UserKeys<IUser> = { name: Joi.string(), age: Joi.number(), phone: Joi.alternatives() };

TypeScript при довольно хитрых и загадочных условиях позволяет получить, например, ключи из интерфейса, словно это нормальный JS-объект, правда, только в конструкции type и через key in keyof T и только через дженерики. В результате работы типа UserKeys, у всех объектов, реализующих интерфейсы, должен быть одинаковый набор свойств, но при этом типы значений могут быть произвольные. Это включает подсказки в IDE, но всё ещё не даёт однозначно обозначить типы значений.

Здесь есть ещё один интересный кейс, который не смог использовать. Возможно, вы подскажете зачем это нужно (хотя я частично догадываюсь, не хватает прикладного примера):

interface IUser { name: string; age: number; phone: string | number; } interface IUserJoi { name: Joi.StringSchema, age: Joi.NumberSchema, phone: Joi.AlternativesSchema } type UserKeys<T> = { : T; } const evan: UserKeys<IUser> = { name: ‘Evan’, age: 32, phone: 791234567890 }; const userJoiValidator: UserKeys<IUserJoi> = { name: Joi.string(), age: Joi.number(), phone: Joi.alternatives() };

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

Можно явно задать типы, а используя «ИЛИ» и извлечение свойств, получить локально работоспособный код:

type TString = string | Joi.StringSchema; type TNumber = number | Joi.NumberSchema; type TStdAlter = TString | TNumber; type TAlter = TStdAlter | Joi.AlternativesSchema; export interface IUser { name: TString; age: TNumber; phone: TAlter; } type UserKeys<T> = { ; } const olex: UserKeys<IUser> = { name: ‘Olex’, age: 67, phone: ‘79998887766’ }; const joiUser: UserKeys<IUser> = { name: Joi.string(), age: Joi.number(), phone: Joi.alternatives() };

Проблема этого кода проявляется когда мы хотим забрать валидный объект, например, из базы, то есть TS заранее не знает какого типа данные будут — простые или Joi. Это может вызвать ошибку при попытке выполнить математические операции с полем, которое ожидается как number:

const someUser: IUser = getUserFromDB({ name: ‘Aleg’ }); const someWeirdMath = someUser.age % 10; // error TS2362: The left-hand side of an arithmetic operation must be of type’any’, ‘number’, ‘bigint’ or an enum type

Данная ошибка приходит из Joi.NumberSchema потому что возраст может быть не только number. За что боролись на то и напоролись.

Соединить два решения в одно?

Где-то к этому моменту рабочий день подходил к логическому завершению. Я перевёл дух, выпил кофе и стёр эту порнографию к чертям. Надо меньше читать эти ваши интернеты! Настало время взять дробовик и пораскинуть мозгами:

  1. Объект должен формироваться с явными типами значений;
  2. Можно использовать дженерики, чтобы прокидывать типы в один интерфейс;
  3. Дженерики поддерживают типы по умолчанию;
  4. Конструкция type явно способна на что-то ещё.

Пишем интерфейс-дженерик с типами по умолчанию:

interface IUser < TName = string, TAge = number, TAlt = string | number > { name: TName; age: TAge; phone: TAlt; }

Для Joi можно было бы создать второй интерфейс, наследовав основной таким образом:

interface IUserJoi extends IUser < Joi.StringSchema, Joi.NumberSchema, Joi.AlternativesSchema > {}

Недостаточно хорошо, ведь следующий разработчик может с лёгким сердцем расширить IUserJoi или что похуже. Более ограниченный вариант получить похожее поведение:

type IUserJoi = IUser<Joi.StringSchema, Joi.NumberSchema, Joi.AlternativesSchema>;

Пробуем:

const aleg: IUser = { name: ‘Aleg’, age: 45, phone: ‘79001231212’ }; const joiUser: IUserJoi = { name: Joi.string(), age: Joi.number(), phone: Joi.alternatives() };

UPD:
Для оборачивания в Joi.object пришлось побороться с ошибкой TS2345 и самым простым решение оказался as any. Думаю, это не критичное допущение, ведь выше объект всё равно по интерфейсу.

const joiUserInfo = { info: Joi.object(joiUser as any).required() };

Компилится, на месте использования выглядит аккуратно и при отсутствии особых условий всегда устанавливает типы по умолчанию! Красота…

… на что я потратил два рабочих дня

Резюмирование

Какие выводы из всего этого можно сделать:

  1. Очевидно, я не научился находить ответы на вопросы. Наверняка при удачном запросе это решение (а то и ещё лучше) находится в первой 5ке ссылок поисковика;
  2. Переключиться на статическое мышление с динамического не так просто, гораздо чаще я просто забиваю на такое копошение;
  3. Дженерики — крутая штука. На хабре и стековерфлоу полно велосипедов неочевидных решений для построения сильной типизации… вне рантайма.

Что мы выиграли:

  1. При изменении интерфейса отваливается весь код, включая валидатор;
  2. В редакторе появились подсказки по именам свойств и типам значений объекта для написания валидатора;
  3. Отсутствие непонятных сторонних библиотек для тех же целей;
  4. Правила Joi будут применяться только там, где это нужно, в остальных случаях — дефолтные типы;
  5. Если кто-то захочет поменять тип значения какого-то свойства, то при правильной организации кода, он попадёт в то место, где вместе собраны все типы, связанные с этим свойством;
  6. Научились красиво и просто скрывать дженерики за абстракцией type, визуально разгружая код от монструзоных конструкций.

Мораль: Опыт бесценен, для остального есть карта «Мир».

Посмотреть, пощупать, запустить итоговый результат можно:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Наверх