Skip to content

Commit ca246fd

Browse files
committed
refactor: move ExecutionContext to new file
1 parent 9fc1d93 commit ca246fd

File tree

2 files changed

+215
-168
lines changed

2 files changed

+215
-168
lines changed

src/execution/compiledDocument.ts

+210
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
import { isObjectLike } from '../jsutils/isObjectLike';
2+
import { isPromise } from '../jsutils/isPromise';
3+
import type { ObjMap } from '../jsutils/ObjMap';
4+
5+
import { GraphQLError } from '../error/GraphQLError';
6+
7+
import type {
8+
FragmentDefinitionNode,
9+
OperationDefinitionNode,
10+
} from '../language/ast';
11+
import { Kind } from '../language/kinds';
12+
13+
import type {
14+
GraphQLFieldResolver,
15+
GraphQLTypeResolver,
16+
} from '../type/definition';
17+
import type { GraphQLSchema } from '../type/schema';
18+
import { assertValidSchema } from '../type/validate';
19+
20+
import type { ExecutionArgs } from './execute';
21+
import { getVariableValues } from './values';
22+
23+
// This file contains a lot of such errors but we plan to refactor it anyway
24+
// so just disable it for entire file.
25+
26+
/**
27+
* Data that must be available at all points during query execution.
28+
*
29+
* Namely, schema of the type system that is currently executing,
30+
* and the fragments defined in the query document
31+
*/
32+
export interface ExecutionContext {
33+
schema: GraphQLSchema;
34+
fragments: ObjMap<FragmentDefinitionNode>;
35+
rootValue: unknown;
36+
contextValue: unknown;
37+
operation: OperationDefinitionNode;
38+
variableValues: { [variable: string]: unknown };
39+
fieldResolver: GraphQLFieldResolver<any, any>;
40+
typeResolver: GraphQLTypeResolver<any, any>;
41+
subscribeFieldResolver: GraphQLFieldResolver<any, any>;
42+
errors: Array<GraphQLError>;
43+
}
44+
45+
/**
46+
* Constructs a ExecutionContext object from the arguments passed to
47+
* execute, which we will pass throughout the other execution methods.
48+
*
49+
* Throws a GraphQLError if a valid execution context cannot be created.
50+
*
51+
* TODO: consider no longer exporting this function
52+
* @internal
53+
*/
54+
export function buildExecutionContext(
55+
args: ExecutionArgs,
56+
): ReadonlyArray<GraphQLError> | ExecutionContext {
57+
const {
58+
schema,
59+
document,
60+
rootValue,
61+
contextValue,
62+
variableValues: rawVariableValues,
63+
operationName,
64+
fieldResolver,
65+
typeResolver,
66+
subscribeFieldResolver,
67+
} = args;
68+
69+
// If the schema used for execution is invalid, throw an error.
70+
assertValidSchema(schema);
71+
72+
let operation: OperationDefinitionNode | undefined;
73+
const fragments: ObjMap<FragmentDefinitionNode> = Object.create(null);
74+
for (const definition of document.definitions) {
75+
switch (definition.kind) {
76+
case Kind.OPERATION_DEFINITION:
77+
if (operationName == null) {
78+
if (operation !== undefined) {
79+
return [
80+
new GraphQLError(
81+
'Must provide operation name if query contains multiple operations.',
82+
),
83+
];
84+
}
85+
operation = definition;
86+
} else if (definition.name?.value === operationName) {
87+
operation = definition;
88+
}
89+
break;
90+
case Kind.FRAGMENT_DEFINITION:
91+
fragments[definition.name.value] = definition;
92+
break;
93+
default:
94+
// ignore non-executable definitions
95+
}
96+
}
97+
98+
if (!operation) {
99+
if (operationName != null) {
100+
return [new GraphQLError(`Unknown operation named "${operationName}".`)];
101+
}
102+
return [new GraphQLError('Must provide an operation.')];
103+
}
104+
105+
// FIXME: https://github.com/graphql/graphql-js/issues/2203
106+
/* c8 ignore next */
107+
const variableDefinitions = operation.variableDefinitions ?? [];
108+
109+
const coercedVariableValues = getVariableValues(
110+
schema,
111+
variableDefinitions,
112+
rawVariableValues ?? {},
113+
{ maxErrors: 50 },
114+
);
115+
116+
if (coercedVariableValues.errors) {
117+
return coercedVariableValues.errors;
118+
}
119+
120+
return {
121+
schema,
122+
fragments,
123+
rootValue,
124+
contextValue,
125+
operation,
126+
variableValues: coercedVariableValues.coerced,
127+
fieldResolver: fieldResolver ?? defaultFieldResolver,
128+
typeResolver: typeResolver ?? defaultTypeResolver,
129+
subscribeFieldResolver: subscribeFieldResolver ?? defaultFieldResolver,
130+
errors: [],
131+
};
132+
}
133+
134+
/**
135+
* @internal
136+
*/
137+
export function buildPerEventExecutionContext(
138+
exeContext: ExecutionContext,
139+
payload: unknown,
140+
): ExecutionContext {
141+
return {
142+
...exeContext,
143+
rootValue: payload,
144+
errors: [],
145+
};
146+
}
147+
148+
/**
149+
* If a resolveType function is not given, then a default resolve behavior is
150+
* used which attempts two strategies:
151+
*
152+
* First, See if the provided value has a `__typename` field defined, if so, use
153+
* that value as name of the resolved type.
154+
*
155+
* Otherwise, test each possible type for the abstract type by calling
156+
* isTypeOf for the object being coerced, returning the first type that matches.
157+
*/
158+
export const defaultTypeResolver: GraphQLTypeResolver<unknown, unknown> =
159+
function (value, contextValue, info, abstractType) {
160+
// First, look for `__typename`.
161+
if (isObjectLike(value) && typeof value.__typename === 'string') {
162+
return value.__typename;
163+
}
164+
165+
// Otherwise, test each possible type.
166+
const possibleTypes = info.schema.getPossibleTypes(abstractType);
167+
const promisedIsTypeOfResults = [];
168+
169+
for (let i = 0; i < possibleTypes.length; i++) {
170+
const type = possibleTypes[i];
171+
172+
if (type.isTypeOf) {
173+
const isTypeOfResult = type.isTypeOf(value, contextValue, info);
174+
175+
if (isPromise(isTypeOfResult)) {
176+
promisedIsTypeOfResults[i] = isTypeOfResult;
177+
} else if (isTypeOfResult) {
178+
return type.name;
179+
}
180+
}
181+
}
182+
183+
if (promisedIsTypeOfResults.length) {
184+
return Promise.all(promisedIsTypeOfResults).then((isTypeOfResults) => {
185+
for (let i = 0; i < isTypeOfResults.length; i++) {
186+
if (isTypeOfResults[i]) {
187+
return possibleTypes[i].name;
188+
}
189+
}
190+
});
191+
}
192+
};
193+
194+
/**
195+
* If a resolve function is not given, then a default resolve behavior is used
196+
* which takes the property of the source object of the same name as the field
197+
* and returns it as the result, or if it's a function, returns the result
198+
* of calling that function while passing along args and context value.
199+
*/
200+
export const defaultFieldResolver: GraphQLFieldResolver<unknown, unknown> =
201+
function (source: any, args, contextValue, info) {
202+
// ensure source is a value for which property access is acceptable.
203+
if (isObjectLike(source) || typeof source === 'function') {
204+
const property = source[info.fieldName];
205+
if (typeof property === 'function') {
206+
return source[info.fieldName](args, contextValue, info);
207+
}
208+
return property;
209+
}
210+
};

0 commit comments

Comments
 (0)