-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(feat): add error convention and implementing on error hook (#22)
* refactor(app/error): yeah last time instanceof is not working so we'll so back * feat(app/error/errorbuilder): create new errorbuilder * feat(app/error/errorbuilder): add new method * refactor(app/error): improving error using errorbuilder * fix(app/error): fix linter * refactor(app/error): modify validator error * refactor(app/error): modify not found error * fix(app/error): change not found code * refactor(app/error/builder): create method for generate MDN web to type * refactor(app/error/builder): add docs on builder
- Loading branch information
Showing
5 changed files
with
120 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import type { StatusCode } from "hono/utils/http-status"; | ||
|
||
export type IErrorBuilder<T> = { | ||
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<T extends {}> implements IErrorBuilder<T> { | ||
public type: string = "about:blank"; | ||
public instance?: string; | ||
public kind?: T; | ||
|
||
public constructor(public title: string, public detail: string) {} | ||
|
||
private setProps<P extends keyof this>(prop: P, val: this[P]): this { | ||
this[prop] = val; | ||
return this; | ||
} | ||
|
||
/** | ||
* Set additional fields on error query | ||
*/ | ||
public setKind<D extends T>(data: D): ErrorBuilder<D> { | ||
return this.setProps("kind", data) as unknown as ErrorBuilder<D>; | ||
} | ||
|
||
/** | ||
* 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<T> { | ||
const data: IErrorBuilder<T> = { | ||
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; | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Env, any, any>): 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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<any, any, any>): 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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<any, any, any>, data: ZodData<any>): 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); | ||
} |