Skip to content

Commit

Permalink
feat: add utility functions
Browse files Browse the repository at this point in the history
  • Loading branch information
TomokiMiyauci committed May 8, 2023
1 parent 8c2cd93 commit f0925a4
Show file tree
Hide file tree
Showing 9 changed files with 392 additions and 0 deletions.
10 changes: 10 additions & 0 deletions _dev_deps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright 2023-latest the httpland authors. All rights reserved. MIT license.

export { describe, it } from "https://deno.land/std@0.185.0/testing/bdd.ts";
export {
assert,
assertEquals,
assertFalse,
assertRejects,
assertThrows,
} from "https://deno.land/std@0.185.0/testing/asserts.ts";
37 changes: 37 additions & 0 deletions create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2023-latest the httpland authors. All rights reserved. MIT license.
// This module is browser compatible.

import { isResponse } from "./is.ts";

/** Create a new `Response`.
*
* If you create a new `Response` from an existing `Response`, any options you set
* in an options argument for the new response replace any corresponding options
* set in the original `Response`.
*
* @example
* ```ts
* import { createResponse } from "https://deno.land/x/response_utils@$VERSION/create.ts";
* import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
*
* declare const init: Response;
* const response = createResponse(init, { status: 201 });
*
* assertEquals(response.status, 201);
* ```
*/
export function createResponse(
input: Response | Body,
init?: ResponseInit,
): Response {
init = isResponse(input)
? {
headers: input.headers,
status: input.status,
statusText: input.statusText,
...init,
}
: init;

return new Response(input.body, init);
}
49 changes: 49 additions & 0 deletions create_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2023-latest the httpland authors. All rights reserved. MIT license.

import { createResponse } from "./create.ts";
import { equalsResponse } from "./equal.ts";
import { assert, assertEquals, describe, it } from "./_dev_deps.ts";

describe("createResponse", () => {
it("should return new response", async () => {
const init = new Response();
const response = createResponse(init);

assert(init !== response);
assert(await equalsResponse(init, response, true));
});

it("should return partial updated response", () => {
const init = new Response(null, { status: 201 });
const response = createResponse(init, { status: 202 });

assertEquals(response.status, 202);
});

it("should extern body", async () => {
const response = createResponse(new Response("test"), { status: 202 });

assert(
await equalsResponse(
response,
new Response("test", { status: 202 }),
true,
),
);
});

it("should not merge headers", async () => {
const response = createResponse(
new Response(null, { headers: { "x-test": "test" } }),
{ headers: { "x-test2": "test2" } },
);

assert(
await equalsResponse(
response,
new Response(null, { headers: { "x-test2": "test2" } }),
true,
),
);
});
});
13 changes: 13 additions & 0 deletions deno.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": "2",
"remote": {
"https://deno.land/std@0.185.0/fmt/colors.ts": "d67e3cd9f472535241a8e410d33423980bec45047e343577554d3356e1f0ef4e",
"https://deno.land/std@0.185.0/testing/_diff.ts": "1a3c044aedf77647d6cac86b798c6417603361b66b54c53331b312caeb447aea",
"https://deno.land/std@0.185.0/testing/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7",
"https://deno.land/std@0.185.0/testing/_test_suite.ts": "30f018feeb3835f12ab198d8a518f9089b1bcb2e8c838a8b615ab10d5005465c",
"https://deno.land/std@0.185.0/testing/asserts.ts": "e16d98b4d73ffc4ed498d717307a12500ae4f2cbe668f1a215632d19fcffc22f",
"https://deno.land/std@0.185.0/testing/bdd.ts": "59f7f7503066d66a12e50ace81bfffae5b735b6be1208f5684b630ae6b4de1d0",
"https://deno.land/x/headers_utils@1.0.0/equal.ts": "ef72aa7e96fe317df43f8e5948b4bb4e3c0d5935cfa4984a335aeb7ecf51f11c",
"https://deno.land/x/isx@1.3.1/is_null.ts": "02b30255073843d001e715a04382f1d6aebd77ed5506ffbb44bf77b9e20ebf7d"
}
}
5 changes: 5 additions & 0 deletions deps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright 2023-latest the httpland authors. All rights reserved. MIT license.
// This module is browser compatible.

export { equalsHeaders } from "https://deno.land/x/headers_utils@1.0.0/equal.ts";
export { isNull } from "https://deno.land/x/isx@1.3.1/is_null.ts";
88 changes: 88 additions & 0 deletions equal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2023-latest the httpland authors. All rights reserved. MIT license.
// This module is browser compatible.

import { equalsHeaders, isNull } from "./deps.ts";

