Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Graphql 17 support #185

Merged
merged 13 commits into from
Mar 13, 2023
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
matrix:
node-version: [14, 16, 18]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
graphql-version: [15, 16]
graphql-version: [15, 16, 17.0.0-alpha.2]

steps:
- uses: actions/checkout@v2
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
},
"coverageThreshold": {
"global": {
"branches": 92,
"branches": 91,
"functions": 96,
"lines": 96,
"statements": 96
Expand All @@ -50,7 +50,7 @@
"graphql": ">=15"
},
"devDependencies": {
"@graphql-tools/schema": "^8.3.1",
"@graphql-tools/schema": "^9.0.8",
"@stryker-mutator/core": "^2.0.0",
"@stryker-mutator/jest-runner": "^2.0.0",
"@stryker-mutator/typescript": "^2.0.0",
Expand Down
3 changes: 1 addition & 2 deletions src/__tests__/json.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import fastJson from "fast-json-stringify";
import {
formatError,
GraphQLBoolean,
GraphQLError,
GraphQLID,
Expand All @@ -11,13 +10,13 @@ import {
GraphQLString,
parse,
GraphQLInt,
GraphQLScalarType,
versionInfo
} from "graphql";
import { buildExecutionContext } from "graphql/execution/execute";
import { compileQuery } from "../index";
import { queryToJSONSchema } from "../json";
import { makeExecutableSchema } from "@graphql-tools/schema";
import { formatError } from "../compat";

describe("json schema creator", () => {
const BlogAuthor = new GraphQLObjectType({
Expand Down
3 changes: 1 addition & 2 deletions src/__tests__/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ import {
GraphQLString,
IntrospectionQuery,
parse,
printSchema,
versionInfo
printSchema
} from "graphql";
import { compileQuery, isCompiledQuery } from "../index";

Expand Down
6 changes: 3 additions & 3 deletions src/__tests__/subscription.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
*/

import {
ExecutionArgs,
ExecutionResult,
GraphQLBoolean,
GraphQLInt,
GraphQLList,
GraphQLObjectType,
GraphQLSchema,
GraphQLString,
parse,
SubscriptionArgs
parse
} from "graphql";
import { compileQuery, isAsyncIterable, isCompiledQuery } from "../execution";

Expand Down Expand Up @@ -68,7 +68,7 @@ async function subscribe({
rootValue,
contextValue,
variableValues
}: SubscriptionArgs): Promise<
}: ExecutionArgs): Promise<
AsyncIterableIterator<ExecutionResult> | ExecutionResult
> {
const prepared = compileQuery(schema, document, operationName || "");
Expand Down
7 changes: 5 additions & 2 deletions src/__tests__/variables.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import {
GraphQLScalarType,
GraphQLSchema,
GraphQLString,
parse
parse,
versionInfo
} from "graphql";
import { GraphQLArgumentConfig } from "graphql/type/definition";
import { compileQuery, isCompiledQuery } from "../index";
Expand Down Expand Up @@ -283,7 +284,9 @@ describe("Execute: Handles inputs", () => {
errors: [
{
message:
'Argument "input" of type "TestInputObject" has invalid value {b: ["A", null, "C"], c: false}.',
versionInfo.major < 17
? 'Argument "input" of type "TestInputObject" has invalid value {b: ["A", null, "C"], c: false}.'
: 'Argument "input" of type "TestInputObject" has invalid value { b: ["A", null, "C"], c: false }.',
locations: [{ line: 3, column: 41 }]
}
]
Expand Down
44 changes: 12 additions & 32 deletions src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,13 @@ import {
typeFromAST,
valueFromASTUntyped,
ValueNode,
VariableNode,
versionInfo
VariableNode
} from "graphql";
import { getFieldDef } from "graphql/execution/execute";
import { Kind, SelectionNode, TypeNode } from "graphql/language";
import { isAbstractType } from "graphql/type";
import { CompilationContext, GLOBAL_VARIABLES_NAME } from "./execution";
import createInspect from "./inspect";
import { Maybe } from "./types";
import { getGraphQLErrorOptions, resolveFieldDef } from "./compat";

export interface JitFieldNode extends FieldNode {
__internalShouldInclude?: string;
Expand Down Expand Up @@ -414,7 +412,7 @@ function compileSkipIncludeDirective(
if (ifNode == null) {
throw new GraphQLError(
`Directive '${directive.name.value}' is missing required arguments: 'if'`,
[directive]
getGraphQLErrorOptions([directive])
);
}

Expand All @@ -431,7 +429,7 @@ function compileSkipIncludeDirective(
}' has an invalid value (${valueFromASTUntyped(
ifNode.value
)}). Expected type 'Boolean!'`,
[ifNode]
getGraphQLErrorOptions([ifNode])
);
}
}
Expand All @@ -454,9 +452,10 @@ function validateSkipIncludeVariableType(
(it) => it.variable.name.value === variable.name.value
);
if (variableDefinition == null) {
throw new GraphQLError(`Variable '${variable.name.value}' is not defined`, [
variable
]);
throw new GraphQLError(
`Variable '${variable.name.value}' is not defined`,
getGraphQLErrorOptions([variable])
);
}

if (
Expand All @@ -470,7 +469,7 @@ function validateSkipIncludeVariableType(
`Variable '${variable.name.value}' of type '${typeNodeToString(
variableDefinition.type
)}' used in position expecting type 'Boolean!'`,
[variableDefinition]
getGraphQLErrorOptions([variableDefinition])
);
}
}
Expand Down Expand Up @@ -526,26 +525,7 @@ function getFieldEntryKey(node: FieldNode): string {
return node.alias ? node.alias.value : node.name.value;
}

/**
* Resolves the field on the given source object. In particular, this
* figures out the value that the field returns by calling its resolve function,
* then calls completeValue to complete promises, serialize scalars, or execute
* the sub-selection-set for objects.
*/
export function resolveFieldDef(
compilationContext: CompilationContext,
parentType: GraphQLObjectType,
fieldNodes: FieldNode[]
): Maybe<GraphQLField<any, any>> {
const fieldNode = fieldNodes[0];

if (versionInfo.major < 16) {
const fieldName = fieldNode.name.value;
return getFieldDef(compilationContext.schema, parentType, fieldName as any);
}

return getFieldDef(compilationContext.schema, parentType, fieldNode as any);
boopathi marked this conversation as resolved.
Show resolved Hide resolved
}
export { resolveFieldDef };
boopathi marked this conversation as resolved.
Show resolved Hide resolved

/**
* A memoized collection of relevant subfields in the context of the return
Expand Down Expand Up @@ -686,7 +666,7 @@ export function getArgumentDefs(
`Argument "${name}" of type "${argType}" has invalid value ${print(
argumentNode.value
)}.`,
argumentNode.value
getGraphQLErrorOptions(argumentNode.value)
);
}

Expand All @@ -709,7 +689,7 @@ export function getArgumentDefs(
`"${argType}" must not be null.`
: `Argument "${name}" of required type ` +
`"${argType}" was not provided.`,
node
getGraphQLErrorOptions(node)
);
}
}
Expand Down
110 changes: 99 additions & 11 deletions src/compat.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
import * as GraphQL from "graphql";
import {
GraphQLSchema,
GraphQLError,
versionInfo,
FieldNode,
GraphQLField
} from "graphql";
import { GraphQLObjectType } from "graphql/type/definition";
import { Maybe } from "graphql/jsutils/Maybe";

import { ASTNode, OperationDefinitionNode } from "graphql/language/ast";
import * as errorUtilities from "graphql/error";
import * as utilities from "graphql/utilities";
import { GraphQLFormattedError } from "graphql/error";
import { CompilationContext } from "./execution";
import * as execute from "graphql/execution/execute";

/**
* A helper file to support backward compatibility for different versions of graphql-js.
*/

/**
* A helper to support backward compatibility for different versions of graphql-js.
*
* v15 does not have schema.getRootType
* v16 has both
* v17 will not have getOperationRootType
Expand All @@ -15,13 +32,84 @@ import * as GraphQL from "graphql";
* GraphQL v17 would remove getOperationRootType.
*/
export function getOperationRootType(
schema: GraphQL.GraphQLSchema,
operation: GraphQL.OperationDefinitionNode
) {
if (GraphQL.getOperationRootType) {
return GraphQL.getOperationRootType(schema, operation);
} else {
// the use of any is to support graphql v15 types which will not use this codepath
return (schema as any).getRootType(operation.operation)!;
schema: GraphQLSchema,
operation: OperationDefinitionNode
): GraphQLObjectType {
if (versionInfo.major < 16) {
return (utilities as any).getOperationRootType(schema, operation);
}

const type = (schema as any).getRootType(operation.operation);

if (!type) {
throw new Error(`No root type for operation ${operation.operation}`);
}

return type;
}

/**
* v16 and lower versions don't have .toJSON method on GraphQLError
* v17 does have .toJSON and doesn't have "formatError" export anymore
*/
export function formatError(error: GraphQLError): GraphQLFormattedError {
if (versionInfo.major < 16) {
return (errorUtilities as any).formatError(error);
}

return (error as any).toJSON();
}

/**
* v17 dropped support for positional arguments in GraphQLError constructor
* https://github.com/graphql/graphql-js/pull/3577
*/
export function getGraphQLErrorOptions(
nodes: Maybe<ReadonlyArray<ASTNode> | ASTNode>
): ConstructorParameters<typeof GraphQLError>[1] {
if (versionInfo.major < 16) {
return nodes as any;
}

return { nodes } as any;
}

/**
* Resolves the field on the given source object. In particular, this
* figures out the value that the field returns by calling its resolve function,
* then calls completeValue to complete promises, serialize scalars, or execute
* the sub-selection-set for objects.
*
* v15 has getFieldDef that accepts field name
* v16 has getFieldDef that accepts field node
* v17 drops getFieldDef support and adds getField method
*/
export function resolveFieldDef(
compilationContext: CompilationContext,
parentType: GraphQLObjectType,
fieldNodes: FieldNode[]
): Maybe<GraphQLField<any, any>> {
const fieldNode = fieldNodes[0];

if (versionInfo.major < 16) {
const fieldName = fieldNode.name.value;
return (execute as any).getFieldDef(
compilationContext.schema,
parentType,
fieldName as any
);
}

if (versionInfo.major < 17) {
return (execute as any).getFieldDef(
compilationContext.schema,
parentType,
fieldNode as any
);
}

return (compilationContext.schema as any).getField(
parentType,
fieldNode.name.value
);
}
4 changes: 2 additions & 2 deletions src/execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ import {
ObjectPath,
resolveFieldDef
} from "./ast";
import { getOperationRootType } from "./compat";
import { GraphQLError as GraphqlJitError } from "./error";
import createInspect from "./inspect";
import { queryToJSONSchema } from "./json";
Expand All @@ -62,6 +61,7 @@ import {
compileVariableParsing,
failToParseVariables
} from "./variables";
import { getGraphQLErrorOptions, getOperationRootType } from "./compat";

const inspect = createInspect();

Expand Down Expand Up @@ -1706,7 +1706,7 @@ function compileSubscriptionOperation(
if (!field) {
throw new GraphQLError(
`The subscription field "${fieldName}" is not defined.`,
fieldNodes
getGraphQLErrorOptions(fieldNodes)
);
}

Expand Down
Loading