Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: graphql/graphql-js
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v15.6.1
Choose a base ref
...
head repository: graphql/graphql-js
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v15.7.0
Choose a head ref
  • 6 commits
  • 10 files changed
  • 1 contributor

Commits on Oct 23, 2021

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    bbb1af5 View commit details

Commits on Oct 26, 2021

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    8ba5c56 View commit details
  2. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    d8ca570 View commit details
  3. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    f49cf34 View commit details
  4. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    6281c06 View commit details
  5. 15.7.0

    IvanGoncharov committed Oct 26, 2021

    Verified

    This commit was signed with the committer’s verified signature.
    IvanGoncharov Ivan Goncharov
    Copy the full SHA
    c7ab7eb View commit details
Showing with 132 additions and 139 deletions.
  1. +2 −2 package-lock.json
  2. +1 −1 package.json
  3. +16 −12 src/error/GraphQLError.d.ts
  4. +47 −96 src/error/GraphQLError.js
  5. +56 −22 src/error/__tests__/GraphQLError-test.js
  6. +1 −1 src/error/formatError.js
  7. +5 −1 src/error/index.d.ts
  8. +1 −0 src/index.d.ts
  9. +0 −1 src/validation/__tests__/validation-test.js
  10. +3 −3 src/version.js
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "graphql",
"version": "15.6.1",
"version": "15.7.0",
"description": "A Query Language and Runtime which can target any service.",
"license": "MIT",
"private": true,
28 changes: 16 additions & 12 deletions src/error/GraphQLError.d.ts
Original file line number Diff line number Diff line change
@@ -4,6 +4,19 @@ import { ASTNode } from '../language/ast';
import { Source } from '../language/source';
import { SourceLocation } from '../language/location';

/**
* Custom extensions
*
* @remarks
* Use a unique identifier name for your extension, for example the name of
* your library or project. Do not use a shortened identifier as this increases
* the risk of conflicts. We recommend you add at most one extension field,
* an object which can contain all the values you need.
*/
export interface GraphQLErrorExtensions {
[attributeName: string]: any;
}

/**
* A GraphQLError describes an Error found during the parse, validate, or
* execute phases of performing a GraphQL operation. In addition to a message
@@ -18,18 +31,9 @@ export class GraphQLError extends Error {
positions?: Maybe<ReadonlyArray<number>>,
path?: Maybe<ReadonlyArray<string | number>>,
originalError?: Maybe<Error>,
extensions?: Maybe<{ [key: string]: any }>,
extensions?: Maybe<GraphQLErrorExtensions>,
);

/**
* A message describing the Error for debugging purposes.
*
* Enumerable, and appears in the result of JSON.stringify().
*
* Note: should be treated as readonly, despite invariant usage.
*/
message: string;

/**
* An array of { line, column } locations within the source GraphQL document
* which correspond to this error.
@@ -72,12 +76,12 @@ export class GraphQLError extends Error {
/**
* The original error thrown from a field resolver during execution.
*/
readonly originalError: Maybe<Error>;
readonly originalError: Error | undefined;

/**
* Extension fields to add to the formatted error.
*/
readonly extensions: { [key: string]: any } | undefined;
readonly extensions: { [key: string]: any };
}

