Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(RequestController): support arbitrary objects in .errorWith() #676

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/RequestController.test.ts
Original file line number Diff line number Diff line change
@@ -26,6 +26,14 @@ it('resolves the response promise with the error provided to "errorWith"', async
await expect(controller[kResponsePromise]).resolves.toEqual(error)
})

it('resolves the response promise with the object provided to "errorWith"', async () => {
const controller = new RequestController(new Request('http://localhost'))
const error = { message: 'Oops!' }
controller.errorWith(error)

await expect(controller[kResponsePromise]).resolves.toEqual(error)
})

it('throws when calling "respondWith" multiple times', () => {
const controller = new RequestController(new Request('http://localhost'))
controller.respondWith(new Response('hello world'))
7 changes: 4 additions & 3 deletions src/RequestController.ts
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ export class RequestController {
* @note This promise cannot be rejected. It's either infinitely
* pending or resolved with whichever Response was passed to `respondWith()`.
*/
[kResponsePromise]: DeferredPromise<Response | Error | undefined>;
[kResponsePromise]: DeferredPromise<Response | Object | undefined>;

/**
* Internal flag indicating if this request has been handled.
@@ -46,7 +46,7 @@ export class RequestController {
this[kResponsePromise].resolve(response)

/**
* @note The request conrtoller doesn't do anything
* @note The request controller doesn't do anything
* apart from letting the interceptor await the response
* provided by the developer through the response promise.
* Each interceptor implements the actual respondWith/errorWith
@@ -59,8 +59,9 @@ export class RequestController {
* @example
* controller.errorWith()
* controller.errorWith(new Error('Oops!'))
* controller.errorWith({ message: 'Oops!'})
*/
public errorWith(error?: Error): void {
public errorWith(error?: Object): void {
invariant.as(
InterceptorError,
!this[kRequestHandled],
42 changes: 24 additions & 18 deletions src/utils/handleRequest.ts
Original file line number Diff line number Diff line change
@@ -43,20 +43,31 @@ interface HandleRequestOptions {
export async function handleRequest(
options: HandleRequestOptions
): Promise<boolean> {
const handleResponse = async (response: Response | Error) => {
const onResolve = async (response: Response): Promise<boolean> => {
// Handle "Response.error()" instances.
if (response instanceof Error) {
options.onError(response)
}

// Handle "Response.error()" instances.
else if (isResponseError(response)) {
} else if (isResponseError(response)) {
options.onRequestError(response)
} else {
await options.onResponse(response)
await handleResponse(response)
}

return true
}
const onReject = async (error: unknown): Promise<boolean> => {
// Handle thrown responses.
if (error instanceof Response) {
await handleResponse(error)
return true
}

return await handleResponseError(error);
}

const handleResponse = async (response: Response) => {
await options.onResponse(response)
}

const handleResponseError = async (error: unknown): Promise<boolean> => {
// Forward the special interceptor error instances
@@ -69,12 +80,7 @@ export async function handleRequest(
if (isNodeLikeError(error)) {
options.onError(error)
return true
}

// Handle thrown responses.
if (error instanceof Response) {
return await handleResponse(error)
}
}

return false
}
@@ -116,7 +122,7 @@ export async function handleRequest(
// for that event are finished (e.g. async listeners awaited).
// By the end of this promise, the developer cannot affect the
// request anymore.
const requestListtenersPromise = emitAsync(options.emitter, 'request', {
const requestListenersPromise = emitAsync(options.emitter, 'request', {
requestId: options.requestId,
request: options.request,
controller: options.controller,
@@ -125,7 +131,7 @@ export async function handleRequest(
await Promise.race([
// Short-circuit the request handling promise if the request gets aborted.
requestAbortPromise,
requestListtenersPromise,
requestListenersPromise,
options.controller[kResponsePromise],
])

@@ -144,7 +150,7 @@ export async function handleRequest(
if (result.error) {
// Handle the error during the request listener execution.
// These can be thrown responses or request errors.
if (await handleResponseError(result.error)) {
if (await onReject(result.error)) {
return true
}

@@ -188,11 +194,11 @@ export async function handleRequest(
* emit of the same event. They are forwarded as-is.
*/
if (nextResult.error) {
return handleResponseError(nextResult.error)
return onReject(nextResult.error)
}

if (nextResult.data) {
return handleResponse(nextResult.data)
return onResolve(nextResult.data)
}
}

@@ -208,7 +214,7 @@ export async function handleRequest(
* unhandled exceptions from intended errors.
*/
if (result.data) {
return handleResponse(result.data)
return onResolve(result.data)
}

// In all other cases, consider the request unhandled.