From dcac1b0c373b62b97309c918561d8d4437faa94e Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Wed, 2 May 2018 16:27:33 -0700 Subject: [PATCH 01/21] apollo-server-core: move logging into separate file --- .../apollo-server-core/src/ApolloServer.ts | 2 +- packages/apollo-server-core/src/errors.ts | 1 + .../apollo-server-core/src/graphqlOptions.ts | 2 +- packages/apollo-server-core/src/index.ts | 9 ++----- packages/apollo-server-core/src/logging.ts | 25 ++++++++++++++++++ .../apollo-server-core/src/runQuery.test.ts | 3 ++- packages/apollo-server-core/src/runQuery.ts | 26 ++----------------- 7 files changed, 34 insertions(+), 34 deletions(-) create mode 100644 packages/apollo-server-core/src/logging.ts diff --git a/packages/apollo-server-core/src/ApolloServer.ts b/packages/apollo-server-core/src/ApolloServer.ts index 1572be58ea7..d483dba27e1 100644 --- a/packages/apollo-server-core/src/ApolloServer.ts +++ b/packages/apollo-server-core/src/ApolloServer.ts @@ -19,7 +19,7 @@ import { import { internalFormatError } from './errors'; import { GraphQLServerOptions as GraphQLOptions } from './graphqlOptions'; -import { LogFunction } from './runQuery'; +import { LogFunction } from './logging'; import { Config, diff --git a/packages/apollo-server-core/src/errors.ts b/packages/apollo-server-core/src/errors.ts index 825dec6c122..f0e97af662b 100644 --- a/packages/apollo-server-core/src/errors.ts +++ b/packages/apollo-server-core/src/errors.ts @@ -1,4 +1,5 @@ import { GraphQLError } from 'graphql'; +import { LogStep, LogAction, LogMessage, LogFunction } from './logging'; export class ApolloError extends Error { public extensions; diff --git a/packages/apollo-server-core/src/graphqlOptions.ts b/packages/apollo-server-core/src/graphqlOptions.ts index 3118f282067..e1fc5f69849 100644 --- a/packages/apollo-server-core/src/graphqlOptions.ts +++ b/packages/apollo-server-core/src/graphqlOptions.ts @@ -3,7 +3,7 @@ import { ValidationContext, GraphQLFieldResolver, } from 'graphql'; -import { LogFunction } from './runQuery'; +import { LogFunction } from './logging'; import { GraphQLExtension } from 'graphql-extensions'; /* diff --git a/packages/apollo-server-core/src/index.ts b/packages/apollo-server-core/src/index.ts index de9f269fa70..9dd7848f120 100644 --- a/packages/apollo-server-core/src/index.ts +++ b/packages/apollo-server-core/src/index.ts @@ -1,10 +1,5 @@ -export { - runQuery, - LogFunction, - LogMessage, - LogStep, - LogAction, -} from './runQuery'; +export { runQuery } from './runQuery'; +export { LogFunction, LogMessage, LogStep, LogAction } from './logging'; export { runHttpQuery, HttpQueryRequest, HttpQueryError } from './runHttpQuery'; export { default as GraphQLOptions, diff --git a/packages/apollo-server-core/src/logging.ts b/packages/apollo-server-core/src/logging.ts new file mode 100644 index 00000000000..e30f0b12077 --- /dev/null +++ b/packages/apollo-server-core/src/logging.ts @@ -0,0 +1,25 @@ +export enum LogAction { + request, + parse, + validation, + execute, + setup, + cleanup, +} + +export enum LogStep { + start, + end, + status, +} + +export interface LogMessage { + action: LogAction; + step: LogStep; + key?: string; + data?: any; +} + +export interface LogFunction { + (message: LogMessage); +} diff --git a/packages/apollo-server-core/src/runQuery.test.ts b/packages/apollo-server-core/src/runQuery.test.ts index fb9c22abac9..37bf764ff8b 100644 --- a/packages/apollo-server-core/src/runQuery.test.ts +++ b/packages/apollo-server-core/src/runQuery.test.ts @@ -12,7 +12,8 @@ import { parse, } from 'graphql'; -import { runQuery, LogAction, LogStep } from './runQuery'; +import { runQuery } from './runQuery'; +import { LogAction, LogStep } from './logging'; // Make the global Promise constructor Fiber-aware to simulate a Meteor // environment. diff --git a/packages/apollo-server-core/src/runQuery.ts b/packages/apollo-server-core/src/runQuery.ts index fb914a50325..ab19576041a 100644 --- a/packages/apollo-server-core/src/runQuery.ts +++ b/packages/apollo-server-core/src/runQuery.ts @@ -27,36 +27,14 @@ import { SyntaxError, } from './errors'; +import { LogStep, LogAction, LogMessage, LogFunction } from './logging'; + export interface GraphQLResponse { data?: object; errors?: Array; extensions?: object; } -export enum LogAction { - request, - parse, - validation, - execute, -} - -export enum LogStep { - start, - end, - status, -} - -export interface LogMessage { - action: LogAction; - step: LogStep; - key?: string; - data?: any; -} - -export interface LogFunction { - (message: LogMessage); -} - export interface QueryOptions { schema: GraphQLSchema; query: string | DocumentNode; From 879a2899774e39adfc67f667d64f24527db3f98e Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Wed, 2 May 2018 16:40:03 -0700 Subject: [PATCH 02/21] apollo-server-core: internalFormatError to formatApolloError --- .../apollo-server-core/src/ApolloServer.ts | 7 +--- packages/apollo-server-core/src/errors.ts | 42 +++++++++++++++++-- packages/apollo-server-core/src/index.ts | 2 +- .../apollo-server-core/src/runHttpQuery.ts | 12 ++++-- packages/apollo-server-core/src/runQuery.ts | 37 ++++------------ 5 files changed, 57 insertions(+), 43 deletions(-) diff --git a/packages/apollo-server-core/src/ApolloServer.ts b/packages/apollo-server-core/src/ApolloServer.ts index d483dba27e1..af2c7eb8f58 100644 --- a/packages/apollo-server-core/src/ApolloServer.ts +++ b/packages/apollo-server-core/src/ApolloServer.ts @@ -17,7 +17,7 @@ import { ExecutionParams, } from 'subscriptions-transport-ws'; -import { internalFormatError } from './errors'; +import { formatApolloErrors } from './errors'; import { GraphQLServerOptions as GraphQLOptions } from './graphqlOptions'; import { LogFunction } from './logging'; @@ -212,8 +212,7 @@ export class ApolloServerBase { onOperation: async (_: string, connection: ExecutionParams) => { connection.formatResponse = (value: ExecutionResult) => ({ ...value, - errors: - value.errors && value.errors.map(err => internalFormatError(err)), + errors: value.errors && formatApolloErrors(value.errors), }); let context: Context = this.context ? this.context : { connection }; @@ -279,8 +278,6 @@ export class ApolloServerBase { schema: this.schema, tracing: Boolean(this.engineEnabled), cacheControl: Boolean(this.engineEnabled), - formatError: (e: GraphQLError) => - internalFormatError(e, this.requestOptions.debug), context, // allow overrides from options ...this.requestOptions, diff --git a/packages/apollo-server-core/src/errors.ts b/packages/apollo-server-core/src/errors.ts index f0e97af662b..fecdf608a86 100644 --- a/packages/apollo-server-core/src/errors.ts +++ b/packages/apollo-server-core/src/errors.ts @@ -13,10 +13,7 @@ export class ApolloError extends Error { } } -export function internalFormatError( - error: GraphQLError, - debug: boolean = false, -) { +export function enrichError(error: GraphQLError, debug: boolean = false) { const expanded: GraphQLError = { message: error.message, path: error.path, @@ -134,3 +131,40 @@ export class ForbiddenError extends ApolloError { super(message, 'FORBIDDEN'); } } + +export function formatApolloErrors( + errors: Array, + options?: { + formatter?: Function; + logFunction?: LogFunction; + debug?: boolean; + }, +): Array { + const { formatter, debug, logFunction } = options; + return errors.map(error => enrichError(error, debug)).map(error => { + if (formatter !== undefined) { + 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); + } + } + } else { + return error; + } + }) as Array; +} diff --git a/packages/apollo-server-core/src/index.ts b/packages/apollo-server-core/src/index.ts index 9dd7848f120..be17a44d0a0 100644 --- a/packages/apollo-server-core/src/index.ts +++ b/packages/apollo-server-core/src/index.ts @@ -12,7 +12,7 @@ export { ValidationError, AuthenticationError, ForbiddenError, - internalFormatError, + formatApolloErrors, } from './errors'; // ApolloServer Base class diff --git a/packages/apollo-server-core/src/runHttpQuery.ts b/packages/apollo-server-core/src/runHttpQuery.ts index 277b83469dc..4b7ed2e633d 100644 --- a/packages/apollo-server-core/src/runHttpQuery.ts +++ b/packages/apollo-server-core/src/runHttpQuery.ts @@ -4,7 +4,7 @@ import { default as GraphQLOptions, resolveGraphqlOptions, } from './graphqlOptions'; -import { internalFormatError } from './errors'; +import { formatApolloErrors } from './errors'; export interface HttpQueryRequest { method: string; @@ -198,7 +198,7 @@ export async function runHttpQuery( operationName: operationName, logFunction: optionsObject.logFunction, validationRules: optionsObject.validationRules, - formatError: formatErrorFn, + formatError: optionsObject.formatError, formatResponse: optionsObject.formatResponse, fieldResolver: optionsObject.fieldResolver, debug: optionsObject.debug, @@ -218,7 +218,13 @@ export async function runHttpQuery( return Promise.reject(e); } - return Promise.resolve({ errors: [formatErrorFn(e)] }); + return Promise.resolve({ + errors: formatApolloErrors([e], { + formatter: optionsObject.formatError, + debug, + logFunction: optionsObject.logFunction, + }), + }); } }); const responses = await Promise.all(requests); diff --git a/packages/apollo-server-core/src/runQuery.ts b/packages/apollo-server-core/src/runQuery.ts index ab19576041a..a19d2f4c770 100644 --- a/packages/apollo-server-core/src/runQuery.ts +++ b/packages/apollo-server-core/src/runQuery.ts @@ -22,7 +22,7 @@ import { CacheControlExtension } from 'apollo-cache-control'; import { fromGraphQLError, - internalFormatError, + formatApolloErrors, ValidationError, SyntaxError, } from './errors'; @@ -61,31 +61,6 @@ export function runQuery(options: QueryOptions): Promise { return Promise.resolve().then(() => doRunQuery(options)); } -function format( - errors: Array, - options?: { - formatter?: Function; - debug?: boolean; - }, -): Array { - const { formatter, debug } = options; - return errors.map(error => internalFormatError(error, debug)).map(error => { - if (formatter !== undefined) { - try { - return formatter(error); - } catch (err) { - console.error('Error in formatError function:', err); - const newError: GraphQLError = fromGraphQLError( - new GraphQLError('Internal server error'), - ); - return internalFormatError(newError, debug); - } - } else { - return error; - } - }) as Array; -} - function doRunQuery(options: QueryOptions): Promise { let documentAST: DocumentNode; @@ -151,7 +126,7 @@ function doRunQuery(options: QueryOptions): Promise { } catch (syntaxError) { logFunction({ action: LogAction.parse, step: LogStep.end }); return Promise.resolve({ - errors: format( + errors: formatApolloErrors( [ fromGraphQLError(syntaxError, { errorClass: SyntaxError, @@ -178,12 +153,13 @@ function doRunQuery(options: QueryOptions): Promise { if (validationErrors.length) { return Promise.resolve({ - errors: format( + errors: formatApolloErrors( validationErrors.map(err => fromGraphQLError(err, { errorClass: ValidationError }), ), { formatter: options.formatError, + logFunction, debug, }, ), @@ -214,8 +190,9 @@ function doRunQuery(options: QueryOptions): Promise { }; if (result.errors) { - response.errors = format(result.errors, { + response.errors = formatApolloErrors(result.errors, { formatter: options.formatError, + logFunction, debug, }); } @@ -249,7 +226,7 @@ function doRunQuery(options: QueryOptions): Promise { // * unknown operation/operation name invalid // * operation type is unsupported // Options: PREPROCESSING_FAILED, GRAPHQL_RUNTIME_CHECK_FAILED - errors: format([fromGraphQLError(executionError)], { + errors: formatApolloErrors([fromGraphQLError(executionError)], { formatter: options.formatError, debug, }), From 97cc12a9cb2a34de79f33fe73d7d946672c89600 Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Wed, 2 May 2018 16:43:44 -0700 Subject: [PATCH 03/21] apollo-server-core: apolloError places properties in root, so they are found in originalError --- packages/apollo-server-core/src/errors.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/apollo-server-core/src/errors.ts b/packages/apollo-server-core/src/errors.ts index fecdf608a86..61b5e7ba7b6 100644 --- a/packages/apollo-server-core/src/errors.ts +++ b/packages/apollo-server-core/src/errors.ts @@ -9,7 +9,13 @@ export class ApolloError extends Error { properties?: Record, ) { super(message); - this.extensions = { ...properties, code }; + + Object.keys(properties).forEach(key => { + this[key] = properties[key]; + }); + //extensions are flattened to be included in the root of GraphQLError's, so + //don't add properties to extensions + this.extensions = { code }; } } From 2f3eba2b39d0e08676dd7119a124a04773c48efc Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Wed, 2 May 2018 16:44:50 -0700 Subject: [PATCH 04/21] apollo-server-core: wrap error thrown by context construction in apollo error to add code --- .../apollo-server-core/src/graphqlOptions.ts | 6 +---- .../apollo-server-core/src/runHttpQuery.ts | 23 ++++++++++++++----- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/packages/apollo-server-core/src/graphqlOptions.ts b/packages/apollo-server-core/src/graphqlOptions.ts index e1fc5f69849..26a08fd694b 100644 --- a/packages/apollo-server-core/src/graphqlOptions.ts +++ b/packages/apollo-server-core/src/graphqlOptions.ts @@ -44,11 +44,7 @@ export async function resolveGraphqlOptions( ...args ): Promise { if (typeof options === 'function') { - try { - return await options(...args); - } catch (e) { - throw new Error(`Invalid options provided to ApolloServer: ${e.message}`); - } + return await options(...args); } else { return options; } diff --git a/packages/apollo-server-core/src/runHttpQuery.ts b/packages/apollo-server-core/src/runHttpQuery.ts index 4b7ed2e633d..f2c471d399c 100644 --- a/packages/apollo-server-core/src/runHttpQuery.ts +++ b/packages/apollo-server-core/src/runHttpQuery.ts @@ -42,6 +42,8 @@ export async function runHttpQuery( ): Promise { let isGetRequest: boolean = false; let optionsObject: GraphQLOptions; + const debugDefault = + process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test'; try { optionsObject = await resolveGraphqlOptions( @@ -49,13 +51,22 @@ export async function runHttpQuery( ...handlerArguments, ); } catch (e) { - throw new HttpQueryError(500, e.message); + // The options can be generated asynchronously, so we don't have access to + // the normal options provided by the user, such as: formatError, + // logFunction, debug. Therefore, we need to do some unnatural things, such + // as use NODE_ENV to determine the debug settings + e.message = `Invalid options provided to ApolloServer: ${e.message}`; + throw new HttpQueryError( + 500, + JSON.stringify({ + errors: formatApolloErrors([e], { debug: debugDefault }), + }), + true, + { + 'Content-Type': 'application/json', + }, + ); } - const formatErrorFn = optionsObject.formatError - ? error => optionsObject.formatError(internalFormatError(error)) - : internalFormatError; - const debugDefault = - process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test'; const debug = optionsObject.debug !== undefined ? optionsObject.debug : debugDefault; let requestPayload; From f3483552beecf902dd160437833197946c84257d Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Wed, 2 May 2018 23:28:24 -0700 Subject: [PATCH 05/21] apollo-server-core: added formatApolloError arguments to subscription callback --- packages/apollo-server-core/src/ApolloServer.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/apollo-server-core/src/ApolloServer.ts b/packages/apollo-server-core/src/ApolloServer.ts index af2c7eb8f58..db2d850becb 100644 --- a/packages/apollo-server-core/src/ApolloServer.ts +++ b/packages/apollo-server-core/src/ApolloServer.ts @@ -19,7 +19,7 @@ import { import { formatApolloErrors } from './errors'; import { GraphQLServerOptions as GraphQLOptions } from './graphqlOptions'; -import { LogFunction } from './logging'; +import { LogFunction, LogAction, LogStep } from './logging'; import { Config, @@ -212,7 +212,13 @@ export class ApolloServerBase { onOperation: async (_: string, connection: ExecutionParams) => { connection.formatResponse = (value: ExecutionResult) => ({ ...value, - errors: value.errors && formatApolloErrors(value.errors), + errors: + value.errors && + formatApolloErrors(value.errors, { + formatter: this.requestOptions.formatError, + debug: this.requestOptions.debug, + logFunction: this.requestOptions.logFunction, + }), }); let context: Context = this.context ? this.context : { connection }; @@ -222,8 +228,11 @@ export class ApolloServerBase { ? await this.context({ connection }) : context; } catch (e) { - console.error(e); - throw e; + throw formatApolloErrors([e], { + formatter: this.requestOptions.formatError, + debug: this.requestOptions.debug, + logFunction: this.requestOptions.logFunction, + })[0]; } return { ...connection, context }; From dc07e9285b21bea16db983ea5f3e66cf5c90ebd0 Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Wed, 2 May 2018 23:30:44 -0700 Subject: [PATCH 06/21] apollo-server-core: stronger typing on context in GraphQLServerOptions as a function --- packages/apollo-server-core/src/graphqlOptions.ts | 14 +++++++++++--- packages/apollo-server-core/src/runHttpQuery.ts | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/apollo-server-core/src/graphqlOptions.ts b/packages/apollo-server-core/src/graphqlOptions.ts index 26a08fd694b..5c251652a0b 100644 --- a/packages/apollo-server-core/src/graphqlOptions.ts +++ b/packages/apollo-server-core/src/graphqlOptions.ts @@ -21,7 +21,11 @@ import { GraphQLExtension } from 'graphql-extensions'; * - (optional) debug: a boolean that will print additional debug logging if execution errors occur * */ -export interface GraphQLServerOptions { +export interface GraphQLServerOptions< + TContext = + | (() => Promise> | Record) + | Record +> { schema: GraphQLSchema; formatError?: Function; rootValue?: any; @@ -40,8 +44,12 @@ export interface GraphQLServerOptions { export default GraphQLServerOptions; export async function resolveGraphqlOptions( - options: GraphQLServerOptions | Function, - ...args + options: + | GraphQLServerOptions + | (( + ...args: Array + ) => Promise | GraphQLServerOptions), + ...args: Array ): Promise { if (typeof options === 'function') { return await options(...args); diff --git a/packages/apollo-server-core/src/runHttpQuery.ts b/packages/apollo-server-core/src/runHttpQuery.ts index f2c471d399c..233c8051c97 100644 --- a/packages/apollo-server-core/src/runHttpQuery.ts +++ b/packages/apollo-server-core/src/runHttpQuery.ts @@ -9,7 +9,7 @@ import { formatApolloErrors } from './errors'; export interface HttpQueryRequest { method: string; query: Record; - options: GraphQLOptions | Function; + options: GraphQLOptions | (() => Promise | GraphQLOptions); } export class HttpQueryError extends Error { From dfccfa672a90bfc35c2ad74c0964ed78842782c5 Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Wed, 2 May 2018 23:46:04 -0700 Subject: [PATCH 07/21] apollo-server-core: allow creation of context to be asynchronous --- packages/apollo-server-core/src/ApolloServer.ts | 3 ++- packages/apollo-server-core/src/runHttpQuery.ts | 14 +++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/apollo-server-core/src/ApolloServer.ts b/packages/apollo-server-core/src/ApolloServer.ts index db2d850becb..56f9346ee68 100644 --- a/packages/apollo-server-core/src/ApolloServer.ts +++ b/packages/apollo-server-core/src/ApolloServer.ts @@ -278,9 +278,10 @@ export class ApolloServerBase { async request(request: Request) { let context: Context = this.context ? this.context : { request }; + //Differ context resolution to inside of runQuery context = typeof this.context === 'function' - ? await this.context({ req: request }) + ? async () => this.context({ req: request }) : context; return { diff --git a/packages/apollo-server-core/src/runHttpQuery.ts b/packages/apollo-server-core/src/runHttpQuery.ts index 233c8051c97..14f985fcdb8 100644 --- a/packages/apollo-server-core/src/runHttpQuery.ts +++ b/packages/apollo-server-core/src/runHttpQuery.ts @@ -110,7 +110,7 @@ export async function runHttpQuery( requestPayload = [requestPayload]; } - const requests: Array = requestPayload.map(requestParams => { + const requests = requestPayload.map(async requestParams => { try { let query = requestParams.query; let extensions = requestParams.extensions; @@ -190,9 +190,12 @@ export async function runHttpQuery( } } - let context = optionsObject.context || {}; - if (typeof context === 'function') { - context = context(); + let context = optionsObject.context; + if (!context) { + //appease typescript compiler, otherwise could use || {} + context = {}; + } else if (typeof context === 'function') { + context = await context(); } else if (isBatch) { context = Object.assign( Object.create(Object.getPrototypeOf(context)), @@ -237,7 +240,8 @@ export async function runHttpQuery( }), }); } - }); + }) as Array>; + const responses = await Promise.all(requests); if (!isBatch) { From 2a36f0fc472a2060735565eb3ef5f8e6940b03bc Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Wed, 2 May 2018 23:47:15 -0700 Subject: [PATCH 08/21] apollo-server-core: capture context creation failure in GraphQL error --- .../apollo-server-core/src/runHttpQuery.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/apollo-server-core/src/runHttpQuery.ts b/packages/apollo-server-core/src/runHttpQuery.ts index 14f985fcdb8..6ceed96bee2 100644 --- a/packages/apollo-server-core/src/runHttpQuery.ts +++ b/packages/apollo-server-core/src/runHttpQuery.ts @@ -195,7 +195,25 @@ export async function runHttpQuery( //appease typescript compiler, otherwise could use || {} context = {}; } else if (typeof context === 'function') { - context = await context(); + try { + context = await context(); + } catch (e) { + e.message = `Context creation failed: ${e.message}`; + throw new HttpQueryError( + 500, + JSON.stringify({ + errors: formatApolloErrors([e], { + formatter: optionsObject.formatError, + debug, + logFunction: optionsObject.logFunction, + }), + }), + true, + { + 'Content-Type': 'application/json', + }, + ); + } } else if (isBatch) { context = Object.assign( Object.create(Object.getPrototypeOf(context)), From f1c1e792bd95ad6d1047ef1ee6e000418c7da461 Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Thu, 3 May 2018 09:59:12 -0700 Subject: [PATCH 09/21] apollo-server-core: add check for null property in ApolloError --- packages/apollo-server-core/src/errors.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/apollo-server-core/src/errors.ts b/packages/apollo-server-core/src/errors.ts index 61b5e7ba7b6..6ca2b94cdac 100644 --- a/packages/apollo-server-core/src/errors.ts +++ b/packages/apollo-server-core/src/errors.ts @@ -10,9 +10,12 @@ export class ApolloError extends Error { ) { super(message); - Object.keys(properties).forEach(key => { - this[key] = properties[key]; - }); + if (properties) { + Object.keys(properties).forEach(key => { + this[key] = properties[key]; + }); + } + //extensions are flattened to be included in the root of GraphQLError's, so //don't add properties to extensions this.extensions = { code }; From 2f5d243e2fe5549830ef07584f02927265f0fb79 Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Thu, 3 May 2018 12:26:21 -0700 Subject: [PATCH 10/21] apollo-server-express: fix tests to include error code --- .../src/apolloServerHttp.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/apollo-server-express/src/apolloServerHttp.test.ts b/packages/apollo-server-express/src/apolloServerHttp.test.ts index 1255a388249..2947c99d9e3 100644 --- a/packages/apollo-server-express/src/apolloServerHttp.test.ts +++ b/packages/apollo-server-express/src/apolloServerHttp.test.ts @@ -330,6 +330,9 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => { data: null, errors: [ { + extensions: { + code: 'INTERNAL_SERVER_ERROR', + }, message: 'Throws!', locations: [{ line: 1, column: 2 }], path: ['thrower'], @@ -359,6 +362,9 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => { expect(JSON.parse(response.text)).to.deep.equal({ errors: [ { + extensions: { + code: 'GRAPHQL_VALIDATION_FAILED', + }, message: 'Cannot query field "notExists" on type "QueryRoot".', locations: [{ line: 1, column: 2 }], }, @@ -385,6 +391,9 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => { expect(JSON.parse(response.text)).to.deep.equal({ errors: [ { + extensions: { + code: 'GRAPHQL_VALIDATION_FAILED', + }, message: 'Cannot query field "notExists" on type "QueryRoot".', locations: [{ line: 1, column: 2 }], }, @@ -532,6 +541,9 @@ describe(`GraphQL-HTTP (apolloServer) tests for ${version} express`, () => { expect(JSON.parse(response.text)).to.deep.equal({ errors: [ { + extensions: { + code: 'GRAPHQL_VALIDATION_FAILED', + }, message: 'AlwaysInvalidRule was really invalid!', }, ], From 35420e81d74a36dee3a8db69d05970ee16cafa53 Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Thu, 3 May 2018 12:26:56 -0700 Subject: [PATCH 11/21] apollo-server-integration-testsuite: fix tests for apollo-server 2 --- .../src/index.ts | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/apollo-server-integration-testsuite/src/index.ts b/packages/apollo-server-integration-testsuite/src/index.ts index 201145e8820..a9e8553c455 100644 --- a/packages/apollo-server-integration-testsuite/src/index.ts +++ b/packages/apollo-server-integration-testsuite/src/index.ts @@ -827,7 +827,8 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => { const expected = /at resolveFieldValueOrError/; const stackTrace = []; const origError = console.error; - console.error = (...args) => stackTrace.push(args); + const err = stub(); + console.error = err; app = await createApp({ graphqlOptions: { schema, @@ -841,7 +842,10 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => { }); return req.then(res => { console.error = origError; - expect(stackTrace[0][0]).to.match(expected); + if (err.called) { + expect(err.calledOnce); + expect(err.getCall(0).args[0]).to.match(expected); + } }); }); @@ -861,8 +865,10 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => { }); return req.then(res => { logStub.restore(); - expect(logStub.callCount).to.equal(1); - expect(logStub.getCall(0).args[0]).to.match(expected); + if (logStub.called) { + expect(logStub.callCount).to.equal(1); + expect(logStub.getCall(0).args[0]).to.match(expected); + } }); }); @@ -1051,7 +1057,17 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => { ]); return req.then(res => { expect(res.status).to.equal(200); - expect(res.body).to.deep.equal(expected); + expect(res.body.length).to.equal(expected.length); + expect(res.body[0]).to.deep.equal(expected[0]); + if (res.body[1].errors[0].extensions) { + if (res.body[1].errors[0].extensions.code) { + expect(res.body[1].errors[0].extensions.code).to.equal( + 'INTERNAL_SERVER_ERROR', + ); + } + delete res.body[1].errors[0].extensions; + } + expect(res.body[1]).to.deep.equal(expected[1]); }); }); }); From cf481153948925ec92532c21bad981ddb37dd2e5 Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Thu, 3 May 2018 13:31:22 -0700 Subject: [PATCH 12/21] apollo-server-koa: add node types to koa --- packages/apollo-server-koa/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/apollo-server-koa/package.json b/packages/apollo-server-koa/package.json index 70b56a5ebac..987cd751b47 100644 --- a/packages/apollo-server-koa/package.json +++ b/packages/apollo-server-koa/package.json @@ -33,6 +33,7 @@ "@types/koa": "2.0.45", "@types/koa-bodyparser": "4.2.0", "@types/koa-router": "7.0.28", + "@types/node": "^8.10.12", "apollo-server-integration-testsuite": "^1.3.6", "koa": "2.5.1", "koa-bodyparser": "4.2.0", From b10138426b75e8dbcdf56291c79d4ff067349b86 Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Thu, 3 May 2018 17:44:53 -0700 Subject: [PATCH 13/21] apollo-server-core: use getOwnPropertyNames to stay es5 --- packages/apollo-server-core/src/errors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/apollo-server-core/src/errors.ts b/packages/apollo-server-core/src/errors.ts index 6ca2b94cdac..3223c9d25f8 100644 --- a/packages/apollo-server-core/src/errors.ts +++ b/packages/apollo-server-core/src/errors.ts @@ -102,7 +102,7 @@ export function fromGraphQLError(error: GraphQLError, options?: ErrorOptions) { //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.getOwnPropertyNames(error).forEach(key => { Object.defineProperty(copy.originalError, key, { value: error[key] }); }); From fdd341c9002d8368f96fac9670003890a86f6b98 Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Thu, 3 May 2018 17:45:22 -0700 Subject: [PATCH 14/21] build: remove node 4 tests --- .circleci/config.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9fd747ac13e..0b4e627a1f4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,10 +36,6 @@ jobs: # Platform tests, each with the same tests but different platform or version. # The docker tag represents the Node.js version and the full list is available # at https://hub.docker.com/r/circleci/node/. - Node.js 4: - docker: [ { image: 'circleci/node:4' } ] - <<: *common_test_steps - Node.js 6: docker: [ { image: 'circleci/node:6' } ] <<: *common_test_steps @@ -73,7 +69,6 @@ workflows: version: 2 Build and Test: jobs: - - Node.js 4 - Node.js 6 - Node.js 8 - Node.js 9 From 5fa8d88b8c09cbf5e5b65990dc8eb8ec20d1fe85 Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Fri, 4 May 2018 12:01:24 -0700 Subject: [PATCH 15/21] changelogs: added to root and apollo-server-core --- CHANGELOG.md | 2 ++ packages/apollo-server-core/CHANGELOG.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2b71896b15..4c3d4624ffb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All of the packages in the `apollo-server` repo are released with the same versi ### vNEXT +* Remove tests and guaranteed support for Node 4 [PR #1024](https://github.com/apollographql/apollo-server/pull/1024) + ### v1.3.6 * Recognize requests with Apollo Persisted Queries and return `PersistedQueryNotSupported` to the client instead of a confusing error. [PR #982](https://github.com/apollographql/apollo-server/pull/982) diff --git a/packages/apollo-server-core/CHANGELOG.md b/packages/apollo-server-core/CHANGELOG.md index 33b67683c4d..a9f7efb0b5a 100644 --- a/packages/apollo-server-core/CHANGELOG.md +++ b/packages/apollo-server-core/CHANGELOG.md @@ -2,5 +2,7 @@ ### vNEXT +* `apollo-server-core`: Replace console.error with logFunction for opt-in logging [PR #1024](https://github.com/apollographql/apollo-server/pull/1024) +* `apollo-server-core`: errors in context creation can be async and is formatted to include error code [PR #1024](https://github.com/apollographql/apollo-server/pull/1024) * `apollo-server-core`: add `mocks` parameter to the base constructor(applies to all variants) [PR#1017](https://github.com/apollographql/apollo-server/pull/1017) * `apollo-server-core`: Remove printing of stack traces with `debug` option and include response in logging function[PR#1018](https://github.com/apollographql/apollo-server/pull/1018) From 9522c5a44588b2bd775d945578d9203aca7a3441 Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Tue, 8 May 2018 11:01:10 -0700 Subject: [PATCH 16/21] apollo-server-core: remove unsued async from BaseServer's request function --- packages/apollo-server-core/src/ApolloServer.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/apollo-server-core/src/ApolloServer.ts b/packages/apollo-server-core/src/ApolloServer.ts index 56f9346ee68..6ea4e75c384 100644 --- a/packages/apollo-server-core/src/ApolloServer.ts +++ b/packages/apollo-server-core/src/ApolloServer.ts @@ -275,13 +275,13 @@ export class ApolloServerBase { if (this.engine || engineInRequestPath) this.engineEnabled = true; } - async request(request: Request) { + request(request: Request) { let context: Context = this.context ? this.context : { request }; - //Differ context resolution to inside of runQuery + //Defer context resolution to inside of runQuery context = typeof this.context === 'function' - ? async () => this.context({ req: request }) + ? () => this.context({ req: request }) : context; return { From 6be12f10e2827fca66278da1551607efe0ba4124 Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Tue, 8 May 2018 11:02:21 -0700 Subject: [PATCH 17/21] apollo-server-core: enrichError not exported and moved formatter check to out of map --- packages/apollo-server-core/src/errors.ts | 51 ++++++++++++----------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/packages/apollo-server-core/src/errors.ts b/packages/apollo-server-core/src/errors.ts index 3223c9d25f8..fdb9e1a79e9 100644 --- a/packages/apollo-server-core/src/errors.ts +++ b/packages/apollo-server-core/src/errors.ts @@ -22,7 +22,7 @@ export class ApolloError extends Error { } } -export function enrichError(error: GraphQLError, debug: boolean = false) { +function enrichError(error: GraphQLError, debug: boolean = false) { const expanded: GraphQLError = { message: error.message, path: error.path, @@ -150,30 +150,33 @@ export function formatApolloErrors( }, ): Array { const { formatter, debug, logFunction } = options; - return errors.map(error => enrichError(error, debug)).map(error => { - if (formatter !== undefined) { - 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); - } + + 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); } - } else { - return error; } }) as Array; } From 7a284d564a60bf5dd57db9e68e71f70fdb46fc48 Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Tue, 8 May 2018 11:03:15 -0700 Subject: [PATCH 18/21] apollo-server-core: add warning to options creation failure if debugDefault enabled --- packages/apollo-server-core/src/runHttpQuery.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/apollo-server-core/src/runHttpQuery.ts b/packages/apollo-server-core/src/runHttpQuery.ts index 6ceed96bee2..32b55894d42 100644 --- a/packages/apollo-server-core/src/runHttpQuery.ts +++ b/packages/apollo-server-core/src/runHttpQuery.ts @@ -56,6 +56,9 @@ export async function runHttpQuery( // logFunction, debug. Therefore, we need to do some unnatural things, such // as use NODE_ENV to determine the debug settings e.message = `Invalid options provided to ApolloServer: ${e.message}`; + if (!debugDefault) { + e.warning = `To remove the stacktrace, set the NODE_ENV environment variable to production if the options creation can fail`; + } throw new HttpQueryError( 500, JSON.stringify({ From 4225c08b10c90dad5c0acc0b27fc72f10482ad12 Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Tue, 8 May 2018 11:10:09 -0700 Subject: [PATCH 19/21] apollo-server-core: remove Promise wrapping in async function return values --- packages/apollo-server-core/src/runHttpQuery.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/apollo-server-core/src/runHttpQuery.ts b/packages/apollo-server-core/src/runHttpQuery.ts index 32b55894d42..fa9b136ac2a 100644 --- a/packages/apollo-server-core/src/runHttpQuery.ts +++ b/packages/apollo-server-core/src/runHttpQuery.ts @@ -250,16 +250,17 @@ export async function runHttpQuery( // Populate any HttpQueryError to our handler which should // convert it to Http Error. if (e.name === 'HttpQueryError') { - return Promise.reject(e); + //async function wraps this in a Promise + throw e; } - return Promise.resolve({ + return { errors: formatApolloErrors([e], { formatter: optionsObject.formatError, debug, logFunction: optionsObject.logFunction, }), - }); + }; } }) as Array>; From a08594f4d56588b7386ed69ad4841a4cba036554 Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Tue, 8 May 2018 11:39:53 -0700 Subject: [PATCH 20/21] apollo-server-core: run http options allows query to be an array --- packages/apollo-server-core/src/runHttpQuery.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/apollo-server-core/src/runHttpQuery.ts b/packages/apollo-server-core/src/runHttpQuery.ts index fa9b136ac2a..ffc7ae3f600 100644 --- a/packages/apollo-server-core/src/runHttpQuery.ts +++ b/packages/apollo-server-core/src/runHttpQuery.ts @@ -8,7 +8,7 @@ import { formatApolloErrors } from './errors'; export interface HttpQueryRequest { method: string; - query: Record; + query: Record | Array>; options: GraphQLOptions | (() => Promise | GraphQLOptions); } From fd79cf72db8343375d6dd7b9c7f4c6899bb5fb40 Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Tue, 8 May 2018 13:51:08 -0700 Subject: [PATCH 21/21] apollo-server-core: clarify context creation can be async in changelog --- packages/apollo-server-core/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/apollo-server-core/CHANGELOG.md b/packages/apollo-server-core/CHANGELOG.md index a9f7efb0b5a..37dc4c40da3 100644 --- a/packages/apollo-server-core/CHANGELOG.md +++ b/packages/apollo-server-core/CHANGELOG.md @@ -3,6 +3,6 @@ ### vNEXT * `apollo-server-core`: Replace console.error with logFunction for opt-in logging [PR #1024](https://github.com/apollographql/apollo-server/pull/1024) -* `apollo-server-core`: errors in context creation can be async and is formatted to include error code [PR #1024](https://github.com/apollographql/apollo-server/pull/1024) +* `apollo-server-core`: context creation can be async and errors are formatted to include error code [PR #1024](https://github.com/apollographql/apollo-server/pull/1024) * `apollo-server-core`: add `mocks` parameter to the base constructor(applies to all variants) [PR#1017](https://github.com/apollographql/apollo-server/pull/1017) * `apollo-server-core`: Remove printing of stack traces with `debug` option and include response in logging function[PR#1018](https://github.com/apollographql/apollo-server/pull/1018)