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/wicked-ties-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@neo4j/graphql": major
---

`enableRegex` has been removed and replaced with `MATCHES` filters in the features configuration object. See the migration guide for more information: https://neo4j.com/docs/graphql-manual/current/guides/v4-migration
5 changes: 0 additions & 5 deletions docs/modules/ROOT/pages/api-reference/neo4jgraphql.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,6 @@ Accepts all of the options from https://www.graphql-tools.com/docs/generate-sche
Type: xref::api-reference/neo4jgraphql.adoc#api-reference-neo4jgraphql-input-neo4jgraphqlconfig-driverconfig[`DriverConfig`]
|Additional driver configuration options.

|`enableRegex` +
+
Type: `boolean`
|Whether to enable RegEx filters, see xref::filtering.adoc#filtering-regex[RegEx matching] for more information.

|`queryOptions` +
+
Type: xref::api-reference/neo4jgraphql.adoc#api-reference-neo4jgraphql-input-neo4jgraphqlconfig-cypherqueryoptions[`CypherQueryOptions`]
Expand Down
3 changes: 0 additions & 3 deletions docs/modules/ROOT/pages/filtering.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,6 @@ const features = {
const neoSchema = new Neo4jGraphQL({ features, typeDefs, driver });
----


Previously to enable to this filter the config option `enableRegex` was used, it has been deprecated. Use the `features` configuration object as described here, as the `enableRegex` will be removed in the future.

> The nature of RegEx matching means that on an unprotected API, this could potentially be used to execute a ReDoS attack (https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS) against the backing Neo4j database.

=== Array comparison
Expand Down
24 changes: 23 additions & 1 deletion docs/modules/ROOT/pages/guides/v4-migration/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,29 @@ npm update @neo4j/graphql
If you were passing any arguments from https://the-guild.dev/graphql/tools/docs/api/interfaces/schema_src.iexecutableschemadefinition[`IExecutableSchemaDefinition`] into the library
other than `typeDefs` and `resolvers`, these are no longer supported.

=== config.enableRegex replaced by `MATCHES` in features.filters

`config.enableRegex` has been replaced by `MATCHES` in features.filters. With this change comes more granularity in the feature configuration. You can now enable the `MATCHES` filter on `String` and `ID` fields separately.

A direct replacement of the `enableRegex: true` configuration would be as follows:

[source, javascript, indent=0]
----
neoSchema = new Neo4jGraphQL({
typeDefs,
features: {
filters: {
String: {
MATCHES: true,
},
ID: {
MATCHES: true,
},
},
},
});
----

== Updated Directives

We have renamed a number of directives and their arguments, in order to make using `@neo4j/graphql` more intuitive.
Expand Down Expand Up @@ -577,7 +600,6 @@ const neoSchema = new Neo4jGraphQL({
});
----


== Miscellaneous changes

[[startup-validation]]
Expand Down
8 changes: 0 additions & 8 deletions packages/graphql/src/classes/Neo4jGraphQL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,6 @@ import { validateDocument } from "../schema/validation";

export interface Neo4jGraphQLConfig {
driverConfig?: DriverConfig;
/**
* @deprecated This argument has been deprecated and will be removed in v4.0.0.
* Please use features.filters instead. More information can be found at
* https://neo4j.com/docs/graphql-manual/current/guides/v4-migration/#features
*/
enableRegex?: boolean;
enableDebug?: boolean;
/**
* @deprecated This argument has been deprecated and will be removed in v4.0.0.
Expand Down Expand Up @@ -334,7 +328,6 @@ class Neo4jGraphQL {

const { nodes, relationships, typeDefs, resolvers } = makeAugmentedSchema(document, {
features: this.features,
enableRegex: this.config?.enableRegex,
validateResolvers: validationConfig.validateResolvers,
generateSubscriptions: Boolean(this.plugins?.subscriptions),
callbacks: this.features?.populatedBy?.callbacks ?? this.config.callbacks,
Expand Down Expand Up @@ -372,7 +365,6 @@ class Neo4jGraphQL {

const { nodes, relationships, typeDefs, resolvers } = makeAugmentedSchema(document, {
features: this.features,
enableRegex: this.config?.enableRegex,
validateResolvers: validationConfig.validateResolvers,
generateSubscriptions: Boolean(this.plugins?.subscriptions),
callbacks: this.features?.populatedBy?.callbacks ?? this.config.callbacks,
Expand Down
33 changes: 13 additions & 20 deletions packages/graphql/src/schema/get-where-fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,11 @@ interface Fields {
function getWhereFields({
typeName,
fields,
enableRegex,
isInterface,
features,
}: {
typeName: string;
fields: Fields;
enableRegex?: boolean;
isInterface?: boolean;
features?: Neo4jFeaturesSettings;
}): { [k: string]: string } {
Expand Down Expand Up @@ -126,30 +124,25 @@ function getWhereFields({
}

if (["String", "ID"].includes(f.typeMeta.name)) {
const matchesSetting: boolean | undefined = features?.filters?.[f.typeMeta.name]?.MATCHES;
if (matchesSetting !== undefined) {
if (matchesSetting === true) {
res[`${f.fieldName}_MATCHES`] = { type: "String", directives: deprecatedDirectives };
}
} else if (enableRegex) {
// TODO: This is deprecated. To be removed in 4.0.0.
res[`${f.fieldName}_MATCHES`] = { type: "String", directives: deprecatedDirectives };
}
Comment on lines -129 to -137
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much tidier! 👏


const stringWhereOperators = ["_CONTAINS", "_STARTS_WITH", "_ENDS_WITH"];
const stringWhereOperators: Array<{ comparator: string; typeName: string }> = [
{ comparator: "_CONTAINS", typeName: f.typeMeta.name },
{ comparator: "_STARTS_WITH", typeName: f.typeMeta.name },
{ comparator: "_ENDS_WITH", typeName: f.typeMeta.name },
];

const stringWhereOperatorsNegate = ["_NOT_CONTAINS", "_NOT_STARTS_WITH", "_NOT_ENDS_WITH"];

Object.entries(features?.filters?.String || {}).forEach(([filter, enabled]) => {
if (filter === "MATCHES") {
return;
}
Object.entries(features?.filters?.[f.typeMeta.name] || {}).forEach(([filter, enabled]) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick Consider creating a varaible for f.typeMeta.name as it is used in several places and the extra context of a explicit name could help with readability here

Copy link
Member

@angrykoala angrykoala Mar 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I think we could really make use of a features helper here, as this seems like it is going to be the way forward for feature flags and we should make our lifes a bit easier on this.

I'm thinking something a bit like:

features(f.typeMeta.name).hasFilter("MATCHES");

This way we have a more unified way of accesing features that don't require concatenation of .? and default objects and we could encapsulate the logic of traversing the filters, avoiding this forEach on our schema generation logic.

Thoughts?

if (enabled) {
stringWhereOperators.push(`_${filter}`);
if (filter === "MATCHES") {
stringWhereOperators.push({ comparator: `_${filter}`, typeName: "String" });
} else {
stringWhereOperators.push({ comparator: `_${filter}`, typeName: f.typeMeta.name });
}
}
});
stringWhereOperators.forEach((comparator) => {
res[`${f.fieldName}${comparator}`] = { type: f.typeMeta.name, directives: deprecatedDirectives };
stringWhereOperators.forEach(({ comparator, typeName }) => {
res[`${f.fieldName}${comparator}`] = { type: typeName, directives: deprecatedDirectives };
});

stringWhereOperatorsNegate.forEach((comparator) => {
Expand Down
23 changes: 0 additions & 23 deletions packages/graphql/src/schema/make-augmented-schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,29 +151,6 @@ describe("makeAugmentedSchema", () => {
expect(matchesField).toBeUndefined();
});

test("should add the MATCHES filter when NEO4J_GRAPHQL_ENABLE_REGEX is set", () => {
const typeDefs = gql`
type User {
name: String
}
`;

const neoSchema = makeAugmentedSchema(typeDefs, {
enableRegex: true,
validateResolvers: true,
});

const document = neoSchema.typeDefs;

const nodeWhereInput = document.definitions.find(
(x) => x.kind === "InputObjectTypeDefinition" && x.name.value === "UserWhere"
) as InputObjectTypeDefinitionNode;

const matchesField = nodeWhereInput.fields?.find((x) => x.name.value.endsWith("_MATCHES"));

expect(matchesField).toBeDefined();
});

test("should add the name_MATCHES filter when Features.Filters.String.MATCHES is set", () => {
const typeDefs = gql`
type User {
Expand Down
5 changes: 0 additions & 5 deletions packages/graphql/src/schema/make-augmented-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,13 @@ function makeAugmentedSchema(
document: DocumentNode,
{
features,
enableRegex,
validateResolvers,
generateSubscriptions,
callbacks,
userCustomResolvers,
subgraph,
}: {
features?: Neo4jFeaturesSettings;
enableRegex?: boolean;
validateResolvers: boolean;
generateSubscriptions?: boolean;
callbacks?: Neo4jGraphQLCallbacks;
Expand Down Expand Up @@ -269,7 +267,6 @@ function makeAugmentedSchema(
pointFields: relFields.pointFields,
primitiveFields: relFields.primitiveFields,
},
enableRegex,
features,
});

Expand Down Expand Up @@ -374,7 +371,6 @@ function makeAugmentedSchema(
pointFields: interfaceFields.pointFields,
primitiveFields: interfaceFields.primitiveFields,
},
enableRegex,
isInterface: true,
features,
});
Expand Down Expand Up @@ -622,7 +618,6 @@ function makeAugmentedSchema(

const queryFields = getWhereFields({
typeName: node.name,
enableRegex,
fields: {
temporalFields: node.temporalFields,
enumFields: node.enumFields,
Expand Down
4 changes: 0 additions & 4 deletions packages/graphql/tests/tck/directives/customResolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,6 @@ describe("@customResolver directive", () => {
neoSchema = new Neo4jGraphQL({
typeDefs,
resolvers,
config: { enableRegex: true },
});
});

Expand Down Expand Up @@ -242,7 +241,6 @@ describe("@customResolver directive", () => {
neoSchema = new Neo4jGraphQL({
typeDefs,
resolvers,
config: { enableRegex: true },
});
});

Expand Down Expand Up @@ -419,7 +417,6 @@ describe("@customResolver directive", () => {
neoSchema = new Neo4jGraphQL({
typeDefs,
resolvers,
config: { enableRegex: true },
});
});

Expand Down Expand Up @@ -612,7 +609,6 @@ describe("@customResolver directive", () => {
neoSchema = new Neo4jGraphQL({
typeDefs,
resolvers,
config: { enableRegex: true },
});
});

Expand Down