/** Check two `Response` fields equality.
*
* @example
* ```ts
* import { equalsResponse } from "https://deno.land/x/response_utils@$VERSION/equal.ts";
* import { assert } from "https://deno.land/std/testing/asserts.ts";
*
* assert(
* equalsResponse(
* new Response(null, { status: 204, headers: { "content-length": "0" } }),
* new Response(null, { status: 204, headers: { "content-length": "0" } }),
* ),
* );
* ```
*/
export function equalsResponse(left: Response, right: Response): boolean;
/** Strict check two `Response` fields equality.
*
* @example
* ```ts
* import { equalsResponse } from "https://deno.land/x/response_utils@$VERSION/equal.ts";
* import { assert } from "https://deno.land/std/testing/asserts.ts";
*
* assert(
* await equalsResponse(
* new Response("test1", { status: 200, headers: { "content-length": "5" } }),
* new Response("test2", { status: 200, headers: { "content-length": "5" } }),
* false,
* ),
* );
* ```
*
* @throws {Error} In strict mode, if response body has already been read.
*/
export function equalsResponse(
left: Response,
right: Response,
strict: boolean,
): boolean | Promise<boolean>;
export function equalsResponse(
left: Response,
right: Response,
strict?: boolean,
): boolean | Promise<boolean> {
strict ??= false;

const staticResult = left.ok === right.ok &&
left.bodyUsed === right.bodyUsed &&
left.redirected === right.redirected &&
left.status === right.status &&
left.statusText === right.statusText &&
left.type === right.type &&
left.url === right.url &&
equalsBodyType(left.body, right.body) &&
equalsHeaders(left.headers, right.headers);

if (!staticResult || !strict) return staticResult;

if (left.bodyUsed || right.bodyUsed) {
throw Error(
"response body has already been read and the body cannot be strictly compared",
);
}

return Promise.all([left.clone().text(), right.clone().text()]).then((
[left, right],
) => Object.is(left, right));
}

function equalsBodyType(
left: Response["body"],
right: Response["body"],
): boolean {
if (isNull(left)) {
return isNull(right);
}

if (isNull(right)) {
return isNull(left);
}

return true;
}
136 changes: 136 additions & 0 deletions equal_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright 2023-latest the httpland authors. All rights reserved. MIT license.

import { equalsResponse } from "./equal.ts";
import {
assert,
assertEquals,
assertFalse,
assertThrows,
describe,
it,
} from "./_dev_deps.ts";

describe("equalsResponse", () => {
it("should pass cases", () => {
const table: [Response, Response, boolean][] = [
[new Response(), new Response(), true],
[new Response(null), new Response(), true],
[new Response(undefined), new Response(), true],
[
new Response(null, {
status: 500,
}),
new Response(null, {
status: 500,
}),
true,
],
[
new Response(null, {
statusText: "",
}),
new Response(null, {
statusText: "",
}),
true,
],
[
new Response(null, {
headers: {
a: "",
},
}),
new Response(null, {
headers: {
a: "",
},
}),
true,
],
[
new Response(null, {
headers: {
a: "test",
},
}),
new Response(null, {
headers: {
a: "",
},
}),
false,
],
[
new Response(null, {
statusText: "",
}),
new Response(null, {
statusText: "a",
}),
false,
],
[
new Response(null, {
status: 200,
}),
new Response(null, {
status: 201,
}),
false,
],
[
new Response(null, {
status: 300,
}),
new Response(),
false,
],
[new Response("test"), new Response(), false],
[new Response(""), new Response(""), true],
[new Response("a"), new Response(""), true],
[new Response("a"), new Response("a"), true],
];

Promise.all(table.map(([left, right, result]) => {
assertEquals(equalsResponse(left, right), result);
}));
});

it("should pass if strict mode", async () => {
const table: [Response, Response, boolean][] = [
[new Response(""), new Response(""), true],
[new Response("a"), new Response("a"), true],
[new Response("test"), new Response(), false],
[new Response("a"), new Response(""), false],
];

await Promise.all(table.map(async ([left, right, result]) => {
assertEquals(await equalsResponse(left, right, true), result);
}));
});

it("should throw error if strict mode and the response body has been read", async () => {
const response = new Response("");
await response.text();

assert(response.bodyUsed);
assertThrows(() => equalsResponse(response, response, true));
});

it("should not throw when the response body has used", async () => {
const res = new Response("");

await res.text();

assert(res.bodyUsed);
assertFalse(equalsResponse(res, new Response("")));
});

it("should use cloned response", async () => {
const res = new Response("");

assert(equalsResponse(res, new Response("")));
assertFalse(res.bodyUsed);
assertEquals(await res.text(), "");
});
});
18 changes: 18 additions & 0 deletions is.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2023-latest the httpland authors. All rights reserved. MIT license.
// This module is browser compatible.

/** Whether the input is `Response` or not.
*
* @example
* ```ts
* import { isResponse } from "https://deno.land/x/response_utils@$VERSION/is.ts";
* import { assert, assertFalse } from "https://deno.land/std/testing/asserts.ts";
*
* assert(isResponse(new Response()));
* assertFalse(isResponse({}));
* assertFalse(isResponse(null));
* ```
*/
export function isResponse(input: unknown): input is Response {
return input instanceof Response;
}

0 comments on commit f0925a4

Please sign in to comment.