Валидация по TypeScript interface с использованием Joi
История о том, как потратить два дня на многократное переписывание одного и того же кода.
Вступление
Немного предыстории
Сейчас я единственный backend разработчик (именно, пишущий код) на проекте. Функциональность — не суть, но ключевая сущность — это довольно длинная анкета с личными данными. Скорость работы и качество кода завязано на моём малом опыте самостоятельной работы над проектами с нуля, ещё более малом опыте работы с JS (всего 4й месяц) и попутно, очень криво-косо, пишу на TypeScript (далее — TS). Сроки сжаты, булки сжаты, постоянно прилетают правки и получается сначала писать код бизнес-логики, а потом сверху интерфейсы. Тем не менее, технический долг способен догнать и настучать по шапке, что, примерно, с нами и случилось.
После 3х месяцев работы над проектом, договорился наконец с коллегами о переходе на единый словарь, чтобы везде свойства объекта назывались и писались одинаково. Под это дело, разумеется, взялся писать интерфейс и плотно застрял с ним на два рабочих дня.
Проблема
В качестве абстрактного примера выступит простая анкета пользователя.
Допустим, на этот код уже написаны тесты, осталось описать данные:
Чтож, тут всё понятно и предельно просто. Весь этот код, как мы помним, на бэкэнде, а точнее, в api, то есть пользователь создаётся на основе данных, которые пришли по сети. Таким образом, нам нужно сделать валидацию входящих данных и поможет в этом Joi:
Решение «в лоб» готово. Очевидный минус такого подхода — валидатор полностью оторван от интерфейса. Если в процессе жизни приложения изменятся / добавятся поля или поменяется их тип, то данное изменение надо будет вручную отследить и указать в валидаторе. Думаю, таких ответственных разработчиков не будет до тех пор, пока что-то не упадёт. Кроме того, в нашем проекте, анкета состоит из 50+ полей на трёх уровнях вложенности и разбираться в этом крайне сложно, даже зная всё наизусть.
Возможно, я неправильно гуглил, или плохо изучал ответы, но все решения сводились к либе extractTypes и каким-то лютым велосипедам, типа такого:
Решение
Использовать сторонние библиотеки
Почему бы нет. Когда я вопрошал к людям со своей задачей, то получил в одном из ответов, а позже, и тут, в комментариях (спасибо keenondrums ), ссылки на данные библиотеки:
https://github.com/typestack/class-validator
https://github.com/typestack/class-transformer
Однако, был интерес разобраться самому, понять лучше работу TS, да и ничего не поджимало решить задачу сиюминутно.
Получить все свойства
Поскольку со статикой ранее дел я не имел, вышеуказанный код открыл Америку в плане применения тернарных операторов в типах. К счастью, применить его в проекте не удалось. Зато нашёл другой интересный велосипед:
Здесь есть ещё один интересный кейс, который не смог использовать. Возможно, вы подскажете зачем это нужно (хотя я частично догадываюсь, не хватает прикладного примера):
Использовать вариативные типы
Можно явно задать типы, а используя «ИЛИ» и извлечение свойств, получить локально работоспособный код:
Проблема этого кода проявляется когда мы хотим забрать валидный объект, например, из базы, то есть TS заранее не знает какого типа данные будут — простые или Joi. Это может вызвать ошибку при попытке выполнить математические операции с полем, которое ожидается как number :
Соединить два решения в одно?
Пишем интерфейс-дженерик с типами по умолчанию:
Для Joi можно было бы создать второй интерфейс, наследовав основной таким образом:
Недостаточно хорошо, ведь следующий разработчик может с лёгким сердцем расширить IUserJoi или что похуже. Более ограниченный вариант получить похожее поведение:
Компилится, на месте использования выглядит аккуратно и при отсутствии особых условий всегда устанавливает типы по умолчанию! Красота… 
… на что я потратил два рабочих дня
Резюмирование
Какие выводы из всего этого можно сделать:
Что мы выиграли:
Мораль: Опыт бесценен, для остального есть карта «Мир».
Посмотреть, пощупать, запустить итоговый результат можно:
https://repl.it/@Melodyn/Joi-by-interface
Joi cei что это такое
Смотреть что такое «JOI» в других словарях:
joi — s.f. Ziua a patra a săptămânii, care urmează după miercuri. *(pop.) Joia Mare = ultima joi din postul3 Paştilor. *expr. De joi până mai (de )apoi = la nesfârşit, mereu; niciodată. Din joi în Paşti = din când în când, foarte rar. ♦ (Adverbial, în… … Dicționar Român
Joi — may refer to: *Joi (band), British/Bengali trance band *Joi (singer), an American R B singer from Atlanta *Joi (television), an Italian television channel … Wikipedia
JOI — Jewish Outreach Institute (Community » Religion) * Joint Oceanographic Institutions, Inc. (Academic & Science » Ocean Science) * Jupiter Orbit Insertion (Governmental » NASA) * Joinville, SC, Brazil (Regional » Airport Codes) * Join conferences… … Abbreviations dictionary
JOI — • Jupiter Orbit Injection (der Galileo Raumsonde) Astronomie • Joinville, Brazil internationale Flughafen Kennung … Acronyms
joi — albigeois anti bourgeois bourgeois brandebourgeois franc bourgeois grégeois hambourgeois joie liégeois luxembourgeois montjoie petit bourgeois rabat joie rougeoie strasbourgeois villageois wurtembergeois … Dictionnaire des rimes
JOI — Justizoberinspektor EN senior court inspector … Abkürzungen und Akronyme in der deutschsprachigen Presse Gebrauchtwagen
JOI — [1] Jupiter Orbit Injection (der Galileo Raumsonde)
JOI — Joint Oceanographic Institutions, Inc. Contributor: LaRC … NASA Acronyms
JOI — abbr. Joint Oceanographic Institutions, Inc … Dictionary of abbreviations




