{"version":3,"file":"MaxIntrospectionDepthRule.js","sourceRoot":"","sources":["../../../src/validation/rules/MaxIntrospectionDepthRule.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,qCAAoC;AAG3D,OAAO,EAAE,IAAI,EAAE,iCAAgC;AAK/C,MAAM,eAAe,GAAG,CAAC,CAAC;AAoC1B,MAAM,UAAU,yBAAyB,CACvC,OAA6B;IAQ7B,SAAS,UAAU,CACjB,IAAa,EACb,mBAEI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EACvB,QAAgB,CAAC;QAEjB,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,eAAe,EAAE,CAAC;YACvC,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;YACrC,IAAI,gBAAgB,CAAC,YAAY,CAAC,KAAK,IAAI,EAAE,CAAC;gBAE5C,OAAO,KAAK,CAAC;YACf,CAAC;YACD,MAAM,QAAQ,GAAG,OAAO,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;YACnD,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAEd,OAAO,KAAK,CAAC;YACf,CAAC;YAOD,IAAI,CAAC;gBACH,gBAAgB,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC;gBACtC,OAAO,UAAU,CAAC,QAAQ,EAAE,gBAAgB,EAAE,KAAK,CAAC,CAAC;YACvD,CAAC;oBAAS,CAAC;gBACT,gBAAgB,CAAC,YAAY,CAAC,GAAG,SAAS,CAAC;YAC7C,CAAC;QACH,CAAC;QAED,IACE,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,KAAK;YAExB,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,KAAK,QAAQ;gBAC3B,IAAI,CAAC,IAAI,CAAC,KAAK,KAAK,YAAY;gBAChC,IAAI,CAAC,IAAI,CAAC,KAAK,KAAK,eAAe;gBACnC,IAAI,CAAC,IAAI,CAAC,KAAK,KAAK,aAAa,CAAC,EACpC,CAAC;YAED,KAAK,EAAE,CAAC;YACR,IAAI,KAAK,IAAI,eAAe,EAAE,CAAC;gBAC7B,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QAGD,IAAI,cAAc,IAAI,IAAI,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YAChD,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,CAAC;gBACjD,IAAI,UAAU,CAAC,KAAK,EAAE,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC;oBAC/C,OAAO,IAAI,CAAC;gBACd,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO;QACL,KAAK,CAAC,IAAI;YACR,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,KAAK,UAAU,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;gBACnE,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;oBACrB,OAAO,CAAC,WAAW,CACjB,IAAI,YAAY,CAAC,sCAAsC,EAAE;wBACvD,KAAK,EAAE,CAAC,IAAI,CAAC;qBACd,CAAC,CACH,CAAC;oBACF,OAAO,KAAK,CAAC;gBACf,CAAC;YACH,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["/** @category Validation Rules */\n\nimport { GraphQLError } from '../../error/GraphQLError.ts';\n\nimport type { ASTNode } from '../../language/ast.ts';\nimport { Kind } from '../../language/kinds.ts';\nimport type { ASTVisitor } from '../../language/visitor.ts';\n\nimport type { ASTValidationContext } from '../ValidationContext.ts';\n\nconst MAX_LISTS_DEPTH = 3;\n\n/**\n * Implements the max introspection depth validation rule.\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 { MaxIntrospectionDepthRule } from 'graphql/validation';\n *\n * const schema = buildSchema(`\n *   type Query {\n *     name: String\n *   }\n * `);\n *\n * const invalidDocument = parse(`\n *   { __schema { types { fields { type { fields { type { fields { name } } } } } } } }\n * `);\n * const invalidErrors = validate(schema, invalidDocument, [\n *   MaxIntrospectionDepthRule,\n * ]);\n *\n * invalidErrors.length; // => 1\n *\n * const validDocument = parse(`\n *   { __schema { queryType { name } } }\n * `);\n * const validErrors = validate(schema, validDocument, [\n *   MaxIntrospectionDepthRule,\n * ]);\n *\n * validErrors; // => []\n * ```\n */\nexport function MaxIntrospectionDepthRule(\n  context: ASTValidationContext,\n): ASTVisitor {\n  /**\n   * Counts the depth of list fields in \"__Type\" recursively and\n   * returns `true` if the limit has been reached.\n   *\n   * @internal\n   */\n  function checkDepth(\n    node: ASTNode,\n    visitedFragments: {\n      [fragmentName: string]: true | undefined;\n    } = Object.create(null),\n    depth: number = 0,\n  ): boolean {\n    if (node.kind === Kind.FRAGMENT_SPREAD) {\n      const fragmentName = node.name.value;\n      if (visitedFragments[fragmentName] === true) {\n        // Fragment cycles are handled by `NoFragmentCyclesRule`.\n        return false;\n      }\n      const fragment = context.getFragment(fragmentName);\n      if (!fragment) {\n        // Missing fragments checks are handled by `KnownFragmentNamesRule`.\n        return false;\n      }\n\n      // Rather than following an immutable programming pattern which has\n      // significant memory and garbage collection overhead, we've opted to\n      // take a mutable approach for efficiency's sake. Importantly visiting a\n      // fragment twice is fine, so long as you don't do one visit inside the\n      // other.\n      try {\n        visitedFragments[fragmentName] = true;\n        return checkDepth(fragment, visitedFragments, depth);\n      } finally {\n        visitedFragments[fragmentName] = undefined;\n      }\n    }\n\n    if (\n      node.kind === Kind.FIELD &&\n      // check all introspection lists\n      (node.name.value === 'fields' ||\n        node.name.value === 'interfaces' ||\n        node.name.value === 'possibleTypes' ||\n        node.name.value === 'inputFields')\n    ) {\n      // eslint-disable-next-line no-param-reassign\n      depth++;\n      if (depth >= MAX_LISTS_DEPTH) {\n        return true;\n      }\n    }\n\n    // handles fields and inline fragments\n    if ('selectionSet' in node && node.selectionSet) {\n      for (const child of node.selectionSet.selections) {\n        if (checkDepth(child, visitedFragments, depth)) {\n          return true;\n        }\n      }\n    }\n\n    return false;\n  }\n\n  return {\n    Field(node) {\n      if (node.name.value === '__schema' || node.name.value === '__type') {\n        if (checkDepth(node)) {\n          context.reportError(\n            new GraphQLError('Maximum introspection depth exceeded', {\n              nodes: [node],\n            }),\n          );\n          return false;\n        }\n      }\n    },\n  };\n}\n"]}