In our GraphQL implementation we have custom errors, like ClientError and InternalServerError (witch extends the first one), where if the error thrown inside GraphQL is an instance of ClientError we show to the user the exactly message thrown, if the error is not, we redirect the error to InternalServerError with a default message and display the original error in the server console. Also, we have code for all of our custom errors to be mapped on the front-end. Some of these custom errors include WrongCredentialError, InvalidEmailError, InvalidOAuthRedirectURIError, etc. So error manipulation is important for us to have on subscriptions as well, specially for subscribe method.
This implementation should consider implementing a method like formatError and use the original GraphQLError to handle it.
To make this possible in the current version we are hooking it with sendMessage and sendError, but it shouldn't be like that.
If this become available the GraphiQL might need to implement this as well, as it only show texts errors.
@ezsper Are you using your own server implementation? In our server you can do a formatError & formatResponse in the onOperation callback.
No, We're using the Appolo Server. But the problem is that formatError is not applied to ws/subscriptions, only for rest requests. As you can see above we need to be able to throw custom errors both in resolve and subscribe methods of the subscription, and we need to be able to format this output on websocket subscriptions messages. It would be the best approach to have formatError wrapping the error messages of the ws/subscriptions.
@ezsper what is the SubscriptionServer version you're using?
"graphql": "^0.10.1",
"graphql-subscriptions": "0.4.3",
"subscriptions-transport-ws": "0.7.3",
"graphql-server-koa": "0.8.5"
Also running into this. Why doesn't the server offer the formatError option?
I managed to work around this by using formatResponse. In your onOperation handler, add formatResponse to the params object and check if the value has errors:
onOperation(msg, params, socket) {
params.formatResponse = value => {
if (value.errors) {
// For instance
value.errors = value.errors.map(e => yourFormatErrorFn(e));
}
}
...
}
good ! is it possible to implement a formatError directly in:
new SubscriptionServer({
schema,
execute,
subscribe,
formatError,
formatResponse
}, {
server,
path,
});
You can also do
onOperation(msg, params, socket) {
params.formatError = errorFormatter;
}
Which works similarly to the vanilla GraphQL formatError option.
@mxstbr, I don't think that works. After some digging I see formatError is called inside a catch() here but errors are resolved, not rejected and therefor will never reach that catch. Instead it will reach the formatResponse here. The work-around of @altschuler seems to work, but it's a hack and should be implemented in the package.
Thanks @altschuler, that worked for me. One small point: you need to return value from the formatResponse function.
I have this:
onOperation(msg, params, socket) {
params.formatResponse = value => ({
...value,
errors: value.errors && value.errors.map(formatError),
})
...
}
I've not yet found the way to handle errors that happen in onConnect
const onConnect = async ({ authToken }, webSocket) => {
if (!authToken) throw new NoauthError();
try {
const token = await validateToken(authToken);
return objOf('token', token);
} catch (error) {
throw error;
}
};
// HACK: https://github.com/apollographql/subscriptions-transport-ws/issues/182
const onOperation = (msg, params, socket) => {
console.log('onOperation');
return ({ ...params, formatResponse });
};
export const createSubscriptionServer = ({ server, path }) => {
console.log(`Apollo SubscriptionServer is now listening on ws://localhost:${PORT}`);
return new SubscriptionServer({
execute,
onConnect,
onOperation,
schema,
subscribe,
}, { server, path });
};
Wow! Spent almost 1 day debugging every layer (from apollo-client to graphql.js itself) to see how I could solve this one... The good thing is that I actually learnt a lot during the process! 😄 Anyway, many thanks to @altschuler for the final tip, as far as I've seen that's the only way we have to customize the errors sent up to the client (when using this subscription server of course). As mentioned above, keep in mind to return params from the onOperation:
// …
function formatError(error) {
const { message, statusCode, body } = error.originalError;
return {
message: body.message || message,
conflict: statusCode === 409,
// …
};
}
// …
new SubscriptionServer({
execute,
subscribe,
schema,
onOperation(msg, params) {
// eslint-disable-next-line no-param-reassign
params.formatResponse = (value) => ({
...value,
errors: value.errors && value.errors.map(formatError),
});
return params;
},
// …
},
// …
🤷♂️
Was there ever a solution here. params.formatError and Response are undefined when I throw and error
Most helpful comment
I managed to work around this by using
formatResponse. In youronOperationhandler, addformatResponseto the params object and check if the value has errors: