@directus/api
Version:
Directus is a real-time API and App dashboard for managing SQL database content
75 lines (73 loc) • 2.67 kB
JavaScript
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 };