diff --git a/src/App/Decorator/Middleware.ts b/src/App/Decorator/Middleware.ts index af9fe90..0044812 100644 --- a/src/App/Decorator/Middleware.ts +++ b/src/App/Decorator/Middleware.ts @@ -32,7 +32,7 @@ export function validator(method: keyof ValidationTargets, data: Z const targetFunc = descriptor.value as Function; const funcValidator = honoValidator(method, (val, c) => { const parsed = z.object(data).safeParse(val); - if (!parsed.success) return onValidateError(c, data); + if (!parsed.success) return onValidateError(c, parsed.error.issues); return parsed.data; }); reflectingMetadata(targetFunc, funcValidator); diff --git a/src/App/Error/ErrorBuilder.ts b/src/App/Error/ErrorBuilder.ts new file mode 100644 index 0000000..2f1bb42 --- /dev/null +++ b/src/App/Error/ErrorBuilder.ts @@ -0,0 +1,91 @@ +import type { StatusCode } from "hono/utils/http-status"; + +export type IErrorBuilder = { + title: string; + detail: string; + type: string; + instance?: string; + kind?: T; +}; + +/** + * Error builder for Agni based on IETF Error Convention. + * + * @see - RFC 9457 + */ +export default class ErrorBuilder implements IErrorBuilder { + public type: string = "about:blank"; + public instance?: string; + public kind?: T; + + public constructor(public title: string, public detail: string) {} + + private setProps

(prop: P, val: this[P]): this { + this[prop] = val; + return this; + } + + /** + * Set additional fields on error query + */ + public setKind(data: D): ErrorBuilder { + return this.setProps("kind", data) as unknown as ErrorBuilder; + } + + /** + * Set instance or path where error raised on error query + */ + public setInstance(instance: string): this { + return this.setProps("instance", instance); + } + + /** + * Set code error type on error query + */ + public setMDNCodeType(code: StatusCode): this { + return this.setProps("type", `https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/${code}`); + } + + /** + * Set type of error URI on error query + */ + public setType(type: string): this { + return this.setProps("type", type); + } + + /** + * Set the title on error query + */ + public setTitle(title: string): this { + return this.setProps("title", title); + } + + /** + * Set the detail on error query + */ + public setDetail(detail: string): this { + return this.setProps("detail", detail); + } + + /** + * Generate the error query + */ + public build(): IErrorBuilder { + const data: IErrorBuilder = { + type: this.type, + title: this.title, + detail: this.detail + }; + + if (this.instance !== undefined) { + data.instance = this.instance; + } + + if (this.kind !== undefined) { + data.kind = this.kind; + } + + return data; + } +} + diff --git a/src/Function/OnError.ts b/src/Function/OnError.ts index ebbb2eb..7e4b63e 100644 --- a/src/Function/OnError.ts +++ b/src/Function/OnError.ts @@ -1,45 +1,32 @@ import { randomUUID } from "node:crypto"; import type { Context, Env } from "hono"; -import type { HTTPException } from "hono/http-exception"; +import { HTTPException } from "hono/http-exception"; import type { StatusCode } from "hono/utils/http-status"; +import ErrorBuilder from "App/Error/ErrorBuilder.js"; import isJson from "App/Function/IsJson.js"; import Logger from "Logger.js"; -type ErrorRecord = { - message: string; - errMessage?: string; - uuidErr?: string; -}; - export default function onError(err: Error, c: Context): Response { - const errResponse: ErrorRecord = { - message: "" - }; - errResponse.message = "Something happened, but don't worry maybe it's you or our developer."; - errResponse.errMessage = err.message; + const builder = new ErrorBuilder( + "Fatal error from server.", err.message + ); let code: StatusCode = 500; // Expecting this is HTTPError - if (Object.keys(err).includes("status")) { - const newErr = err as HTTPException; - code = newErr.status; - - errResponse.message = newErr.message; - delete errResponse.errMessage; + if (err instanceof HTTPException) { + code = err.status; + builder.setTitle("HTTP Error has thrown."); } - // If fatal error if (code === 500) { - errResponse.uuidErr = randomUUID(); - Logger.child({ uuid: errResponse.uuidErr }).error(err, "Fatal error on server"); + const uuid = randomUUID(); + builder.setKind({ uuid }); + Logger.child({ uuid }).error(err, "Fatal error on server"); } const jsonMethod = isJson(c.req.header()); - if (jsonMethod) return c.json(errResponse, code); + if (jsonMethod) return c.json(builder.build(), code); // You can improve on this side, this is just example. - let msgText = errResponse.message; - if (errResponse.errMessage !== undefined) msgText += `\n${errResponse.errMessage}`; - if (errResponse.uuidErr !== undefined) msgText += `\nUUID Code: ${errResponse.uuidErr}`; - return c.text(msgText, code); + return c.text(`Error: ${builder.title}\n${builder.detail}`, code); } diff --git a/src/Function/OnNotFound.ts b/src/Function/OnNotFound.ts index f60e75e..1810395 100644 --- a/src/Function/OnNotFound.ts +++ b/src/Function/OnNotFound.ts @@ -1,12 +1,16 @@ import type { Context } from "hono"; +import ErrorBuilder from "App/Error/ErrorBuilder.js"; import isJson from "App/Function/IsJson.js"; export function onNotFound(c: Context): Response { - const message = "Path not found."; const code = 404; + const builder = new ErrorBuilder("Not found.", "Path or route not found."); + builder + .setInstance(c.req.path) + .setMDNCodeType(code); const jsonMethod = isJson(c.req.header()); - if (jsonMethod) return c.json({ message }, code); + if (jsonMethod) return c.json(builder.build(), code); - return c.text(message, code); + return c.text(builder.detail, code); } diff --git a/src/Function/OnValidateError.ts b/src/Function/OnValidateError.ts index 46d8c6d..1bb49fd 100644 --- a/src/Function/OnValidateError.ts +++ b/src/Function/OnValidateError.ts @@ -1,14 +1,18 @@ import type { Context } from "hono"; import type { ZodData } from "App/Decorator/Middleware.js"; +import ErrorBuilder from "App/Error/ErrorBuilder.js"; import isJson from "App/Function/IsJson.js"; export function onValidateError(c: Context, data: ZodData): Response { - const message = "Bad request performed."; const code = 400; - const propsOnServer = Object.keys(data); + const builder = new ErrorBuilder("Bad request.", "Bad request performed."); + builder + .setKind(data) + .setInstance(c.req.path) + .setMDNCodeType(code); const jsonMethod = isJson(c.req.header()); - if (jsonMethod) return c.json({ message, propsOnServer }, code); + if (jsonMethod) return c.json(builder.build(), code); - return c.text(message, code); + return c.text(builder.detail, code); }