Skip to content

Commit 16fccd0

Browse files
committed
graphql: let GraphQLArgs inherit all parse/validate/execute options
1 parent 1fc34c9 commit 16fccd0

File tree

5 files changed

+185
-46
lines changed

5 files changed

+185
-46
lines changed

src/__tests__/graphql-test.ts

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import { expect } from 'chai';
2+
import { describe, it } from 'mocha';
3+
4+
import { GraphQLError } from '../error/GraphQLError.js';
5+
6+
import { Source } from '../language/source.js';
7+
8+
import { GraphQLObjectType } from '../type/definition.js';
9+
import { GraphQLString } from '../type/scalars.js';
10+
import { GraphQLSchema } from '../type/schema.js';
11+
12+
import type { ValidationRule } from '../validation/ValidationContext.js';
13+
14+
import { graphql, graphqlSync } from '../graphql.js';
15+
16+
const schema = new GraphQLSchema({
17+
query: new GraphQLObjectType({
18+
name: 'Query',
19+
fields: {
20+
a: {
21+
type: GraphQLString,
22+
resolve: () => 'A',
23+
},
24+
b: {
25+
type: GraphQLString,
26+
resolve: () => 'B',
27+
},
28+
contextEcho: {
29+
type: GraphQLString,
30+
resolve: (_source, _args, contextValue) => String(contextValue),
31+
},
32+
syncField: {
33+
type: GraphQLString,
34+
resolve: (rootValue) => rootValue,
35+
},
36+
asyncField: {
37+
type: GraphQLString,
38+
resolve: (rootValue) => Promise.resolve(rootValue),
39+
},
40+
},
41+
}),
42+
});
43+
44+
describe('graphql', () => {
45+
it('passes source through to parse', async () => {
46+
const source = new Source('{', 'custom-query.graphql');
47+
48+
const result = await graphql({ schema, source });
49+
50+
expect(result.errors?.[0]?.source?.name).to.equal('custom-query.graphql');
51+
});
52+
53+
it('passes rules through to validate', async () => {
54+
const customRule: ValidationRule = (context) => ({
55+
Field(node) {
56+
context.reportError(
57+
new GraphQLError('custom rule error', {
58+
nodes: node,
59+
}),
60+
);
61+
},
62+
});
63+
64+
const result = await graphql({
65+
schema,
66+
source: '{ a }',
67+
rules: [customRule],
68+
});
69+
70+
expect(result.errors?.[0]?.message).to.equal('custom rule error');
71+
});
72+
73+
it('passes parse options through to parse', async () => {
74+
const customRule: ValidationRule = (context) => ({
75+
OperationDefinition(node) {
76+
context.reportError(
77+
new GraphQLError(
78+
node.loc === undefined ? 'no location' : 'has location',
79+
{
80+
nodes: node,
81+
},
82+
),
83+
);
84+
},
85+
});
86+
87+
const result = await graphql({
88+
schema,
89+
source: '{ a }',
90+
noLocation: true,
91+
rules: [customRule],
92+
});
93+
94+
expect(result.errors?.[0]?.message).to.equal('no location');
95+
});
96+
97+
it('passes validation options through to validate', async () => {
98+
const result = await graphql({
99+
schema,
100+
source: '{ contextEho }',
101+
hideSuggestions: true,
102+
});
103+
104+
expect(result.errors?.[0]?.message).to.equal(
105+
'Cannot query field "contextEho" on type "Query".',
106+
);
107+
});
108+
109+
it('passes execution args through to execute', async () => {
110+
const result = await graphql({
111+
schema,
112+
source: `
113+
query First {
114+
a
115+
}
116+
117+
query Second {
118+
b
119+
}
120+
`,
121+
operationName: 'Second',
122+
});
123+
124+
expect(result).to.deep.equal({
125+
data: {
126+
b: 'B',
127+
},
128+
});
129+
});
130+
131+
it('returns schema validation errors', async () => {
132+
const badSchema = new GraphQLSchema({});
133+
const result = await graphql({
134+
schema: badSchema,
135+
source: '{ __typename }',
136+
});
137+
138+
expect(result.errors?.[0]?.message).to.equal(
139+
'Query root type must be provided.',
140+
);
141+
});
142+
});
143+
144+
describe('graphqlSync', () => {
145+
it('returns result for synchronous execution', () => {
146+
const result = graphqlSync({
147+
schema,
148+
source: '{ syncField }',
149+
rootValue: 'rootValue',
150+
});
151+
152+
expect(result).to.deep.equal({ data: { syncField: 'rootValue' } });
153+
});
154+
155+
it('throws for asynchronous execution', () => {
156+
expect(() => {
157+
graphqlSync({
158+
schema,
159+
source: '{ asyncField }',
160+
rootValue: 'rootValue',
161+
});
162+
}).to.throw('GraphQL execution failed to complete synchronously.');
163+
});
164+
});

src/graphql.ts

