From 35ba2663822fdef61a4a8c8a972732434ec2e404 Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Mon, 13 Jan 2025 15:56:23 +0100 Subject: [PATCH 1/5] backport --- src/error/GraphQLError.d.ts | 5 +++++ src/error/GraphQLError.js | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/src/error/GraphQLError.d.ts b/src/error/GraphQLError.d.ts index 95f39ba483..3f2c81e13b 100644 --- a/src/error/GraphQLError.d.ts +++ b/src/error/GraphQLError.d.ts @@ -3,6 +3,7 @@ import { Maybe } from '../jsutils/Maybe'; import { ASTNode } from '../language/ast'; import { Source } from '../language/source'; import { SourceLocation } from '../language/location'; +import { GraphQLFormattedError } from './formatError'; /** * Custom extensions @@ -82,6 +83,10 @@ export class GraphQLError extends Error { * Extension fields to add to the formatted error. */ readonly extensions: { [key: string]: any }; + + toString(): string; + + toJSON(): GraphQLFormattedError; } /** diff --git a/src/error/GraphQLError.js b/src/error/GraphQLError.js index 7c5e74e73d..7cd009bbe2 100644 --- a/src/error/GraphQLError.js +++ b/src/error/GraphQLError.js @@ -6,6 +6,8 @@ import type { Source } from '../language/source'; import type { SourceLocation } from '../language/location'; import { getLocation } from '../language/location'; import { printLocation, printSourceLocation } from '../language/printLocation'; +import { formatError } from './formatError'; +import type { GraphQLFormattedError } from './formatError'; /** * A GraphQLError describes an Error found during the parse, validate, or @@ -157,6 +159,10 @@ export class GraphQLError extends Error { return printError(this); } + toJSON(): GraphQLFormattedError { + return formatError(this); + } + // FIXME: workaround to not break chai comparisons, should be remove in v16 // $FlowFixMe[unsupported-syntax] Flow doesn't support computed properties yet get [SYMBOL_TO_STRING_TAG](): string { From 682fce691964864ddbeb7e4795b6a4faf11eece6 Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Mon, 13 Jan 2025 15:59:37 +0100 Subject: [PATCH 2/5] tests --- src/error/__tests__/GraphQLError-test.js | 46 ++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/error/__tests__/GraphQLError-test.js b/src/error/__tests__/GraphQLError-test.js index ef1964fc82..833c6e90e4 100644 --- a/src/error/__tests__/GraphQLError-test.js +++ b/src/error/__tests__/GraphQLError-test.js @@ -293,3 +293,49 @@ describe('printError', () => { `); }); }); + +describe('toJSON', () => { + 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' }, + }); + }); + + it('can be created with full argument list', () => { + const error = new GraphQLError( + 'msg', + [operationNode], + source, + [6], + ['path', 2, 'a'], + new Error('I like turtles'), + { hee: 'I like turtles' }, + ); + + expect(error.toJSON()).to.deep.equal({ + message: 'msg', + locations: [{ column: 5, line: 2 }], + path: ['path', 2, 'a'], + extensions: { hee: 'I like turtles' }, + }); + }); +}); From 4a3252be3e4de681a9740318c44586f1f6e67d3f Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Mon, 13 Jan 2025 16:02:33 +0100 Subject: [PATCH 3/5] ok eslint thanks --- src/error/GraphQLError.d.ts | 2 ++ src/error/GraphQLError.js | 1 + src/error/formatError.d.ts | 1 + 3 files changed, 4 insertions(+) diff --git a/src/error/GraphQLError.d.ts b/src/error/GraphQLError.d.ts index 3f2c81e13b..2accc9c17d 100644 --- a/src/error/GraphQLError.d.ts +++ b/src/error/GraphQLError.d.ts @@ -3,6 +3,8 @@ import { Maybe } from '../jsutils/Maybe'; import { ASTNode } from '../language/ast'; import { Source } from '../language/source'; import { SourceLocation } from '../language/location'; + +// eslint-disable-next-line import/no-cycle import { GraphQLFormattedError } from './formatError'; /** diff --git a/src/error/GraphQLError.js b/src/error/GraphQLError.js index 7cd009bbe2..daf70d83ba 100644 --- a/src/error/GraphQLError.js +++ b/src/error/GraphQLError.js @@ -6,6 +6,7 @@ import type { Source } from '../language/source'; import type { SourceLocation } from '../language/location'; import { getLocation } from '../language/location'; import { printLocation, printSourceLocation } from '../language/printLocation'; + import { formatError } from './formatError'; import type { GraphQLFormattedError } from './formatError'; diff --git a/src/error/formatError.d.ts b/src/error/formatError.d.ts index fb3451b774..5420e30cc0 100644 --- a/src/error/formatError.d.ts +++ b/src/error/formatError.d.ts @@ -1,5 +1,6 @@ import { SourceLocation } from '../language/location'; +// eslint-disable-next-line import/no-cycle import { GraphQLError } from './GraphQLError'; /** From b3d81422f389c5e6a13770cce612d63c9077f4b2 Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Mon, 13 Jan 2025 16:04:21 +0100 Subject: [PATCH 4/5] fix tests --- src/error/__tests__/GraphQLError-test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/error/__tests__/GraphQLError-test.js b/src/error/__tests__/GraphQLError-test.js index 833c6e90e4..508cce7d2b 100644 --- a/src/error/__tests__/GraphQLError-test.js +++ b/src/error/__tests__/GraphQLError-test.js @@ -305,6 +305,7 @@ describe('toJSON', () => { expect(error.toJSON()).to.deep.equal({ message: 'msg', + locations: undefined, path: ['path', 3, 'to', 'field'], }); }); @@ -316,6 +317,8 @@ describe('toJSON', () => { expect(error.toJSON()).to.deep.equal({ message: 'msg', + locations: undefined, + path: undefined, extensions: { foo: 'bar' }, }); }); From 9f4f26d2f0062a04ac0b0ab03b71123ba11ecfb1 Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Mon, 13 Jan 2025 16:17:06 +0100 Subject: [PATCH 5/5] no cyclical deps --- src/error/GraphQLError.d.ts | 40 ++++++++++++++++++++++++++++++++--- src/error/formatError.d.ts | 42 +------------------------------------ 2 files changed, 38 insertions(+), 44 deletions(-) diff --git a/src/error/GraphQLError.d.ts b/src/error/GraphQLError.d.ts index 2accc9c17d..edf0b4d2a6 100644 --- a/src/error/GraphQLError.d.ts +++ b/src/error/GraphQLError.d.ts @@ -4,9 +4,6 @@ import { ASTNode } from '../language/ast'; import { Source } from '../language/source'; import { SourceLocation } from '../language/location'; -// eslint-disable-next-line import/no-cycle -import { GraphQLFormattedError } from './formatError'; - /** * Custom extensions * @@ -96,3 +93,40 @@ export class GraphQLError extends Error { * about the error's position in the source. */ export function printError(error: GraphQLError): string; + +/** + * 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; + +/** + * @see https://github.com/graphql/graphql-spec/blob/master/spec/Section%207%20--%20Response.md#errors + */ +export interface GraphQLFormattedError< + TExtensions extends Record = Record +> { + /** + * 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?: TExtensions; +} diff --git a/src/error/formatError.d.ts b/src/error/formatError.d.ts index 5420e30cc0..869e16f2e0 100644 --- a/src/error/formatError.d.ts +++ b/src/error/formatError.d.ts @@ -1,41 +1 @@ -import { SourceLocation } from '../language/location'; - -// eslint-disable-next-line import/no-cycle -import { 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; - -/** - * @see https://github.com/graphql/graphql-spec/blob/master/spec/Section%207%20--%20Response.md#errors - */ -export interface GraphQLFormattedError< - TExtensions extends Record = Record -> { - /** - * 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?: TExtensions; -} +export { formatError, GraphQLFormattedError } from './GraphQLError';