Skip to content

Commit

Permalink
Merge pull request #1051 from samchon/feature/validate.log
Browse files Browse the repository at this point in the history
Close #1037: validate.log option for @TypedRoute decorators.
  • Loading branch information
samchon authored Sep 26, 2024
2 parents 7002b46 + f2b7229 commit 40d4e04
Show file tree
Hide file tree
Showing 91 changed files with 1,509 additions and 272 deletions.
10 changes: 3 additions & 7 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ name: build
on:
push:
paths:
- 'packages/*/src/**'
- 'packages/*/package.json'
- 'packages/{core,fetcher,sdk}/src/**'
- 'packages/{core,fetcher,sdk}/package.json'
- 'test/**'
- 'test/package.json'
pull_request:
Expand All @@ -24,8 +24,4 @@ jobs:

- name: test
working-directory: ./test
run: npm start

- name: migrate
working-directory: ./packages/migrate
run: npm install && npm run build && npm run test
run: npm start
27 changes: 27 additions & 0 deletions .github/workflows/migrate.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: build
on:
push:
paths:
- 'packages/migrate/assets/input/*.json'
- 'packages/migrate/src/**'
- 'packages/migrate/test/**'
- 'packages/migrate/package.json'
pull_request:
paths:
- 'packages/*/src/**'
- 'packages/*/package.json'
- 'test/**'
- 'test/package.json'

jobs:
Ubuntu:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x

