Skip to content

Commit

Permalink
feat(RequestHandler): make class method arguments an object
Browse files Browse the repository at this point in the history
  • Loading branch information
kettanaito committed Oct 6, 2023
1 parent e1e4bb3 commit ff48c56
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 101 deletions.
59 changes: 31 additions & 28 deletions src/core/handlers/GraphQLHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,37 +114,40 @@ export class GraphQLHandler extends RequestHandler<
this.endpoint = endpoint
}

async parse(request: Request) {
return parseGraphQLRequest(request).catch((error) => {
async parse(args: { request: Request }) {
return parseGraphQLRequest(args.request).catch((error) => {
console.error(error)
return undefined
})
}

predicate(request: Request, parsedResult: ParsedGraphQLRequest) {
if (!parsedResult) {
predicate(args: { request: Request; parsedResult: ParsedGraphQLRequest }) {
if (!args.parsedResult) {
return false
}

if (!parsedResult.operationName && this.info.operationType !== 'all') {
const publicUrl = getPublicUrlFromRequest(request)
if (!args.parsedResult.operationName && this.info.operationType !== 'all') {
const publicUrl = getPublicUrlFromRequest(args.request)

devUtils.warn(`\
Failed to intercept a GraphQL request at "${request.method} ${publicUrl}": anonymous GraphQL operations are not supported.
Failed to intercept a GraphQL request at "${args.request.method} ${publicUrl}": anonymous GraphQL operations are not supported.
Consider 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/operation`)
return false
}

const hasMatchingUrl = matchRequestUrl(new URL(request.url), this.endpoint)
const hasMatchingUrl = matchRequestUrl(
new URL(args.request.url),
this.endpoint,
)
const hasMatchingOperationType =
this.info.operationType === 'all' ||
parsedResult.operationType === this.info.operationType
args.parsedResult.operationType === this.info.operationType

const hasMatchingOperationName =
this.info.operationName instanceof RegExp
? this.info.operationName.test(parsedResult.operationName || '')
: parsedResult.operationName === this.info.operationName
? this.info.operationName.test(args.parsedResult.operationName || '')
: args.parsedResult.operationName === this.info.operationName

return (
hasMatchingUrl.matches &&
Expand All @@ -153,28 +156,28 @@ Consider naming this operation or using "graphql.operation()" request handler to
)
}

protected extendInfo(
_request: Request,
parsedResult: ParsedGraphQLRequest<GraphQLVariables>,
) {
protected extendInfo(args: {
request: Request
parsedResult: ParsedGraphQLRequest<GraphQLVariables>
}) {
return {
query: parsedResult?.query || '',
operationName: parsedResult?.operationName || '',
variables: parsedResult?.variables || {},
query: args.parsedResult?.query || '',
operationName: args.parsedResult?.operationName || '',
variables: args.parsedResult?.variables || {},
}
}

async log(
request: Request,
response: Response,
parsedRequest: ParsedGraphQLRequest,
) {
const loggedRequest = await serializeRequest(request)
const loggedResponse = await serializeResponse(response)
async log(args: {
request: Request
response: Response
parsedResult: ParsedGraphQLRequest
}) {
const loggedRequest = await serializeRequest(args.request)
const loggedResponse = await serializeResponse(args.response)
const statusColor = getStatusCodeColor(loggedResponse.status)
const requestInfo = parsedRequest?.operationName
? `${parsedRequest?.operationType} ${parsedRequest?.operationName}`
: `anonymous ${parsedRequest?.operationType}`
const requestInfo = args.parsedResult?.operationName
? `${args.parsedResult?.operationType} ${args.parsedResult?.operationName}`
: `anonymous ${args.parsedResult?.operationType}`

console.groupCollapsed(
devUtils.formatMessage('%s %s (%c%s%c)'),
Expand Down
39 changes: 21 additions & 18 deletions src/core/handlers/HttpHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,24 +106,27 @@ export class HttpHandler extends RequestHandler<
)
}

async parse(request: Request, resolutionContext?: ResponseResolutionContext) {
const url = new URL(request.url)
async parse(args: {
request: Request
resolutionContext?: ResponseResolutionContext
}) {
const url = new URL(args.request.url)
const match = matchRequestUrl(
url,
this.info.path,
resolutionContext?.baseUrl,
args.resolutionContext?.baseUrl,
)
const cookies = getAllRequestCookies(request)
const cookies = getAllRequestCookies(args.request)

return {
match,
cookies,
}
}

predicate(request: Request, parsedResult: HttpRequestParsedResult) {
const hasMatchingMethod = this.matchMethod(request.method)
const hasMatchingUrl = parsedResult.match.matches
predicate(args: { request: Request; parsedResult: HttpRequestParsedResult }) {
const hasMatchingMethod = this.matchMethod(args.request.method)
const hasMatchingUrl = args.parsedResult.match.matches
return hasMatchingMethod && hasMatchingUrl
}

Expand All @@ -133,26 +136,26 @@ export class HttpHandler extends RequestHandler<
: isStringEqual(this.info.method, actualMethod)
}

protected extendInfo(
_request: Request,
parsedResult: HttpRequestParsedResult,
) {
protected extendInfo(args: {
request: Request
parsedResult: HttpRequestParsedResult
}) {
return {
params: parsedResult.match?.params || {},
cookies: parsedResult.cookies,
params: args.parsedResult.match?.params || {},
cookies: args.parsedResult.cookies,
}
}

async log(request: Request, response: Response) {
const publicUrl = getPublicUrlFromRequest(request)
const loggedRequest = await serializeRequest(request)
const loggedResponse = await serializeResponse(response)
async log(args: { request: Request; response: Response }) {
const publicUrl = getPublicUrlFromRequest(args.request)
const loggedRequest = await serializeRequest(args.request)
const loggedResponse = await serializeResponse(args.response)
const statusColor = getStatusCodeColor(loggedResponse.status)

console.groupCollapsed(
devUtils.formatMessage('%s %s %s (%c%s%c)'),
getTimestamp(),
request.method,
args.request.method,
publicUrl,
`color:${statusColor}`,
`${loggedResponse.status} ${loggedResponse.statusText}`,
Expand Down
116 changes: 62 additions & 54 deletions src/core/handlers/RequestHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,81 +122,86 @@ export abstract class RequestHandler<
/**
* Determine if the intercepted request should be mocked.
*/
abstract predicate(
request: Request,
parsedResult: ParsedResult,
resolutionContext?: ResponseResolutionContext,
): boolean
abstract predicate(args: {
request: Request
parsedResult: ParsedResult
resolutionContext?: ResponseResolutionContext
}): boolean

/**
* Print out the successfully handled request.
*/
abstract log(
request: Request,
response: Response,
parsedResult: ParsedResult,
): void
abstract log(args: {
request: Request
response: Response
parsedResult: ParsedResult
}): void

/**
* Parse the intercepted request to extract additional information from it.
* Parsed result is then exposed to other methods of this request handler.
*/
async parse(
_request: Request,
_resolutionContext?: ResponseResolutionContext,
): Promise<ParsedResult> {
async parse(_args: {
request: Request
resolutionContext?: ResponseResolutionContext
}): Promise<ParsedResult> {
return {} as ParsedResult
}

/**
* Test if this handler matches the given request.
*/
public async test(
request: Request,
resolutionContext?: ResponseResolutionContext,
): Promise<boolean> {
return this.predicate(
request,
await this.parse(request.clone(), resolutionContext),
resolutionContext,
)
public async test(args: {
request: Request
resolutionContext?: ResponseResolutionContext
}): Promise<boolean> {
const parsedResult = await this.parse({
request: args.request.clone(),
resolutionContext: args.resolutionContext,
})

return this.predicate({
request: args.request,
parsedResult,
resolutionContext: args.resolutionContext,
})
}

protected extendInfo(
_request: Request,
_parsedResult: ParsedResult,
): ResolverExtras {
protected extendInfo(_args: {
request: Request
parsedResult: ParsedResult
}): ResolverExtras {
return {} as ResolverExtras
}

/**
* Execute this request handler and produce a mocked response
* using the given resolver function.
*/
public async run(
request: StrictRequest<any>,
resolutionContext?: ResponseResolutionContext,
): Promise<RequestHandlerExecutionResult<ParsedResult> | null> {
public async run(args: {
request: StrictRequest<any>
resolutionContext?: ResponseResolutionContext
}): Promise<RequestHandlerExecutionResult<ParsedResult> | null> {
if (this.isUsed && this.options?.once) {
return null
}

const mainRequestRef = request.clone()
const mainRequestRef = args.request.clone()

// Immediately mark the handler as used.
// Can't await the resolver to be resolved because it's potentially
// asynchronous, and there may be multiple requests hitting this handler.
this.isUsed = true

const parsedResult = await this.parse(
mainRequestRef.clone(),
resolutionContext,
)
const shouldInterceptRequest = this.predicate(
mainRequestRef.clone(),
const parsedResult = await this.parse({
request: mainRequestRef.clone(),
resolutionContext: args.resolutionContext,
})
const shouldInterceptRequest = this.predicate({
request: mainRequestRef.clone(),
parsedResult,
resolutionContext,
)
resolutionContext: args.resolutionContext,
})

if (!shouldInterceptRequest) {
return null
Expand All @@ -206,19 +211,22 @@ export abstract class RequestHandler<
// since it can be both an async function and a generator.
const executeResolver = this.wrapResolver(this.resolver)

const resolverExtras = this.extendInfo(request, parsedResult)
const resolverExtras = this.extendInfo({
request: args.request,
parsedResult,
})
const mockedResponse = (await executeResolver({
...resolverExtras,
request,
request: args.request,
})) as Response

const executionResult = this.createExecutionResult(
const executionResult = this.createExecutionResult({
// Pass the cloned request to the result so that logging
// and other consumers could read its body once more.
mainRequestRef,
request: mainRequestRef,
response: mockedResponse,
parsedResult,
mockedResponse,
)
})

return executionResult
}
Expand Down Expand Up @@ -272,16 +280,16 @@ export abstract class RequestHandler<
}
}

private createExecutionResult(
request: Request,
parsedResult: ParsedResult,
response?: Response,
): RequestHandlerExecutionResult<ParsedResult> {
private createExecutionResult(args: {
request: Request
parsedResult: ParsedResult
response?: Response
}): RequestHandlerExecutionResult<ParsedResult> {
return {
handler: this,
parsedResult,
request,
response,
request: args.request,
response: args.response,
parsedResult: args.parsedResult,
}
}
}
2 changes: 1 addition & 1 deletion src/core/utils/getResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const getResponse = async <Handler extends Array<RequestHandler>>(
let result: RequestHandlerExecutionResult<any> | null = null

for (const handler of handlers) {
result = await handler.run(request, resolutionContext)
result = await handler.run({ request, resolutionContext })

// If the handler produces some result for this request,
// it automatically becomes matching.
Expand Down

0 comments on commit ff48c56

Please sign in to comment.