Skip to content

Commit 6839d75

Browse files
authored
chore(feat): add middleware decorator and change validator trait (#11)
* refactor(app/decorator/validator): change validator trait from being single validator to middleware * refactor(app/decorator): changing and merging validator to middleware * feat(app/decorator/middleware): implementing the working middleware * fix: false import (again, duh) * refactoring(app/decorator/middleware): overloading middleware function * refactor(app/decorator/middleware): add jsdoc for middleware
1 parent ce6b72e commit 6839d75

File tree

6 files changed

+78
-44
lines changed

6 files changed

+78
-44
lines changed

src/App/Decorator/Middleware.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import "reflect-metadata";
2+
import type { MiddlewareHandler, ValidationTargets } from "hono";
3+
import { validator as honoValidator } from "hono/validator";
4+
import { z } from "zod";
5+
import { MetadataMiddlewareConstant } from "App/Types/ControllerConstant.js";
6+
import type Controller from "Controller/Controller.js";
7+
import { onValidateError } from "Function/OnValidateError.js";
8+
9+
export type ZodData<V> = { [VK in keyof V]: any };
10+
11+
function reflectingMetadata(target: Function, func: MiddlewareHandler): void {
12+
if (!Reflect.hasMetadata(MetadataMiddlewareConstant, target)) {
13+
Reflect.defineMetadata(MetadataMiddlewareConstant, [func], target);
14+
return;
15+
}
16+
17+
const metadata = Reflect.getMetadata(MetadataMiddlewareConstant, target) as MiddlewareHandler[];
18+
metadata.push(func);
19+
20+
Reflect.deleteMetadata(MetadataMiddlewareConstant, target);
21+
Reflect.defineMetadata(MetadataMiddlewareConstant, metadata, target);
22+
}
23+
24+
/**
25+
* Validate data for route.
26+
*
27+
* @param method - Target of request
28+
* @param data - Data to validate
29+
*/
30+
export function validator<T extends {}>(method: keyof ValidationTargets, data: ZodData<T>): Function {
31+
return function decorate(target: Controller, propKey: string, descriptor: PropertyDescriptor): void {
32+
const targetFunc = descriptor.value as Function;
33+
const funcValidator = honoValidator(method, (val, c) => {
34+
const parsed = z.object(data).safeParse(val);
35+
if (!parsed.success) return onValidateError(c, data);
36+
return parsed.data;
37+
});
38+
reflectingMetadata(targetFunc, funcValidator);
39+
};
40+
}
41+
42+
/**
43+
* Add middleware on route.
44+
*
45+
* @param handlers - Middleware handlers for route
46+
* @see MiddlewareHandler
47+
*/
48+
export function middleware(...handlers: MiddlewareHandler[] | Promise<MiddlewareHandler>[] | Promise<void>[]): Function {
49+
return function decorate(target: Controller, propKey: string, descriptor: PropertyDescriptor): void {
50+
const targetFunc = descriptor.value as Function;
51+
for (const handler of handlers) {
52+
const targetHandler = handler as MiddlewareHandler;
53+
reflectingMetadata(targetFunc, targetHandler);
54+
}
55+
};
56+
}

src/App/Decorator/Validator.ts

Lines changed: 0 additions & 32 deletions
This file was deleted.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
export const MetadataConstant = "agniRouting";
2-
export const MetadataValidatorConstant = "agniValidator";
2+
export const MetadataMiddlewareConstant = "agniMiddleware";

src/Controller/Controller.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Factory } from "hono/factory";
33
import { createFactory } from "hono/factory";
44
import type { HandlerInterface, MiddlewareHandler } from "hono/types";
55
import { InsufficientControllerMethodError, NotSupportedMethodError } from "App/Error/AppError.js";
6-
import { MetadataConstant, MetadataValidatorConstant } from "App/Types/ControllerConstant.js";
6+
import { MetadataConstant, MetadataMiddlewareConstant } from "App/Types/ControllerConstant.js";
77
import type {
88
AgniRoutingMetadata,
99
AgniSupportedMethod,
@@ -40,19 +40,17 @@ export default class Controller {
4040
throw new NotSupportedMethodError();
4141
}
4242

43-
/**
44-
* TODO [2024-02-27]: Add support for middleware, multi handler/middleware
45-
*/
4643
const ctx: DefaultHonoFunctionContext[] = [];
47-
if (metadataKeys.includes(MetadataValidatorConstant)) {
48-
const middleware = Reflect.getMetadata(MetadataValidatorConstant, func) as MiddlewareHandler;
49-
ctx.push(this._honoFactory.createMiddleware(middleware));
44+
if (metadataKeys.includes(MetadataMiddlewareConstant)) {
45+
const middleware = Reflect.getMetadata(MetadataMiddlewareConstant, func) as MiddlewareHandler[];
46+
for (const midFunc of middleware) {
47+
ctx.push(this._honoFactory.createMiddleware(midFunc));
48+
}
5049
}
5150

5251
ctx.push(func);
53-
Logger.info(`Register ${metadata.method}:${metadata.path} route`);
52+
Logger.info(`Register ${metadata.method}:${metadata.path} route [${ctx.length} handler]`);
5453

55-
// TODO [2024-03-01]: How to bypass this?
5654
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
5755
// @ts-expect-error
5856
const handlers = this._honoFactory.createHandlers(...ctx);

src/Controller/HelloController.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
1+
import type { Next } from "hono";
2+
import { cors } from "hono/cors";
13
import { z } from "zod";
24
import httpRoute from "App/Decorator/HttpRoute.js";
3-
import validator from "App/Decorator/Validator.js";
5+
import { middleware, validator } from "App/Decorator/Middleware.js";
46
import type { DefaultHonoContext, HonoInputContext } from "App/Types/ControllerTypes.js";
7+
import Logger from "Logger.js";
58
import Controller from "./Controller.js";
69

710
type HelloValidator = {
811
message: string;
912
};
1013

14+
// Since we cannot calling this keyword on decorator,
15+
// you must create some function outside the class or make some new function file.
16+
async function indexMiddleware(_: DefaultHonoContext, next: Next): Promise<void> {
17+
Logger.info("You're accessing index!");
18+
await next();
19+
Logger.info("Done!");
20+
}
21+
1122
export default class HelloController extends Controller {
1223
@httpRoute("get", "/")
24+
@middleware(cors(), indexMiddleware)
1325
public hellow(c: DefaultHonoContext): Response {
1426
return c.text("Say hello world!");
1527
}

src/Function/OnValidateError.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Context } from "hono";
2-
import type { ZodData } from "App/Decorator/Validator.js";
2+
import type { ZodData } from "App/Decorator/Middleware.js";
33
import isJson from "App/Function/IsJson.js";
44

55
export function onValidateError(c: Context<any, any, any>, data: ZodData<any>): Response {

0 commit comments

Comments
 (0)