Skip to content

Commit

Permalink
Fix integer input parsing (#230)
Browse files Browse the repository at this point in the history
* Current progress on getting Neo4j values into resolve tree

* Fix for integer input parsing
  • Loading branch information
darrellwarde authored Jun 2, 2021
1 parent cabfe28 commit a6fbe2f
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 22 deletions.
6 changes: 4 additions & 2 deletions packages/graphql/src/classes/Neo4jGraphQL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ import Debug from "debug";
import { Driver } from "neo4j-driver";
import { DocumentNode, GraphQLResolveInfo, GraphQLSchema, parse, printSchema, print } from "graphql";
import { addSchemaLevelResolver, IExecutableSchemaDefinition } from "@graphql-tools/schema";
import { parseResolveInfo, ResolveTree } from "graphql-parse-resolve-info";
import type { DriverConfig } from "../types";
import { makeAugmentedSchema } from "../schema";
import Node from "./Node";
import { checkNeo4jCompat } from "../utils";
import { getJWT } from "../auth/index";
import { DEBUG_GRAPHQL } from "../constants";
import getNeo4jResolveTree from "../utils/get-neo4j-resolve-tree";

const debug = Debug(DEBUG_GRAPHQL);

Expand Down Expand Up @@ -114,7 +114,9 @@ class Neo4jGraphQL {
}

context.neoSchema = this;
context.resolveTree = parseResolveInfo(resolveInfo) as ResolveTree;

context.resolveTree = getNeo4jResolveTree(resolveInfo);

context.jwt = getJWT(context);
});
}
Expand Down
18 changes: 6 additions & 12 deletions packages/graphql/src/schema/scalars/Int.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,16 @@
*/

import { GraphQLScalarType } from "graphql";
import { int, Integer } from "neo4j-driver";
import { isInt, Integer } from "neo4j-driver";

