thebigredgeek / apollo-errors Goto Github PK
View Code? Open in Web Editor NEWMachine-readable custom errors for Apollostack's GraphQL server
License: MIT License
Machine-readable custom errors for Apollostack's GraphQL server
License: MIT License
I have a mutation to create an admin for my system:
mutation {
createAdmin(
name:"Test"
phoneNumber:"8758"
password:"12345"
) {
_id
createdAt
updatedAt
}
}
The mutation definition is like this:
const { UnknownError } = require('../../../helpers/errors')
const createAdmin = async (payload) => {
try {
const { error, value } = Joi.validate(payload, adminReqSchema)
if (error) {
throw new UnknownError({
data: {
reason: 'Schema validation error'
}
})
}
const admin = await new Admin({ name: value.name, phoneNumber: value.phoneNumber }).save()
await new Auth({ userId: admin._id, role: 'admin', password: value.password }).save()
return admin
} catch (err) {
console.log(err)
}
}
I've defined my error here:
const { createError } = require('apollo-errors')
const UnknownError = createError('UnknownError', {
message: 'An unknown error has occured'
})
module.exports = { UnknownError }
The response is get is always:
{
"data": {
"createAdmin": null
}
}
EDIT: I've narrowed down this problem to the fact that I'm using async/await
in my mutation functions and handling errors using try/catch
blocks. I'd like to know the right way to use apollo-errors with async/await
and try/catch
blocks.
Thanks for your library. Its a great little helper.
in formatError function, with es6-error v4.0.6..
!name false
!isInstance(originalError) false
in formatError function, with es6-error v4.1.0..
!name false
!isInstance(originalError) true
Which prevents the serialization of the new merged message and just returns the original message without any data, processing of location/path options etc.
Fixing es6-error at 4.0.6 in project's package.json fixes this as a workaround.
I've managed to set up this awesome package without trouble (I get the expected JSON output in my requests) but I'm not sure what to do from there.
My issue is that when I throw an error inside a mutation handled by react-apollo's graphql
HoC, the error
object returned by the promise doesn't contain the custom error properties defined on the server, only the message
.
Am I doing something wrong? How are you supposed to catch custom errors on the client?
Hi,
I Have just implemented a similar library.
And just now I found out that instead of all the serialize and deserialize with delimiter mechanism you use.
You can just get the custom error that way:
formatError: function (error) {
const customError = error.originalError;
// Add path locations etc'
return customError
}
see more in the graphql source here
I did the same mistake, and I'm planning to change it since it much cleaner and correct.
Changes between versions 1.5.1 and 1.7.1 appear to have broken the message field propagation when using createError:
const myError = createError('BadError', { message: "This is bad"});
var theError = new myError();
console.log(theError.message)
output of message field is undefined....
Works great in 1.5.1, broken in 1.7.1
I tried to pass statusCode
to options (https://github.com/apollographql/graphql-server/blob/master/packages/graphql-server-koa/src/koaApollo.ts#L41)
export const ForbiddenError = createError('ForbiddenError', {
statusCode: 403,
message: 'Forbidden',
});
, but it doesn't work.
Hello,
Thank you for this great module. I want to use it but I need the following improvement.
I think formatError
should take in argument showLocations
in order to hide or show the locations and path of any error.
Actually, to my mind, the implementation where the error is created is not responsible for how the error must be formatted/displayed : this is the role of formatError
function.
If you agree I can open a PR.
What do you think ?
Best,
Yoann
If I put the data value as an object like your example
import { FooError } from './errors';
const resolverThatThrowsError = (root, params, context) => {
throw new FooError({
data: {
something: 'important'
}
});
}
That will throwing something like this.
{
"data": {},
"errors": [
{
"message":"A foo error has occurred",
"name":"FooError",
"time_thrown":"2016-11-11T00:40:50.954Z",
"data":{
"something": "important"
}
}
]
}
But, if I put my data as an array of object.
import { FooError } from './errors';
const resolverThatThrowsError = (root, params, context) => {
throw new FooError({
data: [{
something: 'important'
}, {
something: 'very important'
}]
});
}
Why it's throwing the data as object, not an array of object?
{
"data": {},
"errors": [
{
"message":"A foo error has occurred",
"name":"FooError",
"time_thrown":"2016-11-11T00:40:50.954Z",
"data": {
"0": {
"something": "important"
},
"1": {
"something": "very important"
}
}
}
]
}
I need my data to be an array of object, is that possible?
would be helpful to define errors with message objects. (that can carry more static info regarding the error like severity)
I would like to make a PR changing the current type of message to union of string and object
please let me know your opinion
Hi,
I am throwing a number of custom errors further up my project and these I generally would log before throwing, these are standard custom errors that inherit from the Error object in nodejs.
When I arrive at my resolver, I generally capture the error and throw an "apollo errors" Error but I lose the stack trace, the stack trace only has the entry of where I threw the new apollo error but nothing before it.
Do you know of a good solution or workaround ? I mean I could log the error before throwing the new apollo error but it would be great if it retained the stack trace
Here is what I am doing
} catch (err) {
switch (err.constructor) {
case HttpBadRequestError:
throw new BadRequestError()
default:
throw new InternalError()
}
}
The one above that is most important is the InternalError, this basically means it is an uncaught error.
here is my definition
import { createError } from 'apollo-errors'
export const InternalError = createError('InternalError', {
message: 'An internal error has occurred'
})
As I say, throw the error here now removes all stacktrace from the previous error so all I know is that the code in the resolver threw an error but I have no reference to anything else.
I did try using longjohn - a node module for including longer stack traces, but I don't think this is the problem.
Anybody managed to work around this ?
I have setup my resolvers, one of which is:
export const UnauthorizedError = createError('UnauthorizedError', {
message: 'You must login to do that'
});
I have a login page, which checks to see if a user is logged in (looking in apollo store).
If the user is logged in, the page will kick them into the "authenticated area" of the app.
When a user is inside the app, they can click a logout button. This logout mutation returns a promise. In the .then(), I re-route the user to the login page.
If I were to re-route them to /login prior to the .then(), they would just get kicked back into the auth area (apollo store still thinks they're logged in).
My problem:
The user clicks logout, mutation runs, server is aware the user is no longer logged in
My .then() hasn't re-routed the user, so all of the queries are re-running and throwing "UnauthorizedError: You must login to do that" whenever somebody logs out.
Is my only option to update the store with optimistic UI, and kick them back to login page? Or is there a different pattern to use with my errors/resolvers on the server?
Wondering if there are any features that people would like to see in V2?
Need integration tests
If I set a shared list of known errors:
import { createError, formatError } from 'apollo-errors';
export const PermissionsError = createError('PermissionsError', {
message: 'Invalid permissions, you must be logged in to do this.',
});
export const UsernameTakenError = createError('UsernameTakenError', {
message: 'Sorry, that username is taken.',
});
export const EmailRegisteredError = createError('EmailRegisteredError', {
message: 'That email is already registered.',
});
export const errorTypes = {
PermissionsError,
UsernameTakenError,
EmailRegisteredError,
};
How do you compare a thrown error to it's bound function? None of these seem to work
new UsernameTakenError() instanceof errorTypes.UsernameTakenError; // true for any type, cant distinguish
new UsernameTakenError().isInstance; // true, cant distinguish
Simpliest way I found:
throw new UsernameTakenError("This is SomeCaughtError");
...
...
new errorTypes.UsernameTakenError().name === SomeCaughtError.name;
I feel like I'm missing something simple 😕
Things to note, I am using graphql-server-express
version 0.6.0
, and I have hooked up formatError
to my graphqlExpress, it just doesn't seem to work.
I created a new error with
export const FetchError = createError('FetchError' , { message: 'blark' });
I used the error
// data is the message body from a node-fetch
throw new FetchError({ data });
and I receive the following from GraphQL
{
"data": null,
"errors": [
{
"message": "ApolloError: FetchError/::/2017-03-13T14:42:02.244Z/::/null/::/{\"errorGroup\":\"UNAUTHORIZED\",\"errorCode\":\"AUT_00011\",\"errorMessage\":\"Invalid session cookie\"}",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"installation"
]
}
]
}
Am I missing something obvious, or did I find a bug?
Where are query errors supposed to be caught on the client?
I throw a NotFoundError
on the server if an item can't be found. The error info can be accessed in props.data.error
, but it still results in an uncaught exception.
I might be missing something fairly simple, but the apollo docs are pretty light when it comes to error handling!
Seems like release 2.0 has been on hold for a little while now and important updates like #35 have not been implemented yet.
@thebigredgeek shall we create a v2 branch and start merging features like #35 ?
Not sure what the correct workflow should be for that.
Currently starting my server like so
const server = new GraphQLServer({
typeDefs: this.typeDefs,
resolvers: shieldedResolvers as any
});
which works great. So now I want to add the formatErros into the mix. Although I am unsure what it is..
These are the props that I have that are taken from the contructor.
constructor(props: Props);
export interface Props {
directiveResolvers?: IDirectiveResolvers<any, any>;
schemaDirectives?: {
[name: string]: typeof SchemaDirectiveVisitor;
};
typeDefs?: ITypeDefinitions;
resolvers?: IResolvers;
resolverValidationOptions?: IResolverValidationOptions;
schema?: GraphQLSchema;
context?: Context | ContextCallback;
mocks?: IMocks;
middlewares?: IFieldMiddleware[];
}
Of course its not typeDefs, Resolvers, I did try middleware but it didn't work.
Anyone have any ideas ?
Great work with this module!
There is one thing missing, though: I would like to have dynamic messages for my errors when possible. To make that happen, message
option could be received as function.
As you don't expose ApolloError, things are harder they it should to increment your lib. Here is a work around I'm using to achieve it:
const createDynamicError = (name, opts = {}) => class CustomError {
constructor (...args) {
const config = typeof opts === 'string' || typeof opts === 'function'
? { message: opts }
: opts
if (typeof config.message === 'function') {
config.message = config.message(...args)
}
return new (createError(name, config))(...args.slice(-1))
}
}
You could then use it like so:
const UserNotFoundError = createCustomError('UserNotFoundError', {
message: username => `User ${username} not found`
})
// Or, shurtcuted:
const UserNotFoundError = createCustomError('UserNotFoundError', username => `User ${username} not found`)
// And use it like that:
new UserNotFoundError('lucas')
// Or, with data as in the original createError:
new UserNotFoundError('lucas', { data: { foo: 'bar' } })
Hope it helps someone ;)
Hello,
I was wondering how we would integrate this using the 'graphql-server-hapi' package from Apollo
Just wondering whether you'd tried to get the package working with apollo-server
yet? We are using it within Serverless as a Lambda rather than using Express.
Post here if you want core contrib access. Looking for 1 or 2 people to help
This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.
These updates have been manually edited so Renovate will no longer make changes. To discard all commits and start over, click on a checkbox.
These updates have all been created already. Click a checkbox below to force a retry/rebase of any.
.circleci/config.yml
circleci/node 8
circleci/node 10
circleci/node 12
circleci/node 14
package.json
assert ^2.0.0
extendable-error ^0.1.5
babel-cli 6.26.0
babel-core 6.26.3
babel-eslint 7.2.3
babel-preset-es2015 6.24.1
babel-register 6.26.0
chai 3.5.0
eslint 3.19.0
eslint-plugin-babel 3.3.0
mocha 3.5.3
rimraf 2.6.3
typescript 2.9.2
apollo-errors
doesn't work when using mergeSchemas
.
I'm working with apollo-server-express, and I've created a custom scalar:
const regex = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i;
export default {
name: 'EmailString',
description: 'E-Mail addresses compliant to RFC 822.',
parseValue: (value) => {
if (typeof value !== 'string') {
throw new TypeError('Invalid Email input value (string expected)');
}
if (!regex.test(value)) {
throw new TypeError('Invalid Email input value');
}
return value;
},
serialize: value => value,
parseLiteral: (ast) => {
if (ast.kind !== Kind.STRING) {
throw new TypeError('Invalid Email input value (string expected)');
}
if (!regex.test(ast.value)) {
throw new TypeError('Invalid Email input value');
}
return ast.value;
},
};
But when it throws the error, formatError doesn't parse it 'cause it's an GraphQLError:
if (!name || !isInstance(originalError)) return returnNull ? null : error;
name
is evaluated to GraphQLError
string, then the response it's like this:
{
"errors": [
{
"message": "Variable \"$email\" got invalid value \"asd\".\nExpected type \"UEmail\", found \"asd\": Invalid Email input value",
"locations": [
{
"line": 1,
"column": 45
}
]
}
]
}
And not how I expected to be:
{
"data": {
"addEmail": null
},
"errors": [
{
"message": "Invalid Email input value",
"name": "TypeError",
"time_thrown": "2017-10-22T13:48:28.989Z",
"data": {}
}
]
}
Can you describe the advantage/difference using apollo-errors compared to
https://www.apollographql.com/docs/apollo-server/features/errors.html#Other-errors that are described in the apollo-server docs?
Recently, GraphQL Response Specification got updated with a suggestion to place custom data in error in the extensions
object. The spec can be found here https://github.com/facebook/graphql/blob/master/spec/Section%207%20--%20Response.md
An excerpt of the relevant section:
GraphQL services may provide an additional entry to errors with key
extensions
. This entry, if set, must have a map as its value. This entry is reserved for implementors to add additional information to errors however they see fit, and there are no additional restrictions on its contents.
{
"errors": [
{
"message": "Name for character with ID 1002 could not be fetched.",
"locations": [ { "line": 6, "column": 7 } ],
"path": [ "hero", "heroFriends", 1, "name" ],
"extensions": {
"code": "CAN_NOT_FETCH_BY_ID",
"timestamp": "Fri Feb 9 14:33:09 UTC 2018"
}
}
]
}
showPath (false): Preserve the GraphQLError path data.
createError('SomeError', {
message: 'Some error message.',
options: {
showLocations: false,
showPath: true,
},
});
Expected Behaviour
It shows the path in the GraphQL result.
Actual Behaviour
It is not showing the path, only if I enable showLocations
too, which I don't want.
It would be good to know what query caused the error and what the variables were… Maybe just in dev mode due to lots of data and possible password leaks…
The optional second argument is not really optional
apollo-errors/dist/index.js:87
var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { message: 'An error has occurred', options: options };
^
ReferenceError: options is not defined
var createError = exports.createError = function createError(name) {
var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { message: 'An error has occurred', options: options };
var e = ApolloError.bind(null, name, data);
return e;
};
How would I use apollo-errors
with Apollo Server 2.0? Example:
const { ApolloServer, gql } = require('apollo-server');
// Construct a schema, using GraphQL schema language
const typeDefs = gql`
type Query {
announcement: String
}
`;
// Provide resolver functions for your schema fields
const resolvers = {
Query: {
announcement: () =>
`Say hello to the new Apollo Server! A production ready GraphQL server with an incredible getting started experience.`
}
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
I can't seem to figure out why the message I define as a parameter for createError is not being returned when instantiating a new error.
But when I instantiate the error with a message the message gets returned as expected.
E.g. the following error:
const { createError } = require('apollo-errors');
const InvalidCredentialsError = createError('InvalidCredentialsError', {
message: 'The provided credentials are invalid.'
});
module.exports = { InvalidCredentialsError };
I instantiate the error as follows:
const valid = await bcrypt.compare(password, user.password);
if (!valid) {
throw new InvalidCredentialsError();
}
The resulting response is this (notice the empty message):
{
"data": null,
"errors": [
{
"message": "",
"name": "InvalidCredentialsError",
"time_thrown": "2018-04-08T07:59:39.258Z",
"data": {}
}
]
}
But when I instantiate the error like so:
const valid = await bcrypt.compare(password, user.password);
if (!valid) {
throw new InvalidCredentialsError({ message: 'Testmessage' });
}
I get the following (expected) response:
{
"data": null,
"errors": [
{
"message": "Testmessage",
"name": "InvalidCredentialsError",
"time_thrown": "2018-04-08T08:00:40.677Z",
"data": {}
}
]
}
Not sure if it matters but the formatting options I set up as follows:
const { formatError } = require('apollo-errors');
const { GraphQLServer, Options } = require('graphql-yoga');
const { Prisma } = require('prisma-binding');
const { makeExecutableSchema } = require('graphql-tools');
const { importSchema } = require('graphql-import');
const resolvers = require('./resolvers');
const directiveResolvers = require('./directiveResolvers');
const schema = makeExecutableSchema({
typeDefs: importSchema('./src/schema.graphql'),
resolvers,
directiveResolvers
});
const options = {
formatError
};
const server = new GraphQLServer({
schema,
context: req => ({
...req,
db: new Prisma({
typeDefs: 'src/generated/prisma.graphql',
endpoint: process.env.PRISMA_ENDPOINT,
secret: process.env.PRISMA_SECRET,
debug: true
})
})
});
server.start(options, () => console.log('Server is running on http://localhost:4000'));
Any guidance on how to fix this would be greatly appreciated.
My Apollo client uses subscription endpoint for both queries and mutations. How can I use it for subscription end-point?
It would be awesome if we could have an internalData
property on the Error constructor.
The internalData
object is intended to be for data that is not intended to be sent to the client.
Instead it should be used for sensitive data.
E.g. A third party HTTP call timed out and you want to give your user an unspecified error, but still collect some detail data in your server logs.
At the moment I am doing it like this:
import {
isInstance as isApolloErrorInstance,
createError as createApolloError,
formatError as formatApolloError
} from "apollo-errors";
const UnexpectedError = createApolloError(`UnexpectedError`, {
message: `An unexpected error occured.`
});
const logger = data => console.log(JSON.stringify(data));
const formatError = error => {
const { originalError } = error;
if (isApolloErrorInstance(originalError)) {
// The internal data contains information that should never leave the server!
const internalInformation = originalError.data.internal;
// therefore we delete it
delete originalError.data.internal;
// but we still want to log the data
logger({
type: `error`,
name: originalError.name,
data: internalInformation
});
return formatApolloError(error);
} else {
logger({
type: `error`,
name: `UnexpectedError`,
data: {
error
}
});
return formatApolloError(new UnexpectedError());
}
};
This is what I am doing inside my resolver:
import { createError as createApolloError } from "apollo-errors";
const SomeApolloError = createApolloError(`SomeApolloError`, {
message: `Something predictable happened.`
});
export async function resolver() {
throw new SomeApolloError({
data: {
foo: `Some data for the client.`,
internal: {
foo: `Some data for the logs that should never leave the server`
}
}
});
}
I think this is a pretty common scenario and would like to hear your thoughts about this.
I'm hoping I'll have time to work on this soon, but what are your thoughts on adding Typescript support versus a complete rewrite in Typescript?
The "normal" way to use this package is:
import { createError } from 'apollo-errors';
const FooError = createError('FooError', { message: 'A foo error has occurred' });
throw new FooError({ data: { something: 'important' } });
However, that seems a bit cumbersome to me. Is there a reason why the API doesn't look something like this instead?
import { ApolloError } from 'apollo-errors';
throw new ApolloError({ name: 'FooError', message: 'A foo error has occurred', data: { ... } });
As seen here: graphql/graphql-js#821
There appears to have been a breaking change with scalar resolver error handling
This is more of a question than it is an issue. Is there any way to pass a variable into the error message?
If I have the following error message:
throw new Error(
`There was no user found with the username, ${username}.`
);
How can I get that username into the apollo-errors? Is there a way to pass it as an argument?
throw new err.IncorrectUsernameError(username);
Inside my error file that imports apollo-errors:
exports.IncorrectUsernameError = createError('IncorrectPasswordError', {
message: `There was no user found with the username, ${username}.`,
});
Please let me know if there is some way to accomplish this.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.