UNPKG

@envelop/extended-validation

Version:

Extended validation plugin adds support for writing GraphQL validation rules, that has access to all `execute` parameters, including variables.

102 lines (100 loc) 3.84 kB
let graphql = require("graphql"); let _envelop_core = require("@envelop/core"); //#region src/plugin.ts const symbolExtendedValidationRules = Symbol("extendedValidationContext"); const useExtendedValidation = (options) => { let schemaTypeInfo; function getTypeInfo() { return schemaTypeInfo; } return { onSchemaChange({ schema }) { schemaTypeInfo = new graphql.TypeInfo(schema); }, onContextBuilding({ context, extendContext }) { let validationRulesContext = context[symbolExtendedValidationRules]; if (validationRulesContext === void 0) { validationRulesContext = { rules: [], didRun: false }; extendContext({ ...context, [symbolExtendedValidationRules]: validationRulesContext }); } validationRulesContext.rules.push(...options.rules); }, onSubscribe: buildHandler("subscribe", getTypeInfo, options.onDocument, options.onValidationFailed, options.rejectOnErrors !== false), onExecute: buildHandler("execute", getTypeInfo, options.onDocument, options.onValidationFailed, options.rejectOnErrors !== false) }; }; function buildHandler(name, getTypeInfo, onDocument, onValidationFailed, rejectOnErrors = true) { return function handler({ args, setResultAndStopExecution }) { const validationRulesContext = args.contextValue[symbolExtendedValidationRules]; if (validationRulesContext === void 0) throw new Error(`Plugin has not been properly set up. The 'contextFactory' function is not invoked and the result has not been passed to '${name}'.`); if (validationRulesContext.didRun === false) { validationRulesContext.didRun = true; if (validationRulesContext.rules.length !== 0) { const errors = []; const typeInfo = getTypeInfo() ?? new graphql.TypeInfo(args.schema); const validationContext = new graphql.ValidationContext(args.schema, args.document, typeInfo, (e) => { errors.push(e); }); const visitor = (0, graphql.visitInParallel)(validationRulesContext.rules.map((rule) => rule(validationContext, args))); args.document = (0, graphql.visit)(args.document, (0, graphql.visitWithTypeInfo)(typeInfo, visitor)); if (onDocument) args.document = onDocument(args.document); if (errors.length > 0) if (rejectOnErrors) { let result = { data: null, errors }; if (onValidationFailed) onValidationFailed({ args, result, setResult: (newResult) => result = newResult }); setResultAndStopExecution(result); } else { function onResult({ result, setResult }) { if ((0, _envelop_core.isAsyncIterable)(result)) { setResult({ data: null, errors }); return; } const newResult = { ...result, errors: [...result.errors || [], ...errors] }; function visitPath(path, data = {}) { let currentData = data ||= typeof path[0] === "number" ? [] : {}; for (const pathItemIndex in path.slice(0, -1)) { const pathItem = path[pathItemIndex]; currentData = currentData[pathItem] ||= typeof path[Number(pathItemIndex) + 1] === "number" ? [] : {}; if (Array.isArray(currentData)) { let pathItemIndexInArray = Number(pathItemIndex) + 1; if (path[pathItemIndexInArray] === "@") pathItemIndexInArray = Number(pathItemIndex) + 2; currentData = currentData.map((c) => visitPath(path.slice(pathItemIndexInArray), c)); } } currentData[path[path.length - 1]] = null; return data; } errors.forEach((e) => { if (e.path?.length) newResult.data = visitPath(e.path, newResult.data); }); setResult(newResult); } return { onSubscribeResult: onResult, onExecuteDone: onResult }; } } } }; } //#endregion exports.useExtendedValidation = useExtendedValidation;