UNPKG

graphql-validity

Version:

Make business logic validation easy on the graphql side without adding any declarations or modifications to the existing graphql schema.

267 lines 10.3 kB
"use strict"; /*! * The MIT License (MIT) * * Copyright (c) 2017 Vlad Martynenko <vladimir.martynenko.work@gmail.com> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ Object.defineProperty(exports, "__esModule", { value: true }); /** * Contains configuration options for the main function */ const helpers_1 = require("./helpers"); const profiling_1 = require("./profiling"); const validation_1 = require("./validation"); const hapi_middleware_1 = require("./hapi-middleware"); const express_middleware_1 = require("./express-middleware"); const koa_middleware_1 = require("./koa-middleware"); // Indicates whether schema entity was already processed exports.Processed = Symbol(); let profilingResultHandler = { handler: profiling_1.defaultProfilingResultHandler }; // Set of middleware functions for express, koa and hapi servers exports.graphQLValidityHapiMiddleware = hapi_middleware_1.default(profilingResultHandler); exports.graphQLValidityExpressMiddleware = express_middleware_1.default(profilingResultHandler); exports.graphQLValidityKoaMiddleware = koa_middleware_1.default(profilingResultHandler); /** * Top level wrapper for the GraphQL schema entities * which replaces resolve function if any found * * @param entity - GraphQL object entity * @param {ValidityConfig} config - setup options for the wrapper function */ function wrapResolvers(entity, config) { if (!config) { config = { wrapErrors: false, enableProfiling: false, unhandledErrorWrapper: helpers_1.onUnhandledError }; } else { config.unhandledErrorWrapper = config.unhandledErrorWrapper || helpers_1.onUnhandledError; if (config.enableProfiling) { profilingResultHandler.handler = config.profilingResultHandler ? config.profilingResultHandler : profilingResultHandler.handler; } } if (entity.constructor.name === 'GraphQLSchema') { wrapSchema(entity, config); } else if (entity.constructor.name === 'GraphQLObjectType') { wrapType(entity, config); } else { wrapField(entity, config); } } exports.wrapResolvers = wrapResolvers; /** * Internal function which performs resolvers wrapping with common async function * * @param field - GraphQL entity field * @param {ValidityConfig} config - setup options for the wrapper function */ function wrapField(field, config) { const resolve = field.resolve; if (field[exports.Processed] || !resolve) { return; } field[exports.Processed] = true; field.resolve = validateFieldResolution(field, config, resolve); } /** * Creates new field resolver for a given graphql field node * * @param field - field node which gets resolver replaced * @param {ValidityConfig} config - config options for validation * @param {Function} resolver - original resolver function * * @returns {(...args: any[]) => (Promise<any> | any)} - new resolver function */ function validateFieldResolution(field, config, resolver) { return function (...args) { const requestContext = { fieldName: field.name }; if (config.enableProfiling) { // profiling start time requestContext.pst = Date.now(); } for (let i = 0, s = args.length; i < s; i++) { let arg = args[i]; if (arg && arg.rootValue && arg.rootValue.__graphQLValidity) { arg.rootValue.__graphQLValidity.config = config; requestContext.validity = arg.rootValue.__graphQLValidity; } if (arg && arg.parentType) { requestContext.astPath = arg.path; requestContext.parentTypeName = arg.parentType; } } if (requestContext.validity) { let validationResults = validation_1.getValidationResults(requestContext.validity); let validators = validation_1.getValidators(field, String(requestContext.parentTypeName), requestContext.validity); const result = processValidators(validators, validationResults, args); if (result && result.then) { return new Promise((resolve, reject) => { result.then(() => { if (config.enableProfiling) { // validation end time requestContext.vet = Date.now(); } const result = resolver.apply(this, args); if (config.enableProfiling) { if (result && result.then) { result.then(() => { processProfiling(requestContext); }); } else { processProfiling(requestContext); } } resolve(result); }).catch(e => { reject(e); }); }); } else { if (config.enableProfiling) { // validation end time requestContext.vet = Date.now(); } } } const result = resolver.apply(this, args); if (config.enableProfiling) { if (result && result.then) { result.then(() => { processProfiling(requestContext); }); } else { processProfiling(requestContext); } } return result; }; } /** * Creates profiling data using process context for a field * * @param {FieldValidationObject} requestContext - data gathered during field validation and execution */ function processProfiling(requestContext) { // execution end time requestContext.eet = Date.now(); try { if (requestContext.validity) { const validation = requestContext.vet - requestContext.pst; const execution = requestContext.eet - requestContext.vet; profiling_1.storeProfilingInfo(requestContext.validity, requestContext.astPath, { name: requestContext.fieldName, validation, execution, fieldsExecution: 0, totalExecution: validation + execution }); } } catch (err) { console.error('Profiling failed!', err); } } /** * Validator function executor * * @param {Function[]} validators - array of validation functions * @param {any[]} validationResults - static array of validation results * @param {any[]} args - original resolver arguments * * @returns {Promise<void>} - return promise if at least one validator was returning promise */ function processValidators(validators, validationResults, args) { let promises = []; for (let i = 0, s = validators.length; i < s; i++) { let validator = validators[i]; let validationResult = validator.apply(this, args) || []; if (validationResult.then) { promises.push(validationResult); } else { validationResult = Array.isArray(validationResult) ? validationResult : [validationResult]; Array.prototype.push.apply(validationResults, validationResult); } } if (promises.length) { return handleValidationPromises(promises, validationResults); } } /** * Synchronises validator promises execution * * @param {any[]} promises - array of validator promises * @param {any[]} validationResults - static array of validation results * * @returns {Promise<void>} - general promise for all validator promises */ async function handleValidationPromises(promises, validationResults) { for (let promise of promises) { let validationResult = (await promise) || []; validationResult = Array.isArray(validationResult) ? validationResult : [validationResult]; Array.prototype.push.apply(validationResults, validationResult); } } /** * Wraps each field of the GraphQLObjectType entity * * @param {GraphQLObjectType} type - GraphQLObject schema entity * @param {ValidityConfig} config - setup options for the wrapper function */ function wrapType(type, config) { if (type[exports.Processed] || !type.getFields) { return; } const fields = type.getFields(); for (const fieldName in fields) { if (!Object.hasOwnProperty.call(fields, fieldName)) { continue; } wrapField(fields[fieldName], config); } } /** * Wraps each GraphQLObjectType fields resolver for entire GraphQL Schema * * @param {GraphQLSchema} schema - schema object that must be wrapped * @param {ValidityConfig} config - setup options for the wrapper function */ function wrapSchema(schema, config) { const types = schema.getTypeMap(); for (const typeName in types) { if (!Object.hasOwnProperty.call(types, typeName)) { continue; } wrapType(types[typeName], config); } } //# sourceMappingURL=schema-wrapper.js.map