diff --git a/README.md b/README.md index de9c1cfe..0eb31072 100644 --- a/README.md +++ b/README.md @@ -1000,6 +1000,18 @@ export let loader: LoaderFunction = async ({ request }) => { }; ``` +#### XML + +Helper function to create a XML file response with any header. + +This is useful to create XML files based on data inside a Resource Route. + +```ts +export let loader: LoaderFunction = async ({ request }) => { + return xml(""); +}; +``` + ### Typed Cookies Cookie objects in Remix allows any type, the typed cookies from Remix Utils lets you use Zod to parse the cookie values and ensure they conform to a schema. diff --git a/src/server/responses.ts b/src/server/responses.ts index aa7b3647..aa3c2c7c 100644 --- a/src/server/responses.ts +++ b/src/server/responses.ts @@ -250,6 +250,34 @@ export function html( }); } +/** + * Create a response with a XML file response. + * It receives a string with the XML content and set the Content-Type header to + * `application/xml; charset=utf-8` always. + * + * This is useful to dynamically create a XML file from a Resource Route. + * @example + * export let loader: LoaderFunction = async ({ request }) => { + * return xml(""); + * } + */ +export function xml( + content: string, + init: number | ResponseInit = {} +): Response { + let responseInit = typeof init === "number" ? { status: init } : init; + + let headers = new Headers(responseInit.headers); + if (!headers.has("Content-Type")) { + headers.set("Content-Type", "application/xml; charset=utf-8"); + } + + return new Response(content, { + ...responseInit, + headers, + }); +} + export type ImageType = | "image/jpeg" | "image/png" diff --git a/test/server/responses.test.ts b/test/server/responses.test.ts index 2b790b77..7713a609 100644 --- a/test/server/responses.test.ts +++ b/test/server/responses.test.ts @@ -2,6 +2,7 @@ import { badRequest, forbidden, html, + xml, image, ImageType, javascript, @@ -295,6 +296,39 @@ describe("Responses", () => { }); }); + describe(xml, () => { + let content = ""; + test("Should return Response with status 200", async () => { + let response = xml(content); + await expect(response.text()).resolves.toBe(content); + expect(response.status).toBe(200); + expect(response.headers.get("Content-Type")).toBe( + "application/xml; charset=utf-8" + ); + }); + + test("Should allow defining the status as second options", async () => { + let response = xml(content, 201); + await expect(response.text()).resolves.toBe(content); + expect(response.status).toBe(201); + expect(response.headers.get("Content-Type")).toBe( + "application/xml; charset=utf-8" + ); + }); + + test("Should allow changing the Response headers", async () => { + let response = xml(content, { + headers: { "X-Test": "it worked" }, + }); + await expect(response.text()).resolves.toBe(content); + expect(response.status).toBe(200); + expect(response.headers.get("Content-Type")).toBe( + "application/xml; charset=utf-8" + ); + expect(response.headers.get("X-Test")).toBe("it worked"); + }); + }); + describe(image, () => { let content = new ArrayBuffer(0); test.each([