feat(errors): add @bytelyst/errors package
- ServiceError base class with statusCode, message, details - HTTP errors: BadRequest, Unauthorized, Forbidden, NotFound, Conflict, TooManyRequests - 10 tests passing (vitest) - Superset of all 4 service error files in LysnrAI
This commit is contained in:
parent
d875df09a3
commit
9c0ab36171
18
packages/errors/package.json
Normal file
18
packages/errors/package.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "@bytelyst/errors",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"files": ["dist"],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"test": "vitest run"
|
||||||
|
}
|
||||||
|
}
|
||||||
56
packages/errors/src/__tests__/errors.test.ts
Normal file
56
packages/errors/src/__tests__/errors.test.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import {
|
||||||
|
ServiceError,
|
||||||
|
BadRequestError,
|
||||||
|
UnauthorizedError,
|
||||||
|
ForbiddenError,
|
||||||
|
NotFoundError,
|
||||||
|
ConflictError,
|
||||||
|
TooManyRequestsError,
|
||||||
|
} from "../index.js";
|
||||||
|
|
||||||
|
describe("ServiceError", () => {
|
||||||
|
it("sets statusCode and message", () => {
|
||||||
|
const err = new ServiceError(500, "boom");
|
||||||
|
expect(err.statusCode).toBe(500);
|
||||||
|
expect(err.message).toBe("boom");
|
||||||
|
expect(err).toBeInstanceOf(Error);
|
||||||
|
expect(err).toBeInstanceOf(ServiceError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("supports optional details", () => {
|
||||||
|
const err = new ServiceError(500, "boom", { field: "name" });
|
||||||
|
expect(err.details).toEqual({ field: "name" });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("HTTP error classes", () => {
|
||||||
|
const cases: [string, new () => ServiceError, number, string][] = [
|
||||||
|
["BadRequestError", BadRequestError, 400, "Bad request"],
|
||||||
|
["UnauthorizedError", UnauthorizedError, 401, "Unauthorized"],
|
||||||
|
["ForbiddenError", ForbiddenError, 403, "Forbidden"],
|
||||||
|
["NotFoundError", NotFoundError, 404, "Not found"],
|
||||||
|
["ConflictError", ConflictError, 409, "Conflict"],
|
||||||
|
["TooManyRequestsError", TooManyRequestsError, 429, "Too many requests"],
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const [name, Ctor, expectedStatus, expectedMessage] of cases) {
|
||||||
|
it(`${name} has status ${expectedStatus}`, () => {
|
||||||
|
const err = new Ctor();
|
||||||
|
expect(err.statusCode).toBe(expectedStatus);
|
||||||
|
expect(err.message).toBe(expectedMessage);
|
||||||
|
expect(err).toBeInstanceOf(ServiceError);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it("accepts custom message", () => {
|
||||||
|
const err = new NotFoundError("User not found");
|
||||||
|
expect(err.message).toBe("User not found");
|
||||||
|
expect(err.statusCode).toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accepts details", () => {
|
||||||
|
const err = new TooManyRequestsError("Rate limited", { retryAfter: 60 });
|
||||||
|
expect(err.details).toEqual({ retryAfter: 60 });
|
||||||
|
});
|
||||||
|
});
|
||||||
37
packages/errors/src/http-errors.ts
Normal file
37
packages/errors/src/http-errors.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { ServiceError } from "./service-error.js";
|
||||||
|
|
||||||
|
export class BadRequestError extends ServiceError {
|
||||||
|
constructor(message = "Bad request", details?: Record<string, unknown>) {
|
||||||
|
super(400, message, details);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnauthorizedError extends ServiceError {
|
||||||
|
constructor(message = "Unauthorized", details?: Record<string, unknown>) {
|
||||||
|
super(401, message, details);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ForbiddenError extends ServiceError {
|
||||||
|
constructor(message = "Forbidden", details?: Record<string, unknown>) {
|
||||||
|
super(403, message, details);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NotFoundError extends ServiceError {
|
||||||
|
constructor(message = "Not found", details?: Record<string, unknown>) {
|
||||||
|
super(404, message, details);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ConflictError extends ServiceError {
|
||||||
|
constructor(message = "Conflict", details?: Record<string, unknown>) {
|
||||||
|
super(409, message, details);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TooManyRequestsError extends ServiceError {
|
||||||
|
constructor(message = "Too many requests", details?: Record<string, unknown>) {
|
||||||
|
super(429, message, details);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
packages/errors/src/index.ts
Normal file
9
packages/errors/src/index.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export { ServiceError } from "./service-error.js";
|
||||||
|
export {
|
||||||
|
BadRequestError,
|
||||||
|
UnauthorizedError,
|
||||||
|
ForbiddenError,
|
||||||
|
NotFoundError,
|
||||||
|
ConflictError,
|
||||||
|
TooManyRequestsError,
|
||||||
|
} from "./http-errors.js";
|
||||||
14
packages/errors/src/service-error.ts
Normal file
14
packages/errors/src/service-error.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Base error class for typed HTTP service errors.
|
||||||
|
* All specific error types extend this class.
|
||||||
|
*/
|
||||||
|
export class ServiceError extends Error {
|
||||||
|
constructor(
|
||||||
|
public statusCode: number,
|
||||||
|
message: string,
|
||||||
|
public details?: Record<string, unknown>,
|
||||||
|
) {
|
||||||
|
super(message);
|
||||||
|
this.name = "ServiceError";
|
||||||
|
}
|
||||||
|
}
|
||||||
9
packages/errors/tsconfig.json
Normal file
9
packages/errors/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src"
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["src/**/*.test.ts"]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user