import { ResponseResolutionContext } from '../utils/executeHandlers' import { devUtils } from '../utils/internal/devUtils' import { isStringEqual } from '../utils/internal/isStringEqual' import { getStatusCodeColor } from '../utils/logging/getStatusCodeColor' import { getTimestamp } from '../utils/logging/getTimestamp' import { serializeRequest } from '../utils/logging/serializeRequest' import { serializeResponse } from '../utils/logging/serializeResponse' import { matchRequestUrl, Match, Path, PathParams, } from '../utils/matching/matchRequestUrl' import { toPublicUrl } from '../utils/request/toPublicUrl' import { getAllRequestCookies } from '../utils/request/getRequestCookies' import { cleanUrl, getSearchParams } from '../utils/url/cleanUrl' import { RequestHandler, RequestHandlerDefaultInfo, RequestHandlerOptions, ResponseResolver, } from './RequestHandler' type HttpHandlerMethod = string | RegExp export interface HttpHandlerInfo extends RequestHandlerDefaultInfo { method: HttpHandlerMethod path: Path } export enum HttpMethods { HEAD = 'HEAD', GET = 'GET', POST = 'POST', PUT = 'PUT', PATCH = 'PATCH', OPTIONS = 'OPTIONS', DELETE = 'DELETE', } export type RequestQuery = { [queryName: string]: string } export type HttpRequestParsedResult = { match: Match cookies: Record } export type HttpRequestResolverExtras = { params: Params cookies: Record } /** * Request handler for HTTP requests. * Provides request matching based on method and URL. */ export class HttpHandler extends RequestHandler< HttpHandlerInfo, HttpRequestParsedResult, HttpRequestResolverExtras > { constructor( method: HttpHandlerMethod, path: Path, resolver: ResponseResolver, any, any>, options?: RequestHandlerOptions, ) { super({ info: { header: `${method} ${path}`, path, method, }, resolver, options, }) this.checkRedundantQueryParameters() } private checkRedundantQueryParameters() { const { method, path } = this.info if (path instanceof RegExp) { return } const url = cleanUrl(path) // Bypass request handler URLs that have no redundant characters. if (url === path) { return } const searchParams = getSearchParams(path) const queryParams: string[] = [] searchParams.forEach((_, paramName) => { queryParams.push(paramName) }) devUtils.warn( `Found a redundant usage of query parameters in the request handler URL for "${method} ${path}". Please match against a path instead and access query parameters using "new URL(request.url).searchParams" instead. Learn more: https://mswjs.io/docs/recipes/query-parameters`, ) } async parse(args: { request: Request resolutionContext?: ResponseResolutionContext }) { const url = new URL(args.request.url) const match = matchRequestUrl( url, this.info.path, args.resolutionContext?.baseUrl, ) const cookies = getAllRequestCookies(args.request) return { match, cookies, } } predicate(args: { request: Request; parsedResult: HttpRequestParsedResult }) { const hasMatchingMethod = this.matchMethod(args.request.method) const hasMatchingUrl = args.parsedResult.match.matches return hasMatchingMethod && hasMatchingUrl } private matchMethod(actualMethod: string): boolean { return this.info.method instanceof RegExp ? this.info.method.test(actualMethod) : isStringEqual(this.info.method, actualMethod) } protected extendResolverArgs(args: { request: Request parsedResult: HttpRequestParsedResult }) { return { params: args.parsedResult.match?.params || {}, cookies: args.parsedResult.cookies, } } async log(args: { request: Request; response: Response }) { const publicUrl = toPublicUrl(args.request.url) const loggedRequest = await serializeRequest(args.request) const loggedResponse = await serializeResponse(args.response) const statusColor = getStatusCodeColor(loggedResponse.status) // eslint-disable-next-line no-console console.groupCollapsed( devUtils.formatMessage( `${getTimestamp()} ${args.request.method} ${publicUrl} (%c${ loggedResponse.status } ${loggedResponse.statusText}%c)`, ), `color:${statusColor}`, 'color:inherit', ) // eslint-disable-next-line no-console console.log('Request', loggedRequest) // eslint-disable-next-line no-console console.log('Handler:', this) // eslint-disable-next-line no-console console.log('Response', loggedResponse) // eslint-disable-next-line no-console console.groupEnd() } }