Skip to content
This repository has been archived by the owner on Jun 16, 2024. It is now read-only.

Commit

Permalink
feat: add API response functions and ApiError
Browse files Browse the repository at this point in the history
  • Loading branch information
Ted Kesgar committed Sep 23, 2020
1 parent fe4da03 commit 8c332cf
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 0 deletions.
138 changes: 138 additions & 0 deletions src/__tests__/api.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { createApiSuccess, createApiFail, createApiError } from "..";
import { ApiCode, ApiError, ApiStatus } from "../api";

describe("createSuccess", () => {
it("should return success object with null data by default", () => {
const apiResponse = createApiSuccess();

expect(apiResponse).toEqual({
status: ApiStatus.Success,
data: null,
});
});

it("should return success object with data", () => {
const apiResponse = createApiSuccess({ subaru: "shuba shuba shuba" });

expect(apiResponse).toEqual({
status: ApiStatus.Success,
data: { subaru: "shuba shuba shuba" },
});
});
});

describe("createFail", () => {
it("should return fail object with null data by default", () => {
const apiResponse = createApiFail(
"ILLEGAL_STATE",
"Resource is in invalid state"
);

expect(apiResponse).toEqual({
status: ApiStatus.Fail,
code: "ILLEGAL_STATE",
message: "Resource is in invalid state",
data: null,
});
});

it("should return fail object with data", () => {
const apiResponse = createApiFail(
"ILLEGAL_STATE",
"Resource is in invalid state",
{ subaru: "shuba shuba shuba" }
);

expect(apiResponse).toEqual({
status: ApiStatus.Fail,
code: "ILLEGAL_STATE",
message: "Resource is in invalid state",
data: { subaru: "shuba shuba shuba" },
});
});
});

describe("createError", () => {
it("should return error object with null data by default", () => {
const apiResponse = createApiError(
"UPSTREAM_ERROR",
"Upstream resource returns error"
);

expect(apiResponse).toEqual({
status: ApiStatus.Error,
code: "UPSTREAM_ERROR",
message: "Upstream resource returns error",
data: null,
});
});

it("should return error object with data", () => {
const apiResponse = createApiError(
"UPSTREAM_ERROR",
"Upstream resource returns error",
{ subaru: "shuba shuba shuba" }
);

expect(apiResponse).toEqual({
status: ApiStatus.Error,
code: "UPSTREAM_ERROR",
message: "Upstream resource returns error",
data: { subaru: "shuba shuba shuba" },
});
});
});

describe("ApiError", () => {
it("should return ApiError with default values", () => {
const err = new ApiError("Bad API request");

expect(err.message).toBe("Bad API request");
expect(err.status).toBe(ApiStatus.Fail);
expect(err.code).toBe(ApiCode.Failed);
expect(err.statusCode).toBe(400);
expect(err.data).toEqual(null);
});

it("should return ApiError with custom values", () => {
const err = new ApiError("Database unavailable", {
status: ApiStatus.Error,
statusCode: 500,
code: "DB_ERROR",
data: { subaru: "shuba shuba shuba" },
});

expect(err.message).toBe("Database unavailable");
expect(err.status).toBe(ApiStatus.Error);
expect(err.code).toBe("DB_ERROR");
expect(err.statusCode).toBe(500);
expect(err.data).toEqual({ subaru: "shuba shuba shuba" });
});

it("should return fail object if ApiError with status = fail is serialized into JSON", () => {
const err = new ApiError("Bad API request");

expect(JSON.parse(JSON.stringify(err))).toEqual({
status: ApiStatus.Fail,
code: ApiCode.Failed,
message: "Bad API request",
data: null,
});
});

it("should return error object if ApiError with status = error is serialized into JSON", () => {
const err = new ApiError("Database unavailable", {
status: ApiStatus.Error,
statusCode: 500,
code: "DB_ERROR",
data: { subaru: "shuba shuba shuba" },
});

expect(JSON.parse(JSON.stringify(err))).toEqual({
status: ApiStatus.Error,
code: "DB_ERROR",
message: "Database unavailable",
data: { subaru: "shuba shuba shuba" },
});
});
});
79 changes: 79 additions & 0 deletions src/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
export enum ApiStatus {
Success = "success",
Fail = "fail",
Error = "error",
}

export enum ApiCode {
Failed = "FAILED",
AuthInvalid = "AUTH_INVALID",
AuthForbidden = "AUTH_FORBIDDEN",
}

export interface ApiResponse<T = unknown> {
status: ApiStatus;
data: T;
code?: string;
message?: string;
}

export function createApiSuccess<T = unknown>(data: T = null): ApiResponse<T> {
return { status: ApiStatus.Success, data };
}

export function createApiFail<T = unknown>(
code: string,
message: string,
data: T = null
): ApiResponse<T> {
return { status: ApiStatus.Fail, code, message, data };
}

export function createApiError<T = unknown>(
code: string,
message: string,
data: T = null
): ApiResponse<T> {
return { status: ApiStatus.Error, code, message, data };
}

interface ApiErrorConstructorOpts<T> {
status?: ApiStatus.Fail | ApiStatus.Error;
code?: string;
data?: T;
statusCode?: number;
}

export class ApiError<T = unknown> extends Error {
readonly status: ApiStatus.Fail | ApiStatus.Error;
readonly code: string;
readonly data: T;
readonly statusCode: number;

constructor(message: string, opts: ApiErrorConstructorOpts<T> = {}) {
const {
status = ApiStatus.Fail,
code = ApiCode.Failed,
data = null,
statusCode = 400,
} = opts;

super(message);

this.code = code;
this.status = status;
this.statusCode = statusCode;
this.data = data;
}

toJSON(): ApiResponse<T> {
switch (this.status) {
case ApiStatus.Fail:
return createApiFail(this.code, this.message, this.data);
case ApiStatus.Error:
return createApiError(this.code, this.message, this.data);
default:
throw new Error(`Unknown status: ${this.status}`);
}
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./handle";
export * from "./api";

0 comments on commit 8c332cf

Please sign in to comment.