Я ищу способ изменить объект ответа на запрос или мутацию graphql до того, как он будет отправлен.
В основном в дополнение к объекту данных я хочу иметь дополнительные поля, такие как код и сообщение.
На данный момент я решаю это, добавляя поля непосредственно в мои схемы GQL, например, это определение типа:
type Query {
myItems: myItemResponse
}
type myItemResponse {
myItem: Item
code: String!
success: Boolean!
message: String!
}
Сам ответ будет выглядеть так:
{
data: {
myItems: {
myItem: [ ... fancy Items ... ],
message: 'successfully retrieved fancy Items',
code: <CODE_FOR_SUCCESSFUL_QUERY>
}
}
}
Я нахожу это решение не очень хорошим, потому что оно слишком усложняет вещи в моем FrontEnd.
Я бы предпочел решение, в котором код сообщения и другие метаданные отделены от фактических данных, например:
{
data: {
myItems: [ ... fancy Items ... ],
},
message: 'successfully retrieved fancy Items',
code: <CODE_FOR_SUCCESSFUL_QUERY>
}
С apollo-server я уже пробовал объект formatResponse в конструкторе:
const server = new ApolloServer({
...
formatResponse({ data }) {
return {
data,
test: 'Property to test if shown in the FrontEnd',
}
}
...
}
к сожалению, это не имеет должного эффекта. Прежде чем использовать промежуточное ПО, я хочу спросить, есть ли возможность сделать это через apollo-server из коробки или я просто что-то упускаю в функции formatResponse.
ну, я имею в виду, что даже если я возвращаю {data, test}, я получаю только данные, возвращаемые в ответе, ожидаемое поведение будет заключаться в том, что ответ содержит {data, test}
GraphQL имеет явная поддержка ошибок даже на уровне каждого поля, и эти ошибки могут содержать значения расширения, такие как сообщения, читаемые человеком, и машиночитаемые коды. У Apollo также есть несколько слегка документированная обработка ошибок, которые могут удовлетворить ваши потребности.
@Matt Посмотрите этот КодПесочница. formatResponse работает как положено. Если ваш клиент на внешнем интерфейсе не раскрывает эту информацию, это отдельная проблема. Вы можете проверить, так ли это, открыв вкладку сети и посмотрев фактический ответ, полученный от сервера.
Если это действительно то, что происходит, этот вопрос, вероятно, следует переписать как что-то вроде «Почему [КЛИЕНТ] не читает пользовательские свойства, которые я установил в своем ответе?»
это не то, что происходит, измененный ответ никогда не доходит до клиента. Я могу подтвердить, что ваша песочница работает именно так, как я этого хочу. По какой-то причине это не для меня, чего я просто не понимаю, потому что console.info в formatResponse запускается и отображается в консоли Node, что делает его более запутанным для меня.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Проведя много исследований, я обнаружил, что единственными разрешенными свойствами верхнего уровня в ответах graphql являются данные, ошибки и расширения. Здесь вы можете найти соответствующую проблему в GitHub
для моей цели я, вероятно, буду использовать поле расширений.
с сайта graphql.org: Ответом на операцию GraphQL должна быть карта.
Если операция обнаружила какие-либо ошибки, карта ответов должна содержать запись с ключевыми ошибками. Значение этой записи описано в разделе «Ошибки». Если операция завершена без каких-либо ошибок, эта запись не должна присутствовать.
Если операция включала выполнение, карта ответов должна содержать запись с ключевыми данными. Значение этой записи описано в разделе «Данные». Если операция завершилась неудачно перед выполнением из-за синтаксической ошибки, отсутствия информации или ошибки проверки, эта запись не должна присутствовать.
Карта ответов может также содержать запись с ключевыми расширениями. Эта запись, если она установлена, должна иметь карту в качестве значения. Эта запись зарезервирована для разработчиков, которые могут расширить протокол по своему усмотрению, и, следовательно, нет никаких дополнительных ограничений на ее содержимое.
Чтобы будущие изменения протокола не нарушали работу существующих серверов и клиентов, карта ответов верхнего уровня не должна содержать никаких записей, кроме трех, описанных выше.
Эта функция будет объединять суффикс «: OK» для каждой строки в выходном объекте.
// Data/output modifier - concat ":OK" after each string
function outputModifier(input: any): any {
const inputType = typeof input;
if (inputType === 'string') {
return input + ':OK';
} else if (Array.isArray(input)) {
const inputLength = input.length;
for (let i = 0; i < inputLength; i += 1) {
input[i] = outputModifier(input[i]);
}
} else if (inputType === 'object') {
for (const key in input) {
if (input.hasOwnProperty(key)) {
input[key] = outputModifier(input[key]);
}
}
}
return input;
}
Короче говоря: у вас есть 3 основных типа (запрос, мутация и подписка). У каждого основного типа есть поля с преобразователями. Резолверы возвращают выходные данные.
Поэтому, если вы переопределите распознаватели, вы сможете изменить выходные данные.
import { GraphQLSchema } from 'graphql';
export const exampleTransformer = (schema: GraphQLSchema): GraphQLSchema => {
// Collect all main types & override the resolvers
[
schema?.getQueryType()?.getFields(),
schema?.getMutationType()?.getFields(),
schema?.getSubscriptionType()?.getFields()
].forEach(fields => {
// Resolvers override
Object.values(fields ?? {}).forEach(field => {
// Check is there any resolver at all
if (typeof field.resolve !== 'function') {
return;
}
// Save the original resolver
const originalResolve = field.resolve;
// Override the current resolver
field.resolve = async (source, inputData, context, info) => {
// Get the original output
const outputData: any = await originalResolve.apply(originalResolve.prototype, [source, inputData, context, info]);
// Modify and return the output
return outputModifier(outputData);
};
});
});
return schema;
};
// Attach it to the GraphQLSchema > https://graphql.org/graphql-js/type/
let schema = makeExecutableSchema({...});
schema = exampleTransformer(schema);
const server = new ApolloServer({schema});
server.listen(serverConfig.port);
Это решение будет работать на любом сервисе GraphQL-JS (apollo, express-graphql, graphql-tools и т. д.).
Имейте в виду, что с этим решением вы также сможете манипулировать inputData.
Это решение более элегантно, но реализуется после реализации директив и скалярных типов и не может манипулировать входными данными.
Особенностью выходного объекта является то, что данные являются null-prototype объектом (нет методов экземпляра, таких как .hasOwnProperty(), .toString(), ...), а ошибки являются заблокированными объектами (только для чтения).
В примере я разблокирую объект ошибки... будьте осторожны с этим и не меняйте структуру объектов.
import { Translator } from '@helpers/translations';
import type { GraphQLResponse, GraphQLRequestContext } from 'apollo-server-types';
import type { GraphQLFormattedError } from 'graphql';
export const exampleResponseFormatter = () => (response: GraphQLResponse, requestContext: GraphQLRequestContext) => {
// Parse locked error fields
response?.errors?.forEach(error => {
(error['message'] as GraphQLFormattedError['message']) = exampleTransformer(error['message']);
(error['extensions'] as GraphQLFormattedError['extensions']) = exampleTransformer(error['extensions']);
});
// Parse response data
response.data = exampleTransformer(response.data);
// Response
return response;
};
// Provide the schema to the ApolloServer constructor
const server = new ApolloServer({
schema,
formatResponse: exampleResponseFormatter()
});
Я использую оба решения в своих проектах. С первым вы можете управлять вводом и выводом на основе определенных директив доступа в коде или проверять весь поток данных (для любого типа graphql). И во-вторых, перевести все строки на основе заголовков контекста, предоставленных пользователем, не путая преобразователи и код с языковыми переменными.
Эти примеры протестированы на TS 4+ и GraphQL 15 и 16.
formatResponse— правильный способ добавить эти поля. Выражение «не дает желаемого эффекта» не описывает проблему, с которой вы столкнулись. Укажите, какое поведение вы ожидали и с каким неожиданным поведением столкнулись.