UNPKG

@directus/api

Version:

Directus is a real-time API and App dashboard for managing SQL database content

75 lines (73 loc) 2.67 kB
import { useLogger } from "../../../logger/index.js"; //#region src/services/graphql/utils/sanitize-gql-schema.ts /** * Regex was taken from the spec * https://spec.graphql.org/June2018/#sec-Names */ const GRAPHQL_NAME_REGEX = /^[_A-Za-z][_0-9A-Za-z]*$/; /** * Manually curated list of GraphQL reserved names to cover the most likely naming footguns. * This list is not exhaustive and does not cover generated type names. */ const GRAPHQL_RESERVED_NAMES = [ "Subscription", "Query", "Mutation", "Int", "Float", "String", "Boolean", "DateTime", "ID", "uid", "Point", "PointList", "Polygon", "MultiPolygon", "JSON", "Hash", "Date", "Void" ]; /** * Filters out invalid collections to prevent graphql from errorring on schema generation * * @param schema * @returns sanitized schema */ function sanitizeGraphqlSchema(schema) { const logger = useLogger(); const collectionEntries = Object.entries(schema.collections).filter(([collectionName, _data]) => { if (collectionName.startsWith("__") || !collectionName.match(GRAPHQL_NAME_REGEX)) { logger.warn(`GraphQL skipping collection "${collectionName}" because it is not a valid name matching /^[_A-Za-z][_0-9A-Za-z]*$/ or starts with __`); return false; } if (GRAPHQL_RESERVED_NAMES.includes(collectionName)) { logger.warn(`GraphQL skipping collection "${collectionName}" because it is a reserved keyword`); return false; } return true; }); const collections = Object.fromEntries(collectionEntries); const collectionExists = (collection) => Boolean(collections[collection]); const skipRelation = (relation) => { const relationName = relation.schema?.constraint_name ?? `${relation.collection}.${relation.field}`; logger.warn(`GraphQL skipping relation "${relationName}" because it links to a non-existent or invalid collection.`); return false; }; return { collections, relations: schema.relations.filter((relation) => { if (relation.collection && !collectionExists(relation.collection)) return skipRelation(relation); if (relation.related_collection && !collectionExists(relation.related_collection)) return skipRelation(relation); if (relation.meta) { if (relation.meta.many_collection && !collectionExists(relation.meta.many_collection)) return skipRelation(relation); if (relation.meta.one_collection && !collectionExists(relation.meta.one_collection)) return skipRelation(relation); if (relation.meta.one_allowed_collections && relation.meta.one_allowed_collections.some((allowed_collection) => !collectionExists(allowed_collection))) return skipRelation(relation); } return true; }) }; } //#endregion export { sanitizeGraphqlSchema };