Skip to content

Commit

Permalink
feat: support handleError export from entry.server
Browse files Browse the repository at this point in the history
  • Loading branch information
brophdawg11 committed May 26, 2023
1 parent fc512e5 commit 469253c
Show file tree
Hide file tree
Showing 6 changed files with 293 additions and 71 deletions.
5 changes: 5 additions & 0 deletions .changeset/handle-error.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@remix-run/server-runtime": minor
---

Add optional `handleError` export to entry.server for custom server-side error processing
189 changes: 189 additions & 0 deletions integration/error-sanitization-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -417,4 +417,193 @@ test.describe("Error Sanitization", () => {
expect(errorLogs[0][0].stack).toMatch(" at ");
});
});

test.describe("serverMode=production (user-provided handleError)", () => {
test.beforeAll(async () => {
fixture = await createFixture(
{
files: {
"app/entry.server.tsx": js`
import type { EntryContext } from "@remix-run/node";
import { RemixServer, isRouteErrorResponse } from "@remix-run/react";
import { renderToString } from "react-dom/server";
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
let markup = renderToString(
<RemixServer context={remixContext} url={request.url} />
);
responseHeaders.set("Content-Type", "text/html");
return new Response("<!DOCTYPE html>" + markup, {
status: responseStatusCode,
headers: responseHeaders,
});
}
export function handleError(error: unknown, { request }: { request: Request }) {
console.error("App Specific Error Logging:");
console.error(" Request: " + request.method + " " + request.url);
let msg;
if (isRouteErrorResponse(error)) {
console.error(" Error: " + error.status + " " + error.statusText);
} else if (error instanceof Error) {
console.error(" Error: " + error.message);
console.error(" Stack: " + error.stack);
} else {
console.error("Dunno what this is");
}
}
`,
...routeFiles,
},
},
ServerMode.Production
);
});

test("renders document without errors", async () => {
let response = await fixture.requestDocument("/");
let html = await response.text();
expect(html).toMatch("Index Route");
expect(html).toMatch("LOADER");
expect(html).not.toMatch("MESSAGE:");
expect(html).not.toMatch(/stack/i);
});

test("sanitizes loader errors in document requests", async () => {
let response = await fixture.requestDocument("/?loader");
let html = await response.text();
expect(html).toMatch("Index Error");
expect(html).not.toMatch("LOADER");
expect(html).toMatch("MESSAGE:Unexpected Server Error");
expect(html).toMatch(
'{"routes/index":{"message":"Unexpected Server Error","__type":"Error"}}'
);
expect(html).not.toMatch(/stack/i);
expect(errorLogs[0][0]).toEqual("App Specific Error Logging:");
expect(errorLogs[1][0]).toEqual(" Request: GET test://test/?loader");
expect(errorLogs[2][0]).toEqual(" Error: Loader Error");
expect(errorLogs[3][0]).toMatch(" at ");
expect(errorLogs.length).toBe(4);
});

test("sanitizes render errors in document requests", async () => {
let response = await fixture.requestDocument("/?render");
let html = await response.text();
expect(html).toMatch("Index Error");
expect(html).toMatch("MESSAGE:Unexpected Server Error");
expect(html).toMatch(
'{"routes/index":{"message":"Unexpected Server Error","__type":"Error"}}'
);
expect(html).not.toMatch(/stack/i);
expect(errorLogs[0][0]).toEqual("App Specific Error Logging:");
expect(errorLogs[1][0]).toEqual(" Request: GET test://test/?render");
expect(errorLogs[2][0]).toEqual(" Error: Render Error");
expect(errorLogs[3][0]).toMatch(" at ");
expect(errorLogs.length).toBe(4);
});

test("renders deferred document without errors", async () => {
let response = await fixture.requestDocument("/defer");
let html = await response.text();
expect(html).toMatch("Defer Route");
expect(html).toMatch("RESOLVED");
expect(html).not.toMatch("MESSAGE:");
// Defer errors are not not part of the JSON blob but rather rejected
// against a pending promise and therefore are inlined JS.
expect(html).not.toMatch("x.stack=e.stack;");
});

test("sanitizes defer errors in document requests", async () => {
let response = await fixture.requestDocument("/defer?loader");
let html = await response.text();
expect(html).toMatch("Defer Error");
expect(html).not.toMatch("RESOLVED");
expect(html).toMatch('{"message":"Unexpected Server Error"}');
// Defer errors are not not part of the JSON blob but rather rejected
// against a pending promise and therefore are inlined JS.
expect(html).toMatch("x.stack=undefined;");
// defer errors are not logged to the server console since the request
// has "succeeded"
expect(errorLogs.length).toBe(0);
});

test("returns data without errors", async () => {
let response = await fixture.requestData("/", "routes/index");
let text = await response.text();
expect(text).toMatch("LOADER");
expect(text).not.toMatch("MESSAGE:");
expect(text).not.toMatch(/stack/i);
});

test("sanitizes loader errors in data requests", async () => {
let response = await fixture.requestData("/?loader", "routes/index");
let text = await response.text();
expect(text).toBe('{"message":"Unexpected Server Error"}');
expect(errorLogs[0][0]).toEqual("App Specific Error Logging:");
expect(errorLogs[1][0]).toEqual(
" Request: GET test://test/?loader=&_data=routes%2Findex"
);
expect(errorLogs[2][0]).toEqual(" Error: Loader Error");
expect(errorLogs[3][0]).toMatch(" at ");
expect(errorLogs.length).toBe(4);
});

test("returns deferred data without errors", async () => {
let response = await fixture.requestData("/defer", "routes/defer");
let text = await response.text();
expect(text).toMatch("RESOLVED");
expect(text).not.toMatch("REJECTED");
expect(text).not.toMatch(/stack/i);
});

test("sanitizes loader errors in deferred data requests", async () => {
let response = await fixture.requestData("/defer?loader", "routes/defer");
let text = await response.text();
expect(text).toBe(
'{"lazy":"__deferred_promise:lazy"}\n\n' +
'error:{"lazy":{"message":"Unexpected Server Error"}}\n\n'
);
// defer errors are not logged to the server console since the request
// has "succeeded"
expect(errorLogs.length).toBe(0);
});

test("sanitizes loader errors in resource requests", async () => {
let response = await fixture.requestData(
"/resource?loader",
"routes/resource"
);
let text = await response.text();
expect(text).toBe('{"message":"Unexpected Server Error"}');
expect(errorLogs[0][0]).toEqual("App Specific Error Logging:");
expect(errorLogs[1][0]).toEqual(
" Request: GET test://test/resource?loader=&_data=routes%2Fresource"
);
expect(errorLogs[2][0]).toEqual(" Error: Loader Error");
expect(errorLogs[3][0]).toMatch(" at ");
expect(errorLogs.length).toBe(4);
});

test("sanitizes mismatched route errors in data requests", async () => {
let response = await fixture.requestData("/", "not-a-route");
let text = await response.text();
expect(text).toBe('{"message":"Unexpected Server Error"}');
expect(errorLogs[0][0]).toEqual("App Specific Error Logging:");
expect(errorLogs[1][0]).toEqual(
" Request: GET test://test/?_data=not-a-route"
);
expect(errorLogs[2][0]).toEqual(
' Error: Route "not-a-route" does not match URL "/"'
);
expect(errorLogs[3][0]).toMatch(" at ");
expect(errorLogs.length).toBe(4);
});
});
});
Loading

0 comments on commit 469253c

Please sign in to comment.