Skip to content

Commit

Permalink
Add middlewaare error test and remove ErrorResponse
Browse files Browse the repository at this point in the history
  • Loading branch information
DustinJSilk committed Jan 11, 2025
1 parent 4ed0206 commit 6aa4edf
Show file tree
Hide file tree
Showing 10 changed files with 58 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@
}
],
"kind": "Interface",
"content": "```typescript\nexport interface RequestEventCommon<PLATFORM = QwikCityPlatform> extends RequestEventBase<PLATFORM> \n```\n**Extends:** [RequestEventBase](#requesteventbase)<!-- -->&lt;PLATFORM&gt;\n\n\n<table><thead><tr><th>\n\nProperty\n\n\n</th><th>\n\nModifiers\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\n[error](#)\n\n\n</td><td>\n\n`readonly`\n\n\n</td><td>\n\n(statusCode: ErrorCodes, message: string) =&gt; ErrorResponse\n\n\n</td><td>\n\nWhen called, the response will immediately end with the given status code. This could be useful to end a response with `404`<!-- -->, and use the 404 handler in the routes directory. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status for which status code should be used.\n\n\n</td></tr>\n<tr><td>\n\n[exit](#)\n\n\n</td><td>\n\n`readonly`\n\n\n</td><td>\n\n() =&gt; [AbortMessage](#abortmessage)\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\n[html](#)\n\n\n</td><td>\n\n`readonly`\n\n\n</td><td>\n\n(statusCode: StatusCodes, html: string) =&gt; [AbortMessage](#abortmessage)\n\n\n</td><td>\n\nConvenience method to send an HTML body response. The response will be automatically set the `Content-Type` header to`text/html; charset=utf-8`<!-- -->. An `html()` response can only be called once.\n\n\n</td></tr>\n<tr><td>\n\n[json](#)\n\n\n</td><td>\n\n`readonly`\n\n\n</td><td>\n\n(statusCode: StatusCodes, data: any) =&gt; [AbortMessage](#abortmessage)\n\n\n</td><td>\n\nConvenience method to JSON stringify the data and send it in the response. The response will be automatically set the `Content-Type` header to `application/json; charset=utf-8`<!-- -->. A `json()` response can only be called once.\n\n\n</td></tr>\n<tr><td>\n\n[locale](#)\n\n\n</td><td>\n\n`readonly`\n\n\n</td><td>\n\n(local?: string) =&gt; string\n\n\n</td><td>\n\nWhich locale the content is in.\n\nThe locale value can be retrieved from selected methods using `getLocale()`<!-- -->:\n\n\n</td></tr>\n<tr><td>\n\n[redirect](#)\n\n\n</td><td>\n\n`readonly`\n\n\n</td><td>\n\n(statusCode: RedirectCode, url: string) =&gt; [RedirectMessage](#redirectmessage)\n\n\n</td><td>\n\nURL to redirect to. When called, the response will immediately end with the correct redirect status and headers.\n\nhttps://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections\n\n\n</td></tr>\n<tr><td>\n\n[send](#)\n\n\n</td><td>\n\n`readonly`\n\n\n</td><td>\n\nSendMethod\n\n\n</td><td>\n\nSend a body response. The `Content-Type` response header is not automatically set when using `send()` and must be set manually. A `send()` response can only be called once.\n\n\n</td></tr>\n<tr><td>\n\n[status](#)\n\n\n</td><td>\n\n`readonly`\n\n\n</td><td>\n\n(statusCode?: StatusCodes) =&gt; number\n\n\n</td><td>\n\nHTTP response status code. Sets the status code when called with an argument. Always returns the status code, so calling `status()` without an argument will can be used to return the current status code.\n\nhttps://developer.mozilla.org/en-US/docs/Web/HTTP/Status\n\n\n</td></tr>\n<tr><td>\n\n[text](#)\n\n\n</td><td>\n\n`readonly`\n\n\n</td><td>\n\n(statusCode: StatusCodes, text: string) =&gt; [AbortMessage](#abortmessage)\n\n\n</td><td>\n\nConvenience method to send an text body response. The response will be automatically set the `Content-Type` header to`text/plain; charset=utf-8`<!-- -->. An `text()` response can only be called once.\n\n\n</td></tr>\n</tbody></table>",
"content": "```typescript\nexport interface RequestEventCommon<PLATFORM = QwikCityPlatform> extends RequestEventBase<PLATFORM> \n```\n**Extends:** [RequestEventBase](#requesteventbase)<!-- -->&lt;PLATFORM&gt;\n\n\n<table><thead><tr><th>\n\nProperty\n\n\n</th><th>\n\nModifiers\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\n[error](#)\n\n\n</td><td>\n\n`readonly`\n\n\n</td><td>\n\n&lt;T = Record&lt;any, any&gt;&gt;(statusCode: ErrorCodes, message: T) =&gt; [ServerError](#servererror)<!-- -->&lt;T&gt;\n\n\n</td><td>\n\nWhen called, the response will immediately end with the given status code. This could be useful to end a response with `404`<!-- -->, and use the 404 handler in the routes directory. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status for which status code should be used.\n\n\n</td></tr>\n<tr><td>\n\n[exit](#)\n\n\n</td><td>\n\n`readonly`\n\n\n</td><td>\n\n() =&gt; [AbortMessage](#abortmessage)\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\n[html](#)\n\n\n</td><td>\n\n`readonly`\n\n\n</td><td>\n\n(statusCode: StatusCodes, html: string) =&gt; [AbortMessage](#abortmessage)\n\n\n</td><td>\n\nConvenience method to send an HTML body response. The response will be automatically set the `Content-Type` header to`text/html; charset=utf-8`<!-- -->. An `html()` response can only be called once.\n\n\n</td></tr>\n<tr><td>\n\n[json](#)\n\n\n</td><td>\n\n`readonly`\n\n\n</td><td>\n\n(statusCode: StatusCodes, data: any) =&gt; [AbortMessage](#abortmessage)\n\n\n</td><td>\n\nConvenience method to JSON stringify the data and send it in the response. The response will be automatically set the `Content-Type` header to `application/json; charset=utf-8`<!-- -->. A `json()` response can only be called once.\n\n\n</td></tr>\n<tr><td>\n\n[locale](#)\n\n\n</td><td>\n\n`readonly`\n\n\n</td><td>\n\n(local?: string) =&gt; string\n\n\n</td><td>\n\nWhich locale the content is in.\n\nThe locale value can be retrieved from selected methods using `getLocale()`<!-- -->:\n\n\n</td></tr>\n<tr><td>\n\n[redirect](#)\n\n\n</td><td>\n\n`readonly`\n\n\n</td><td>\n\n(statusCode: RedirectCode, url: string) =&gt; [RedirectMessage](#redirectmessage)\n\n\n</td><td>\n\nURL to redirect to. When called, the response will immediately end with the correct redirect status and headers.\n\nhttps://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections\n\n\n</td></tr>\n<tr><td>\n\n[send](#)\n\n\n</td><td>\n\n`readonly`\n\n\n</td><td>\n\nSendMethod\n\n\n</td><td>\n\nSend a body response. The `Content-Type` response header is not automatically set when using `send()` and must be set manually. A `send()` response can only be called once.\n\n\n</td></tr>\n<tr><td>\n\n[status](#)\n\n\n</td><td>\n\n`readonly`\n\n\n</td><td>\n\n(statusCode?: StatusCodes) =&gt; number\n\n\n</td><td>\n\nHTTP response status code. Sets the status code when called with an argument. Always returns the status code, so calling `status()` without an argument will can be used to return the current status code.\n\nhttps://developer.mozilla.org/en-US/docs/Web/HTTP/Status\n\n\n</td></tr>\n<tr><td>\n\n[text](#)\n\n\n</td><td>\n\n`readonly`\n\n\n</td><td>\n\n(statusCode: StatusCodes, text: string) =&gt; [AbortMessage](#abortmessage)\n\n\n</td><td>\n\nConvenience method to send an text body response. The response will be automatically set the `Content-Type` header to`text/plain; charset=utf-8`<!-- -->. An `text()` response can only be called once.\n\n\n</td></tr>\n</tbody></table>",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/middleware/request-handler/types.ts",
"mdFile": "qwik-city.requesteventcommon.md"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1216,7 +1216,7 @@ Description
</td><td>
(statusCode: ErrorCodes, message: string) =&gt; ErrorResponse
&lt;T = Record&lt;any, any&gt;&gt;(statusCode: ErrorCodes, message: T) =&gt; [ServerError](#servererror)&lt;T&gt;
</td><td>
Expand Down
3 changes: 1 addition & 2 deletions packages/qwik-city/src/middleware/request-handler/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,7 @@ export interface RequestEventBase<PLATFORM = QwikCityPlatform> {
// @public (undocumented)
export interface RequestEventCommon<PLATFORM = QwikCityPlatform> extends RequestEventBase<PLATFORM> {
// Warning: (ae-forgotten-export) The symbol "ErrorCodes" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "ErrorResponse" needs to be exported by the entry point index.d.ts
readonly error: (statusCode: ErrorCodes, message: string) => ErrorResponse;
readonly error: <T = Record<any, any>>(statusCode: ErrorCodes, message: T) => ServerError<T>;
// (undocumented)
readonly exit: () => AbortMessage;
readonly html: (statusCode: StatusCodes, html: string) => AbortMessage;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,6 @@ export class ServerError<T = Record<any, any>> extends Error {
}
}

export class ErrorResponse extends Error {
constructor(
public status: number,
message?: string
) {
super(message);
}
}

/** @public */
export function getErrorHtml(status: number, e: any) {
let message = 'Server Error';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import type {
FailReturn,
} from '../../runtime/src/types';
import { Cookie } from './cookie';
import { ErrorResponse } from './error-handler';
import { AbortMessage, RedirectMessage } from './redirect-handler';
import { encoder } from './resolve-request-handlers';
import { createCacheControl } from './cache-control';
Expand All @@ -27,6 +26,7 @@ import type { QwikManifest, ResolvedManifest } from '@builder.io/qwik/optimizer'
import { IsQData, QDATA_JSON, QDATA_JSON_LEN } from './user-response';
import { isPromise } from './../../runtime/src/utils';
import { QDATA_KEY } from '../../runtime/src/constants';
import { ServerError } from './error-handler';

const RequestEvLoaders = Symbol('RequestEvLoaders');
const RequestEvMode = Symbol('RequestEvMode');
Expand Down Expand Up @@ -192,9 +192,9 @@ export function createRequestEvent(
return locale || '';
},

error: (statusCode: number, message: string) => {
error: <T = Record<any, any>>(statusCode: number, message: T) => {
status = statusCode;
return new ErrorResponse(statusCode, message);
return new ServerError(statusCode, message);
},

redirect: (statusCode: number, url: string) => {
Expand Down
7 changes: 4 additions & 3 deletions packages/qwik-city/src/middleware/request-handler/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { Render, RenderOptions } from '@builder.io/qwik/server';
import type { QwikCityPlan, FailReturn, Action, Loader } from '@builder.io/qwik-city';
import type { ErrorResponse } from './error-handler';
import type { AbortMessage, RedirectMessage } from './redirect-handler';
import type { RequestEventInternal } from './request-event';
import type { _deserializeData, _serializeData, _verifySerializable } from '@builder.io/qwik';
import type { ServerError } from './error-handler';

/** @public */
export interface EnvGetter {
Expand Down Expand Up @@ -154,7 +154,8 @@ export type ClientErrorCode =
| 428 // Precondition Required
| 429 // Too Many Requests
| 431 // Request Header Fields Too Large
| 451; // Unavailable For Legal Reasons
| 451 // Unavailable For Legal Reasons
| 499; // Client closed request

/**
* HTTP Server Error Status Codes Status codes in the 5xx range indicate that the server encountered
Expand Down Expand Up @@ -205,7 +206,7 @@ export interface RequestEventCommon<PLATFORM = QwikCityPlatform>
* to end a response with `404`, and use the 404 handler in the routes directory. See
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Status for which status code should be used.
*/
readonly error: (statusCode: ErrorCodes, message: string) => ErrorResponse;
readonly error: <T = Record<any, any>>(statusCode: ErrorCodes, message: T) => ServerError<T>;

/**
* Convenience method to send an text body response. The response will be automatically set the
Expand Down
19 changes: 7 additions & 12 deletions packages/qwik-city/src/middleware/request-handler/user-response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
getRequestMode,
type RequestEventInternal,
} from './request-event';
import { ErrorResponse, ServerError, getErrorHtml, minimalHtmlResponse } from './error-handler';
import { ServerError, minimalHtmlResponse } from './error-handler';
import { AbortMessage, RedirectMessage } from './redirect-handler';
import type { LoadedRoute } from '../../runtime/src/types';
import { encoder } from './resolve-request-handlers';
Expand Down Expand Up @@ -70,19 +70,14 @@ async function runNext(requestEv: RequestEventInternal, resolve: (value: any) =>
if (e instanceof RedirectMessage) {
const stream = requestEv.getWritableStream();
await stream.close();
} else if (e instanceof ErrorResponse) {
console.error(e);
if (!requestEv.headersSent) {
const html = getErrorHtml(e.status, e);
const status = e.status as StatusCodes;
requestEv.html(status, html);
}
} else if (e instanceof ServerError) {
if (!requestEv.headersSent) {
const status = e.status as StatusCodes;
const qwikSerializer = requestEv[RequestEvQwikSerializer];
requestEv.headers.set('Content-Type', 'application/qwik-json');
requestEv.send(status, await qwikSerializer._serializeData(e.data, true));
if (typeof e.data === 'object') {
const status = e.status as StatusCodes;
const qwikSerializer = requestEv[RequestEvQwikSerializer];
requestEv.headers.set('Content-Type', 'application/qwik-json');
requestEv.send(status, await qwikSerializer._serializeData(e.data, true));
}
}
} else if (!(e instanceof AbortMessage)) {
if (getRequestMode(requestEv) !== 'dev') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,34 @@ import { server$ } from "@builder.io/qwik-city";
import { ServerError } from "@builder.io/qwik-city/middleware/request-handler";
import { delay } from "../../actions/login";

type ErrorReason = { reason: string };
type ErrorReason = {
reason: string;
middleware: string;
};

const serverFunctionA = server$(async function a(): Promise<string> {
throw new ServerError<ErrorReason>(401, { reason: "my error" });
throw new ServerError<ErrorReason>(401, {
reason: "my error",
middleware: "uncaught",
});
});

const serverFunctionB = server$(async function b(): Promise<string> {
return this.method;
});

export const MultipleServerFunctionsInvokedInTask = component$(() => {
const methodA = useSignal("");
const errorReason = useSignal("");
const errorMiddleware = useSignal("");
const methodB = useSignal("");

useVisibleTask$(async () => {
try {
await serverFunctionA();
} catch (err: any) {
if (isErrorReason(err)) {
methodA.value = err.reason;
errorReason.value = err.reason;
errorMiddleware.value = err.middleware;
}
}

Expand All @@ -34,7 +42,8 @@ export const MultipleServerFunctionsInvokedInTask = component$(() => {

return (
<div id="server-error">
{methodA.value}
{errorReason.value}
{errorMiddleware.value}
{methodB.value}
</div>
);
Expand All @@ -48,8 +57,8 @@ export default component$(() => {
);
});

function isErrorReason(err: any): err is ErrorReason {
if (typeof err.reason === "string") {
export function isErrorReason(err: any): err is ErrorReason {
if (typeof err.reason === "string" && typeof err.middleware === "string") {
return true;
}

Expand Down
24 changes: 24 additions & 0 deletions starters/apps/qwikcity-test/src/routes/[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { type RequestHandler } from "@builder.io/qwik-city";
import { ServerError } from "@builder.io/qwik-city/middleware/request-handler";
import { isDev } from "@builder.io/qwik/build";
import { isErrorReason } from "./(common)/server-func/server-error";

export const onRequest: RequestHandler = async ({ next, error }) => {
try {
return await next();
} catch (err) {
// Test error middleware by updating the error data
if (isServerError(err) && isErrorReason(err.data)) {
err.data.middleware = "caught";
}

throw err;
}
};

function isServerError(err: unknown): err is ServerError {
return (
err instanceof ServerError ||
(isDev && err instanceof Error && err.constructor.name === "ServerError")
);
}
2 changes: 1 addition & 1 deletion starters/e2e/qwikcity/server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ test.describe("server$", () => {
await page.goto("/qwikcity-test/server-func/server-error");
const serverConfigContainer = page.locator("#server-error");

await expect(serverConfigContainer).toContainText("my errorPOST");
await expect(serverConfigContainer).toContainText("my errorcaughtPOST");
});
});
});

0 comments on commit 6aa4edf

Please sign in to comment.