Implemented HttpError

This commit is contained in:
Kir_Antipov 2023-01-11 09:23:29 +00:00
parent 3f8d7d2115
commit c566a4cfec
2 changed files with 202 additions and 0 deletions

View file

@ -0,0 +1,86 @@
import { HttpResponse } from "@/utils/net";
import { SoftError } from "./soft-error";
/**
* Represents an HTTP error.
*/
export class HttpError extends SoftError {
/**
* The HTTP Response object associated with this error.
*/
private readonly _response: HttpResponse;
/**
* Creates a new {@link HttpError} instance.
*
* @param response - The HTTP Response object associated with this error.
* @param message - The error message.
* @param isSoft - Indicates whether the error is recoverable or not.
*/
constructor(response: HttpResponse, message?: string, isSoft?: boolean) {
super(isSoft ?? isServerError(response), message);
this.name = "HttpError";
this._response = response;
}
/**
* Gets the HTTP Response object associated with this error.
*/
get response(): HttpResponse {
return this._response;
}
/**
* Extracts error information from the given HTTP Response object
* and returns an {@link HttpError} instance.
*
* @param response - The HTTP Response object to extract the error information from.
* @param isSoft - Indicates whether the error is recoverable or not.
*
* @returns A `Promise` that resolves to an {@link HttpError} instance.
*/
static async fromResponse(response: HttpResponse, isSoft?: boolean): Promise<HttpError> {
const cachedResponse = HttpResponse.cache(response);
const errorText = `${response.status} (${
await cachedResponse.text()
.then(x => x && !isHtmlDocument(x) ? `${response.statusText}, ${x}` : response.statusText)
.catch(() => response.statusText)
})`;
return new HttpError(cachedResponse, errorText, isSoft);
}
}
/**
* Determines if the given error is an {@link HttpError}.
*
* @param error - The error to be checked.
*
* @returns `true` if the provided error is an instance of HttpError; otherwise, `false`.
*/
export function isHttpError(error: unknown): error is HttpError {
return error instanceof HttpError;
}
/**
* Determines whether the given `HttpResponse` represents a server error.
*
* @param response - The `HttpResponse` to check.
*
* @returns `true` if the response is a server error; otherwise, `false`.
*/
function isServerError(response: HttpResponse): boolean {
return response && (response.status === 429 || response.status >= 500);
}
/**
* Determines if the given text is an HTML document.
*
* @param text - The string to check.
*
* @returns `true` if the provided string is an HTML document; otherwise, `false`.
*/
function isHtmlDocument(text: string): boolean {
return text.startsWith("<!DOCTYPE html");
}

View file

@ -0,0 +1,116 @@
import { HttpResponse } from "@/utils/net/http-response";
import { HttpError, isHttpError } from "@/utils/errors/http-error";
describe("HttpError", () => {
describe("constructor", () => {
test("initializes with given response and message", () => {
const response = HttpResponse.text("Resource does not exist", { status: 404, statusText: "Not Found" });
const error = new HttpError(response, "An error occurred.", false);
expect(error).toBeInstanceOf(HttpError);
expect(error.name).toBe("HttpError")
expect(error.message).toBe("An error occurred.");
expect(error.isSoft).toBe(false);
expect(error.response).toBe(response);
});
test("initializes with isSoft set to true for server error", () => {
const response = HttpResponse.text("Somebody knows what happened?", { status: 500, statusText: "Internal Server Error" });
const error = new HttpError(response);
expect(error.isSoft).toBe(true);
expect(error.response).toBe(response);
});
test("initializes with isSoft set to false for client error", () => {
const response = HttpResponse.text("It's not my fault!", { status: 400, statusText: "Bad Request" });
const error = new HttpError(response);
expect(error.isSoft).toBe(false);
expect(error.response).toBe(response);
});
});
describe("fromResponse", () => {
test("creates HttpError from given response", async () => {
const response = HttpResponse.text("Resource does not exist", { status: 404, statusText: "Not Found" });
const error = await HttpError.fromResponse(response, false);
expect(error).toBeInstanceOf(HttpError);
expect(error.name).toBe("HttpError")
});
test("includes text content in the error message", async () => {
const response = HttpResponse.json({ error: "Resource does not exist" }, { status: 404, statusText: "Not Found" });
const error = await HttpError.fromResponse(response, false);
expect(error.message).toBe(`404 (Not Found, ${JSON.stringify({ error: "Resource does not exist" })})`);
});
test("does not include HTML content in the error message", async () => {
const htmlContent = "<!DOCTYPE html><html><head><title>Not Found</title></head><body>Page Not Found</body></html>";
const response = HttpResponse.text(htmlContent, { status: 404, statusText: "Not Found" });
const error = await HttpError.fromResponse(response);
expect(error.message).toBe("404 (Not Found)");
});
test("returns soft error for server error codes", async () => {
const response = HttpResponse.text("Somebody knows what happened?", { status: 500, statusText: "Internal Server Error" });
const error = await HttpError.fromResponse(response);
expect(error.isSoft).toBe(true);
});
test("returns soft error for Too Many Requests error code (429)", async () => {
const response = HttpResponse.text("", { status: 429, statusText: "Too Many Requests" });
const error = await HttpError.fromResponse(response);
expect(error.isSoft).toBe(true);
});
test("returns non-soft error for client error codes", async () => {
const response = HttpResponse.text("It's not my fault!", { status: 400, statusText: "Bad Request" });
const error = await HttpError.fromResponse(response);
expect(error.isSoft).toBe(false);
});
test("can still read the response contents after the error is created", async () => {
const response = HttpResponse.json({ error: "Resource does not exist" }, { status: 404, statusText: "Not Found" });
const error = await HttpError.fromResponse(response);
const responseJson = await error.response.json();
expect(error.message).toBe(`404 (Not Found, ${JSON.stringify({ error: "Resource does not exist" })})`);
expect(responseJson).toEqual({ error: "Resource does not exist" });
});
});
});
describe("isHttpError", () => {
test("returns true for HttpError", () => {
const response = HttpResponse.text("Resource does not exist", { status: 404, statusText: "Not Found" });
const error = new HttpError(response, "An error occurred.", false);
expect(isHttpError(error)).toBe(true);
});
test("returns false for non-HttpError errors", () => {
expect(isHttpError(new Error("An error occurred."))).toBe(false);
});
test("returns false for non-error values", () => {
expect(isHttpError("An error occurred.")).toBe(false);
});
});