Всем привет!
Сегодня я хотел бы рассмотреть один кейс, который хоть и не очень распространенный, но все же встречается, а его реализация требует поднапрячь мозги. Вот и сейчас я опять с ним столкнулся и решил более ответственно подойти к реализации, а заодно и задокументировать это.
Задача: добавить на уровне GraphQL фильтр по нескольким полям, к примеру, по юзернейму, имени и емейлу. В штатном режиме запрос у нас бы выглядел примерно вот так:
Уточню, что при выполнении этого запроса мы должны получить пользователей, у которых имя, юзернейм или емейл содержат заданную строку.
Иногда такое перечисления полей в интерфейсах не очень удобно, тем более для простых сотрудников, которые не хотят заполнять несколько отдельных полей (в нашем случае username, fullname, email), а хотят заполнять одно поисковое поле и чтобы по нему "все искалось". При чем логика бывает более сложная (к примеру, заказы по номеру или владельцу, у которого имя или емейл совпадает С). Но GraphQL просто так ничего не принимает лишнего в запросах. Если вы хотите передавать какое-то новое поле в запросе, вы его должны описать в схеме и заставить сервер обрабатывать этот запрос. Вот решение для подобных случаев я и хочу описать.
1. Добавляем свое поле в API-схему
Напоминаю, что при сборке API-схемы командой yarn build-api выполняется суммирование всех объектов схемы, таким образом на выходе мы получим UserWhereInput не только с полем search, но и с другими полями, описанными ранее, то есть фактически мы именно добавляем поле, а не просто объявляем новый объект.
2. Расширяем резолвер
Как я говорил ранее, API состоят из двух частей: 1. Низкоуровневое, генерируемое самой призмой (и которое обрабатывается в docker-сервере). 2. Внешнее общедоступное API.
Так вот, сейчас мы добавили новое поле search в наше фронтовое API и оно в запросе это поле примет. Но далее запрос улетает на низкоуровневое API, а там об этом поле ничего не известно, и в случае отправки такого запроса туда, мы получим ошибку от сервера, что такое поле в схеме не описано и запрос выполнить нельзя. Но мы сейчас и не будем пытаться заставить низкоуровневое API понимать поле search, наша задача в другом: приняв search на входе, отправить на низкоуровневое API измененный запрос, а именно с условием:
в то время как на вход (во внешнем API) мы будем принимать запрос попроще:
То есть в нашем резолвере мы должны, получив условие с полем search, удалить его из запроса, вместо него подставить измененное условие и отправить запрос далее.
Вот здесь я переопределяю два резолвера (users и usersConnection). Прежде чем отправить запрос далее, я вызываю метод addQueryConditions и передаю в него текущие условия параметры запроса. Вот этот метод:
Он в свою очередь берет из контекста метод modifyArgs и передает в него модификатор this.injectWhere. Вот его код для наглядности:
В принципе, хоть и несколько запутанно написано, но на самом деле здесь всего понимать не надо и достаточно только сосредоточиться на написании своего метода injectWhere, а он не особо сложный.
А вот результат для примера: https://prisma-cms.com/people?filters=%7B%22search%22%3A%22test%22%7D
Что хорошо в данной реализации, так это то, что она работает на любом уровне вложенности запроса и даже в массивах условий (AND и OR). Вот пример: https://prisma-cms.com/people?filters=%7B%22Resources_some%22%3A%7B%22CreatedBy%22%3A%7B%22search%22%3A%22test%22%7D%7D%7D
А еще этот метод за раз можно несколько раз вызывать (если вы написали несколько отдельных модификаторов, а не прописали все условия в одном).
Но если и минус, который скорее всего не будет решен. Дело в том, что мы добавили кастомное поле search в объект UserWhereInput. А этот объект используется не только в выборках пользователей, но и в запросах других объектов, связанных с пользователем, к примеру, в ресурсах. То есть если мы во фронте пропишем вот такой запрос:
то синтаксических ошибок мы не получим, технически здесь все ОК. Но при выполнении запроса мы получим ошибку
Потому что в бэк-API такое поле неизвестно. Для решения этой проблемы придется еще и модификатор писать для ресурс-запросов.
Но в целом эта проблема не особо критична, о ней просто надо знать (чтобы не выполнять). Но для самостоятельных запросов метод очень полезный.
очень интересно