mirror of
https://github.com/vale981/apollo-server
synced 2025-03-05 09:41:40 -05:00
errors: send stack in debug, codes, make ApolloErrors
This commit is contained in:
parent
e1cfd83124
commit
4230220e2b
5 changed files with 162 additions and 19 deletions
98
packages/apollo-server-core/src/errors.ts
Normal file
98
packages/apollo-server-core/src/errors.ts
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
import { GraphQLError } from 'graphql';
|
||||||
|
export interface IApolloError {}
|
||||||
|
|
||||||
|
export class ApolloError extends Error {
|
||||||
|
public extensions;
|
||||||
|
constructor(message: string, code: string, properties?: Record<string, any>) {
|
||||||
|
super(message);
|
||||||
|
this.extensions = { ...properties, code };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatError(error: GraphQLError, debug: boolean = false) {
|
||||||
|
const expanded: GraphQLError = {
|
||||||
|
...error,
|
||||||
|
extensions: {
|
||||||
|
...error.extensions,
|
||||||
|
code: (error.extensions && error.extensions.code) || 'INTERNAL_ERROR',
|
||||||
|
exception: {
|
||||||
|
...(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;
|
||||||
|
if (debug) {
|
||||||
|
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
|
||||||
|
expanded.extensions.exception = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toApolloError(
|
||||||
|
error: Error,
|
||||||
|
code: string = 'INTERNAL_ERROR',
|
||||||
|
): 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> };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fromGraphQLError(
|
||||||
|
error: GraphQLError,
|
||||||
|
code: string = 'INTERNAL_ERROR',
|
||||||
|
) {
|
||||||
|
const copy: GraphQLError = {
|
||||||
|
...error,
|
||||||
|
};
|
||||||
|
copy.extensions = {
|
||||||
|
...copy.extensions,
|
||||||
|
code,
|
||||||
|
};
|
||||||
|
|
||||||
|
//copy the original error, while keeping all values non-enumerable, so they
|
||||||
|
//are not printed unless directly referenced
|
||||||
|
Object.defineProperty(copy, 'originalError', { value: {} });
|
||||||
|
Reflect.ownKeys(error).forEach(key => {
|
||||||
|
Object.defineProperty(copy.originalError, key, { value: error[key] });
|
||||||
|
});
|
||||||
|
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ParseError extends ApolloError {
|
||||||
|
name = 'MalformedQueryError';
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message, 'MALFORMED_QUERY');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ValidationError extends ApolloError {
|
||||||
|
name = 'ValidationError';
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message, 'QUERY_VALIDATION_FAILED');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AuthenticationError extends ApolloError {
|
||||||
|
name = 'UnauthorizedError';
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message, 'UNAUTHORIZED');
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,3 +10,10 @@ export {
|
||||||
default as GraphQLOptions,
|
default as GraphQLOptions,
|
||||||
resolveGraphqlOptions,
|
resolveGraphqlOptions,
|
||||||
} from './graphqlOptions';
|
} from './graphqlOptions';
|
||||||
|
export {
|
||||||
|
ApolloError,
|
||||||
|
toApolloError,
|
||||||
|
ParseError,
|
||||||
|
ValidationError,
|
||||||
|
AuthenticationError,
|
||||||
|
} from './errors';
|
||||||
|
|
|
@ -1,15 +1,10 @@
|
||||||
import {
|
import { parse, getOperationAST, DocumentNode, ExecutionResult } from 'graphql';
|
||||||
parse,
|
|
||||||
getOperationAST,
|
|
||||||
DocumentNode,
|
|
||||||
formatError,
|
|
||||||
ExecutionResult,
|
|
||||||
} from 'graphql';
|
|
||||||
import { runQuery } from './runQuery';
|
import { runQuery } from './runQuery';
|
||||||
import {
|
import {
|
||||||
default as GraphQLOptions,
|
default as GraphQLOptions,
|
||||||
resolveGraphqlOptions,
|
resolveGraphqlOptions,
|
||||||
} from './graphqlOptions';
|
} from './graphqlOptions';
|
||||||
|
import { formatError } from './errors';
|
||||||
|
|
||||||
export interface HttpQueryRequest {
|
export interface HttpQueryRequest {
|
||||||
method: string;
|
method: string;
|
||||||
|
@ -56,7 +51,12 @@ export async function runHttpQuery(
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new HttpQueryError(500, e.message);
|
throw new HttpQueryError(500, e.message);
|
||||||
}
|
}
|
||||||
const formatErrorFn = optionsObject.formatError || formatError;
|
const formatErrorFn = error =>
|
||||||
|
optionsObject.formatError(formatError(error)) || formatError;
|
||||||
|
const debugDefault =
|
||||||
|
process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test';
|
||||||
|
const debug =
|
||||||
|
optionsObject.debug !== undefined ? optionsObject.debug : debugDefault;
|
||||||
let requestPayload;
|
let requestPayload;
|
||||||
|
|
||||||
switch (request.method) {
|
switch (request.method) {
|
||||||
|
@ -149,7 +149,7 @@ export async function runHttpQuery(
|
||||||
operationName: operationName,
|
operationName: operationName,
|
||||||
logFunction: optionsObject.logFunction,
|
logFunction: optionsObject.logFunction,
|
||||||
validationRules: optionsObject.validationRules,
|
validationRules: optionsObject.validationRules,
|
||||||
formatError: formatErrorFn,
|
formatError: optionsObject.formatError,
|
||||||
formatResponse: optionsObject.formatResponse,
|
formatResponse: optionsObject.formatResponse,
|
||||||
fieldResolver: optionsObject.fieldResolver,
|
fieldResolver: optionsObject.fieldResolver,
|
||||||
debug: optionsObject.debug,
|
debug: optionsObject.debug,
|
||||||
|
@ -176,6 +176,8 @@ export async function runHttpQuery(
|
||||||
|
|
||||||
if (!isBatch) {
|
if (!isBatch) {
|
||||||
const gqlResponse = responses[0];
|
const gqlResponse = responses[0];
|
||||||
|
//This code is run on parse/validation errors and any other error that
|
||||||
|
//doesn't reach GraphQL execution
|
||||||
if (gqlResponse.errors && typeof gqlResponse.data === 'undefined') {
|
if (gqlResponse.errors && typeof gqlResponse.data === 'undefined') {
|
||||||
throw new HttpQueryError(400, JSON.stringify(gqlResponse), true, {
|
throw new HttpQueryError(400, JSON.stringify(gqlResponse), true, {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
|
@ -8,7 +8,6 @@ import {
|
||||||
validate,
|
validate,
|
||||||
execute,
|
execute,
|
||||||
GraphQLError,
|
GraphQLError,
|
||||||
formatError,
|
|
||||||
specifiedRules,
|
specifiedRules,
|
||||||
ValidationContext,
|
ValidationContext,
|
||||||
} from 'graphql';
|
} from 'graphql';
|
||||||
|
@ -24,6 +23,8 @@ import {
|
||||||
CacheControlExtensionOptions,
|
CacheControlExtensionOptions,
|
||||||
} from 'apollo-cache-control';
|
} from 'apollo-cache-control';
|
||||||
|
|
||||||
|
import { fromGraphQLError, formatError } from './errors';
|
||||||
|
|
||||||
export interface GraphQLResponse {
|
export interface GraphQLResponse {
|
||||||
data?: object;
|
data?: object;
|
||||||
errors?: Array<GraphQLError & object>;
|
errors?: Array<GraphQLError & object>;
|
||||||
|
@ -83,18 +84,28 @@ function printStackTrace(error: Error) {
|
||||||
console.error(error.stack);
|
console.error(error.stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
function format(errors: Array<Error>, formatter?: Function): Array<Error> {
|
function format(
|
||||||
return errors.map(error => {
|
errors: Array<Error>,
|
||||||
|
options?: {
|
||||||
|
formatter?: Function;
|
||||||
|
debug?: boolean;
|
||||||
|
},
|
||||||
|
): Array<Error> {
|
||||||
|
const { formatter, debug } = options;
|
||||||
|
return errors.map(error => formatError(error, debug)).map(error => {
|
||||||
if (formatter !== undefined) {
|
if (formatter !== undefined) {
|
||||||
try {
|
try {
|
||||||
return formatter(error);
|
return formatter(error);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error in formatError function:', err);
|
console.error('Error in formatError function:', err);
|
||||||
const newError = new Error('Internal server error');
|
const newError: GraphQLError = fromGraphQLError(
|
||||||
return formatError(newError);
|
new GraphQLError('Internal server error'),
|
||||||
|
'INTERNAL_ERROR',
|
||||||
|
);
|
||||||
|
return formatError(newError, debug);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return formatError(error);
|
return error;
|
||||||
}
|
}
|
||||||
}) as Array<Error>;
|
}) as Array<Error>;
|
||||||
}
|
}
|
||||||
|
@ -164,7 +175,10 @@ function doRunQuery(options: QueryOptions): Promise<GraphQLResponse> {
|
||||||
} catch (syntaxError) {
|
} catch (syntaxError) {
|
||||||
logFunction({ action: LogAction.parse, step: LogStep.end });
|
logFunction({ action: LogAction.parse, step: LogStep.end });
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
errors: format([syntaxError], options.formatError),
|
errors: format([fromGraphQLError(syntaxError, 'MALFORMED_QUERY')], {
|
||||||
|
formatter: options.formatError,
|
||||||
|
debug,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -178,9 +192,18 @@ function doRunQuery(options: QueryOptions): Promise<GraphQLResponse> {
|
||||||
logFunction({ action: LogAction.validation, step: LogStep.start });
|
logFunction({ action: LogAction.validation, step: LogStep.start });
|
||||||
const validationErrors = validate(options.schema, documentAST, rules);
|
const validationErrors = validate(options.schema, documentAST, rules);
|
||||||
logFunction({ action: LogAction.validation, step: LogStep.end });
|
logFunction({ action: LogAction.validation, step: LogStep.end });
|
||||||
|
|
||||||
if (validationErrors.length) {
|
if (validationErrors.length) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
errors: format(validationErrors, options.formatError),
|
errors: format(
|
||||||
|
validationErrors.map(err =>
|
||||||
|
fromGraphQLError(err, 'QUERY_VALIDATION_FAILED'),
|
||||||
|
),
|
||||||
|
{
|
||||||
|
formatter: options.formatError,
|
||||||
|
debug,
|
||||||
|
},
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,7 +232,10 @@ function doRunQuery(options: QueryOptions): Promise<GraphQLResponse> {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (result.errors) {
|
if (result.errors) {
|
||||||
response.errors = format(result.errors, options.formatError);
|
response.errors = format(result.errors, {
|
||||||
|
formatter: options.formatError,
|
||||||
|
debug,
|
||||||
|
});
|
||||||
if (debug) {
|
if (debug) {
|
||||||
result.errors.map(printStackTrace);
|
result.errors.map(printStackTrace);
|
||||||
}
|
}
|
||||||
|
@ -231,7 +257,10 @@ function doRunQuery(options: QueryOptions): Promise<GraphQLResponse> {
|
||||||
logFunction({ action: LogAction.execute, step: LogStep.end });
|
logFunction({ action: LogAction.execute, step: LogStep.end });
|
||||||
logFunction({ action: LogAction.request, step: LogStep.end });
|
logFunction({ action: LogAction.request, step: LogStep.end });
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
errors: format([executionError], options.formatError),
|
errors: format([fromGraphQLError(executionError, 'EXECUTION_ERROR')], {
|
||||||
|
formatter: options.formatError,
|
||||||
|
debug,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
// Expose types which can be used by both middleware flavors.
|
// Expose types which can be used by both middleware flavors.
|
||||||
export { GraphQLOptions } from 'apollo-server-core';
|
export { GraphQLOptions } from 'apollo-server-core';
|
||||||
|
export {
|
||||||
|
toApolloError,
|
||||||
|
ApolloError,
|
||||||
|
AuthenticationError,
|
||||||
|
ParseError,
|
||||||
|
ValidationError,
|
||||||
|
} from 'apollo-server-core';
|
||||||
|
|
||||||
// Express Middleware
|
// Express Middleware
|
||||||
export {
|
export {
|
||||||
|
|
Loading…
Add table
Reference in a new issue