msw
Version:
1 lines • 14.3 kB
Source Map (JSON)
{"version":3,"sources":["../../../src/core/handlers/GraphQLHandler.ts"],"sourcesContent":["import type { DocumentNode, GraphQLError, OperationTypeNode } from 'graphql'\nimport {\n DefaultBodyType,\n RequestHandler,\n RequestHandlerDefaultInfo,\n RequestHandlerOptions,\n ResponseResolver,\n} from './RequestHandler'\nimport { getTimestamp } from '../utils/logging/getTimestamp'\nimport { getStatusCodeColor } from '../utils/logging/getStatusCodeColor'\nimport { serializeRequest } from '../utils/logging/serializeRequest'\nimport { serializeResponse } from '../utils/logging/serializeResponse'\nimport { Match, matchRequestUrl, Path } from '../utils/matching/matchRequestUrl'\nimport {\n ParsedGraphQLRequest,\n GraphQLMultipartRequestBody,\n parseGraphQLRequest,\n parseDocumentNode,\n} from '../utils/internal/parseGraphQLRequest'\nimport { toPublicUrl } from '../utils/request/toPublicUrl'\nimport { devUtils } from '../utils/internal/devUtils'\nimport { getAllRequestCookies } from '../utils/request/getRequestCookies'\n\nexport type GraphQLOperationType = OperationTypeNode | 'all'\nexport type GraphQLHandlerNameSelector = DocumentNode | RegExp | string\n\nexport type GraphQLQuery = Record<string, any> | null\nexport type GraphQLVariables = Record<string, any>\n\nexport interface GraphQLHandlerInfo extends RequestHandlerDefaultInfo {\n operationType: GraphQLOperationType\n operationName: GraphQLHandlerNameSelector | GraphQLCustomPredicate\n}\n\nexport type GraphQLRequestParsedResult = {\n match: Match\n cookies: Record<string, string>\n} & (\n | ParsedGraphQLRequest<GraphQLVariables>\n /**\n * An empty version of the ParsedGraphQLRequest\n * which simplifies the return type of the resolver\n * when the request is to a non-matching endpoint\n */\n | {\n operationType?: undefined\n operationName?: undefined\n query?: undefined\n variables?: undefined\n }\n)\n\nexport type GraphQLResolverExtras<Variables extends GraphQLVariables> = {\n query: string\n operationName: string\n variables: Variables\n cookies: Record<string, string>\n}\n\nexport type GraphQLRequestBody<VariablesType extends GraphQLVariables> =\n | GraphQLJsonRequestBody<VariablesType>\n | GraphQLMultipartRequestBody\n | Record<string, any>\n | undefined\n\nexport interface GraphQLJsonRequestBody<Variables extends GraphQLVariables> {\n query: string\n variables?: Variables\n}\n\nexport type GraphQLResponseBody<BodyType extends DefaultBodyType> =\n | {\n data?: BodyType | null\n errors?: readonly Partial<GraphQLError>[] | null\n extensions?: Record<string, any>\n }\n | null\n | undefined\n\nexport type GraphQLCustomPredicate = (args: {\n request: Request\n query: string\n operationType: GraphQLOperationType\n operationName: string\n variables: GraphQLVariables\n cookies: Record<string, string>\n}) => GraphQLCustomPredicateResult | Promise<GraphQLCustomPredicateResult>\n\nexport type GraphQLCustomPredicateResult = boolean | { matches: boolean }\n\nexport type GraphQLPredicate =\n | GraphQLHandlerNameSelector\n | GraphQLCustomPredicate\n\nexport function isDocumentNode(\n value: DocumentNode | any,\n): value is DocumentNode {\n if (value == null) {\n return false\n }\n\n return typeof value === 'object' && 'kind' in value && 'definitions' in value\n}\n\nexport class GraphQLHandler extends RequestHandler<\n GraphQLHandlerInfo,\n GraphQLRequestParsedResult,\n GraphQLResolverExtras<any>\n> {\n private endpoint: Path\n\n static parsedRequestCache = new WeakMap<\n Request,\n ParsedGraphQLRequest<GraphQLVariables>\n >()\n\n constructor(\n operationType: GraphQLOperationType,\n predicate: GraphQLPredicate,\n endpoint: Path,\n resolver: ResponseResolver<GraphQLResolverExtras<any>, any, any>,\n options?: RequestHandlerOptions,\n ) {\n let resolvedOperationName = predicate\n\n if (isDocumentNode(resolvedOperationName)) {\n const parsedNode = parseDocumentNode(resolvedOperationName)\n\n if (parsedNode.operationType !== operationType) {\n throw new Error(\n `Failed to create a GraphQL handler: provided a DocumentNode with a mismatched operation type (expected \"${operationType}\", but got \"${parsedNode.operationType}\").`,\n )\n }\n\n if (!parsedNode.operationName) {\n throw new Error(\n `Failed to create a GraphQL handler: provided a DocumentNode with no operation name.`,\n )\n }\n\n resolvedOperationName = parsedNode.operationName\n }\n\n const displayOperationName =\n typeof resolvedOperationName === 'function'\n ? '[custom predicate]'\n : resolvedOperationName\n\n const header =\n operationType === 'all'\n ? `${operationType} (origin: ${endpoint.toString()})`\n : `${operationType}${displayOperationName ? ` ${displayOperationName}` : ''} (origin: ${endpoint.toString()})`\n\n super({\n info: {\n header,\n operationType,\n operationName: resolvedOperationName,\n },\n resolver,\n options,\n })\n\n this.endpoint = endpoint\n }\n\n /**\n * Parses the request body, once per request, cached across all\n * GraphQL handlers. This is done to avoid multiple parsing of the\n * request body, which each requires a clone of the request.\n */\n async parseGraphQLRequestOrGetFromCache(\n request: Request,\n ): Promise<ParsedGraphQLRequest<GraphQLVariables>> {\n if (!GraphQLHandler.parsedRequestCache.has(request)) {\n GraphQLHandler.parsedRequestCache.set(\n request,\n await parseGraphQLRequest(request).catch((error) => {\n console.error(error)\n return undefined\n }),\n )\n }\n\n return GraphQLHandler.parsedRequestCache.get(request)\n }\n\n async parse(args: { request: Request }): Promise<GraphQLRequestParsedResult> {\n /**\n * If the request doesn't match a specified endpoint, there's no\n * need to parse it since there's no case where we would handle this\n */\n const match = matchRequestUrl(new URL(args.request.url), this.endpoint)\n const cookies = getAllRequestCookies(args.request)\n\n if (!match.matches) {\n return {\n match,\n cookies,\n }\n }\n\n const parsedResult = await this.parseGraphQLRequestOrGetFromCache(\n args.request,\n )\n\n if (typeof parsedResult === 'undefined') {\n return {\n match,\n cookies,\n }\n }\n\n return {\n match,\n cookies,\n query: parsedResult.query,\n operationType: parsedResult.operationType,\n operationName: parsedResult.operationName,\n variables: parsedResult.variables,\n }\n }\n\n async predicate(args: {\n request: Request\n parsedResult: GraphQLRequestParsedResult\n }): Promise<boolean> {\n if (args.parsedResult.operationType === undefined) {\n return false\n }\n\n if (!args.parsedResult.operationName && this.info.operationType !== 'all') {\n const publicUrl = toPublicUrl(args.request.url)\n\n devUtils.warn(`\\\nFailed to intercept a GraphQL request at \"${args.request.method} ${publicUrl}\": anonymous GraphQL operations are not supported.\n\nConsider naming this operation or using \"graphql.operation()\" request handler to intercept GraphQL requests regardless of their operation name/type. Read more: https://mswjs.io/docs/api/graphql/#graphqloperationresolver`)\n return false\n }\n\n const hasMatchingOperationType =\n this.info.operationType === 'all' ||\n args.parsedResult.operationType === this.info.operationType\n\n /**\n * Check if the operation name matches the outgoing GraphQL request.\n * @note Unlike the HTTP handler, the custom predicate functions are invoked\n * during predicate, not parsing, because GraphQL request parsing happens first,\n * and non-GraphQL requests are filtered out automatically.\n */\n const hasMatchingOperationName = await this.matchOperationName({\n request: args.request,\n parsedResult: args.parsedResult,\n })\n\n return (\n args.parsedResult.match.matches &&\n hasMatchingOperationType &&\n hasMatchingOperationName\n )\n }\n\n private async matchOperationName(args: {\n request: Request\n parsedResult: GraphQLRequestParsedResult\n }): Promise<boolean> {\n if (typeof this.info.operationName === 'function') {\n const customPredicateResult = await this.info.operationName({\n request: args.request,\n ...this.extendResolverArgs({\n request: args.request,\n parsedResult: args.parsedResult,\n }),\n })\n\n /**\n * @note Keep the { matches } signature in case we decide to support path parameters\n * in GraphQL handlers. If that happens, the custom predicate would have to be moved\n * to the parsing phase, the same as we have for the HttpHandler, and the user will\n * have a possibility to return parsed path parameters from the custom predicate.\n */\n return typeof customPredicateResult === 'boolean'\n ? customPredicateResult\n : customPredicateResult.matches\n }\n\n if (this.info.operationName instanceof RegExp) {\n return this.info.operationName.test(args.parsedResult.operationName || '')\n }\n\n return args.parsedResult.operationName === this.info.operationName\n }\n\n protected extendResolverArgs(args: {\n request: Request\n parsedResult: GraphQLRequestParsedResult\n }) {\n return {\n query: args.parsedResult.query || '',\n operationType: args.parsedResult.operationType!,\n operationName: args.parsedResult.operationName || '',\n variables: args.parsedResult.variables || {},\n cookies: args.parsedResult.cookies,\n }\n }\n\n async log(args: {\n request: Request\n response: Response\n parsedResult: GraphQLRequestParsedResult\n }) {\n const loggedRequest = await serializeRequest(args.request)\n const loggedResponse = await serializeResponse(args.response)\n const statusColor = getStatusCodeColor(loggedResponse.status)\n const requestInfo = args.parsedResult.operationName\n ? `${args.parsedResult.operationType} ${args.parsedResult.operationName}`\n : `anonymous ${args.parsedResult.operationType}`\n\n console.groupCollapsed(\n devUtils.formatMessage(\n `${getTimestamp()} ${requestInfo} (%c${loggedResponse.status} ${\n loggedResponse.statusText\n }%c)`,\n ),\n `color:${statusColor}`,\n 'color:inherit',\n )\n // eslint-disable-next-line no-console\n console.log('Request:', loggedRequest)\n // eslint-disable-next-line no-console\n console.log('Handler:', this)\n // eslint-disable-next-line no-console\n console.log('Response:', loggedResponse)\n console.groupEnd()\n }\n}\n"],"mappings":"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","names":[]}