Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/twelve-forks-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@neo4j/graphql": patch
---

`cypherParams` added to the `Neo4jGraphQLContext` type, and the fields within it can be referred to directly.
41 changes: 18 additions & 23 deletions packages/graphql/src/classes/NodeDirective.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
* limitations under the License.
*/

import dotProp from "dot-prop";
import { Neo4jGraphQLError } from "./Error";
import type { Context } from "../types";
import ContextParser from "../utils/context-parser";
import Cypher from "@neo4j/cypher-builder";

export interface NodeDirectiveConstructor {
Expand Down Expand Up @@ -48,35 +48,30 @@ export class NodeDirective {

private mapLabelsWithContext(labels: string[], context: Context): string[] {
return labels.map((label: string) => {
const jwtPath = ContextParser.parseTag(label, "jwt");
let ctxPath = ContextParser.parseTag(label, "context");

if (jwtPath) {
ctxPath = `jwt.${jwtPath}`;
}

if (ctxPath) {
let mappedLabel = ContextParser.getProperty(ctxPath, context);
if (mappedLabel) {
return mappedLabel;
}

// Try the new authorization path - this will become default in 4.0.0
if (jwtPath) {
ctxPath = `authorization.jwt.${jwtPath}`;
mappedLabel = ContextParser.getProperty(ctxPath, context);
if (mappedLabel) {
return mappedLabel;
}
if (label.startsWith("$")) {
// Trim $context. OR $ off the beginning of the string
const path = label.substring(label.startsWith("$context") ? 9 : 1);
const labelValue = this.searchLabel(context, path);
if (!labelValue) {
throw new Error(`Label value not found in context.`);
}

throw new Error(`Label value not found in context.`);
return labelValue;
}

return label;
});
}

private searchLabel(context, path): string | undefined {
// Search for the key at the root of the context
let labelValue = dotProp.get<string>(context, path);
if (!labelValue) {
// Search for the key in cypherParams
labelValue = dotProp.get<string>(context.cypherParams, path);
}
return labelValue;
}

private escapeLabel(label: string): string {
return Cypher.utils.escapeLabel(label);
}
Expand Down
6 changes: 3 additions & 3 deletions packages/graphql/src/schema/resolvers/wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,12 @@ export const wrapResolver =
if (!context.jwt) {
if (authorization) {
try {
const jwt = await authorization.decode(context);
context.jwt = await authorization.decode(context);
const isAuthenticated = true;
context.authorization = {
isAuthenticated,
jwt,
jwtParam: new Cypher.NamedParam("jwt", jwt),
jwt: context.jwt,
jwtParam: new Cypher.NamedParam("jwt", context.jwt),
isAuthenticatedParam: new Cypher.NamedParam("isAuthenticated", isAuthenticated),
claims: jwtPayloadFieldsMap,
};
Expand Down
2 changes: 1 addition & 1 deletion packages/graphql/src/translate/translate-aggregate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { compileCypher } from "../utils/compile-cypher";
function translateAggregate({ node, context }: { node: Node; context: Context }): [Cypher.Clause, any] {
const { fieldsByTypeName } = context.resolveTree;
const varName = "this";
let cypherParams: { [k: string]: any } = context.cypherParams ? { cypherParams: context.cypherParams } : {};
let cypherParams: Record<string, any> = context.cypherParams ? { ...context.cypherParams } : {};
const cypherStrs: Cypher.Clause[] = [];
const matchNode = new Cypher.NamedNode(varName, { labels: node.getLabels(context) });
const where = context.resolveTree.args.where as GraphQLWhereArg | undefined;
Expand Down
2 changes: 1 addition & 1 deletion packages/graphql/src/translate/translate-delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function translateDelete({ context, node }: { context: Context; node: Nod
const varName = "this";
let matchAndWhereStr = "";
let deleteStr = "";
let cypherParams: { [k: string]: any } = context.cypherParams ? { cypherParams: context.cypherParams } : {};
let cypherParams: Record<string, any> = context.cypherParams ? { ...context.cypherParams } : {};

const withVars = [varName];

Expand Down
2 changes: 1 addition & 1 deletion packages/graphql/src/translate/translate-read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export function translateRead(
projectionSubqueries,
projectionClause
);
const result = readQuery.build(undefined, context.cypherParams ? { cypherParams: context.cypherParams } : {});
const result = readQuery.build(undefined, context.cypherParams ? { ...context.cypherParams } : {});

return result;
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,5 @@ export function translateResolveReference({
returnClause
);

return readQuery.build(undefined, context.cypherParams ? { cypherParams: context.cypherParams } : {});
return readQuery.build(undefined, context.cypherParams ? { ...context.cypherParams } : {});
}
13 changes: 3 additions & 10 deletions packages/graphql/src/translate/translate-top-level-cypher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,6 @@ export function translateTopLevelCypher({
);
}

const initApocParamsStrs = ["auth: $auth", ...(context.cypherParams ? ["cypherParams: $cypherParams"] : [])];

// Null default argument values are not passed into the resolve tree therefore these are not being passed to
// `apocParams` below causing a runtime error when executing.
const nullArgumentValues = field.arguments.reduce(
Expand All @@ -183,18 +181,13 @@ export function translateTopLevelCypher({
);

const apocParams = Object.entries({ ...nullArgumentValues, ...resolveTree.args }).reduce(
(result: { strs: string[]; params: { [key: string]: unknown } }, entry) => ({
strs: [...result.strs, `${entry[0]}: $${entry[0]}`],
(result: { params: { [key: string]: unknown } }, entry) => ({
params: { ...result.params, [entry[0]]: entry[1] },
}),
{ strs: initApocParamsStrs, params }
{ params }
);

if (statement.includes("$jwt")) {
apocParams.strs.push("jwt: $jwt");
}

params = { ...params, ...apocParams.params };
params = { ...params, ...apocParams.params, ...(context.cypherParams || {}) };

if (type === "Query") {
const cypherStatement = createCypherDirectiveSubquery({
Expand Down
2 changes: 1 addition & 1 deletion packages/graphql/src/translate/translate-update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export default async function translateUpdate({
const createStrs: string[] = [];
let deleteStr = "";
let projAuth: Cypher.Clause | undefined = undefined;
let cypherParams: { [k: string]: any } = context.cypherParams ? { cypherParams: context.cypherParams } : {};
let cypherParams: Record<string, any> = context.cypherParams ? { ...context.cypherParams } : {};
const assumeReconnecting = Boolean(connectInput) && Boolean(disconnectInput);
const matchNode = new Cypher.NamedNode(varName, { labels: node.getLabels(context) });
const where = resolveTree.args.where as GraphQLWhereArg | undefined;
Expand Down
13 changes: 13 additions & 0 deletions packages/graphql/src/types/neo4j-graphql-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ import type { JWTPayload } from "jose";
import type { ExecutionContext, Neo4jGraphQLSessionConfig } from "../classes/Executor";

export interface Neo4jGraphQLContext {
/**
* Parameters to be used when querying with Cypher.
*
* To be used with directives such as `@node` and `@cypher`, and can be used directly as named here.
*
* @example
* Given a `cypherParams` value as follows:
* ```
* { title: "The Matrix" }
* ```
* This can be referred to like `@cypher(statement: "RETURN $title AS title", columnName: "title")`.
*/
cypherParams?: Record<string, any>;
/**
* Configures which {@link https://neo4j.com/docs/cypher-manual/current/query-tuning/query-options/ | Cypher query options} to use when executing the translated query.
*/
Expand Down
32 changes: 0 additions & 32 deletions packages/graphql/src/utils/context-parser.ts

This file was deleted.

6 changes: 3 additions & 3 deletions packages/graphql/tests/integration/cypher-params.int.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ describe("cypherParams", () => {
}

type Query {
id: String! @cypher(statement: "RETURN $cypherParams.id AS id", columnName: "id")
id: String! @cypher(statement: "RETURN $id AS id", columnName: "id")
}
`;

Expand Down Expand Up @@ -128,7 +128,7 @@ describe("cypherParams", () => {
variableValues: {
id: movieId,
},
contextValue: neo4j.getContextValues({ cypherParams: { id: cypherParamsId } }),
contextValue: neo4j.getContextValues({ cypherParams: { cypherParams: { id: cypherParamsId } } }),
});

expect(gqlResult.errors).toBeFalsy();
Expand All @@ -153,7 +153,7 @@ describe("cypherParams", () => {
}

type Mutation {
id: String! @cypher(statement: "RETURN $cypherParams.id AS id", columnName:"id")
id: String! @cypher(statement: "RETURN $id AS id", columnName:"id")
}
`;

Expand Down
2 changes: 1 addition & 1 deletion packages/graphql/tests/integration/issues/1249.int.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe("https://github.com/neo4j/graphql/issues/1249", () => {
const typeDefs = `
type Bulk
@exclude(operations: [CREATE, DELETE, UPDATE])
@node(labels: ["Bulk", "$context.cypherParams.tenant"]) {
@node(labels: ["Bulk", "$tenant"]) {
id: ID!
supplierMaterialNumber: String!
material: Material! @relationship(type: "MATERIAL_BULK", direction: OUT)
Expand Down
8 changes: 2 additions & 6 deletions packages/graphql/tests/tck/issues/1249.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ describe("https://github.com/neo4j/graphql/issues/1249", () => {

beforeAll(() => {
typeDefs = gql`
type Bulk
@exclude(operations: [CREATE, DELETE, UPDATE])
@node(labels: ["Bulk", "$context.cypherParams.tenant"]) {
type Bulk @exclude(operations: [CREATE, DELETE, UPDATE]) @node(labels: ["Bulk", "$tenant"]) {
id: ID!
supplierMaterialNumber: String!
material: Material! @relationship(type: "MATERIAL_BULK", direction: OUT)
Expand Down Expand Up @@ -103,9 +101,7 @@ describe("https://github.com/neo4j/graphql/issues/1249", () => {

expect(formatParams(result.params)).toMatchInlineSnapshot(`
"{
\\"cypherParams\\": {
\\"tenant\\": \\"BULK\\"
}
\\"tenant\\": \\"BULK\\"
}"
`);
});
Expand Down