{"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"]}