Skip to content

Commit

Permalink
fix(breaking): response
Browse files Browse the repository at this point in the history
  • Loading branch information
izatop committed Aug 16, 2020
1 parent 8beaa72 commit de20cbe
Show file tree
Hide file tree
Showing 13 changed files with 188 additions and 155 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
"license": "MIT",
"private": true,
"devDependencies": {
"@commitlint/cli": "^9.1.1",
"@commitlint/config-conventional": "^9.1.1",
"@commitlint/cli": "^9.1.2",
"@commitlint/config-conventional": "^9.1.2",
"@types/jest": "^26.0.10",
"@types/node": "^14.0.27",
"@typescript-eslint/eslint-plugin": "^3.9.0",
Expand Down
11 changes: 4 additions & 7 deletions packages/web/src/Transport/Request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ export class Request extends RequestAbstract {

constructor(incomingMessage: IncomingMessage, serverResponse: ServerResponse, options?: IServerOptions) {
super();
this.#options = options ?? {};
this.#message = incomingMessage;
this.#response = serverResponse;
this.#options = options ?? {};
this.#method = incomingMessage.method?.toUpperCase() ?? "GET";

const headers: [string, string][] = [];
Expand Down Expand Up @@ -109,7 +109,7 @@ export class Request extends RequestAbstract {
if (isError(response)) {
const transform = new TransformError(response);
const accept = this.headers.get("accept");
const {response: transformed, ...status} = accept.includes("application/json")
const {body: transformed, ...status} = accept.includes("application/json")
? transform.toJSON()
: transform.toString();

Expand All @@ -121,11 +121,8 @@ export class Request extends RequestAbstract {
}

if (response instanceof ResponseAbstract) {
const {code, status} = response;
return this.send(
response.stringify(),
{code, status, headers: response.getHeaders()},
);
const {code, status, body, headers} = await response.getResponse();
return this.send(body, {code, status, headers});
}

return this.send(JSON.stringify(response));
Expand Down
4 changes: 2 additions & 2 deletions packages/web/src/Transport/Response/HTMLResponse.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {TextResponse} from "./TextResponse";
import {TextPlainResponse} from "./TextPlainResponse";

export class HTMLResponse extends TextResponse {
export class HTMLResponse extends TextPlainResponse {
public readonly type = "text/html";
}
4 changes: 2 additions & 2 deletions packages/web/src/Transport/Response/JSONResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {ResponseAbstract} from "./ResponseAbstract";
export class JSONResponse extends ResponseAbstract<any> {
public readonly type = "application/json";

public stringify(): string {
return JSON.stringify(this.data);
public stringify(data: any): string {
return JSON.stringify(data);
}
}
6 changes: 3 additions & 3 deletions packages/web/src/Transport/Response/NoContentResponse.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {IResponseOptions, ResponseAbstract} from "./ResponseAbstract";

export class NoContentResponse extends ResponseAbstract<string> {
export class NoContentResponse extends ResponseAbstract<undefined> {
constructor(options: IResponseOptions & { code?: never } = {}) {
super("", {status: "No Content", ...options, code: 204});
super(undefined, {status: "No Content", ...options, code: 204});
}

public stringify(): string {
return this.data;
return "";
}
}
38 changes: 33 additions & 5 deletions packages/web/src/Transport/Response/ResponseAbstract.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
import {isInstanceOf, isNumber} from "@typesafeunit/util";
import {isInstanceOf, isNumber, Promisify, isFunction} from "@typesafeunit/util";
import * as HTTP from "http-status";
import {Headers} from "../Headers";
import {TransformError} from "../TransformError";

export interface IResponseOptions {
code?: number;
status?: string;
headers?: { [key: string]: string } | Headers;
}

export interface IResponseAnswer {
code: number;
status?: string;
body: string;
headers: { [key: string]: string };
}

export abstract class ResponseAbstract<T> {
public readonly code: number = 200;
public readonly status?: string;
public readonly type: string = "text/plain";
public readonly encoding: string = "utf-8";
protected readonly data: T;
readonly #data: Promisify<T>;
readonly #headers: { [key: string]: string };

constructor(data: T, options: IResponseOptions = {}) {
this.data = data;
constructor(data: Promisify<T> | (() => Promisify<T>), options: IResponseOptions = {}) {
this.#data = isFunction(data) ? data() : data;

const {code, status, headers} = options;
if (isNumber(code) && code > 0) {
this.code = code;
Expand All @@ -42,9 +51,28 @@ export abstract class ResponseAbstract<T> {
};
}

public abstract stringify(): string;
public async getResponse(): Promise<IResponseAnswer> {
const {status, code} = this;
const headers = this.getHeaders();
try {
return {
code,
status,
headers,
body: this.stringify(await this.#data),
};
} catch (error) {
const transform = new TransformError(error);
return {
...transform.toJSON(),
headers,
};
}
}

public getContentType(): string {
return `${this.type}; charset=${this.encoding}`;
}

protected abstract stringify(data: T): string;
}
9 changes: 9 additions & 0 deletions packages/web/src/Transport/Response/TextPlainResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {ResponseAbstract} from "./ResponseAbstract";

export class TextPlainResponse extends ResponseAbstract<string> {
public readonly type: string = "text/plain";

public stringify(data: string): string {
return data;
}
}
9 changes: 0 additions & 9 deletions packages/web/src/Transport/Response/TextResponse.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/web/src/Transport/Response/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export * from "./ResponseAbstract";
export * from "./HTMLResponse";
export * from "./JSONResponse";
export * from "./TextResponse";
export * from "./TextPlainResponse";
export * from "./NoContentResponse";
export * from "./Redirect";
6 changes: 3 additions & 3 deletions packages/web/src/Transport/TransformError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {ServerError} from "./ServerError";
export interface IErrorResponse {
code: number;
status?: string;
response: string;
body: string;
}

const map = new Map([
Expand All @@ -22,14 +22,14 @@ export class TransformError {

public toString(): IErrorResponse {
return {
response: this.getResponse(),
body: this.getResponse(),
...this.getStatus(),
};
}

public toJSON(): IErrorResponse {
return {
response: JSON.stringify(this.getResponseJSON()),
body: JSON.stringify(this.getResponseJSON()),
...this.getStatus(),
};
}
Expand Down
10 changes: 6 additions & 4 deletions packages/web/test/src/Main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,27 @@ import {JSONResponse, NoContentResponse, Redirect} from "../../src";
import * as HTTP from "http-status";

describe("Response", () => {
test("Main", () => {
test("Main", async () => {
const resp = new NoContentResponse({headers: {"foo": "123"}});
expect(resp.getContentType()).toMatchSnapshot();
expect(resp.getHeaders()).toMatchSnapshot();
expect(await resp.getResponse()).toMatchSnapshot();
expect(resp.code).toBe(204);
});

test("JSON", () => {
test("JSON", async () => {
const resp = new JSONResponse({foo: "bar"}, {code: 200});
expect(resp.getContentType()).toMatchSnapshot();
expect(resp.getHeaders()).toMatchSnapshot();
expect(resp.stringify()).toMatchSnapshot();
expect(await resp.getResponse()).toMatchSnapshot();
expect(resp.code).toBe(200);
});

test("Redirect", () => {
test("Redirect", async () => {
const redirect = new Redirect("/redirect");
expect(redirect.code).toBe(HTTP["MOVED_PERMANENTLY"]);
expect(redirect.status).toBe(HTTP["301"]);
expect(await redirect.getResponse()).toMatchSnapshot();
expect(redirect.getHeaders()).toMatchSnapshot();

expect(() => new Redirect("/", 500)).toThrow();
Expand Down
35 changes: 34 additions & 1 deletion packages/web/test/src/__snapshots__/Main.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@ Object {
}
`;

exports[`Response JSON 3`] = `"{\\"foo\\":\\"bar\\"}"`;
exports[`Response JSON 3`] = `
Object {
"body": "{\\"foo\\":\\"bar\\"}",
"code": 200,
"headers": Object {
"Content-Type": "application/json; charset=utf-8",
},
"status": "OK",
}
`;

exports[`Response Main 1`] = `"text/plain; charset=utf-8"`;

Expand All @@ -19,7 +28,31 @@ Object {
}
`;

exports[`Response Main 3`] = `
Object {
"body": "",
"code": 204,
"headers": Object {
"Content-Type": "text/plain; charset=utf-8",
"foo": "123",
},
"status": "No Content",
}
`;

exports[`Response Redirect 1`] = `
Object {
"body": "",
"code": 301,
"headers": Object {
"Content-Type": "text/plain; charset=utf-8",
"location": "/redirect",
},
"status": "Moved Permanently",
}
`;

exports[`Response Redirect 2`] = `
Object {
"Content-Type": "text/plain; charset=utf-8",
"location": "/redirect",
Expand Down
Loading

0 comments on commit de20cbe

Please sign in to comment.