@neo4j/graphql
Version:
A GraphQL to Cypher query execution layer for Neo4j and JavaScript GraphQL implementations
156 lines • 7.25 kB
JavaScript
;
/*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ValidRelayID = ValidRelayID;
const document_validation_error_1 = require("../utils/document-validation-error");
const interface_to_implementing_types_1 = require("../utils/interface-to-implementing-types");
const path_parser_1 = require("../utils/path-parser");
function ValidRelayID(context) {
const typeNameToGlobalId = new Map();
const interfaceToImplementingTypes = new Map();
const typeNameToAliasedFields = new Map();
const addToAliasedFieldsMap = function (typeName, fieldName) {
const x = typeNameToAliasedFields.get(typeName) || new Set();
fieldName && x.add(fieldName);
typeNameToAliasedFields.set(typeName, x);
};
const getAliasedFieldsFromMap = function (typeName) {
return typeNameToAliasedFields.get(typeName);
};
const doOnObjectType = {
enter(objectType) {
(0, interface_to_implementing_types_1.hydrateInterfaceWithImplementedTypesMap)(objectType, interfaceToImplementingTypes);
},
leave(objectType) {
addToAliasedFieldsMap(objectType.name.value);
const fieldNamedID = getUnaliasedFieldNamedID(objectType);
if (!fieldNamedID || !hasGlobalIDField(objectType, typeNameToGlobalId, interfaceToImplementingTypes)) {
return;
}
const inheritedAliasedFields = ((0, interface_to_implementing_types_1.getInheritedTypeNames)(objectType, interfaceToImplementingTypes) || []).map(getAliasedFieldsFromMap);
const { isValid, errorMsg, errorPath } = (0, document_validation_error_1.assertValid)(() => assertGlobalIDDoesNotClash(inheritedAliasedFields));
if (!isValid) {
context.reportError((0, document_validation_error_1.createGraphQLError)({
nodes: [objectType, fieldNamedID],
path: [objectType.name.value, ...errorPath],
errorMsg,
}));
}
},
};
const doOnInterfaceType = {
leave(interfaceType) {
addToAliasedFieldsMap(interfaceType.name.value);
const fieldNamedID = getUnaliasedFieldNamedID(interfaceType);
if (!fieldNamedID || !hasGlobalIDField(interfaceType, typeNameToGlobalId, interfaceToImplementingTypes)) {
return;
}
const inheritedAliasedFields = ((0, interface_to_implementing_types_1.getInheritedTypeNames)(interfaceType, interfaceToImplementingTypes) || []).map(getAliasedFieldsFromMap);
const { isValid, errorMsg, errorPath } = (0, document_validation_error_1.assertValid)(() => assertGlobalIDDoesNotClash(inheritedAliasedFields));
if (!isValid) {
context.reportError((0, document_validation_error_1.createGraphQLError)({
nodes: [interfaceType],
path: [interfaceType.name.value, ...errorPath],
errorMsg,
}));
}
},
};
return {
Directive(directiveNode, _key, _parent, path, ancestors) {
const [pathToNode, traversedDef, parentOfTraversedDef] = (0, path_parser_1.getPathToNode)(path, ancestors);
if (!traversedDef) {
console.error("No last definition traversed");
return;
}
if (!parentOfTraversedDef) {
console.error("No parent of last definition traversed");
return;
}
if (directiveNode.name.value === "alias") {
addToAliasedFieldsMap(parentOfTraversedDef.name.value, traversedDef.name.value);
}
if (directiveNode.name.value !== "relayId") {
return;
}
const { isValid, errorMsg, errorPath } = (0, document_validation_error_1.assertValid)(() => assertValidGlobalID({
directiveNode,
typeDef: parentOfTraversedDef,
typeNameToGlobalId,
interfaceToImplementingTypes,
}));
if (!isValid) {
context.reportError((0, document_validation_error_1.createGraphQLError)({
nodes: [directiveNode, traversedDef],
path: [...pathToNode, ...errorPath],
errorMsg,
}));
}
},
ObjectTypeDefinition: doOnObjectType,
ObjectTypeExtension: doOnObjectType,
InterfaceTypeDefinition: doOnInterfaceType,
InterfaceTypeExtension: doOnInterfaceType,
};
}
function getUnaliasedFieldNamedID(mainType) {
const fieldNamedID = mainType.fields?.find((x) => x.name.value === "id");
if (!fieldNamedID) {
return;
}
const isFieldAliased = !!fieldNamedID.directives?.find((x) => x.name.value === "alias");
if (!isFieldAliased) {
return fieldNamedID;
}
return;
}
function hasGlobalIDField(mainType, typeNameToGlobalId, interfaceToImplementingTypes) {
if (typeNameToGlobalId.get(mainType.name.value)) {
return true;
}
return !!(0, interface_to_implementing_types_1.getInheritedTypeNames)(mainType, interfaceToImplementingTypes)?.find((typeName) => typeNameToGlobalId.get(typeName) === true);
}
function assertGlobalIDDoesNotClash(aliasedFieldsFromInheritedTypes) {
let shouldDeferCheck = false;
let hasAlias = false;
for (const aliasedFields of aliasedFieldsFromInheritedTypes) {
if (!aliasedFields) {
shouldDeferCheck = true;
}
else {
if (aliasedFields.has("id")) {
hasAlias = true;
return;
}
}
}
if (hasAlias === false && shouldDeferCheck === false) {
throw new document_validation_error_1.DocumentValidationError(`Type already has a field \`id\`, which is reserved for Relay global node identification.\nEither remove it, or if you need access to this property, consider using the \`@alias\` directive to access it via another field.`, ["id"]);
}
}
function assertValidGlobalID({ typeDef, typeNameToGlobalId, interfaceToImplementingTypes, }) {
if (hasGlobalIDField(typeDef, typeNameToGlobalId, interfaceToImplementingTypes)) {
throw new document_validation_error_1.DocumentValidationError("Invalid directive usage: Only one field may be decorated with the `@relayId` directive.", ["@relayId"]);
}
else {
typeNameToGlobalId.set(typeDef.name.value, true);
}
}
//# sourceMappingURL=valid-relay-id.js.map