/**
143 changes: 47 additions & 96 deletions src/error/GraphQLError.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// FIXME:
// flowlint uninitialized-instance-property:off

import isObjectLike from '../jsutils/isObjectLike';
import { SYMBOL_TO_STRING_TAG } from '../polyfills/symbols';

@@ -17,15 +14,6 @@ import { printLocation, printSourceLocation } from '../language/printLocation';
* GraphQL document and/or execution result that correspond to the Error.
*/
export class GraphQLError extends Error {
/**
* A message describing the Error for debugging purposes.
*
* Enumerable, and appears in the result of JSON.stringify().
*
* Note: should be treated as readonly, despite invariant usage.
*/
message: string;

/**
* An array of { line, column } locations within the source GraphQL document
* which correspond to this error.
@@ -68,12 +56,12 @@ export class GraphQLError extends Error {
/**
* The original error thrown from a field resolver during execution.
*/
+originalError: ?Error;
+originalError: Error | void;

/**
* Extension fields to add to the formatted error.
*/
+extensions: { [key: string]: mixed, ... } | void;
+extensions: { [key: string]: mixed, ... };

constructor(
message: string,
@@ -86,103 +74,60 @@ export class GraphQLError extends Error {
) {
super(message);

this.name = 'GraphQLError';
this.originalError = originalError ?? undefined;

// Compute list of blame nodes.
const _nodes = Array.isArray(nodes)
? nodes.length !== 0
? nodes
: undefined
: nodes
? [nodes]
: undefined;
this.nodes = undefinedIfEmpty(
Array.isArray(nodes) ? nodes : nodes ? [nodes] : undefined,
);

let nodeLocations = [];
for (const { loc } of this.nodes ?? []) {
if (loc != null) {
nodeLocations.push(loc);
}
}
nodeLocations = undefinedIfEmpty(nodeLocations);

// Compute locations in the source for the given nodes/positions.
let _source = source;
if (!_source && _nodes) {
_source = _nodes[0].loc?.source;
}
this.source = source ?? nodeLocations?.[0].source;

let _positions = positions;
if (!_positions && _nodes) {
_positions = _nodes.reduce((list, node) => {
if (node.loc) {
list.push(node.loc.start);
}
return list;
}, []);
}
if (_positions && _positions.length === 0) {
_positions = undefined;
}
this.positions = positions ?? nodeLocations?.map((loc) => loc.start);

let _locations;
if (positions && source) {
_locations = positions.map((pos) => getLocation(source, pos));
} else if (_nodes) {
_locations = _nodes.reduce((list, node) => {
if (node.loc) {
list.push(getLocation(node.loc.source, node.loc.start));
}
return list;
}, []);
}
this.locations =
positions && source
? positions.map((pos) => getLocation(source, pos))
: nodeLocations?.map((loc) => getLocation(loc.source, loc.start));

let _extensions = extensions;
if (_extensions == null && originalError != null) {
const originalExtensions = originalError.extensions;
if (isObjectLike(originalExtensions)) {
_extensions = originalExtensions;
}
this.path = path ?? undefined;

this.extensions = extensions ?? {};

const originalExtensions = originalError?.extensions;
if (isObjectLike(originalExtensions)) {
this.extensions = { ...originalExtensions };
}

// By being enumerable, JSON.stringify will include bellow properties in the resulting output.
// This ensures that the simplest possible GraphQL service adheres to the spec.
Object.defineProperties((this: any), {
name: { value: 'GraphQLError' },
message: {
value: message,
// By being enumerable, JSON.stringify will include `message` in the
// resulting output. This ensures that the simplest possible GraphQL
// service adheres to the spec.
enumerable: true,
writable: true,
},
message: { enumerable: true },
locations: {
// Coercing falsy values to undefined ensures they will not be included
// in JSON.stringify() when not provided.
value: _locations ?? undefined,
// By being enumerable, JSON.stringify will include `locations` in the
// resulting output. This ensures that the simplest possible GraphQL
// service adheres to the spec.
enumerable: _locations != null,
enumerable: this.locations != null,
},
path: {
// Coercing falsy values to undefined ensures they will not be included
// in JSON.stringify() when not provided.
value: path ?? undefined,
// By being enumerable, JSON.stringify will include `path` in the
// resulting output. This ensures that the simplest possible GraphQL
// service adheres to the spec.
enumerable: path != null,
},
nodes: {
value: _nodes ?? undefined,
},
source: {
value: _source ?? undefined,
},
positions: {
value: _positions ?? undefined,
},
originalError: {
value: originalError,
enumerable: this.path != null,
},
extensions: {
// Coercing falsy values to undefined ensures they will not be included
// in JSON.stringify() when not provided.
value: _extensions ?? undefined,
// By being enumerable, JSON.stringify will include `path` in the
// resulting output. This ensures that the simplest possible GraphQL
// service adheres to the spec.
enumerable: _extensions != null,
enumerable:
this.extensions != null && Object.keys(this.extensions).length > 0,
},
name: { enumerable: false },
nodes: { enumerable: false },
source: { enumerable: false },
positions: { enumerable: false },
originalError: { enumerable: false },
});

// Include (non-enumerable) stack trace.
@@ -218,6 +163,12 @@ export class GraphQLError extends Error {
}
}

function undefinedIfEmpty<T>(
array: $ReadOnlyArray<T> | void,
): $ReadOnlyArray<T> | void {
return array === undefined || array.length === 0 ? undefined : array;
}

/**
* Prints a GraphQLError to a string, representing useful location information
* about the error's position in the source.
78 changes: 56 additions & 22 deletions src/error/__tests__/GraphQLError-test.js
Original file line number Diff line number Diff line change
@@ -28,10 +28,14 @@ describe('GraphQLError', () => {
expect(new GraphQLError('str')).to.be.instanceof(GraphQLError);
});

it('has a name, message, and stack trace', () => {
it('has a name, message, extensions, and stack trace', () => {
const e = new GraphQLError('msg');

expect(e).to.include({ name: 'GraphQLError', message: 'msg' });
expect(e).to.deep.include({
name: 'GraphQLError',
message: 'msg',
extensions: {},
});
expect(e.stack).to.be.a('string');
});

@@ -96,6 +100,18 @@ describe('GraphQLError', () => {
});
});

it('converts node without location to undefined source, positions and locations', () => {
const documentNode = parse('{ foo }', { noLocation: true });

const e = new GraphQLError('msg', documentNode);
expect(e).to.deep.include({
nodes: [documentNode],
source: undefined,
positions: undefined,
locations: undefined,
});
});

it('converts source and positions to locations', () => {
const e = new GraphQLError('msg', null, source, [6]);
expect(e).to.have.property('source', source);
@@ -106,29 +122,47 @@ describe('GraphQLError', () => {
});
});

it('serializes to include message', () => {
const e = new GraphQLError('msg');
expect(JSON.stringify(e)).to.equal('{"message":"msg"}');
});
it('serializes to include all standard fields', () => {
const eShort = new GraphQLError('msg');
expect(JSON.stringify(eShort, null, 2) + '\n').to.equal(dedent`
{
"message": "msg"
}
`);

it('serializes to include message and locations', () => {
const e = new GraphQLError('msg', fieldNode);
expect(JSON.stringify(e)).to.equal(
'{"message":"msg","locations":[{"line":2,"column":3}]}',
const path = ['path', 2, 'field'];
const extensions = { foo: 'bar' };
const eFull = new GraphQLError(
'msg',
fieldNode,
undefined,
undefined,
path,
undefined,
extensions,
);
});

it('serializes to include path', () => {
const e = new GraphQLError('msg', null, null, null, [
'path',
3,
'to',
'field',
]);
expect(e).to.have.deep.property('path', ['path', 3, 'to', 'field']);
expect(JSON.stringify(e)).to.equal(
'{"message":"msg","path":["path",3,"to","field"]}',
);
// We should try to keep order of fields stable
// Changing it wouldn't be breaking change but will fail some tests in other libraries.
expect(JSON.stringify(eFull, null, 2) + '\n').to.equal(dedent`
{
"message": "msg",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"path",
2,
"field"
],
"extensions": {
"foo": "bar"
}
}
`);
});
});

2 changes: 1 addition & 1 deletion src/error/formatError.js
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ export function formatError(error: GraphQLError): GraphQLFormattedError {
const path = error.path;
const extensions = error.extensions;

return extensions
return extensions && Object.keys(extensions).length > 0
? { message, locations, path, extensions }
: { message, locations, path };
}
6 changes: 5 additions & 1 deletion src/error/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
export { GraphQLError, printError } from './GraphQLError';
export {
GraphQLError,
GraphQLErrorExtensions,
printError,
} from './GraphQLError';
export { syntaxError } from './syntaxError';
export { locatedError } from './locatedError';
export { formatError, GraphQLFormattedError } from './formatError';
Loading