gpt4 book ai didi

error-handling - 无法将已处理的GraphQL错误从一个Apollo服务器API传递到另一个API

转载 作者:行者123 更新时间:2023-12-03 08:45:32 26 4
gpt4 key购买 nike

我正在尝试通过Apollo Server Express和Passport JWT使用GraphQL构建微服务Web应用示例,以进行 token 认证。

到目前为止,我有4个微服务(用户,博客,项目,配置文件)和网关API,在其中将它们与关系的片段(例如Blog.authorUser.projects等)拼接在一起。一切工作正常,我可以全面执行CRUD。

然后,当我尝试实现身份验证时,一切都陷入了困境(这真让人大吃一惊),尽管奇怪的是没有实现身份验证本身,这不是问题。

问题出在错误处理上,更具体地说,是将GraphQL错误从远程API传递到网关进行拼接。网关接收到一个错误,但是实际的详细信息(例如{password: 'password incorrect'})被网关API吞没了。

用户API错误

{
"errors": [
{
"message": "The request is invalid.",
"type": "ValidationError",
"state": {
"password": [
"password incorrect"
]
},
"path": [
"loginUser"
],
"stack": [
...
]
}
],
"data": {
"loginUser": null
}
}

GATEWAY API错误
{
"errors": [
{
"message": "The request is invalid.",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"loginUser"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"errors": [
{
"message": "The request is invalid.",
"locations": [],
"path": [
"loginUser"
]
}
],
"stacktrace": [
"Error: The request is invalid.",
... // stacktrace refers to node_modules/graphql-
tools/src/stitching
],
"data": {
"loginUser": null
}
}

GATEWAY src/index.js
从'expres进口 express
s';
import { ApolloServer } from 'apollo-server-express';
// ...
import errorHandler from '../error-handling/errorHandler';

// ... app setup

const startGateway = async () => {
const schema = await makeSchema(); // stitches schema
const app = express();

app.use('/graphql', (req, res, next) => {
// passport
// ...
});

const server = new ApolloServer({
schema,
context: ({ req }) => ({ authScope: req.headers.authorization }),
// custom error handler that tries to unravel, clean and return error
formatError: (err) => errorHandler(true)(err)
});

server.applyMiddleware({ app });
app.listen({ port: PORT }, () => console.log(`\n Gateway Server ready at http://localhost:${PORT}${server.graphqlPath} \n`));
};

startGateway().catch(err => console.log(err));

网关src/remoteSchema/index.js
(拼接发生的地方)
import { makeRemoteExecutableSchema, introspectSchema } from 'graphql-tools';
import { ApolloLink } from 'apollo-link';
import { setContext } from 'apollo-link-context';
import { introspectionLink, stitchingLink } from './link';


// graphql API metadata
const graphqlApis = [
{ uri: config.USER_DEV_API },
{ uri: config.BLOG_DEV_API },
{ uri: config.PROJECT_DEV_API },
{ uri: config.PROFILE_DEV_API }
];

// create executable schemas from remote GraphQL APIs
export default async () => {
const schemas = [];

for (const api of graphqlApis) {

const contextLink = setContext((request, previousContext) => {
const { authScope } = previousContext.graphqlContext;
return {
headers: {
authorization: authScope
}
};
});

// INTROSPECTION LINK
const apiIntroSpectionLink = await introspectionLink(api.uri);

// INTROSPECT SCHEMA
const remoteSchema = await introspectSchema(apiIntroSpectionLink);

// STITCHING LINK
const apiSticthingLink = stitchingLink(api.uri);

// MAKE REMOTE SCHEMA
const remoteExecutableSchema = makeRemoteExecutableSchema({
schema: remoteSchema,
link: ApolloLink.from([contextLink, apiSticthingLink])
});

schemas.push(remoteExecutableSchema);
}

return schemas;
};

缝线还有更多,但这里太多了。但它缝合成功。

USER API src/resolver
const resolvers = {
Query: {/*...*/},
Mutation: {
loginUser: async (parent, user) => {
const errorArray = [];

// ...get the data...

const valid = await bcrypt.compare(user.password, ifUser.password);

if (!valid) {
errorArray.push(validationError('password', 'password incorrect'));
// throws a formatted error in USER API but not handled in GATEWAY
throw new GraphQlValidationError(errorArray);
}

// ... return json web token if valid

}
}
}

USER errors.js
export class GraphQlValidationError extends GraphQLError {
constructor(errors) {
super('The request is invalid.');
this.state = errors.reduce((result, error) => {
if (Object.prototype.hasOwnProperty.call(result, error.key)) {
result[error.key].push(error.message);
} else {
result[error.key] = [error.message];
}
return result;
}, {});
this.type = errorTypes.VALIDATION_ERROR;
}
}

export const validationError = (key, message) => ({ key, message });

网关和用户errorHandler.js
import formatError from './formatError';

export default includeStack => (error) => {
const formattedError = formatError(includeStack)(error);

return formattedError;
};

formatError.js
import errorTypes from './errorTypes';
import unwrapErrors from './unwrapErrors';

export default shouldIncludeStack => (error) => {
const unwrappedError = unwrapErrors(error);

const formattedError = {
message: unwrappedError.message || error.message,
type: unwrappedError.type || error.type || errorTypes.ERROR,
state: unwrappedError.state || error.state,
detail: unwrappedError.detail || error.detail,
path: unwrappedError.path || error.path,
};

if (shouldIncludeStack) {
formattedError.stack = unwrappedError.stack || error.extensions.exception.stacktrace;
}

return formattedError;
};

unwrapErrors.js
export default function unwrapErrors(err) {
if (err.extensions) {
return unwrapErrors(err.extensions);
}

if (err.exception) {
return unwrapErrors(err.exception);
}

if (err.errors) {
return unwrapErrors(err.errors);
}


return err;
}

如果代码段不是必需的,我提前致歉。我很高兴回答任何问题。

提前致谢!

最佳答案

Ok似乎已经在this discussion指向this gist的帮助下修复了该问题。这是一个拼接错误,其中包含一些不必要的错误格式。我从ApolloServer({})中都删除了formatError,然后将./src/remoteSchema/index.js重新格式化为:

import { makeRemoteExecutableSchema, introspectSchema } from 'graphql-tools';
import { ApolloLink } from 'apollo-link';
import { HttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { onError } from 'apollo-link-error';
import fetch from 'node-fetch';
import config from '../../config/config';


// graphql API metadata
const graphqlApis = [
{ uri: config.USER_DEV_API },
{ uri: config.BLOG_DEV_API },
{ uri: config.PROJECT_DEV_API },
{ uri: config.PROFILE_DEV_API }
];

// create executable schemas from remote GraphQL APIs
export default async () => {
const schemas = [];

/*eslint-disable*/
for (const api of graphqlApis) {

let remoteLink = new HttpLink({ uri : api.uri, fetch });
let remoteContext = setContext((req, previous) => {
// if the authorization token doesn't exist, or is malformed, do not pass it upstream
if (
!previous.graphqlContext.authorization
||
!previous.graphqlContext.authorization.match(/^Bearer /)
) {
return;
}

return {
headers: {
'Authorization': previous.graphqlContext.authorization,
}
}
});

let remoteError = onError(({ networkError, graphQLErrors }) => {
if (graphQLErrors) {
graphQLErrors.forEach((val) => {
Object.setPrototypeOf(val, Error.prototype);
});
}
});
let remoteSchema = await introspectSchema(remoteLink);
let remoteExecutableSchema = makeRemoteExecutableSchema({
schema : remoteSchema,
link : ApolloLink.from([
remoteContext,
remoteError,
remoteLink
])
});

schemas.push(remoteExecutableSchema);
}

return schemas;
};

这是一个星期的痛苦,但是从我所见,他们的问题有望在graphql-tools 5.0中解决

关于error-handling - 无法将已处理的GraphQL错误从一个Apollo服务器API传递到另一个API,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55595592/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com