2018-04-17 21:19:44 -07:00
|
|
|
import { GraphQLError } from 'graphql';
|
2018-05-02 16:27:33 -07:00
|
|
|
import { LogStep, LogAction, LogMessage, LogFunction } from './logging';
|
2018-04-17 21:19:44 -07:00
|
|
|
|
|
|
|
export class ApolloError extends Error {
|
|
|
|
public extensions;
|
2018-04-23 15:09:36 -07:00
|
|
|
constructor(
|
|
|
|
message: string,
|
|
|
|
code?: string,
|
|
|
|
properties?: Record<string, any>,
|
|
|
|
) {
|
2018-04-17 21:19:44 -07:00
|
|
|
super(message);
|
2018-05-02 16:43:44 -07:00
|
|
|
|
2018-05-03 09:59:12 -07:00
|
|
|
if (properties) {
|
|
|
|
Object.keys(properties).forEach(key => {
|
|
|
|
this[key] = properties[key];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-05-02 16:43:44 -07:00
|
|
|
//extensions are flattened to be included in the root of GraphQLError's, so
|
|
|
|
//don't add properties to extensions
|
|
|
|
this.extensions = { code };
|
2018-04-17 21:19:44 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-08 11:02:21 -07:00
|
|
|
function enrichError(error: GraphQLError, debug: boolean = false) {
|
2018-04-17 21:19:44 -07:00
|
|
|
const expanded: GraphQLError = {
|
2018-04-27 10:41:59 -07:00
|
|
|
message: error.message,
|
|
|
|
path: error.path,
|
|
|
|
locations: error.locations,
|
2018-04-17 21:19:44 -07:00
|
|
|
...error,
|
|
|
|
extensions: {
|
|
|
|
...error.extensions,
|
2018-04-18 09:52:36 -07:00
|
|
|
code:
|
|
|
|
(error.extensions && error.extensions.code) || 'INTERNAL_SERVER_ERROR',
|
2018-04-17 21:19:44 -07:00
|
|
|
exception: {
|
2018-04-18 09:56:02 -04:00
|
|
|
...(error.extensions && error.extensions.exception),
|
2018-04-17 21:19:44 -07:00
|
|
|
...(error.originalError as any),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
//ensure that extensions is not taken from the originalError
|
|
|
|
//graphql-js ensures that the originalError's extensions are hoisted
|
|
|
|
//https://github.com/graphql/graphql-js/blob/0bb47b2/src/error/GraphQLError.js#L138
|
|
|
|
delete expanded.extensions.exception.extensions;
|
2018-04-18 09:56:02 -04:00
|
|
|
if (debug && !expanded.extensions.exception.stacktrace) {
|
2018-04-17 21:19:44 -07:00
|
|
|
expanded.extensions.exception.stacktrace =
|
|
|
|
(error.originalError &&
|
|
|
|
error.originalError.stack &&
|
|
|
|
error.originalError.stack.split('\n')) ||
|
|
|
|
(error.stack && error.stack.split('\n'));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Object.keys(expanded.extensions.exception).length === 0) {
|
|
|
|
//remove from printing an empty object
|
2018-04-18 09:56:02 -04:00
|
|
|
delete expanded.extensions.exception;
|
2018-04-17 21:19:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return expanded;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function toApolloError(
|
|
|
|
error: Error,
|
2018-04-18 09:52:36 -07:00
|
|
|
code: string = 'INTERNAL_SERVER_ERROR',
|
2018-04-17 21:19:44 -07:00
|
|
|
): Error & { extensions: Record<string, any> } {
|
|
|
|
let err: GraphQLError = error;
|
|
|
|
if (err.extensions) {
|
|
|
|
err.extensions.code = code;
|
|
|
|
} else {
|
|
|
|
err.extensions = { code };
|
|
|
|
}
|
|
|
|
return err as Error & { extensions: Record<string, any> };
|
|
|
|
}
|
|
|
|
|
2018-04-23 15:09:36 -07:00
|
|
|
export interface ErrorOptions {
|
|
|
|
code?: string;
|
|
|
|
errorClass?: typeof ApolloError;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function fromGraphQLError(error: GraphQLError, options?: ErrorOptions) {
|
2018-04-23 16:24:08 -07:00
|
|
|
const copy: GraphQLError =
|
|
|
|
options && options.errorClass
|
|
|
|
? new options.errorClass(error.message)
|
|
|
|
: new ApolloError(error.message);
|
2018-04-23 15:09:36 -07:00
|
|
|
|
|
|
|
//copy enumerable keys
|
|
|
|
Object.keys(error).forEach(key => {
|
|
|
|
copy[key] = error[key];
|
|
|
|
});
|
|
|
|
|
|
|
|
//extensions are non enumerable, so copy them directly
|
2018-04-17 21:19:44 -07:00
|
|
|
copy.extensions = {
|
|
|
|
...copy.extensions,
|
2018-04-23 15:09:36 -07:00
|
|
|
...error.extensions,
|
2018-04-17 21:19:44 -07:00
|
|
|
};
|
|
|
|
|
2018-04-23 15:09:36 -07:00
|
|
|
//Fallback on default for code
|
|
|
|
if (!copy.extensions.code) {
|
2018-04-23 16:24:08 -07:00
|
|
|
copy.extensions.code = (options && options.code) || 'INTERNAL_SERVER_ERROR';
|
2018-04-23 15:09:36 -07:00
|
|
|
}
|
|
|
|
|
2018-04-17 21:19:44 -07:00
|
|
|
//copy the original error, while keeping all values non-enumerable, so they
|
|
|
|
//are not printed unless directly referenced
|
|
|
|
Object.defineProperty(copy, 'originalError', { value: {} });
|
2018-05-03 17:44:53 -07:00
|
|
|
Object.getOwnPropertyNames(error).forEach(key => {
|
2018-04-17 21:19:44 -07:00
|
|
|
Object.defineProperty(copy.originalError, key, { value: error[key] });
|
|
|
|
});
|
|
|
|
|
|
|
|
return copy;
|
|
|
|
}
|
|
|
|
|
2018-04-23 15:09:36 -07:00
|
|
|
export class SyntaxError extends ApolloError {
|
2018-04-23 16:24:08 -07:00
|
|
|
// TODO make the name nonenumerable
|
|
|
|
// name = 'SyntaxError';
|
2018-04-17 21:19:44 -07:00
|
|
|
constructor(message: string) {
|
2018-04-23 15:09:36 -07:00
|
|
|
super(message, 'GRAPHQL_PARSE_FAILED');
|
2018-04-17 21:19:44 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class ValidationError extends ApolloError {
|
2018-04-23 16:24:08 -07:00
|
|
|
// TODO make the name nonenumerable
|
|
|
|
// name = 'ValidationError';
|
2018-04-17 21:19:44 -07:00
|
|
|
constructor(message: string) {
|
2018-04-23 15:09:36 -07:00
|
|
|
super(message, 'GRAPHQL_VALIDATION_FAILED');
|
2018-04-17 21:19:44 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class AuthenticationError extends ApolloError {
|
2018-04-23 16:24:08 -07:00
|
|
|
// TODO make the name nonenumerable
|
|
|
|
// name = 'AuthenticationError';
|
2018-04-23 15:09:36 -07:00
|
|
|
constructor(message: string) {
|
|
|
|
super(message, 'UNAUTHENTICATED');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class ForbiddenError extends ApolloError {
|
2018-04-23 16:24:08 -07:00
|
|
|
// TODO make the name nonenumerable
|
|
|
|
// name = 'ForbiddenError';
|
2018-04-17 21:19:44 -07:00
|
|
|
constructor(message: string) {
|
2018-04-23 15:09:36 -07:00
|
|
|
super(message, 'FORBIDDEN');
|
2018-04-17 21:19:44 -07:00
|
|
|
}
|
|
|
|
}
|
2018-05-02 16:40:03 -07:00
|
|
|
|
|
|
|
export function formatApolloErrors(
|
|
|
|
errors: Array<Error>,
|
|
|
|
options?: {
|
|
|
|
formatter?: Function;
|
|
|
|
logFunction?: LogFunction;
|
|
|
|
debug?: boolean;
|
|
|
|
},
|
|
|
|
): Array<Error> {
|
|
|
|
const { formatter, debug, logFunction } = options;
|
2018-05-08 11:02:21 -07:00
|
|
|
|
|
|
|
const enrichedErrors = errors.map(error => enrichError(error, debug));
|
|
|
|
|
|
|
|
if (!formatter) {
|
|
|
|
return enrichedErrors;
|
|
|
|
}
|
|
|
|
|
|
|
|
return enrichedErrors.map(error => {
|
|
|
|
try {
|
|
|
|
return formatter(error);
|
|
|
|
} catch (err) {
|
|
|
|
logFunction({
|
|
|
|
action: LogAction.cleanup,
|
|
|
|
step: LogStep.status,
|
|
|
|
data: err,
|
|
|
|
key: 'error',
|
|
|
|
});
|
|
|
|
|
|
|
|
if (debug) {
|
|
|
|
return enrichError(err, debug);
|
|
|
|
} else {
|
|
|
|
//obscure error
|
|
|
|
const newError: GraphQLError = fromGraphQLError(
|
|
|
|
new GraphQLError('Internal server error'),
|
|
|
|
);
|
|
|
|
return enrichError(newError, debug);
|
2018-05-02 16:40:03 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}) as Array<Error>;
|
|
|
|
}
|