From 40c28517f2650800ca610307d4bf3f30120cfaee Mon Sep 17 00:00:00 2001 From: Ivan Goncharov Date: Thu, 2 Sep 2021 17:39:35 +0300 Subject: [PATCH] Deprecate 'formatError' and added 'GraphQLError.toJSON' instead --- src/error/GraphQLError.ts | 64 +++++++++++++++++++++++- src/error/__tests__/GraphQLError-test.ts | 36 ++++++++++++- src/error/__tests__/formatError-test.ts | 58 --------------------- src/error/formatError.ts | 50 ------------------ src/error/index.ts | 6 +-- src/execution/execute.ts | 2 +- 6 files changed, 101 insertions(+), 115 deletions(-) delete mode 100644 src/error/__tests__/formatError-test.ts delete mode 100644 src/error/formatError.ts diff --git a/src/error/GraphQLError.ts b/src/error/GraphQLError.ts index 448d74593d..a3a7992526 100644 --- a/src/error/GraphQLError.ts +++ b/src/error/GraphQLError.ts @@ -215,12 +215,64 @@ export class GraphQLError extends Error { return output; } - // FIXME: workaround to not break chai comparisons, should be remove in v16 + toJSON(): GraphQLFormattedError { + type WritableFormattedError = { + -readonly [P in keyof GraphQLFormattedError]: GraphQLFormattedError[P]; + }; + + const formattedError: WritableFormattedError = { + message: this.message, + }; + + if (this.locations != null) { + formattedError.locations = this.locations; + } + + if (this.path != null) { + formattedError.path = this.path; + } + + if (this.extensions != null) { + formattedError.extensions = this.extensions; + } + + return formattedError; + } + get [Symbol.toStringTag](): string { return 'Object'; } } +/** + * See: https://spec.graphql.org/draft/#sec-Errors + */ +export interface GraphQLFormattedError { + /** + * A short, human-readable summary of the problem that **SHOULD NOT** change + * from occurrence to occurrence of the problem, except for purposes of + * localization. + */ + readonly message: string; + /** + * If an error can be associated to a particular point in the requested + * GraphQL document, it should contain a list of locations. + */ + readonly locations?: ReadonlyArray; + /** + * If an error can be associated to a particular field in the GraphQL result, + * it _must_ contain an entry with the key `path` that details the path of + * the response field which experienced the error. This allows clients to + * identify whether a null result is intentional or caused by a runtime error. + */ + readonly path?: ReadonlyArray; + /** + * Reserved for implementors to extend the protocol however they see fit, + * and hence there are no additional restrictions on its contents. + */ + readonly extensions?: { [key: string]: unknown }; +} + /** * Prints a GraphQLError to a string, representing useful location information * about the error's position in the source. @@ -230,3 +282,13 @@ export class GraphQLError extends Error { export function printError(error: GraphQLError): string { return error.toString(); } + +/** + * Given a GraphQLError, format it according to the rules described by the + * Response Format, Errors section of the GraphQL Specification. + * + * @deprecated Please use `error.toString` instead. Will be removed in v17 + */ +export function formatError(error: GraphQLError): GraphQLFormattedError { + return error.toJSON(); +} diff --git a/src/error/__tests__/GraphQLError-test.ts b/src/error/__tests__/GraphQLError-test.ts index d4cdf92a82..ca5679710e 100644 --- a/src/error/__tests__/GraphQLError-test.ts +++ b/src/error/__tests__/GraphQLError-test.ts @@ -9,7 +9,7 @@ import { Kind } from '../../language/kinds'; import { parse } from '../../language/parser'; import { Source } from '../../language/source'; -import { GraphQLError, printError } from '../GraphQLError'; +import { GraphQLError, printError, formatError } from '../GraphQLError'; const source = new Source(dedent` { @@ -204,3 +204,37 @@ describe('toString', () => { `); }); }); + +describe('toJSON', () => { + it('Deprecated: format an error using formatError', () => { + const error = new GraphQLError('Example Error'); + expect(formatError(error)).to.deep.equal({ + message: 'Example Error', + }); + }); + + it('includes path', () => { + const error = new GraphQLError('msg', null, null, null, [ + 'path', + 3, + 'to', + 'field', + ]); + + expect(error.toJSON()).to.deep.equal({ + message: 'msg', + path: ['path', 3, 'to', 'field'], + }); + }); + + it('includes extension fields', () => { + const error = new GraphQLError('msg', null, null, null, null, null, { + foo: 'bar', + }); + + expect(error.toJSON()).to.deep.equal({ + message: 'msg', + extensions: { foo: 'bar' }, + }); + }); +}); diff --git a/src/error/__tests__/formatError-test.ts b/src/error/__tests__/formatError-test.ts deleted file mode 100644 index b0798c958e..0000000000 --- a/src/error/__tests__/formatError-test.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { expect } from 'chai'; -import { describe, it } from 'mocha'; - -import { formatError } from '../formatError'; -import { GraphQLError } from '../GraphQLError'; - -describe('formatError: default error formatter', () => { - it('uses default message', () => { - // @ts-expect-error - const e = new GraphQLError(); - - expect(formatError(e)).to.deep.equal({ - message: 'An unknown error occurred.', - path: undefined, - locations: undefined, - }); - }); - - it('includes path', () => { - const e = new GraphQLError('msg', null, null, null, [ - 'path', - 3, - 'to', - 'field', - ]); - - expect(formatError(e)).to.deep.equal({ - message: 'msg', - locations: undefined, - path: ['path', 3, 'to', 'field'], - }); - }); - - it('includes extension fields', () => { - const e = new GraphQLError('msg', null, null, null, null, null, { - foo: 'bar', - }); - - expect(formatError(e)).to.deep.equal({ - message: 'msg', - locations: undefined, - path: undefined, - extensions: { foo: 'bar' }, - }); - }); - - it('rejects null and undefined errors', () => { - // @ts-expect-error (formatError expects a value) - expect(() => formatError(undefined)).to.throw( - 'Received null or undefined error.', - ); - - // @ts-expect-error (formatError expects a value) - expect(() => formatError(null)).to.throw( - 'Received null or undefined error.', - ); - }); -}); diff --git a/src/error/formatError.ts b/src/error/formatError.ts deleted file mode 100644 index 259f24e559..0000000000 --- a/src/error/formatError.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { devAssert } from '../jsutils/devAssert'; - -import type { SourceLocation } from '../language/location'; - -import type { GraphQLError } from './GraphQLError'; - -/** - * Given a GraphQLError, format it according to the rules described by the - * Response Format, Errors section of the GraphQL Specification. - */ -export function formatError(error: GraphQLError): GraphQLFormattedError { - devAssert(error, 'Received null or undefined error.'); - const message = error.message ?? 'An unknown error occurred.'; - const locations = error.locations; - const path = error.path; - const extensions = error.extensions; - - return extensions - ? { message, locations, path, extensions } - : { message, locations, path }; -} - -/** - * See: https://spec.graphql.org/draft/#sec-Errors - */ -export interface GraphQLFormattedError { - /** - * A short, human-readable summary of the problem that **SHOULD NOT** change - * from occurrence to occurrence of the problem, except for purposes of - * localization. - */ - readonly message: string; - /** - * If an error can be associated to a particular point in the requested - * GraphQL document, it should contain a list of locations. - */ - readonly locations?: ReadonlyArray; - /** - * If an error can be associated to a particular field in the GraphQL result, - * it _must_ contain an entry with the key `path` that details the path of - * the response field which experienced the error. This allows clients to - * identify whether a null result is intentional or caused by a runtime error. - */ - readonly path?: ReadonlyArray; - /** - * Reserved for implementors to extend the protocol however they see fit, - * and hence there are no additional restrictions on its contents. - */ - readonly extensions?: { [key: string]: unknown }; -} diff --git a/src/error/index.ts b/src/error/index.ts index 914a6dbe46..b947c93fc7 100644 --- a/src/error/index.ts +++ b/src/error/index.ts @@ -1,8 +1,6 @@ -export { GraphQLError, printError } from './GraphQLError'; +export { GraphQLError, printError, formatError } from './GraphQLError'; +export type { GraphQLFormattedError } from './GraphQLError'; export { syntaxError } from './syntaxError'; export { locatedError } from './locatedError'; - -export { formatError } from './formatError'; -export type { GraphQLFormattedError } from './formatError'; diff --git a/src/execution/execute.ts b/src/execution/execute.ts index 8429cae8c5..1ef3adc82f 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -13,7 +13,7 @@ import { promiseForObject } from '../jsutils/promiseForObject'; import { addPath, pathToArray } from '../jsutils/Path'; import { isIterableObject } from '../jsutils/isIterableObject'; -import type { GraphQLFormattedError } from '../error/formatError'; +import type { GraphQLFormattedError } from '../error/GraphQLError'; import { GraphQLError } from '../error/GraphQLError'; import { locatedError } from '../error/locatedError';