Lines changed: 13 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
import { isPromise } from './jsutils/isPromise.js';
2-
import type { Maybe } from './jsutils/Maybe.js';
32
import type { PromiseOrValue } from './jsutils/PromiseOrValue.js';
43

4+
import type { ParseOptions } from './language/parser.js';
55
import { parse } from './language/parser.js';
66
import type { Source } from './language/source.js';
77

8-
import type {
9-
GraphQLFieldResolver,
10-
GraphQLTypeResolver,
11-
} from './type/definition.js';
12-
import type { GraphQLSchema } from './type/schema.js';
138
import { validateSchema } from './type/validate.js';
149

10+
import type { ValidationOptions } from './validation/validate.js';
1511
import { validate } from './validation/validate.js';
12+
import type { ValidationRule } from './validation/ValidationContext.js';
1613

14+
import type { ExecutionArgs } from './execution/execute.js';
1715
import { execute } from './execution/execute.js';
1816
import type { ExecutionResult } from './execution/Executor.js';
1917

@@ -58,17 +56,12 @@ import type { ExecutionResult } from './execution/Executor.js';
5856
* If not provided, the default type resolver is used (which looks for a
5957
* `__typename` field or alternatively calls the `isTypeOf` method).
6058
*/
61-
export interface GraphQLArgs {
62-
schema: GraphQLSchema;
59+
export interface GraphQLArgs
60+
extends ParseOptions,
61+
ValidationOptions,
62+
Omit<ExecutionArgs, 'document'> {
6363
source: string | Source;
64-
hideSuggestions?: Maybe<boolean>;
65-
rootValue?: unknown;
66-
contextValue?: unknown;
67-
variableValues?: Maybe<{ readonly [variable: string]: unknown }>;
68-
operationName?: Maybe<string>;
69-
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
70-
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
71-
abortSignal?: Maybe<AbortSignal>;
64+
rules?: ReadonlyArray<ValidationRule> | undefined;
7265
}
7366

7467
export function graphql(args: GraphQLArgs): Promise<ExecutionResult> {
@@ -94,18 +87,7 @@ export function graphqlSync(args: GraphQLArgs): ExecutionResult {
9487
}
9588

9689
function graphqlImpl(args: GraphQLArgs): PromiseOrValue<ExecutionResult> {
97-
const {
98-
schema,
99-
source,
100-
rootValue,
101-
contextValue,
102-
variableValues,
103-
operationName,
104-
fieldResolver,
105-
typeResolver,
106-
hideSuggestions,
107-
abortSignal,
108-
} = args;
90+
const { schema, source } = args;
10991

11092
// Validate Schema
11193
const schemaValidationErrors = validateSchema(schema);
@@ -116,30 +98,17 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue<ExecutionResult> {
11698
// Parse
11799
let document;
118100
try {
119-
document = parse(source);
101+
document = parse(source, args);
120102
} catch (syntaxError) {
121103
return { errors: [syntaxError] };
122104
}
123105

124106
// Validate
125-
const validationErrors = validate(schema, document, undefined, {
126-
hideSuggestions,
127-
});
107+
const validationErrors = validate(schema, document, args.rules, args);
128108
if (validationErrors.length > 0) {
129109
return { errors: validationErrors };
130110
}
131111

132112
// Execute
133-
return execute({
134-
schema,
135-
document,
136-
rootValue,
137-
contextValue,
138-
variableValues,
139-
operationName,
140-
fieldResolver,
141-
typeResolver,
142-
hideSuggestions,
143-
abortSignal,
144-
});
113+
return execute({ ...args, document });
145114
}

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ export {
422422
NoSchemaIntrospectionCustomRule,
423423
} from './validation/index.js';
424424

425-
export type { ValidationRule } from './validation/index.js';
425+
export type { ValidationOptions, ValidationRule } from './validation/index.js';
426426

427427
// Create, format, and print GraphQL errors.
428428
export { GraphQLError, syntaxError, locatedError } from './error/index.js';

src/validation/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { validate } from './validate.js';
2+
export type { ValidationOptions } from './validate.js';
23

34
export { ValidationContext } from './ValidationContext.js';
45
export type { ValidationRule } from './ValidationContext.js';

src/validation/validate.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ import {
1717
ValidationContext,
1818
} from './ValidationContext.js';
1919

20+
export interface ValidationOptions {
21+
maxErrors?: number;
22+
hideSuggestions?: Maybe<boolean>;
23+
}
24+
2025
/**
2126
* Implements the "Validation" section of the spec.
2227
*
@@ -41,7 +46,7 @@ export function validate(
4146
schema: GraphQLSchema,
4247
documentAST: DocumentNode,
4348
rules: ReadonlyArray<ValidationRule> = specifiedRules,
44-
options?: { maxErrors?: number; hideSuggestions?: Maybe<boolean> },
49+
options?: ValidationOptions,
4550
): ReadonlyArray<GraphQLError> {
4651
const maxErrors = options?.maxErrors ?? 100;
4752
const hideSuggestions = options?.hideSuggestions ?? false;

0 commit comments

Comments
 (0)