UNPKG

graphql

Version:

A Query Language and Runtime which can target any service.

1 lines 5.61 kB
{"version":3,"file":"NoFragmentCyclesRule.js","sourceRoot":"","sources":["../../../src/validation/rules/NoFragmentCyclesRule.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,qCAAoC;AA6C3D,MAAM,UAAU,oBAAoB,CAClC,OAA6B;IAI7B,MAAM,YAAY,GAAG,IAAI,GAAG,EAAU,CAAC;IAGvC,MAAM,UAAU,GAA8B,EAAE,CAAC;IAGjD,MAAM,qBAAqB,GAA+B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAE9E,OAAO;QACL,mBAAmB,EAAE,GAAG,EAAE,CAAC,KAAK;QAChC,kBAAkB,CAAC,IAAI;YACrB,oBAAoB,CAAC,IAAI,CAAC,CAAC;YAC3B,OAAO,KAAK,CAAC;QACf,CAAC;KACF,CAAC;IAKF,SAAS,oBAAoB,CAAC,QAAgC;QAC5D,IAAI,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1C,OAAO;QACT,CAAC;QAED,MAAM,YAAY,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC;QACzC,YAAY,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAE/B,MAAM,WAAW,GAAG,OAAO,CAAC,kBAAkB,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QACtE,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7B,OAAO;QACT,CAAC;QAED,qBAAqB,CAAC,YAAY,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC;QAExD,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;YACrC,MAAM,UAAU,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;YACzC,MAAM,UAAU,GAAG,qBAAqB,CAAC,UAAU,CAAC,CAAC;YAErD,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC5B,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;gBAC7B,MAAM,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;gBACvD,IAAI,cAAc,EAAE,CAAC;oBACnB,oBAAoB,CAAC,cAAc,CAAC,CAAC;gBACvC,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;gBAC/C,MAAM,OAAO,GAAG,SAAS;qBACtB,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;qBACZ,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC;qBACpC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAEd,OAAO,CAAC,WAAW,CACjB,IAAI,YAAY,CACd,2BAA2B,UAAU,iBAAiB;oBACpD,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,OAAO,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAC7C,EAAE,KAAK,EAAE,SAAS,EAAE,CACrB,CACF,CAAC;YACJ,CAAC;YACD,UAAU,CAAC,GAAG,EAAE,CAAC;QACnB,CAAC;QAED,qBAAqB,CAAC,YAAY,CAAC,GAAG,SAAS,CAAC;IAClD,CAAC;AACH,CAAC","sourcesContent":["/** @category Validation Rules */\n\nimport type { ObjMap } from '../../jsutils/ObjMap.ts';\n\nimport { GraphQLError } from '../../error/GraphQLError.ts';\n\nimport type {\n FragmentDefinitionNode,\n FragmentSpreadNode,\n} from '../../language/ast.ts';\nimport type { ASTVisitor } from '../../language/visitor.ts';\n\nimport type { ASTValidationContext } from '../ValidationContext.ts';\n\n/**\n * No fragment cycles\n *\n * The graph of fragment spreads must not form any cycles including spreading itself.\n * Otherwise an operation could infinitely spread or infinitely execute on cycles in the underlying data.\n *\n * See https://spec.graphql.org/draft/#sec-Fragment-spreads-must-not-form-cycles\n * @param context - The validation context used while checking the document.\n * @returns A visitor that reports validation errors for this rule.\n * @example\n * ```ts\n * import { buildSchema, parse, validate } from 'graphql';\n * import { NoFragmentCyclesRule } from 'graphql/validation';\n *\n * const schema = buildSchema(`\n * type Query {\n * name: String\n * }\n * `);\n *\n * const invalidDocument = parse(`\n * fragment A on Query { ...B } fragment B on Query { ...A } query { ...A }\n * `);\n * const invalidErrors = validate(schema, invalidDocument, [NoFragmentCyclesRule]);\n *\n * invalidErrors.length; // => 1\n *\n * const validDocument = parse(`\n * fragment A on Query { name } query { ...A }\n * `);\n * const validErrors = validate(schema, validDocument, [NoFragmentCyclesRule]);\n *\n * validErrors; // => []\n * ```\n */\nexport function NoFragmentCyclesRule(\n context: ASTValidationContext,\n): ASTVisitor {\n // Tracks already visited fragments to maintain O(N) and to ensure that cycles\n // are not redundantly reported.\n const visitedFrags = new Set<string>();\n\n // Array of AST nodes used to produce meaningful errors\n const spreadPath: Array<FragmentSpreadNode> = [];\n\n // Position in the spread path\n const spreadPathIndexByName: ObjMap<number | undefined> = Object.create(null);\n\n return {\n OperationDefinition: () => false,\n FragmentDefinition(node) {\n detectCycleRecursive(node);\n return false;\n },\n };\n\n // This does a straight-forward DFS to find cycles.\n // It does not terminate when a cycle was found but continues to explore\n // the graph to find all possible cycles.\n function detectCycleRecursive(fragment: FragmentDefinitionNode): void {\n if (visitedFrags.has(fragment.name.value)) {\n return;\n }\n\n const fragmentName = fragment.name.value;\n visitedFrags.add(fragmentName);\n\n const spreadNodes = context.getFragmentSpreads(fragment.selectionSet);\n if (spreadNodes.length === 0) {\n return;\n }\n\n spreadPathIndexByName[fragmentName] = spreadPath.length;\n\n for (const spreadNode of spreadNodes) {\n const spreadName = spreadNode.name.value;\n const cycleIndex = spreadPathIndexByName[spreadName];\n\n spreadPath.push(spreadNode);\n if (cycleIndex === undefined) {\n const spreadFragment = context.getFragment(spreadName);\n if (spreadFragment) {\n detectCycleRecursive(spreadFragment);\n }\n } else {\n const cyclePath = spreadPath.slice(cycleIndex);\n const viaPath = cyclePath\n .slice(0, -1)\n .map((s) => '\"' + s.name.value + '\"')\n .join(', ');\n\n context.reportError(\n new GraphQLError(\n `Cannot spread fragment \"${spreadName}\" within itself` +\n (viaPath !== '' ? ` via ${viaPath}.` : '.'),\n { nodes: cyclePath },\n ),\n );\n }\n spreadPath.pop();\n }\n\n spreadPathIndexByName[fragmentName] = undefined;\n }\n}\n"]}