Изменение ответа Graphql перед отправкой

Я ищу способ изменить объект ответа на запрос или мутацию 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.

formatResponse — правильный способ добавить эти поля. Выражение «не дает желаемого эффекта» не описывает проблему, с которой вы столкнулись. Укажите, какое поведение вы ожидали и с каким неожиданным поведением столкнулись.
Daniel Rearden 10.05.2019 12:46

ну, я имею в виду, что даже если я возвращаю {data, test}, я получаю только данные, возвращаемые в ответе, ожидаемое поведение будет заключаться в том, что ответ содержит {data, test}

Matt 10.05.2019 13:33

GraphQL имеет явная поддержка ошибок даже на уровне каждого поля, и эти ошибки могут содержать значения расширения, такие как сообщения, читаемые человеком, и машиночитаемые коды. У Apollo также есть несколько слегка документированная обработка ошибок, которые могут удовлетворить ваши потребности.

David Maze 10.05.2019 14:04

@Matt Посмотрите этот КодПесочница. formatResponse работает как положено. Если ваш клиент на внешнем интерфейсе не раскрывает эту информацию, это отдельная проблема. Вы можете проверить, так ли это, открыв вкладку сети и посмотрев фактический ответ, полученный от сервера.

Daniel Rearden 10.05.2019 14:29

Если это действительно то, что происходит, этот вопрос, вероятно, следует переписать как что-то вроде «Почему [КЛИЕНТ] не читает пользовательские свойства, которые я установил в своем ответе?»

Daniel Rearden 10.05.2019 14:33

это не то, что происходит, измененный ответ никогда не доходит до клиента. Я могу подтвердить, что ваша песочница работает именно так, как я этого хочу. По какой-то причине это не для меня, чего я просто не понимаю, потому что console.info в formatResponse запускается и отображается в консоли Node, что делает его более запутанным для меня.

Matt 10.05.2019 17:13
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
6
2 212
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Проведя много исследований, я обнаружил, что единственными разрешенными свойствами верхнего уровня в ответах graphql являются данные, ошибки и расширения. Здесь вы можете найти соответствующую проблему в GitHub

Ошибка 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;
}

Решение 1. Переопределить преобразователи GraphQL

Короче говоря: у вас есть 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.

Решение 2. Изменить ответ

Это решение более элегантно, но реализуется после реализации директив и скалярных типов и не может манипулировать входными данными.

Особенностью выходного объекта является то, что данные являются 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.

Другие вопросы по теме