- name: test
working-directory: ./test
run: npm start
2 changes: 1 addition & 1 deletion benchmark/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"reflect-metadata": "^0.2.2",
"tgrid": "^1.0.3",
"tstl": "^3.0.0",
"typia": "^6.10.4"
"typia": "^6.11.0"
},
"devDependencies": {
"@types/autocannon": "^7.9.0",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": true,
"name": "@nestia/station",
"version": "3.14.1",
"version": "3.15.0",
"description": "Nestia station",
"scripts": {
"build": "node build/index.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/benchmark/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"ts-patch": "^3.2.1",
"typescript": "5.5.4",
"typescript-transform-paths": "^3.4.7",
"typia": "^6.10.0",
"typia": "^6.11.0",
"uuid": "^10.0.0"
},
"dependencies": {
Expand Down
12 changes: 6 additions & 6 deletions packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nestia/core",
"version": "3.14.1",
"version": "3.15.0",
"description": "Super-fast validation decorators of NestJS",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
Expand Down Expand Up @@ -36,10 +36,10 @@
},
"homepage": "https://nestia.io",
"dependencies": {
"@nestia/fetcher": "^3.14.1",
"@nestia/fetcher": "^3.15.0",
"@nestjs/common": ">=7.0.1",
"@nestjs/core": ">=7.0.1",
"@samchon/openapi": "^1.0.2",
"@samchon/openapi": "^1.1.0",
"detect-ts-node": "^1.0.5",
"get-function-location": "^2.0.0",
"glob": "^7.2.0",
Expand All @@ -49,16 +49,16 @@
"reflect-metadata": ">=0.1.12",
"rxjs": ">=6.0.3",
"tgrid": "^1.0.0",
"typia": "^6.10.0",
"typia": "^6.11.0",
"ws": "^7.5.3"
},
"peerDependencies": {
"@nestia/fetcher": ">=3.14.1",
"@nestia/fetcher": ">=3.15.0",
"@nestjs/common": ">=7.0.1",
"@nestjs/core": ">=7.0.1",
"reflect-metadata": ">=0.1.12",
"rxjs": ">=6.0.3",
"typia": ">=6.10.0 <7.0.0"
"typia": ">=6.11.0 <7.0.0"
},
"devDependencies": {
"@fastify/multipart": "^8.1.0",
Expand Down
52 changes: 44 additions & 8 deletions packages/core/src/decorators/EncryptedRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import typia from "typia";

import { IResponseBodyStringifier } from "../options/IResponseBodyStringifier";
import { Singleton } from "../utils/Singleton";
import { TypedRoute } from "./TypedRoute";
import { ENCRYPTION_METADATA_KEY } from "./internal/EncryptedConstant";
import { get_path_and_stringify } from "./internal/get_path_and_stringify";
import { headers_to_object } from "./internal/headers_to_object";
Expand Down Expand Up @@ -85,6 +86,33 @@ export namespace EncryptedRoute {
*/
export const Delete = Generator("Delete");

/**
* Set the logger function for the response validation failure.
*
* If you've configured the transformation option to `validate.log`
* in the `tsconfig.json` file, then the error log information of the
* response validation failure would be logged through this function
* instead of throwing the 400 bad request error.
*
* By the way, be careful. If you've configured the response
* transformation option to be `validate.log` or `validateEquals.log`,
* client may get wrong response data. Therefore, this way is not
* recommended in the common backend server case.
*
* @param func Logger function
* @default console.log
*/
export function setValidateErrorLogger(
func: (log: IValidateErrorLog) => void,
): void {
TypedRoute.setValidateErrorLogger(func);
}

export import IValidateErrorLog = TypedRoute.IValidateErrorLog;

/**
* @internal
*/
function Generator(method: "Get" | "Post" | "Put" | "Patch" | "Delete") {
function route(path?: string | string[]): MethodDecorator;
function route<T>(
Expand All @@ -97,8 +125,8 @@ export namespace EncryptedRoute {

function route(...args: any[]): MethodDecorator {
const [path, stringify] = get_path_and_stringify(
`EncryptedRoute.${method}`,
)(...args);
() => TypedRoute.__logger,
)(`EncryptedRoute.${method}`)(...args);
return applyDecorators(
ROUTERS[method](path),
UseInterceptors(new EncryptedRouteInterceptor(method, stringify)),
Expand Down Expand Up @@ -130,7 +158,11 @@ for (const method of [
class EncryptedRouteInterceptor implements NestInterceptor {
public constructor(
private readonly method: string,
private readonly stringify: (input: any) => string,
private readonly stringify: (
input: any,
method: string,
path: string,
) => string,
) {}

public intercept(context: ExecutionContext, next: CallHandler) {
Expand All @@ -149,11 +181,15 @@ class EncryptedRouteInterceptor implements NestInterceptor {
`Error on EncryptedRoute.${this.method}(): no password found.`,
);

const headers: Singleton<Record<string, string>> = new Singleton(() => {
const request: express.Request = http.getRequest();
return headers_to_object(request.headers);
});
const body: string | undefined = this.stringify(value);
const request: express.Request = http.getRequest();
const headers: Singleton<Record<string, string>> = new Singleton(() =>
headers_to_object(request.headers),
);
const body: string | undefined = this.stringify(
value,
request.method,
request.url,
);
const password: IEncryptionPassword =
typeof param === "function"
? param({
Expand Down
77 changes: 71 additions & 6 deletions packages/core/src/decorators/TypedRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import { HttpArgumentsHost } from "@nestjs/common/interfaces";
import type express from "express";
import { catchError, map } from "rxjs/operators";
import typia from "typia";
import typia, { IValidation } from "typia";

import { IResponseBodyStringifier } from "../options/IResponseBodyStringifier";
import { get_path_and_stringify } from "./internal/get_path_and_stringify";
Expand Down Expand Up @@ -75,6 +75,64 @@ export namespace TypedRoute {
*/
export const Delete = Generator("Delete");

/**
* Set the logger function for the response validation failure.
*
* If you've configured the transformation option to `validate.log`
* in the `tsconfig.json` file, then the error log information of the
* response validation failure would be logged through this function
* instead of throwing the 400 bad request error.
*
* By the way, be careful. If you've configured the response
* transformation option to be `validate.log` or `validateEquals.log`,
* client may get wrong response data. Therefore, this way is not
* recommendedin the common backend server case.
*
* @param func Logger function
* @default console.log
*/
export function setValidateErrorLogger(
func: (log: IValidateErrorLog) => void,
): void {
__logger = func;
}

/**
* Error log information of the response validation failure.
*
* `IValidationErrorLog` is a structure representing the error log
* information when the returned value from the `@TypedRoute` or
* `@EncryptedRoute` decorated controller method is not following
* the promised type `T`.
*
* If you've configured the transformation option to `validate.log` or
* `validateEquals.log` in the `tsconfig.json` file, then this error log
* information `IValidateErrorLog` would be logged through the
* {@link setValidateErrorLogger} function instead of throwing the
* 400 bad request error.
*/
export interface IValidateErrorLog {
/**
* HTTP method of the request.
*/
method: string;

/**
* HTTP path of the request.
*/
path: string;

/**
* Validation error informations with detailed reasons.
*/
errors: IValidation.IError[];
}

/**
* @internal
*/
export let __logger: (log: IValidateErrorLog) => void = console.log;

/**
* @internal
*/
Expand All @@ -87,9 +145,9 @@ export namespace TypedRoute {
): MethodDecorator;

function route(...args: any[]): MethodDecorator {
const [path, stringify] = get_path_and_stringify(`TypedRoute.${method}`)(
...args,
);
const [path, stringify] = get_path_and_stringify(() => __logger)(
`TypedRoute.${method}`,
)(...args);
return applyDecorators(
ROUTERS[method](path),
UseInterceptors(new TypedRouteInterceptor(stringify)),
Expand Down Expand Up @@ -118,15 +176,22 @@ for (const method of [
* @internal
*/
class TypedRouteInterceptor implements NestInterceptor {
public constructor(private readonly stringify: (input: any) => string) {}
public constructor(
private readonly stringify: (
input: any,
method: string,
path: string,
) => string,
) {}

public intercept(context: ExecutionContext, next: CallHandler) {
const http: HttpArgumentsHost = context.switchToHttp();
const request: express.Request = http.getRequest();
const response: express.Response = http.getResponse();
response.header("Content-Type", "application/json");

return next.handle().pipe(
map((value) => this.stringify(value)),
map((value) => this.stringify(value, request.method, request.url)),
catchError((err) => route_error(http.getRequest(), err)),
);
}
Expand Down
Loading

0 comments on commit 40d4e04

Please sign in to comment.