Skip to content

Commit f50aadd

Browse files
committed
Integrate into introspection
1 parent 01ae7a9 commit f50aadd

12 files changed

+193
-11
lines changed

src/__tests__/starWarsIntrospection-test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ describe('Star Wars Introspection Tests', () => {
3737
{ name: 'Droid' },
3838
{ name: 'Query' },
3939
{ name: 'Boolean' },
40+
{ name: '__ErrorBehavior' },
4041
{ name: '__Schema' },
4142
{ name: '__Type' },
4243
{ name: '__TypeKind' },

src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export {
7979
__InputValue,
8080
__EnumValue,
8181
__TypeKind,
82+
__ErrorBehavior,
8283
// Meta-field definitions.
8384
SchemaMetaFieldDef,
8485
TypeMetaFieldDef,

src/type/__tests__/introspection-test.ts

+68-5
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,32 @@ describe('Introspection', () => {
8080
enumValues: null,
8181
possibleTypes: null,
8282
},
83+
{
84+
enumValues: [
85+
{
86+
deprecationReason: null,
87+
isDeprecated: false,
88+
name: 'NO_PROPAGATE',
89+
},
90+
{
91+
deprecationReason: null,
92+
isDeprecated: false,
93+
name: 'PROPAGATE',
94+
},
95+
{
96+
deprecationReason: null,
97+
isDeprecated: false,
98+
name: 'ABORT',
99+
},
100+
],
101+
fields: null,
102+
inputFields: null,
103+
interfaces: null,
104+
kind: 'ENUM',
105+
name: '__ErrorBehavior',
106+
possibleTypes: null,
107+
specifiedByURL: null,
108+
},
83109
{
84110
kind: 'OBJECT',
85111
name: '__Schema',
@@ -179,6 +205,21 @@ describe('Introspection', () => {
179205
isDeprecated: false,
180206
deprecationReason: null,
181207
},
208+
{
209+
name: 'defaultErrorBehavior',
210+
args: [],
211+
type: {
212+
kind: 'NON_NULL',
213+
name: null,
214+
ofType: {
215+
kind: 'ENUM',
216+
name: '__ErrorBehavior',
217+
ofType: null,
218+
},
219+
},
220+
isDeprecated: false,
221+
deprecationReason: null,
222+
},
182223
],
183224
inputFields: null,
184225
interfaces: [],
@@ -1008,6 +1049,26 @@ describe('Introspection', () => {
10081049
locations: ['INPUT_OBJECT'],
10091050
args: [],
10101051
},
1052+
{
1053+
args: [
1054+
{
1055+
defaultValue: 'PROPAGATE',
1056+
name: 'onError',
1057+
type: {
1058+
kind: 'NON_NULL',
1059+
name: null,
1060+
ofType: {
1061+
kind: 'ENUM',
1062+
name: '__ErrorBehavior',
1063+
ofType: null,
1064+
},
1065+
},
1066+
},
1067+
],
1068+
isRepeatable: false,
1069+
locations: ['SCHEMA'],
1070+
name: 'behavior',
1071+
},
10111072
],
10121073
},
10131074
},
@@ -1768,11 +1829,13 @@ describe('Introspection', () => {
17681829
}
17691830
`);
17701831

1771-
const source = getIntrospectionQuery({
1772-
descriptions: false,
1773-
specifiedByUrl: true,
1774-
directiveIsRepeatable: true,
1775-
});
1832+
const source = /* GraphQL */ `
1833+
{
1834+
__schema {
1835+
defaultErrorBehavior
1836+
}
1837+
}
1838+
`;
17761839

17771840
const result = graphqlSync({ schema, source });
17781841
expect(result).to.deep.equal({

src/type/__tests__/schema-test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ describe('Type System: Schema', () => {
296296
'ASub',
297297
'Boolean',
298298
'String',
299+
'__ErrorBehavior',
299300
'__Schema',
300301
'__Type',
301302
'__TypeKind',

src/type/directives.ts

+17
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
defineArguments,
1919
GraphQLNonNull,
2020
} from './definition';
21+
import { __ErrorBehavior } from './introspection';
2122
import { GraphQLBoolean, GraphQLString } from './scalars';
2223

2324
/**
@@ -220,6 +221,21 @@ export const GraphQLOneOfDirective: GraphQLDirective = new GraphQLDirective({
220221
args: {},
221222
});
222223

224+
/**
225+
* Used to indicate the default error behavior.
226+
*/
227+
export const GraphQLBehaviorDirective: GraphQLDirective = new GraphQLDirective({
228+
name: 'behavior',
229+
description: 'Indicates the default error behavior of the schema.',
230+
locations: [DirectiveLocation.SCHEMA],
231+
args: {
232+
onError: {
233+
type: new GraphQLNonNull(__ErrorBehavior),
234+
defaultValue: 'PROPAGATE',
235+
},
236+
},
237+
});
238+
223239
/**
224240
* The full list of specified directives.
225241
*/
@@ -230,6 +246,7 @@ export const specifiedDirectives: ReadonlyArray<GraphQLDirective> =
230246
GraphQLDeprecatedDirective,
231247
GraphQLSpecifiedByDirective,
232248
GraphQLOneOfDirective,
249+
GraphQLBehaviorDirective,
233250
]);
234251

235252
export function isSpecifiedDirective(directive: GraphQLDirective): boolean {

src/type/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ export {
172172
__InputValue,
173173
__EnumValue,
174174
__TypeKind,
175+
__ErrorBehavior,
175176
// "Enum" of Type Kinds
176177
TypeKind,
177178
// Meta-field definitions.

src/type/introspection.ts

+30
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ export const __Schema: GraphQLObjectType = new GraphQLObjectType({
7474
),
7575
resolve: (schema) => schema.getDirectives(),
7676
},
77+
defaultErrorBehavior: {
78+
description:
79+
'The default error behavior that will be used for requests which do not specify `onError`.',
80+
type: new GraphQLNonNull(__ErrorBehavior),
81+
resolve: (schema) => schema.defaultErrorBehavior,
82+
},
7783
} as GraphQLFieldConfigMap<GraphQLSchema, unknown>),
7884
});
7985

@@ -500,6 +506,29 @@ export const __TypeKind: GraphQLEnumType = new GraphQLEnumType({
500506
},
501507
});
502508

509+
export const __ErrorBehavior: GraphQLEnumType = new GraphQLEnumType({
510+
name: '__ErrorBehavior',
511+
description:
512+
'An enum detailing the error behavior a GraphQL request should use.',
513+
values: {
514+
NO_PROPAGATE: {
515+
value: 'NO_PROPAGATE',
516+
description:
517+
'Indicates that an error should result in the response position becoming null, even if it is marked as non-null.',
518+
},
519+
PROPAGATE: {
520+
value: 'PROPAGATE',
521+
description:
522+
'Indicates that an error that occurs in a non-null position should propagate to the nearest nullable response position.',
523+
},
524+
ABORT: {
525+
value: 'ABORT',
526+
description:
527+
'Indicates execution should cease when the first error occurs, and that the response data should be null.',
528+
},
529+
},
530+
});
531+
503532
/**
504533
* Note that these are GraphQLField and not GraphQLFieldConfig,
505534
* so the format for args is different.
@@ -558,6 +587,7 @@ export const introspectionTypes: ReadonlyArray<GraphQLNamedType> =
558587
__InputValue,
559588
__EnumValue,
560589
__TypeKind,
590+
__ErrorBehavior,
561591
]);
562592

563593
export function isIntrospectionType(type: GraphQLNamedType): boolean {

src/utilities/__tests__/buildASTSchema-test.ts

+11-4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
} from '../../type/definition';
2222
import {
2323
assertDirective,
24+
GraphQLBehaviorDirective,
2425
GraphQLDeprecatedDirective,
2526
GraphQLIncludeDirective,
2627
GraphQLOneOfDirective,
@@ -223,7 +224,7 @@ describe('Schema Builder', () => {
223224
it('Maintains @include, @skip & @specifiedBy', () => {
224225
const schema = buildSchema('type Query');
225226

226-
expect(schema.getDirectives()).to.have.lengthOf(5);
227+
expect(schema.getDirectives()).to.have.lengthOf(6);
227228
expect(schema.getDirective('skip')).to.equal(GraphQLSkipDirective);
228229
expect(schema.getDirective('include')).to.equal(GraphQLIncludeDirective);
229230
expect(schema.getDirective('deprecated')).to.equal(
@@ -232,6 +233,7 @@ describe('Schema Builder', () => {
232233
expect(schema.getDirective('specifiedBy')).to.equal(
233234
GraphQLSpecifiedByDirective,
234235
);
236+
expect(schema.getDirective('behavior')).to.equal(GraphQLBehaviorDirective);
235237
expect(schema.getDirective('oneOf')).to.equal(GraphQLOneOfDirective);
236238
});
237239

@@ -241,10 +243,11 @@ describe('Schema Builder', () => {
241243
directive @include on FIELD
242244
directive @deprecated on FIELD_DEFINITION
243245
directive @specifiedBy on FIELD_DEFINITION
246+
directive @behavior on SCHEMA
244247
directive @oneOf on OBJECT
245248
`);
246249

247-
expect(schema.getDirectives()).to.have.lengthOf(5);
250+
expect(schema.getDirectives()).to.have.lengthOf(6);
248251
expect(schema.getDirective('skip')).to.not.equal(GraphQLSkipDirective);
249252
expect(schema.getDirective('include')).to.not.equal(
250253
GraphQLIncludeDirective,
@@ -255,19 +258,23 @@ describe('Schema Builder', () => {
255258
expect(schema.getDirective('specifiedBy')).to.not.equal(
256259
GraphQLSpecifiedByDirective,
257260
);
261+
expect(schema.getDirective('behavior')).to.not.equal(
262+
GraphQLBehaviorDirective,
263+
);
258264
expect(schema.getDirective('oneOf')).to.not.equal(GraphQLOneOfDirective);
259265
});
260266

261-
it('Adding directives maintains @include, @skip, @deprecated, @specifiedBy, and @oneOf', () => {
267+
it('Adding directives maintains @include, @skip, @deprecated, @specifiedBy, @behavior and @oneOf', () => {
262268
const schema = buildSchema(`
263269
directive @foo(arg: Int) on FIELD
264270
`);
265271

266-
expect(schema.getDirectives()).to.have.lengthOf(6);
272+
expect(schema.getDirectives()).to.have.lengthOf(7);
267273
expect(schema.getDirective('skip')).to.not.equal(undefined);
268274
expect(schema.getDirective('include')).to.not.equal(undefined);
269275
expect(schema.getDirective('deprecated')).to.not.equal(undefined);
270276
expect(schema.getDirective('specifiedBy')).to.not.equal(undefined);
277+
expect(schema.getDirective('behavior')).to.not.equal(undefined);
271278
expect(schema.getDirective('oneOf')).to.not.equal(undefined);
272279
});
273280

src/utilities/__tests__/findBreakingChanges-test.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { expect } from 'chai';
22
import { describe, it } from 'mocha';
33

44
import {
5+
GraphQLBehaviorDirective,
56
GraphQLDeprecatedDirective,
67
GraphQLIncludeDirective,
78
GraphQLOneOfDirective,
@@ -803,6 +804,7 @@ describe('findBreakingChanges', () => {
803804
GraphQLSkipDirective,
804805
GraphQLIncludeDirective,
805806
GraphQLSpecifiedByDirective,
807+
GraphQLBehaviorDirective,
806808
GraphQLOneOfDirective,
807809
],
808810
});

src/utilities/__tests__/printSchema-test.ts

+26
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,27 @@ describe('Type System Printer', () => {
694694
"""
695695
directive @oneOf on INPUT_OBJECT
696696
697+
"""Indicates the default error behavior of the schema."""
698+
directive @behavior(onError: __ErrorBehavior! = PROPAGATE) on SCHEMA
699+
700+
"""An enum detailing the error behavior a GraphQL request should use."""
701+
enum __ErrorBehavior {
702+
"""
703+
Indicates that an error should result in the response position becoming null, even if it is marked as non-null.
704+
"""
705+
NO_PROPAGATE
706+
707+
"""
708+
Indicates that an error that occurs in a non-null position should propagate to the nearest nullable response position.
709+
"""
710+
PROPAGATE
711+
712+
"""
713+
Indicates execution should cease when the first error occurs, and that the response data should be null.
714+
"""
715+
ABORT
716+
}
717+
697718
"""
698719
A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.
699720
"""
@@ -718,6 +739,11 @@ describe('Type System Printer', () => {
718739
719740
"""A list of all directives supported by this server."""
720741
directives: [__Directive!]!
742+
743+
"""
744+
The default error behavior that will be used for requests which do not specify \`onError\`.
745+
"""
746+
defaultErrorBehavior: __ErrorBehavior!
721747
}
722748
723749
"""

src/utilities/extendSchema.ts

+22
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import {
6464
isUnionType,
6565
} from '../type/definition';
6666
import {
67+
GraphQLBehaviorDirective,
6768
GraphQLDeprecatedDirective,
6869
GraphQLDirective,
6970
GraphQLOneOfDirective,
@@ -82,6 +83,7 @@ import { assertValidSDLExtension } from '../validation/validate';
8283
import { getDirectiveValues } from '../execution/values';
8384

8485
import { valueFromAST } from './valueFromAST';
86+
import type { GraphQLErrorBehavior } from '../error';
8587

8688
interface Options extends GraphQLSchemaValidationOptions {
8789
/**
@@ -165,6 +167,14 @@ export function extendSchemaImpl(
165167
}
166168
}
167169

170+
let defaultErrorBehavior: Maybe<GraphQLErrorBehavior> = schemaDef
171+
? getDefaultErrorBehavior(schemaDef)
172+
: null;
173+
for (const extensionNode of schemaExtensions) {
174+
defaultErrorBehavior =
175+
getDefaultErrorBehavior(extensionNode) ?? defaultErrorBehavior;
176+
}
177+
168178
// If this document contains no new types, extensions, or directives then
169179
// return the same unmodified GraphQLSchema instance.
170180
if (
@@ -201,6 +211,7 @@ export function extendSchemaImpl(
201211
// Then produce and return a Schema config with these types.
202212
return {
203213
description: schemaDef?.description?.value,
214+
defaultErrorBehavior,
204215
...operationTypes,
205216
types: Object.values(typeMap),
206217
directives: [
@@ -691,3 +702,14 @@ function getSpecifiedByURL(
691702
function isOneOf(node: InputObjectTypeDefinitionNode): boolean {
692703
return Boolean(getDirectiveValues(GraphQLOneOfDirective, node));
693704
}
705+
706+
/**
707+
* Given a schema node, returns the GraphQL error behavior from the `@behavior(onError:)` argument.
708+
*/
709+
function getDefaultErrorBehavior(
710+
node: SchemaDefinitionNode | SchemaExtensionNode,
711+
): Maybe<GraphQLErrorBehavior> {
712+
const behavior = getDirectiveValues(GraphQLBehaviorDirective, node);
713+
// @ts-expect-error validated by `getDirectiveValues`
714+
return behavior?.onError;
715+
}

0 commit comments

Comments
 (0)