export default new GraphQLScalarType({
name: "Int",
parseValue(value) {
if (typeof value !== "number") {
throw new Error("Cannot represent non number as Int");
serialize(outputValue: unknown) {
// @ts-ignore: outputValue is unknown, and to cast to object would be an antipattern
if (isInt(outputValue)) {
return (outputValue as Integer).toNumber();
}

return int(value);
},
serialize(value: Integer) {
if (value.toNumber) {
return value.toNumber();
}

return value;
return outputValue;
},
});
6 changes: 3 additions & 3 deletions packages/graphql/src/schema/scalars/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
* limitations under the License.
*/

export { default as Float } from "./Float";
export { default as Int } from "./Int";
export { default as BigInt } from "./BigInt";
export { default as ID } from "./ID";
export { default as DateTime } from "./DateTime";
export { default as Float } from "./Float";
export { default as ID } from "./ID";
export { default as Int } from "./Int";
157 changes: 157 additions & 0 deletions packages/graphql/src/utils/get-neo4j-resolve-tree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {
GraphQLField,
GraphQLInterfaceType,
GraphQLNamedType,
GraphQLNonNull,
GraphQLObjectType,
GraphQLResolveInfo,
GraphQLInputObjectType,
GraphQLInputType,
GraphQLList,
GraphQLScalarType,
} from "graphql";
import { parseResolveInfo, ResolveTree } from "graphql-parse-resolve-info";
import neo4j from "neo4j-driver";

function getNeo4jArgumentValue({ argument, type }: { argument: unknown | unknown[]; type: GraphQLInputType }) {
if (argument === null) {
return argument;
}

if (type.toString().endsWith("!")) {
return getNeo4jArgumentValue({ argument, type: (type as GraphQLNonNull<any>).ofType });
}

if (type.toString().startsWith("[") && type.toString().endsWith("]")) {
return (argument as unknown[]).map((a) =>
getNeo4jArgumentValue({ argument: a, type: (type as GraphQLList<any>).ofType })
);
}

if (type instanceof GraphQLInputObjectType) {
return Object.entries(argument as Record<string, unknown>).reduce((res, [key, value]) => {
const field = Object.values(type.getFields()).find((f) => f.name === key);

if (!field) {
throw new Error(
`Error whilst generating Neo4j resolve tree: could not find field ${key} in type ${type.name}`
);
}

return {
...res,
[key]: getNeo4jArgumentValue({ argument: value, type: field.type }),
};
}, {});
}

if (type instanceof GraphQLScalarType) {
return type.name === "Int" ? neo4j.int(argument as number) : argument;
}

return argument;
}

interface GetNeo4jResolveTreeOptions {
resolveTree: ResolveTree;
field: GraphQLField<any, any>;
}

function getNeo4jResolveTree(resolveInfo: GraphQLResolveInfo, options?: GetNeo4jResolveTreeOptions) {
const resolveTree = options?.resolveTree || (parseResolveInfo(resolveInfo) as ResolveTree);

let field: GraphQLField<any, any>;

if (options?.field) {
field = options.field;
} else {
const queryType = resolveInfo.schema.getQueryType();
const mutationType = resolveInfo.schema.getMutationType();

field = Object.values({ ...queryType?.getFields(), ...mutationType?.getFields() }).find(
(f) => f.name === resolveTree.name
) as GraphQLField<any, any>;
}

const args = Object.entries(resolveTree.args).reduce((res, [name, value]) => {
const argument = field.args.find((arg) => arg.name === name);

if (!argument) {
throw new Error(
`Error whilst generating Neo4j resolve tree: could not find argument ${name} on field ${field.name}`
);
}

return {
...res,
[name]: getNeo4jArgumentValue({ argument: value, type: argument.type }),
};
}, {});

const fieldsByTypeName = Object.entries(resolveTree.fieldsByTypeName).reduce((res, [typeName, fields]) => {
let type: GraphQLObjectType | GraphQLInterfaceType;

// eslint-disable-next-line no-underscore-dangle,@typescript-eslint/naming-convention
const _type = resolveInfo.schema.getType(typeName) as GraphQLNamedType;

if (!_type) {
throw new Error(
`Error whilst generating Neo4j resolve tree: could not find type with name ${typeName} in schema`
);
}

/* isTypeOf and resolveType are defining for GraphQLObjectType and GraphQLInterfaceType */
if ((_type as GraphQLObjectType).isTypeOf) {
type = _type as GraphQLObjectType;
} else if ((_type as GraphQLInterfaceType).resolveType) {
type = _type as GraphQLInterfaceType;
} else {
return {
...res,
[typeName]: fields,
};
}

const resolveTrees = Object.entries(fields).reduce((trees, [fieldName, f]) => {
return {
...trees,
[fieldName]: getNeo4jResolveTree(resolveInfo, {
resolveTree: f,
field: Object.values(type.getFields()).find(
(typeField) => typeField.name === f.name
) as GraphQLField<any, any>,
}),
};
}, {});

return {
...res,
[typeName]: resolveTrees,
};
}, {});

const { alias, name } = resolveTree;

return { alias, args, fieldsByTypeName, name } as ResolveTree;
}

export default getNeo4jResolveTree;
11 changes: 6 additions & 5 deletions packages/graphql/tests/tck/tck.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import pluralize from "pluralize";
import jsonwebtoken from "jsonwebtoken";
import { IncomingMessage } from "http";
import { Socket } from "net";
import { parseResolveInfo, ResolveTree } from "graphql-parse-resolve-info";
// import { parseResolveInfo, ResolveTree } from "graphql-parse-resolve-info";
import { SchemaDirectiveVisitor, printSchemaWithDirectives } from "@graphql-tools/utils";
import { translateCreate, translateDelete, translateRead, translateUpdate } from "../../src/translate";
import { Context } from "../../src/types";
Expand All @@ -50,6 +50,7 @@ import { trimmer } from "../../src/utils";
import * as Scalars from "../../src/schema/scalars";
import { Node } from "../../src/classes";
import createAuthParam from "../../src/translate/create-auth-param";
import getNeo4jResolveTree from "../../src/utils/get-neo4j-resolve-tree";

const TCK_DIR = path.join(__dirname, "tck-test-files");

Expand Down Expand Up @@ -140,7 +141,7 @@ describe("TCK Generated tests", () => {
context: Context,
info: GraphQLResolveInfo
) => {
const resolveTree = parseResolveInfo(info) as ResolveTree;
const resolveTree = getNeo4jResolveTree(info);

context.neoSchema = neoSchema;
context.resolveTree = resolveTree;
Expand Down Expand Up @@ -176,7 +177,7 @@ describe("TCK Generated tests", () => {
context: any,
info: GraphQLResolveInfo
) => {
const resolveTree = parseResolveInfo(info) as ResolveTree;
const resolveTree = getNeo4jResolveTree(info);

context.neoSchema = neoSchema;
context.resolveTree = resolveTree;
Expand Down Expand Up @@ -204,7 +205,7 @@ describe("TCK Generated tests", () => {
context: any,
info: GraphQLResolveInfo
) => {
const resolveTree = parseResolveInfo(info) as ResolveTree;
const resolveTree = getNeo4jResolveTree(info);

context.neoSchema = neoSchema;
context.resolveTree = resolveTree;
Expand All @@ -227,7 +228,7 @@ describe("TCK Generated tests", () => {
};
},
[`delete${pluralize(def.name.value)}`]: (_root: any, _params: any, context: any, info) => {
const resolveTree = parseResolveInfo(info) as ResolveTree;
const resolveTree = getNeo4jResolveTree(info);

context.neoSchema = neoSchema;
context.resolveTree = resolveTree;
Expand Down

0 comments on commit a6fbe2f

Please sign in to comment.