UNPKG

graphql

Version:

A Query Language and Runtime which can target any service.

1 lines 13 kB
{"version":3,"file":"DeferStreamDirectiveOnValidOperationsRule.js","sourceRoot":"","sources":["../../../src/validation/rules/DeferStreamDirectiveOnValidOperationsRule.ts"],"names":[],"mappings":"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","sourcesContent":["/** @category Validation Rules */\n\nimport { GraphQLError } from '../../error/GraphQLError.ts';\n\nimport type {\n DirectiveNode,\n FragmentDefinitionNode,\n FragmentSpreadNode,\n SelectionSetNode,\n} from '../../language/ast.ts';\nimport { OperationTypeNode } from '../../language/ast.ts';\nimport { Kind } from '../../language/kinds.ts';\nimport type { ASTVisitor } from '../../language/visitor.ts';\n\nimport {\n GraphQLDeferDirective,\n GraphQLIncludeDirective,\n GraphQLSkipDirective,\n GraphQLStreamDirective,\n} from '../../type/directives.ts';\n\nimport type { ValidationContext } from '../ValidationContext.ts';\n\nfunction ifArgumentCanBeFalse(node: DirectiveNode): boolean {\n // @defer(if: false) / @stream(if: false)\n // @defer(if: $shouldDefer) / @stream(if: $shouldStream)\n const ifArgument = node.arguments?.find((arg) => arg.name.value === 'if');\n if (!ifArgument) {\n return false;\n }\n if (ifArgument.value.kind === Kind.BOOLEAN) {\n if (ifArgument.value.value) {\n return false;\n }\n } else if (ifArgument.value.kind !== Kind.VARIABLE) {\n return false;\n }\n return true;\n}\n\nfunction canBeSkippedViaSkipDirective(node: DirectiveNode): boolean {\n // @skip(if: true)\n // @skip(if: $shouldSkip)\n const ifArgument = node.arguments?.find((arg) => arg.name.value === 'if');\n if (!ifArgument) {\n // Missing `if` is reported by ProvidedRequiredArgumentsRule. For this rule,\n // treat malformed @skip as potentially skipped to avoid duplicate errors.\n return true;\n }\n\n if (ifArgument.value.kind === Kind.BOOLEAN) {\n // If argument is a Static boolean\n if (ifArgument.value.value) {\n // always skipped\n return true;\n }\n // Never skipped\n return false;\n }\n\n // Can be skipped via variable\n return true;\n}\n\nfunction canBeSkippedViaIncludeDirective(node: DirectiveNode): boolean {\n // @include(if: false)\n // @include(if: $shouldInclude)\n const ifArgument = node.arguments?.find((arg) => arg.name.value === 'if');\n if (!ifArgument) {\n // Missing `if` is reported by ProvidedRequiredArgumentsRule. For this rule,\n // treat malformed @include as not skippable.\n return false;\n }\n if (ifArgument?.value.kind === Kind.BOOLEAN) {\n // If argument is a Static boolean\n if (ifArgument.value.value) {\n // Never skipped\n return false;\n }\n // Always skipped\n return true;\n }\n\n // Can be skipped via variable\n return true;\n}\n\n/**\n * Defer And Stream Directives Are Used On Valid Operations\n *\n * A GraphQL document is only valid if defer and stream directives are not used on root mutation or subscription types.\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 { parse } from 'graphql/language';\n * import { buildSchema } from 'graphql/utilities';\n * import {\n * validate,\n * DeferStreamDirectiveOnValidOperationsRule,\n * } from 'graphql/validation';\n *\n * const schema = buildSchema(`\n * type Query {\n * message: Message\n * }\n *\n * type Subscription {\n * message: Message\n * }\n *\n * type Message {\n * body: String\n * }\n * `);\n * const invalidDocument = parse(`\n * subscription {\n * message {\n * ...MessageBody @defer\n * }\n * }\n *\n * fragment MessageBody on Message {\n * body\n * }\n * `);\n * const validDocument = parse(`\n * subscription {\n * message {\n * ...MessageBody @defer(if: false)\n * }\n * }\n *\n * fragment MessageBody on Message {\n * body\n * }\n * `);\n *\n * validate(schema, invalidDocument, [DeferStreamDirectiveOnValidOperationsRule])\n * .length; // => 1\n * validate(schema, validDocument, [DeferStreamDirectiveOnValidOperationsRule]); // => []\n * ```\n */\nexport function DeferStreamDirectiveOnValidOperationsRule(\n context: ValidationContext,\n): ASTVisitor {\n return {\n OperationDefinition(operation) {\n if (operation.operation !== OperationTypeNode.SUBSCRIPTION) {\n return;\n }\n const document = context.getDocument();\n const fragments = new Map<string, FragmentDefinitionNode>();\n\n for (const definition of document.definitions) {\n if (definition.kind === Kind.FRAGMENT_DEFINITION) {\n fragments.set(definition.name.value, definition);\n }\n }\n const visitedFragments = new Set<string>();\n forbidUnconditionalDeferStream({\n context,\n fragments,\n selectionSet: operation.selectionSet,\n parentNodes: [],\n visitedFragments,\n });\n },\n };\n}\n\nfunction forbidUnconditionalDeferStream({\n context,\n fragments,\n selectionSet,\n parentNodes,\n visitedFragments,\n}: {\n context: ValidationContext;\n fragments: Map<string, FragmentDefinitionNode>;\n selectionSet: SelectionSetNode;\n parentNodes: Array<FragmentSpreadNode>;\n visitedFragments: Set<string>;\n}) {\n for (const selection of selectionSet.selections) {\n const skip = selection.directives?.find(\n (d) => d.name.value === GraphQLSkipDirective.name,\n );\n if (skip && canBeSkippedViaSkipDirective(skip)) {\n continue;\n }\n const include = selection.directives?.find(\n (d) => d.name.value === GraphQLIncludeDirective.name,\n );\n if (include && canBeSkippedViaIncludeDirective(include)) {\n continue;\n }\n for (const directive of selection.directives ?? []) {\n if (directive.name.value === GraphQLDeferDirective.name) {\n if (!ifArgumentCanBeFalse(directive)) {\n context.reportError(\n new GraphQLError(\n 'Defer directive not supported on subscription operations. Disable `@defer` by setting the `if` argument to `false`.',\n { nodes: [directive, ...parentNodes] },\n ),\n );\n }\n } else if (directive.name.value === GraphQLStreamDirective.name) {\n if (!ifArgumentCanBeFalse(directive)) {\n context.reportError(\n new GraphQLError(\n 'Stream directive not supported on subscription operations. Disable `@stream` by setting the `if` argument to `false`.',\n { nodes: [directive, ...parentNodes] },\n ),\n );\n }\n }\n }\n if (selection.kind === 'FragmentSpread') {\n const fragmentName = selection.name.value;\n if (visitedFragments.has(fragmentName)) {\n continue;\n }\n visitedFragments.add(fragmentName);\n const fragment = fragments.get(fragmentName);\n if (fragment) {\n forbidUnconditionalDeferStream({\n context,\n fragments,\n parentNodes: [selection, ...parentNodes],\n selectionSet: fragment?.selectionSet,\n visitedFragments,\n });\n }\n } else if (selection.selectionSet) {\n forbidUnconditionalDeferStream({\n context,\n fragments,\n selectionSet: selection.selectionSet,\n parentNodes,\n visitedFragments,\n });\n }\n }\n}\n\n// - For each {selection} in {selectionSet}:\n// - If {selection} provides the `@skip` directive, and the \"if\" argument on that\n// directive is not the boolean value {true}:\n// - Continue to the next {selection} in {selectionSet}.\n// - If {selection} provides the `@include` directive, and the \"if\" argument on\n// that directive is not the boolean value {false}:\n// - Continue to the next {selection} in {selectionSet}.\n// - For each {directive} on {selection}:\n// - If {directive} is `@defer` or `@stream`:\n// - Let {if} be the argument named \"if\" on {directive}.\n// - {if} must be defined.\n// - Let {argumentValue} be the value passed to {if}.\n// - {argumentValue} must be a variable, or the boolean value \"false\".\n// - If {selection} is a {FragmentSpread}:\n// - Let {fragmentSpreadName} be the name of {selection}.\n// - If {fragmentSpreadName} is in {visitedFragments}, continue with the next\n// {selection} in {selectionSet}.\n// - Add {fragmentSpreadName} to {visitedFragments}.\n// - Let {fragment} be the Fragment in the current Document whose name is\n// {fragmentSpreadName}.\n// - Let {fragmentSelectionSet} be the selection set of {selection}.\n// - {ForbidUnconditionalDeferStream(fragmentSelectionSet)}\n// - If {selection} is an {InlineFragment}:\n// - Let {fragmentSelectionSet} be the selection set of {selection}.\n// - {ForbidUnconditionalDeferStream(fragmentSelectionSet)}\n// - If {selection} is a {Field}:\n// - Let {fieldSelectionSet} be the selection set of {selection}.\n// - {ForbidUnconditionalDeferStream(fieldSelectionSet)}\n"]}