UNPKG

@graphql-inspector/core

Version:

Tooling for GraphQL. Compare GraphQL Schemas, check documents, find breaking changes, find similar types.

165 lines (164 loc) 6.75 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.validate = validate; const dependency_graph_1 = require("dependency-graph"); const graphql_1 = require("graphql"); const document_js_1 = require("../ast/document.js"); const apollo_js_1 = require("../utils/apollo.js"); const graphql_js_1 = require("../utils/graphql.js"); const alias_count_js_1 = require("./alias-count.js"); const complexity_js_1 = require("./complexity.js"); const directive_count_js_1 = require("./directive-count.js"); const query_depth_js_1 = require("./query-depth.js"); const token_count_js_1 = require("./token-count.js"); function validate(schema, sources, options) { const config = { strictDeprecated: true, strictFragments: true, keepClientFields: false, apollo: false, ...options, }; const invalidDocuments = []; // read documents const documents = sources.map(document_js_1.readDocument); // keep all named fragments const fragments = []; const fragmentNames = []; const graph = new dependency_graph_1.DepGraph({ circular: true }); for (const doc of documents) { for (const fragment of doc.fragments) { fragmentNames.push(fragment.node.name.value); fragments.push(fragment); graph.addNode(fragment.node.name.value, fragment.node); } } for (const fragment of fragments) { const depends = extractFragments((0, graphql_1.print)(fragment.node)); if (depends) { for (const name of depends) { graph.addDependency(fragment.node.name.value, name); } } } for (const doc of documents // since we include fragments, validate only operations .filter(doc => doc.hasOperations)) { const docWithOperations = { kind: graphql_1.Kind.DOCUMENT, definitions: doc.operations.map(d => d.node), }; const extractedFragments = (extractFragments((0, graphql_1.print)(docWithOperations)) || []) // resolve all nested fragments .map(fragmentName => resolveFragment(graph.getNodeData(fragmentName), graph)) // flatten arrays .reduce((list, current) => list.concat(current), []) // remove duplicates .filter((def, i, all) => all.findIndex(item => item.name.value === def.name.value) === i); const merged = { kind: graphql_1.Kind.DOCUMENT, definitions: [...docWithOperations.definitions, ...extractedFragments], }; const transformedSchema = config.apollo ? (0, apollo_js_1.transformSchemaWithApollo)(schema) : schema; const transformedDoc = config.apollo ? (0, apollo_js_1.transformDocumentWithApollo)(merged, { keepClientFields: config.keepClientFields, }) : merged; const errors = (0, graphql_1.validate)(transformedSchema, transformedDoc) || []; if (config.maxDepth) { const depthError = (0, query_depth_js_1.validateQueryDepth)({ source: doc.source, doc: transformedDoc, maxDepth: config.maxDepth, fragmentGraph: graph, }); if (depthError) { errors.push(depthError); } } if (config.validateComplexityConfig) { const complexityScoreError = (0, complexity_js_1.validateComplexity)({ source: doc.source, doc: transformedDoc, maxComplexityScore: config.validateComplexityConfig.maxComplexityScore, config: { scalarCost: config.validateComplexityConfig.complexityScalarCost, objectCost: config.validateComplexityConfig.complexityObjectCost, depthCostFactor: config.validateComplexityConfig.complexityDepthCostFactor, }, fragmentGraph: graph, }); if (complexityScoreError) { errors.push(complexityScoreError); } } if (config.maxAliasCount) { const aliasError = (0, alias_count_js_1.validateAliasCount)({ source: doc.source, doc: transformedDoc, maxAliasCount: config.maxAliasCount, fragmentGraph: graph, }); if (aliasError) { errors.push(aliasError); } } if (config.maxDirectiveCount) { const directiveError = (0, directive_count_js_1.validateDirectiveCount)({ source: doc.source, doc: transformedDoc, maxDirectiveCount: config.maxDirectiveCount, fragmentGraph: graph, }); if (directiveError) { errors.push(directiveError); } } if (config.maxTokenCount) { const tokenCountError = (0, token_count_js_1.validateTokenCount)({ source: doc.source, document: transformedDoc, maxTokenCount: config.maxTokenCount, getReferencedFragmentSource: fragmentName => (0, graphql_1.print)(graph.getNodeData(fragmentName)), }); if (tokenCountError) { errors.push(tokenCountError); } } const deprecated = config.strictDeprecated ? (0, graphql_js_1.findDeprecatedUsages)(transformedSchema, transformedDoc) : []; const duplicatedFragments = config.strictFragments ? findDuplicatedFragments(fragmentNames) : []; if (sumLengths(errors, duplicatedFragments, deprecated) > 0) { invalidDocuments.push({ source: doc.source, errors: [...errors, ...duplicatedFragments], deprecated, }); } } return invalidDocuments; } function findDuplicatedFragments(fragmentNames) { return fragmentNames .filter((name, i, all) => all.indexOf(name) !== i) .map(name => new graphql_1.GraphQLError(`Name of '${name}' fragment is not unique`)); } // // PostInfo -> AuthorInfo // AuthorInfo -> None // function resolveFragment(fragment, graph) { return graph .dependenciesOf(fragment.name.value) .reduce((list, current) => [...list, ...resolveFragment(graph.getNodeData(current), graph)], [fragment]); } function extractFragments(document) { return (document.match(/[.]{3}[a-z0-9_]+\b/gi) || []).map(name => name.replace('...', '')); } function sumLengths(...arrays) { return arrays.reduce((sum, { length }) => sum + length, 0); }