From f10b22444465484b5ed99efdd06781ca204e7e7e Mon Sep 17 00:00:00 2001 From: Jeongho Nam Date: Thu, 26 Sep 2024 15:05:05 +0900 Subject: [PATCH 1/4] Close #1037: `validate.log` option for `@TypedRoute` decorators. --- package.json | 2 +- packages/core/package.json | 6 +- .../core/src/decorators/EncryptedRoute.ts | 52 +++++++-- packages/core/src/decorators/TypedRoute.ts | 77 +++++++++++-- .../internal/get_path_and_stringify.ts | 44 ++++++-- .../src/options/INestiaTransformOptions.ts | 9 +- .../src/options/IResponseBodyStringifier.ts | 7 +- .../src/programmers/TypedRouteProgrammer.ts | 54 ++++++++-- packages/fetcher/package.json | 2 +- packages/sdk/package.json | 10 +- .../nestia.config.ts | 15 +++ .../src/Backend.ts | 25 +++++ .../src/api/HttpError.ts | 1 + .../src/api/IConnection.ts | 1 + .../src/api/Primitive.ts | 1 + .../src/api/Resolved.ts | 1 + .../src/api/functional/bbs/articles/index.ts | 44 ++++++++ .../src/api/functional/bbs/index.ts | 7 ++ .../src/api/functional/health/index.ts | 35 ++++++ .../src/api/functional/index.ts | 2 +- .../src/api/index.ts | 4 + .../src/api/module.ts | 6 ++ .../src/api/structures/IBbsArticle.ts | 11 ++ .../src/controllers/BbsArticleController.ts | 21 ++++ .../src/controllers/HealthController.ts | 8 ++ .../features/api/test_api_bbs_article_at.ts | 34 ++++++ .../features/api/test_api_health_check.ts | 5 + .../src/test/index.ts | 52 +++++++++ .../swagger.json | 1 + .../tsconfig.json | 102 ++++++++++++++++++ .../nestia.config.ts | 15 +++ .../src/Backend.ts | 25 +++++ .../src/api/HttpError.ts | 1 + .../src/api/IConnection.ts | 1 + .../src/api/Primitive.ts | 1 + .../src/api/Resolved.ts | 1 + .../src/api/functional/bbs/articles/index.ts | 44 ++++++++ .../src/api/functional/bbs/index.ts | 7 ++ .../src/api/functional/health/index.ts | 35 ++++++ .../src/api/functional/index.ts | 8 ++ .../src/api/index.ts | 4 + .../src/api/module.ts | 6 ++ .../src/api/structures/IBbsArticle.ts | 11 ++ .../src/controllers/BbsArticleController.ts | 21 ++++ .../src/controllers/HealthController.ts | 8 ++ .../features/api/test_api_bbs_article_at.ts | 34 ++++++ .../features/api/test_api_health_check.ts | 5 + .../src/test/index.ts | 52 +++++++++ .../swagger.json | 1 + .../tsconfig.json | 102 ++++++++++++++++++ .../nestia.config.ts | 15 +++ .../route-manual-validate-log/src/Backend.ts | 25 +++++ .../src/api/HttpError.ts | 1 + .../src/api/IConnection.ts | 1 + .../src/api/Primitive.ts | 1 + .../src/api/Resolved.ts | 1 + .../src/api/functional/bbs/articles/index.ts | 44 ++++++++ .../src/api/functional/bbs/index.ts | 7 ++ .../src/api/functional/health/index.ts | 35 ++++++ .../src/api/functional/index.ts | 8 ++ .../src/api/index.ts | 4 + .../src/api/module.ts | 6 ++ .../src/api/structures/IBbsArticle.ts | 11 ++ .../src/controllers/BbsArticleController.ts | 21 ++++ .../src/controllers/HealthController.ts | 8 ++ .../features/api/test_api_bbs_article_at.ts | 34 ++++++ .../features/api/test_api_health_check.ts | 5 + .../src/test/index.ts | 52 +++++++++ .../route-manual-validate-log/swagger.json | 1 + .../route-manual-validate-log/tsconfig.json | 102 ++++++++++++++++++ test/package.json | 8 +- .../src/api/functional/health/index.ts | 29 ----- .../src/api/functional/performance/index.ts | 33 ------ .../src/api/structures/IPerformance.ts | 10 -- .../success/src/api/structures/ISystem.ts | 81 -------------- .../src/controllers/PerformanceController.ts | 16 --- test/template/success/src/test/index.ts | 14 ++- 77 files changed, 1375 insertions(+), 219 deletions(-) create mode 100644 test/features/route-manual-validate-log-encrypted/nestia.config.ts create mode 100644 test/features/route-manual-validate-log-encrypted/src/Backend.ts create mode 100644 test/features/route-manual-validate-log-encrypted/src/api/HttpError.ts create mode 100644 test/features/route-manual-validate-log-encrypted/src/api/IConnection.ts create mode 100644 test/features/route-manual-validate-log-encrypted/src/api/Primitive.ts create mode 100644 test/features/route-manual-validate-log-encrypted/src/api/Resolved.ts create mode 100644 test/features/route-manual-validate-log-encrypted/src/api/functional/bbs/articles/index.ts create mode 100644 test/features/route-manual-validate-log-encrypted/src/api/functional/bbs/index.ts create mode 100644 test/features/route-manual-validate-log-encrypted/src/api/functional/health/index.ts rename test/{template/success => features/route-manual-validate-log-encrypted}/src/api/functional/index.ts (83%) create mode 100644 test/features/route-manual-validate-log-encrypted/src/api/index.ts create mode 100644 test/features/route-manual-validate-log-encrypted/src/api/module.ts create mode 100644 test/features/route-manual-validate-log-encrypted/src/api/structures/IBbsArticle.ts create mode 100644 test/features/route-manual-validate-log-encrypted/src/controllers/BbsArticleController.ts create mode 100644 test/features/route-manual-validate-log-encrypted/src/controllers/HealthController.ts create mode 100644 test/features/route-manual-validate-log-encrypted/src/test/features/api/test_api_bbs_article_at.ts create mode 100644 test/features/route-manual-validate-log-encrypted/src/test/features/api/test_api_health_check.ts create mode 100644 test/features/route-manual-validate-log-encrypted/src/test/index.ts create mode 100644 test/features/route-manual-validate-log-encrypted/swagger.json create mode 100644 test/features/route-manual-validate-log-encrypted/tsconfig.json create mode 100644 test/features/route-manual-validate-log-fastify/nestia.config.ts create mode 100644 test/features/route-manual-validate-log-fastify/src/Backend.ts create mode 100644 test/features/route-manual-validate-log-fastify/src/api/HttpError.ts create mode 100644 test/features/route-manual-validate-log-fastify/src/api/IConnection.ts create mode 100644 test/features/route-manual-validate-log-fastify/src/api/Primitive.ts create mode 100644 test/features/route-manual-validate-log-fastify/src/api/Resolved.ts create mode 100644 test/features/route-manual-validate-log-fastify/src/api/functional/bbs/articles/index.ts create mode 100644 test/features/route-manual-validate-log-fastify/src/api/functional/bbs/index.ts create mode 100644 test/features/route-manual-validate-log-fastify/src/api/functional/health/index.ts create mode 100644 test/features/route-manual-validate-log-fastify/src/api/functional/index.ts create mode 100644 test/features/route-manual-validate-log-fastify/src/api/index.ts create mode 100644 test/features/route-manual-validate-log-fastify/src/api/module.ts create mode 100644 test/features/route-manual-validate-log-fastify/src/api/structures/IBbsArticle.ts create mode 100644 test/features/route-manual-validate-log-fastify/src/controllers/BbsArticleController.ts create mode 100644 test/features/route-manual-validate-log-fastify/src/controllers/HealthController.ts create mode 100644 test/features/route-manual-validate-log-fastify/src/test/features/api/test_api_bbs_article_at.ts create mode 100644 test/features/route-manual-validate-log-fastify/src/test/features/api/test_api_health_check.ts create mode 100644 test/features/route-manual-validate-log-fastify/src/test/index.ts create mode 100644 test/features/route-manual-validate-log-fastify/swagger.json create mode 100644 test/features/route-manual-validate-log-fastify/tsconfig.json create mode 100644 test/features/route-manual-validate-log/nestia.config.ts create mode 100644 test/features/route-manual-validate-log/src/Backend.ts create mode 100644 test/features/route-manual-validate-log/src/api/HttpError.ts create mode 100644 test/features/route-manual-validate-log/src/api/IConnection.ts create mode 100644 test/features/route-manual-validate-log/src/api/Primitive.ts create mode 100644 test/features/route-manual-validate-log/src/api/Resolved.ts create mode 100644 test/features/route-manual-validate-log/src/api/functional/bbs/articles/index.ts create mode 100644 test/features/route-manual-validate-log/src/api/functional/bbs/index.ts create mode 100644 test/features/route-manual-validate-log/src/api/functional/health/index.ts create mode 100644 test/features/route-manual-validate-log/src/api/functional/index.ts create mode 100644 test/features/route-manual-validate-log/src/api/index.ts create mode 100644 test/features/route-manual-validate-log/src/api/module.ts create mode 100644 test/features/route-manual-validate-log/src/api/structures/IBbsArticle.ts create mode 100644 test/features/route-manual-validate-log/src/controllers/BbsArticleController.ts create mode 100644 test/features/route-manual-validate-log/src/controllers/HealthController.ts create mode 100644 test/features/route-manual-validate-log/src/test/features/api/test_api_bbs_article_at.ts create mode 100644 test/features/route-manual-validate-log/src/test/features/api/test_api_health_check.ts create mode 100644 test/features/route-manual-validate-log/src/test/index.ts create mode 100644 test/features/route-manual-validate-log/swagger.json create mode 100644 test/features/route-manual-validate-log/tsconfig.json delete mode 100644 test/template/success/src/api/functional/health/index.ts delete mode 100644 test/template/success/src/api/functional/performance/index.ts delete mode 100644 test/template/success/src/api/structures/IPerformance.ts delete mode 100644 test/template/success/src/api/structures/ISystem.ts delete mode 100644 test/template/success/src/controllers/PerformanceController.ts diff --git a/package.json b/package.json index 33bd4396c..2e39a09ba 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@nestia/station", - "version": "3.14.1", + "version": "3.15.0-dev.20240926", "description": "Nestia station", "scripts": { "build": "node build/index.js", diff --git a/packages/core/package.json b/packages/core/package.json index e0b0033bb..d0449cd39 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@nestia/core", - "version": "3.14.1", + "version": "3.15.0-dev.20240926", "description": "Super-fast validation decorators of NestJS", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -36,7 +36,7 @@ }, "homepage": "https://nestia.io", "dependencies": { - "@nestia/fetcher": "^3.14.1", + "@nestia/fetcher": "../fetcher/nestia-fetcher-3.15.0-dev.20240926.tgz", "@nestjs/common": ">=7.0.1", "@nestjs/core": ">=7.0.1", "@samchon/openapi": "^1.0.2", @@ -53,7 +53,7 @@ "ws": "^7.5.3" }, "peerDependencies": { - "@nestia/fetcher": ">=3.14.1", + "@nestia/fetcher": ">=3.15.0-dev.20240926", "@nestjs/common": ">=7.0.1", "@nestjs/core": ">=7.0.1", "reflect-metadata": ">=0.1.12", diff --git a/packages/core/src/decorators/EncryptedRoute.ts b/packages/core/src/decorators/EncryptedRoute.ts index 8758f43c1..711121d05 100644 --- a/packages/core/src/decorators/EncryptedRoute.ts +++ b/packages/core/src/decorators/EncryptedRoute.ts @@ -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"; @@ -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( @@ -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)), @@ -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) { @@ -149,11 +181,15 @@ class EncryptedRouteInterceptor implements NestInterceptor { `Error on EncryptedRoute.${this.method}(): no password found.`, ); - const headers: Singleton> = 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> = 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({ diff --git a/packages/core/src/decorators/TypedRoute.ts b/packages/core/src/decorators/TypedRoute.ts index e335318ae..4b47f2c07 100644 --- a/packages/core/src/decorators/TypedRoute.ts +++ b/packages/core/src/decorators/TypedRoute.ts @@ -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"; @@ -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 */ @@ -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)), @@ -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)), ); } diff --git a/packages/core/src/decorators/internal/get_path_and_stringify.ts b/packages/core/src/decorators/internal/get_path_and_stringify.ts index 7301e83df..fbbd367b1 100644 --- a/packages/core/src/decorators/internal/get_path_and_stringify.ts +++ b/packages/core/src/decorators/internal/get_path_and_stringify.ts @@ -3,13 +3,20 @@ import typia, { IValidation, TypeGuardError } from "typia"; import { IResponseBodyStringifier } from "../../options/IResponseBodyStringifier"; import { NoTransformConfigurationError } from "../NoTransformConfigurationError"; +import { TypedRoute } from "../TypedRoute"; /** * @internal */ export const get_path_and_stringify = + (logger: () => (log: TypedRoute.IValidateErrorLog) => void) => (method: string) => - (...args: any[]): [string | string[] | undefined, (input: any) => string] => { + ( + ...args: any[] + ): [ + string | string[] | undefined, + (input: any, _method: string, _path: string) => string, + ] => { const path: string | string[] | null | undefined = args[0] === undefined || typeof args[0] === "string" || @@ -18,23 +25,32 @@ export const get_path_and_stringify = : null; const functor: IResponseBodyStringifier | undefined = path === null ? args[0] : args[1]; - return [path ?? undefined, take(method)(functor)]; + return [path ?? undefined, take(logger)(method)(functor)]; }; /** * @internal */ const take = + (logger: () => (log: TypedRoute.IValidateErrorLog) => void) => (method: string) => (functor?: IResponseBodyStringifier | null) => { if (functor === undefined) { NoTransformConfigurationError(method); - return JSON.stringify; - } else if (functor === null) return JSON.stringify; + return (input: T, _method: string, _path: string) => + JSON.stringify(input); + } else if (functor === null) + return (input: T, _method: string, _path: string) => + JSON.stringify(input); else if (functor.type === "stringify") return functor.stringify; else if (functor.type === "assert") return assert(functor.assert); else if (functor.type === "is") return is(functor.is); else if (functor.type === "validate") return validate(functor.validate); + else if ( + functor.type === "validate.log" || + functor.type === "validateEquals.log" + ) + return validateLog(logger)(functor.validate); throw new Error( `Error on nestia.core.${method}(): invalid typed stringify function.`, ); @@ -45,7 +61,7 @@ const take = */ const assert = (closure: (data: T) => string) => - (data: T) => { + (data: T): string => { try { return closure(data); } catch (exp) { @@ -66,7 +82,7 @@ const assert = */ const is = (closure: (data: T) => string | null) => - (data: T) => { + (data: T, _method: string, _path: string) => { const result: string | null = closure(data); if (result === null) throw new InternalServerErrorException(MESSAGE); return result; @@ -77,7 +93,7 @@ const is = */ const validate = (closure: (data: T) => IValidation) => - (data: T) => { + (data: T, _method: string, _path: string): string => { const result: IValidation = closure(data); if (result.success === false) throw new InternalServerErrorException({ @@ -87,6 +103,20 @@ const validate = return result.data; }; +const validateLog = + (logger: () => (log: TypedRoute.IValidateErrorLog) => void) => + (closure: (data: T) => IValidation) => + (data: T, method: string, path: string): string => { + const result: IValidation = closure(data); + if (result.success === false) + logger()({ + errors: result.errors, + method, + path, + }); + return JSON.stringify(data); + }; + /** * @internal */ diff --git a/packages/core/src/options/INestiaTransformOptions.ts b/packages/core/src/options/INestiaTransformOptions.ts index a7a1e4943..cd3a9fc80 100644 --- a/packages/core/src/options/INestiaTransformOptions.ts +++ b/packages/core/src/options/INestiaTransformOptions.ts @@ -13,6 +13,13 @@ export interface INestiaTransformOptions { // PRUNE | "assertPrune" | "validatePrune"; - stringify?: "stringify" | "assert" | "is" | "validate" | null; + stringify?: + | "stringify" + | "assert" + | "is" + | "validate" + | "validate.log" + | "validateEquals.log" + | null; throws?: boolean; } diff --git a/packages/core/src/options/IResponseBodyStringifier.ts b/packages/core/src/options/IResponseBodyStringifier.ts index b6c06d534..4f4aaf0a7 100644 --- a/packages/core/src/options/IResponseBodyStringifier.ts +++ b/packages/core/src/options/IResponseBodyStringifier.ts @@ -4,7 +4,8 @@ export type IResponseBodyStringifier = | IResponseBodyStringifier.IStringify | IResponseBodyStringifier.IIs | IResponseBodyStringifier.IAssert - | IResponseBodyStringifier.IValidate; + | IResponseBodyStringifier.IValidate + | IResponseBodyStringifier.IValidateLog; export namespace IResponseBodyStringifier { export interface IStringify { type: "stringify"; @@ -22,4 +23,8 @@ export namespace IResponseBodyStringifier { type: "validate"; validate: (input: T) => IValidation; } + export interface IValidateLog { + type: "validate.log" | "validateEquals.log"; + validate: (input: T) => IValidation; + } } diff --git a/packages/core/src/programmers/TypedRouteProgrammer.ts b/packages/core/src/programmers/TypedRouteProgrammer.ts index 233290d02..6818f455d 100644 --- a/packages/core/src/programmers/TypedRouteProgrammer.ts +++ b/packages/core/src/programmers/TypedRouteProgrammer.ts @@ -1,4 +1,5 @@ import ts from "typescript"; +import { ValidateProgrammer } from "typia/lib/programmers/ValidateProgrammer"; import { JsonAssertStringifyProgrammer } from "typia/lib/programmers/json/JsonAssertStringifyProgrammer"; import { JsonIsStringifyProgrammer } from "typia/lib/programmers/json/JsonIsStringifyProgrammer"; import { JsonStringifyProgrammer } from "typia/lib/programmers/json/JsonStringifyProgrammer"; @@ -13,22 +14,23 @@ export namespace TypedRouteProgrammer { (modulo: ts.LeftHandSideExpression) => (type: ts.Type): ts.Expression => { // GENERATE STRINGIFY PLAN - const parameter = ( - key: string, + const parameter = (props: { + type: string; + key: string; programmer: ( project: IProject, ) => ( modulo: ts.LeftHandSideExpression, - ) => (type: ts.Type) => ts.Expression, - ) => + ) => (type: ts.Type) => ts.Expression; + }) => ts.factory.createObjectLiteralExpression([ ts.factory.createPropertyAssignment( ts.factory.createIdentifier("type"), - ts.factory.createStringLiteral(key), + ts.factory.createStringLiteral(props.type), ), ts.factory.createPropertyAssignment( - ts.factory.createIdentifier(key), - programmer({ + ts.factory.createIdentifier(props.key), + props.programmer({ ...project, options: {}, // use default option })(modulo)(type), @@ -37,15 +39,45 @@ export namespace TypedRouteProgrammer { // RETURNS if (project.options.stringify === "is") - return parameter("is", JsonIsStringifyProgrammer.write); + return parameter({ + type: "is", + key: "is", + programmer: JsonIsStringifyProgrammer.write, + }); else if (project.options.stringify === "validate") - return parameter("validate", JsonValidateStringifyProgrammer.write); + return parameter({ + type: "validate", + key: "validate", + programmer: JsonValidateStringifyProgrammer.write, + }); else if (project.options.stringify === "stringify") - return parameter("stringify", JsonStringifyProgrammer.write); + return parameter({ + type: "stringify", + key: "stringify", + programmer: JsonStringifyProgrammer.write, + }); + else if (project.options.stringify === "validate.log") + return parameter({ + type: "validate.log", + key: "validate", + programmer: (project) => (modulo) => + ValidateProgrammer.write(project)(modulo)(false), + }); + else if (project.options.stringify === "validateEquals.log") + return parameter({ + type: "validateEquals.log", + key: "validate", + programmer: (project) => (modulo) => + ValidateProgrammer.write(project)(modulo)(true), + }); else if (project.options.stringify === null) return ts.factory.createNull(); // ASSERT IS DEFAULT - return parameter("assert", JsonAssertStringifyProgrammer.write); + return parameter({ + type: "assert", + key: "assert", + programmer: JsonAssertStringifyProgrammer.write, + }); }; } diff --git a/packages/fetcher/package.json b/packages/fetcher/package.json index d583fc644..e23bb89f1 100644 --- a/packages/fetcher/package.json +++ b/packages/fetcher/package.json @@ -1,6 +1,6 @@ { "name": "@nestia/fetcher", - "version": "3.14.1", + "version": "3.15.0-dev.20240926", "description": "Fetcher library of Nestia SDK", "main": "lib/index.js", "typings": "lib/index.d.ts", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 49b522c19..a2d7c8698 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@nestia/sdk", - "version": "3.14.1", + "version": "3.15.0-dev.20240926", "description": "Nestia SDK and Swagger generator", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -32,8 +32,8 @@ }, "homepage": "https://nestia.io", "dependencies": { - "@nestia/core": "^3.14.1", - "@nestia/fetcher": "^3.14.1", + "@nestia/core": "../core/nestia-core-3.15.0-dev.20240926.tgz", + "@nestia/fetcher": "../fetcher/nestia-fetcher-3.15.0-dev.20240926.tgz", "@samchon/openapi": "^1.0.2", "cli": "^1.0.1", "get-function-location": "^2.0.0", @@ -47,8 +47,8 @@ "typia": "^6.10.0" }, "peerDependencies": { - "@nestia/core": ">=3.14.1", - "@nestia/fetcher": ">=3.14.1", + "@nestia/core": ">=3.15.0-dev.20240926", + "@nestia/fetcher": ">=3.15.0-dev.20240926", "@nestjs/common": ">=7.0.1", "@nestjs/core": ">=7.0.1", "reflect-metadata": ">=0.1.12", diff --git a/test/features/route-manual-validate-log-encrypted/nestia.config.ts b/test/features/route-manual-validate-log-encrypted/nestia.config.ts new file mode 100644 index 000000000..ca670ea91 --- /dev/null +++ b/test/features/route-manual-validate-log-encrypted/nestia.config.ts @@ -0,0 +1,15 @@ +import { INestiaConfig } from "@nestia/sdk"; + +export const NESTIA_CONFIG: INestiaConfig = { + input: ["src/controllers"], + output: "src/api", + swagger: { + output: "swagger.json", + security: { + bearer: { + type: "apiKey", + }, + }, + }, +}; +export default NESTIA_CONFIG; diff --git a/test/features/route-manual-validate-log-encrypted/src/Backend.ts b/test/features/route-manual-validate-log-encrypted/src/Backend.ts new file mode 100644 index 000000000..fc5f679b2 --- /dev/null +++ b/test/features/route-manual-validate-log-encrypted/src/Backend.ts @@ -0,0 +1,25 @@ +import core from "@nestia/core"; +import { INestApplication } from "@nestjs/common"; +import { NestFactory } from "@nestjs/core"; +import { Singleton } from "tstl"; + +export class Backend { + public readonly application: Singleton> = + new Singleton(async () => + NestFactory.create( + await core.EncryptedModule.dynamic(__dirname + "/controllers", { + key: "A".repeat(32), + iv: "B".repeat(16), + }), + { logger: false }, + ), + ); + + public async open(): Promise { + return (await this.application.get()).listen(37_000); + } + + public async close(): Promise { + return (await this.application.get()).close(); + } +} diff --git a/test/features/route-manual-validate-log-encrypted/src/api/HttpError.ts b/test/features/route-manual-validate-log-encrypted/src/api/HttpError.ts new file mode 100644 index 000000000..5df328ae4 --- /dev/null +++ b/test/features/route-manual-validate-log-encrypted/src/api/HttpError.ts @@ -0,0 +1 @@ +export { HttpError } from "@nestia/fetcher"; diff --git a/test/features/route-manual-validate-log-encrypted/src/api/IConnection.ts b/test/features/route-manual-validate-log-encrypted/src/api/IConnection.ts new file mode 100644 index 000000000..107bdb8f8 --- /dev/null +++ b/test/features/route-manual-validate-log-encrypted/src/api/IConnection.ts @@ -0,0 +1 @@ +export type { IConnection } from "@nestia/fetcher"; diff --git a/test/features/route-manual-validate-log-encrypted/src/api/Primitive.ts b/test/features/route-manual-validate-log-encrypted/src/api/Primitive.ts new file mode 100644 index 000000000..60d394424 --- /dev/null +++ b/test/features/route-manual-validate-log-encrypted/src/api/Primitive.ts @@ -0,0 +1 @@ +export type { Primitive } from "@nestia/fetcher"; diff --git a/test/features/route-manual-validate-log-encrypted/src/api/Resolved.ts b/test/features/route-manual-validate-log-encrypted/src/api/Resolved.ts new file mode 100644 index 000000000..a4f457e60 --- /dev/null +++ b/test/features/route-manual-validate-log-encrypted/src/api/Resolved.ts @@ -0,0 +1 @@ +export type { Resolved } from "@nestia/fetcher"; diff --git a/test/features/route-manual-validate-log-encrypted/src/api/functional/bbs/articles/index.ts b/test/features/route-manual-validate-log-encrypted/src/api/functional/bbs/articles/index.ts new file mode 100644 index 000000000..06869941f --- /dev/null +++ b/test/features/route-manual-validate-log-encrypted/src/api/functional/bbs/articles/index.ts @@ -0,0 +1,44 @@ +/** + * @packageDocumentation + * @module api.functional.bbs.articles + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +//================================================================ +import type { IConnection, Primitive } from "@nestia/fetcher"; +import { PlainFetcher } from "@nestia/fetcher/lib/PlainFetcher"; +import type { Format } from "typia/lib/tags/Format"; + +import type { IBbsArticle } from "../../../structures/IBbsArticle"; + +/** + * @controller BbsArticlesController.at + * @path GET /bbs/articles/:id + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +export async function at( + connection: IConnection, + id: string & Format<"uuid">, +): Promise { + return PlainFetcher.fetch(connection, { + ...at.METADATA, + template: at.METADATA.path, + path: at.path(id), + }); +} +export namespace at { + export type Output = Primitive; + + export const METADATA = { + method: "GET", + path: "/bbs/articles/:id", + request: null, + response: { + type: "application/json", + encrypted: false, + }, + status: 200, + } as const; + + export const path = (id: string & Format<"uuid">) => + `/bbs/articles/${encodeURIComponent(id?.toString() ?? "null")}`; +} diff --git a/test/features/route-manual-validate-log-encrypted/src/api/functional/bbs/index.ts b/test/features/route-manual-validate-log-encrypted/src/api/functional/bbs/index.ts new file mode 100644 index 000000000..7a891f888 --- /dev/null +++ b/test/features/route-manual-validate-log-encrypted/src/api/functional/bbs/index.ts @@ -0,0 +1,7 @@ +/** + * @packageDocumentation + * @module api.functional.bbs + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +//================================================================ +export * as articles from "./articles"; diff --git a/test/features/route-manual-validate-log-encrypted/src/api/functional/health/index.ts b/test/features/route-manual-validate-log-encrypted/src/api/functional/health/index.ts new file mode 100644 index 000000000..36ae23908 --- /dev/null +++ b/test/features/route-manual-validate-log-encrypted/src/api/functional/health/index.ts @@ -0,0 +1,35 @@ +/** + * @packageDocumentation + * @module api.functional.health + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +//================================================================ +import type { IConnection } from "@nestia/fetcher"; +import { PlainFetcher } from "@nestia/fetcher/lib/PlainFetcher"; + +/** + * @controller HealthController.get + * @path GET /health + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +export async function get(connection: IConnection): Promise { + return PlainFetcher.fetch(connection, { + ...get.METADATA, + template: get.METADATA.path, + path: get.path(), + }); +} +export namespace get { + export const METADATA = { + method: "GET", + path: "/health", + request: null, + response: { + type: "application/json", + encrypted: false, + }, + status: 200, + } as const; + + export const path = () => "/health"; +} diff --git a/test/template/success/src/api/functional/index.ts b/test/features/route-manual-validate-log-encrypted/src/api/functional/index.ts similarity index 83% rename from test/template/success/src/api/functional/index.ts rename to test/features/route-manual-validate-log-encrypted/src/api/functional/index.ts index 9e6869080..3c05c315c 100644 --- a/test/template/success/src/api/functional/index.ts +++ b/test/features/route-manual-validate-log-encrypted/src/api/functional/index.ts @@ -4,5 +4,5 @@ * @nestia Generated by Nestia - https://github.com/samchon/nestia */ //================================================================ +export * as bbs from "./bbs"; export * as health from "./health"; -export * as performance from "./performance"; diff --git a/test/features/route-manual-validate-log-encrypted/src/api/index.ts b/test/features/route-manual-validate-log-encrypted/src/api/index.ts new file mode 100644 index 000000000..1705f43c8 --- /dev/null +++ b/test/features/route-manual-validate-log-encrypted/src/api/index.ts @@ -0,0 +1,4 @@ +import * as api from "./module"; + +export * from "./module"; +export default api; diff --git a/test/features/route-manual-validate-log-encrypted/src/api/module.ts b/test/features/route-manual-validate-log-encrypted/src/api/module.ts new file mode 100644 index 000000000..2137b4473 --- /dev/null +++ b/test/features/route-manual-validate-log-encrypted/src/api/module.ts @@ -0,0 +1,6 @@ +export type * from "./IConnection"; +export type * from "./Primitive"; +export type * from "./Resolved"; +export * from "./HttpError"; + +export * as functional from "./functional"; diff --git a/test/features/route-manual-validate-log-encrypted/src/api/structures/IBbsArticle.ts b/test/features/route-manual-validate-log-encrypted/src/api/structures/IBbsArticle.ts new file mode 100644 index 000000000..f741b6bcf --- /dev/null +++ b/test/features/route-manual-validate-log-encrypted/src/api/structures/IBbsArticle.ts @@ -0,0 +1,11 @@ +import { tags } from "typia"; + +export interface IBbsArticle { + id: string & tags.Format<"uuid">; + title: string; + body: string; + thumbnail: + | null + | (string & tags.Format<"uri"> & tags.ContentMediaType<"image/*">); + created_at: string & tags.Format<"date-time">; +} diff --git a/test/features/route-manual-validate-log-encrypted/src/controllers/BbsArticleController.ts b/test/features/route-manual-validate-log-encrypted/src/controllers/BbsArticleController.ts new file mode 100644 index 000000000..370cbf1f3 --- /dev/null +++ b/test/features/route-manual-validate-log-encrypted/src/controllers/BbsArticleController.ts @@ -0,0 +1,21 @@ +import { TypedParam, TypedRoute } from "@nestia/core"; +import { Controller } from "@nestjs/common"; +import { tags } from "typia"; + +import { IBbsArticle } from "@api/lib/structures/IBbsArticle"; + +@Controller("bbs/articles") +export class BbsArticlesController { + @TypedRoute.Get(":id") + public async at( + @TypedParam("id") id: string & tags.Format<"uuid">, + ): Promise { + return { + id, + title: "Hello, world!", + body: "This is a test article.", + thumbnail: null, + created_at: "wrong-data", + }; + } +} diff --git a/test/features/route-manual-validate-log-encrypted/src/controllers/HealthController.ts b/test/features/route-manual-validate-log-encrypted/src/controllers/HealthController.ts new file mode 100644 index 000000000..f7e06aef4 --- /dev/null +++ b/test/features/route-manual-validate-log-encrypted/src/controllers/HealthController.ts @@ -0,0 +1,8 @@ +import core from "@nestia/core"; +import { Controller } from "@nestjs/common"; + +@Controller("health") +export class HealthController { + @core.TypedRoute.Get() + public get(): void {} +} diff --git a/test/features/route-manual-validate-log-encrypted/src/test/features/api/test_api_bbs_article_at.ts b/test/features/route-manual-validate-log-encrypted/src/test/features/api/test_api_bbs_article_at.ts new file mode 100644 index 000000000..781b53fc2 --- /dev/null +++ b/test/features/route-manual-validate-log-encrypted/src/test/features/api/test_api_bbs_article_at.ts @@ -0,0 +1,34 @@ +import { TypedRoute } from "@nestia/core"; +import { TestValidator } from "@nestia/e2e"; +import typia from "typia"; +import { v4 } from "uuid"; + +import api from "@api"; +import { IBbsArticle } from "@api/lib/structures/IBbsArticle"; + +export const test_api_bbs_article_at = async ( + connection: api.IConnection, +): Promise => { + const logs: TypedRoute.IValidateErrorLog[] = []; + TypedRoute.setValidateErrorLogger((l) => logs.push(l)); + + const id: string = v4(); + const article: IBbsArticle = await api.functional.bbs.articles.at( + connection, + id, + ); + TestValidator.error("wrong data")(() => typia.assert(article)); + TestValidator.equals("logs")(logs)([ + { + errors: [ + { + path: "$input.created_at", + expected: `string & Format<"date-time">`, + value: "wrong-data", + }, + ], + method: "GET", + path: `/bbs/articles/${id}`, + }, + ]); +}; diff --git a/test/features/route-manual-validate-log-encrypted/src/test/features/api/test_api_health_check.ts b/test/features/route-manual-validate-log-encrypted/src/test/features/api/test_api_health_check.ts new file mode 100644 index 000000000..4dceb4cec --- /dev/null +++ b/test/features/route-manual-validate-log-encrypted/src/test/features/api/test_api_health_check.ts @@ -0,0 +1,5 @@ +import api from "@api"; + +export const test_api_monitor_health_check = ( + connection: api.IConnection, +): Promise => api.functional.health.get(connection); diff --git a/test/features/route-manual-validate-log-encrypted/src/test/index.ts b/test/features/route-manual-validate-log-encrypted/src/test/index.ts new file mode 100644 index 000000000..31a7190ab --- /dev/null +++ b/test/features/route-manual-validate-log-encrypted/src/test/index.ts @@ -0,0 +1,52 @@ +import { DynamicExecutor } from "@nestia/e2e"; +import chalk from "chalk"; + +import { Backend } from "../Backend"; + +async function main(): Promise { + const server: Backend = new Backend(); + await server.open(); + + const report: DynamicExecutor.IReport = await DynamicExecutor.validate({ + extension: __filename.substring(__filename.length - 2), + prefix: "test", + parameters: () => [ + { + host: "http://127.0.0.1:37000", + encryption: { + key: "A".repeat(32), + iv: "B".repeat(16), + }, + }, + ], + location: `${__dirname}/features`, + onComplete: (exec) => { + const trace = (str: string) => + console.log(` - ${chalk.green(exec.name)}: ${str}`); + if (exec.error === null) { + const elapsed: number = + new Date(exec.completed_at).getTime() - + new Date(exec.started_at).getTime(); + trace(`${chalk.yellow(elapsed.toLocaleString())} ms`); + } else trace(chalk.red(exec.error.name)); + }, + }); + await server.close(); + + const exceptions: Error[] = report.executions + .filter((exec) => exec.error !== null) + .map((exec) => exec.error!); + if (exceptions.length === 0) { + console.log("Success"); + console.log("Elapsed time", report.time.toLocaleString(), `ms`); + } else { + for (const exp of exceptions) console.log(exp); + console.log("Failed"); + console.log("Elapsed time", report.time.toLocaleString(), `ms`); + process.exit(-1); + } +} +main().catch((exp) => { + console.log(exp); + process.exit(-1); +}); diff --git a/test/features/route-manual-validate-log-encrypted/swagger.json b/test/features/route-manual-validate-log-encrypted/swagger.json new file mode 100644 index 000000000..e92f2ae6a --- /dev/null +++ b/test/features/route-manual-validate-log-encrypted/swagger.json @@ -0,0 +1 @@ +{"openapi":"3.1.0","servers":[{"url":"https://github.com/samchon/nestia","description":"insert your server url"}],"info":{"version":"3.15.0-dev.20240926","title":"@samchon/nestia-test","description":"Test program of Nestia","license":{"name":"MIT"}},"paths":{"/bbs/articles/{id}":{"get":{"tags":[],"parameters":[{"name":"id","in":"path","schema":{"type":"string","format":"uuid"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IBbsArticle"}}}}}}},"/health":{"get":{"tags":[],"parameters":[],"responses":{"200":{"description":"","content":{"application/json":{}}}}}}},"components":{"schemas":{"IBbsArticle":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"body":{"type":"string"},"thumbnail":{"oneOf":[{"type":"null"},{"type":"string","format":"uri","contentMediaType":"image/*"}]},"created_at":{"type":"string","format":"date-time"}},"required":["id","title","body","thumbnail","created_at"]}},"securitySchemes":{"bearer":{"type":"apiKey"}}},"tags":[],"x-samchon-emended":true} \ No newline at end of file diff --git a/test/features/route-manual-validate-log-encrypted/tsconfig.json b/test/features/route-manual-validate-log-encrypted/tsconfig.json new file mode 100644 index 000000000..1a65dafd1 --- /dev/null +++ b/test/features/route-manual-validate-log-encrypted/tsconfig.json @@ -0,0 +1,102 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */// "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + "paths": { + "@api": ["./src/api"], + "@api/lib/*": ["./src/api/*"], + }, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./bin", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. *//* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true, /* Skip type checking all .d.ts files. */ + "plugins": [ + { "transform": "typescript-transform-paths" }, + { "transform": "typia/lib/transform" }, + { + "transform": "@nestia/core/lib/transform", + "stringify": "validate.log", + }, + ], + }, + "include": ["src"], + } \ No newline at end of file diff --git a/test/features/route-manual-validate-log-fastify/nestia.config.ts b/test/features/route-manual-validate-log-fastify/nestia.config.ts new file mode 100644 index 000000000..ca670ea91 --- /dev/null +++ b/test/features/route-manual-validate-log-fastify/nestia.config.ts @@ -0,0 +1,15 @@ +import { INestiaConfig } from "@nestia/sdk"; + +export const NESTIA_CONFIG: INestiaConfig = { + input: ["src/controllers"], + output: "src/api", + swagger: { + output: "swagger.json", + security: { + bearer: { + type: "apiKey", + }, + }, + }, +}; +export default NESTIA_CONFIG; diff --git a/test/features/route-manual-validate-log-fastify/src/Backend.ts b/test/features/route-manual-validate-log-fastify/src/Backend.ts new file mode 100644 index 000000000..fc5f679b2 --- /dev/null +++ b/test/features/route-manual-validate-log-fastify/src/Backend.ts @@ -0,0 +1,25 @@ +import core from "@nestia/core"; +import { INestApplication } from "@nestjs/common"; +import { NestFactory } from "@nestjs/core"; +import { Singleton } from "tstl"; + +export class Backend { + public readonly application: Singleton> = + new Singleton(async () => + NestFactory.create( + await core.EncryptedModule.dynamic(__dirname + "/controllers", { + key: "A".repeat(32), + iv: "B".repeat(16), + }), + { logger: false }, + ), + ); + + public async open(): Promise { + return (await this.application.get()).listen(37_000); + } + + public async close(): Promise { + return (await this.application.get()).close(); + } +} diff --git a/test/features/route-manual-validate-log-fastify/src/api/HttpError.ts b/test/features/route-manual-validate-log-fastify/src/api/HttpError.ts new file mode 100644 index 000000000..5df328ae4 --- /dev/null +++ b/test/features/route-manual-validate-log-fastify/src/api/HttpError.ts @@ -0,0 +1 @@ +export { HttpError } from "@nestia/fetcher"; diff --git a/test/features/route-manual-validate-log-fastify/src/api/IConnection.ts b/test/features/route-manual-validate-log-fastify/src/api/IConnection.ts new file mode 100644 index 000000000..107bdb8f8 --- /dev/null +++ b/test/features/route-manual-validate-log-fastify/src/api/IConnection.ts @@ -0,0 +1 @@ +export type { IConnection } from "@nestia/fetcher"; diff --git a/test/features/route-manual-validate-log-fastify/src/api/Primitive.ts b/test/features/route-manual-validate-log-fastify/src/api/Primitive.ts new file mode 100644 index 000000000..60d394424 --- /dev/null +++ b/test/features/route-manual-validate-log-fastify/src/api/Primitive.ts @@ -0,0 +1 @@ +export type { Primitive } from "@nestia/fetcher"; diff --git a/test/features/route-manual-validate-log-fastify/src/api/Resolved.ts b/test/features/route-manual-validate-log-fastify/src/api/Resolved.ts new file mode 100644 index 000000000..a4f457e60 --- /dev/null +++ b/test/features/route-manual-validate-log-fastify/src/api/Resolved.ts @@ -0,0 +1 @@ +export type { Resolved } from "@nestia/fetcher"; diff --git a/test/features/route-manual-validate-log-fastify/src/api/functional/bbs/articles/index.ts b/test/features/route-manual-validate-log-fastify/src/api/functional/bbs/articles/index.ts new file mode 100644 index 000000000..06869941f --- /dev/null +++ b/test/features/route-manual-validate-log-fastify/src/api/functional/bbs/articles/index.ts @@ -0,0 +1,44 @@ +/** + * @packageDocumentation + * @module api.functional.bbs.articles + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +//================================================================ +import type { IConnection, Primitive } from "@nestia/fetcher"; +import { PlainFetcher } from "@nestia/fetcher/lib/PlainFetcher"; +import type { Format } from "typia/lib/tags/Format"; + +import type { IBbsArticle } from "../../../structures/IBbsArticle"; + +/** + * @controller BbsArticlesController.at + * @path GET /bbs/articles/:id + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +export async function at( + connection: IConnection, + id: string & Format<"uuid">, +): Promise { + return PlainFetcher.fetch(connection, { + ...at.METADATA, + template: at.METADATA.path, + path: at.path(id), + }); +} +export namespace at { + export type Output = Primitive; + + export const METADATA = { + method: "GET", + path: "/bbs/articles/:id", + request: null, + response: { + type: "application/json", + encrypted: false, + }, + status: 200, + } as const; + + export const path = (id: string & Format<"uuid">) => + `/bbs/articles/${encodeURIComponent(id?.toString() ?? "null")}`; +} diff --git a/test/features/route-manual-validate-log-fastify/src/api/functional/bbs/index.ts b/test/features/route-manual-validate-log-fastify/src/api/functional/bbs/index.ts new file mode 100644 index 000000000..7a891f888 --- /dev/null +++ b/test/features/route-manual-validate-log-fastify/src/api/functional/bbs/index.ts @@ -0,0 +1,7 @@ +/** + * @packageDocumentation + * @module api.functional.bbs + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +//================================================================ +export * as articles from "./articles"; diff --git a/test/features/route-manual-validate-log-fastify/src/api/functional/health/index.ts b/test/features/route-manual-validate-log-fastify/src/api/functional/health/index.ts new file mode 100644 index 000000000..36ae23908 --- /dev/null +++ b/test/features/route-manual-validate-log-fastify/src/api/functional/health/index.ts @@ -0,0 +1,35 @@ +/** + * @packageDocumentation + * @module api.functional.health + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +//================================================================ +import type { IConnection } from "@nestia/fetcher"; +import { PlainFetcher } from "@nestia/fetcher/lib/PlainFetcher"; + +/** + * @controller HealthController.get + * @path GET /health + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +export async function get(connection: IConnection): Promise { + return PlainFetcher.fetch(connection, { + ...get.METADATA, + template: get.METADATA.path, + path: get.path(), + }); +} +export namespace get { + export const METADATA = { + method: "GET", + path: "/health", + request: null, + response: { + type: "application/json", + encrypted: false, + }, + status: 200, + } as const; + + export const path = () => "/health"; +} diff --git a/test/features/route-manual-validate-log-fastify/src/api/functional/index.ts b/test/features/route-manual-validate-log-fastify/src/api/functional/index.ts new file mode 100644 index 000000000..3c05c315c --- /dev/null +++ b/test/features/route-manual-validate-log-fastify/src/api/functional/index.ts @@ -0,0 +1,8 @@ +/** + * @packageDocumentation + * @module api.functional + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +//================================================================ +export * as bbs from "./bbs"; +export * as health from "./health"; diff --git a/test/features/route-manual-validate-log-fastify/src/api/index.ts b/test/features/route-manual-validate-log-fastify/src/api/index.ts new file mode 100644 index 000000000..1705f43c8 --- /dev/null +++ b/test/features/route-manual-validate-log-fastify/src/api/index.ts @@ -0,0 +1,4 @@ +import * as api from "./module"; + +export * from "./module"; +export default api; diff --git a/test/features/route-manual-validate-log-fastify/src/api/module.ts b/test/features/route-manual-validate-log-fastify/src/api/module.ts new file mode 100644 index 000000000..2137b4473 --- /dev/null +++ b/test/features/route-manual-validate-log-fastify/src/api/module.ts @@ -0,0 +1,6 @@ +export type * from "./IConnection"; +export type * from "./Primitive"; +export type * from "./Resolved"; +export * from "./HttpError"; + +export * as functional from "./functional"; diff --git a/test/features/route-manual-validate-log-fastify/src/api/structures/IBbsArticle.ts b/test/features/route-manual-validate-log-fastify/src/api/structures/IBbsArticle.ts new file mode 100644 index 000000000..f741b6bcf --- /dev/null +++ b/test/features/route-manual-validate-log-fastify/src/api/structures/IBbsArticle.ts @@ -0,0 +1,11 @@ +import { tags } from "typia"; + +export interface IBbsArticle { + id: string & tags.Format<"uuid">; + title: string; + body: string; + thumbnail: + | null + | (string & tags.Format<"uri"> & tags.ContentMediaType<"image/*">); + created_at: string & tags.Format<"date-time">; +} diff --git a/test/features/route-manual-validate-log-fastify/src/controllers/BbsArticleController.ts b/test/features/route-manual-validate-log-fastify/src/controllers/BbsArticleController.ts new file mode 100644 index 000000000..370cbf1f3 --- /dev/null +++ b/test/features/route-manual-validate-log-fastify/src/controllers/BbsArticleController.ts @@ -0,0 +1,21 @@ +import { TypedParam, TypedRoute } from "@nestia/core"; +import { Controller } from "@nestjs/common"; +import { tags } from "typia"; + +import { IBbsArticle } from "@api/lib/structures/IBbsArticle"; + +@Controller("bbs/articles") +export class BbsArticlesController { + @TypedRoute.Get(":id") + public async at( + @TypedParam("id") id: string & tags.Format<"uuid">, + ): Promise { + return { + id, + title: "Hello, world!", + body: "This is a test article.", + thumbnail: null, + created_at: "wrong-data", + }; + } +} diff --git a/test/features/route-manual-validate-log-fastify/src/controllers/HealthController.ts b/test/features/route-manual-validate-log-fastify/src/controllers/HealthController.ts new file mode 100644 index 000000000..f7e06aef4 --- /dev/null +++ b/test/features/route-manual-validate-log-fastify/src/controllers/HealthController.ts @@ -0,0 +1,8 @@ +import core from "@nestia/core"; +import { Controller } from "@nestjs/common"; + +@Controller("health") +export class HealthController { + @core.TypedRoute.Get() + public get(): void {} +} diff --git a/test/features/route-manual-validate-log-fastify/src/test/features/api/test_api_bbs_article_at.ts b/test/features/route-manual-validate-log-fastify/src/test/features/api/test_api_bbs_article_at.ts new file mode 100644 index 000000000..781b53fc2 --- /dev/null +++ b/test/features/route-manual-validate-log-fastify/src/test/features/api/test_api_bbs_article_at.ts @@ -0,0 +1,34 @@ +import { TypedRoute } from "@nestia/core"; +import { TestValidator } from "@nestia/e2e"; +import typia from "typia"; +import { v4 } from "uuid"; + +import api from "@api"; +import { IBbsArticle } from "@api/lib/structures/IBbsArticle"; + +export const test_api_bbs_article_at = async ( + connection: api.IConnection, +): Promise => { + const logs: TypedRoute.IValidateErrorLog[] = []; + TypedRoute.setValidateErrorLogger((l) => logs.push(l)); + + const id: string = v4(); + const article: IBbsArticle = await api.functional.bbs.articles.at( + connection, + id, + ); + TestValidator.error("wrong data")(() => typia.assert(article)); + TestValidator.equals("logs")(logs)([ + { + errors: [ + { + path: "$input.created_at", + expected: `string & Format<"date-time">`, + value: "wrong-data", + }, + ], + method: "GET", + path: `/bbs/articles/${id}`, + }, + ]); +}; diff --git a/test/features/route-manual-validate-log-fastify/src/test/features/api/test_api_health_check.ts b/test/features/route-manual-validate-log-fastify/src/test/features/api/test_api_health_check.ts new file mode 100644 index 000000000..4dceb4cec --- /dev/null +++ b/test/features/route-manual-validate-log-fastify/src/test/features/api/test_api_health_check.ts @@ -0,0 +1,5 @@ +import api from "@api"; + +export const test_api_monitor_health_check = ( + connection: api.IConnection, +): Promise => api.functional.health.get(connection); diff --git a/test/features/route-manual-validate-log-fastify/src/test/index.ts b/test/features/route-manual-validate-log-fastify/src/test/index.ts new file mode 100644 index 000000000..31a7190ab --- /dev/null +++ b/test/features/route-manual-validate-log-fastify/src/test/index.ts @@ -0,0 +1,52 @@ +import { DynamicExecutor } from "@nestia/e2e"; +import chalk from "chalk"; + +import { Backend } from "../Backend"; + +async function main(): Promise { + const server: Backend = new Backend(); + await server.open(); + + const report: DynamicExecutor.IReport = await DynamicExecutor.validate({ + extension: __filename.substring(__filename.length - 2), + prefix: "test", + parameters: () => [ + { + host: "http://127.0.0.1:37000", + encryption: { + key: "A".repeat(32), + iv: "B".repeat(16), + }, + }, + ], + location: `${__dirname}/features`, + onComplete: (exec) => { + const trace = (str: string) => + console.log(` - ${chalk.green(exec.name)}: ${str}`); + if (exec.error === null) { + const elapsed: number = + new Date(exec.completed_at).getTime() - + new Date(exec.started_at).getTime(); + trace(`${chalk.yellow(elapsed.toLocaleString())} ms`); + } else trace(chalk.red(exec.error.name)); + }, + }); + await server.close(); + + const exceptions: Error[] = report.executions + .filter((exec) => exec.error !== null) + .map((exec) => exec.error!); + if (exceptions.length === 0) { + console.log("Success"); + console.log("Elapsed time", report.time.toLocaleString(), `ms`); + } else { + for (const exp of exceptions) console.log(exp); + console.log("Failed"); + console.log("Elapsed time", report.time.toLocaleString(), `ms`); + process.exit(-1); + } +} +main().catch((exp) => { + console.log(exp); + process.exit(-1); +}); diff --git a/test/features/route-manual-validate-log-fastify/swagger.json b/test/features/route-manual-validate-log-fastify/swagger.json new file mode 100644 index 000000000..e92f2ae6a --- /dev/null +++ b/test/features/route-manual-validate-log-fastify/swagger.json @@ -0,0 +1 @@ +{"openapi":"3.1.0","servers":[{"url":"https://github.com/samchon/nestia","description":"insert your server url"}],"info":{"version":"3.15.0-dev.20240926","title":"@samchon/nestia-test","description":"Test program of Nestia","license":{"name":"MIT"}},"paths":{"/bbs/articles/{id}":{"get":{"tags":[],"parameters":[{"name":"id","in":"path","schema":{"type":"string","format":"uuid"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IBbsArticle"}}}}}}},"/health":{"get":{"tags":[],"parameters":[],"responses":{"200":{"description":"","content":{"application/json":{}}}}}}},"components":{"schemas":{"IBbsArticle":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"body":{"type":"string"},"thumbnail":{"oneOf":[{"type":"null"},{"type":"string","format":"uri","contentMediaType":"image/*"}]},"created_at":{"type":"string","format":"date-time"}},"required":["id","title","body","thumbnail","created_at"]}},"securitySchemes":{"bearer":{"type":"apiKey"}}},"tags":[],"x-samchon-emended":true} \ No newline at end of file diff --git a/test/features/route-manual-validate-log-fastify/tsconfig.json b/test/features/route-manual-validate-log-fastify/tsconfig.json new file mode 100644 index 000000000..1a65dafd1 --- /dev/null +++ b/test/features/route-manual-validate-log-fastify/tsconfig.json @@ -0,0 +1,102 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */// "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + "paths": { + "@api": ["./src/api"], + "@api/lib/*": ["./src/api/*"], + }, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./bin", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. *//* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true, /* Skip type checking all .d.ts files. */ + "plugins": [ + { "transform": "typescript-transform-paths" }, + { "transform": "typia/lib/transform" }, + { + "transform": "@nestia/core/lib/transform", + "stringify": "validate.log", + }, + ], + }, + "include": ["src"], + } \ No newline at end of file diff --git a/test/features/route-manual-validate-log/nestia.config.ts b/test/features/route-manual-validate-log/nestia.config.ts new file mode 100644 index 000000000..ca670ea91 --- /dev/null +++ b/test/features/route-manual-validate-log/nestia.config.ts @@ -0,0 +1,15 @@ +import { INestiaConfig } from "@nestia/sdk"; + +export const NESTIA_CONFIG: INestiaConfig = { + input: ["src/controllers"], + output: "src/api", + swagger: { + output: "swagger.json", + security: { + bearer: { + type: "apiKey", + }, + }, + }, +}; +export default NESTIA_CONFIG; diff --git a/test/features/route-manual-validate-log/src/Backend.ts b/test/features/route-manual-validate-log/src/Backend.ts new file mode 100644 index 000000000..fc5f679b2 --- /dev/null +++ b/test/features/route-manual-validate-log/src/Backend.ts @@ -0,0 +1,25 @@ +import core from "@nestia/core"; +import { INestApplication } from "@nestjs/common"; +import { NestFactory } from "@nestjs/core"; +import { Singleton } from "tstl"; + +export class Backend { + public readonly application: Singleton> = + new Singleton(async () => + NestFactory.create( + await core.EncryptedModule.dynamic(__dirname + "/controllers", { + key: "A".repeat(32), + iv: "B".repeat(16), + }), + { logger: false }, + ), + ); + + public async open(): Promise { + return (await this.application.get()).listen(37_000); + } + + public async close(): Promise { + return (await this.application.get()).close(); + } +} diff --git a/test/features/route-manual-validate-log/src/api/HttpError.ts b/test/features/route-manual-validate-log/src/api/HttpError.ts new file mode 100644 index 000000000..5df328ae4 --- /dev/null +++ b/test/features/route-manual-validate-log/src/api/HttpError.ts @@ -0,0 +1 @@ +export { HttpError } from "@nestia/fetcher"; diff --git a/test/features/route-manual-validate-log/src/api/IConnection.ts b/test/features/route-manual-validate-log/src/api/IConnection.ts new file mode 100644 index 000000000..107bdb8f8 --- /dev/null +++ b/test/features/route-manual-validate-log/src/api/IConnection.ts @@ -0,0 +1 @@ +export type { IConnection } from "@nestia/fetcher"; diff --git a/test/features/route-manual-validate-log/src/api/Primitive.ts b/test/features/route-manual-validate-log/src/api/Primitive.ts new file mode 100644 index 000000000..60d394424 --- /dev/null +++ b/test/features/route-manual-validate-log/src/api/Primitive.ts @@ -0,0 +1 @@ +export type { Primitive } from "@nestia/fetcher"; diff --git a/test/features/route-manual-validate-log/src/api/Resolved.ts b/test/features/route-manual-validate-log/src/api/Resolved.ts new file mode 100644 index 000000000..a4f457e60 --- /dev/null +++ b/test/features/route-manual-validate-log/src/api/Resolved.ts @@ -0,0 +1 @@ +export type { Resolved } from "@nestia/fetcher"; diff --git a/test/features/route-manual-validate-log/src/api/functional/bbs/articles/index.ts b/test/features/route-manual-validate-log/src/api/functional/bbs/articles/index.ts new file mode 100644 index 000000000..06869941f --- /dev/null +++ b/test/features/route-manual-validate-log/src/api/functional/bbs/articles/index.ts @@ -0,0 +1,44 @@ +/** + * @packageDocumentation + * @module api.functional.bbs.articles + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +//================================================================ +import type { IConnection, Primitive } from "@nestia/fetcher"; +import { PlainFetcher } from "@nestia/fetcher/lib/PlainFetcher"; +import type { Format } from "typia/lib/tags/Format"; + +import type { IBbsArticle } from "../../../structures/IBbsArticle"; + +/** + * @controller BbsArticlesController.at + * @path GET /bbs/articles/:id + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +export async function at( + connection: IConnection, + id: string & Format<"uuid">, +): Promise { + return PlainFetcher.fetch(connection, { + ...at.METADATA, + template: at.METADATA.path, + path: at.path(id), + }); +} +export namespace at { + export type Output = Primitive; + + export const METADATA = { + method: "GET", + path: "/bbs/articles/:id", + request: null, + response: { + type: "application/json", + encrypted: false, + }, + status: 200, + } as const; + + export const path = (id: string & Format<"uuid">) => + `/bbs/articles/${encodeURIComponent(id?.toString() ?? "null")}`; +} diff --git a/test/features/route-manual-validate-log/src/api/functional/bbs/index.ts b/test/features/route-manual-validate-log/src/api/functional/bbs/index.ts new file mode 100644 index 000000000..7a891f888 --- /dev/null +++ b/test/features/route-manual-validate-log/src/api/functional/bbs/index.ts @@ -0,0 +1,7 @@ +/** + * @packageDocumentation + * @module api.functional.bbs + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +//================================================================ +export * as articles from "./articles"; diff --git a/test/features/route-manual-validate-log/src/api/functional/health/index.ts b/test/features/route-manual-validate-log/src/api/functional/health/index.ts new file mode 100644 index 000000000..36ae23908 --- /dev/null +++ b/test/features/route-manual-validate-log/src/api/functional/health/index.ts @@ -0,0 +1,35 @@ +/** + * @packageDocumentation + * @module api.functional.health + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +//================================================================ +import type { IConnection } from "@nestia/fetcher"; +import { PlainFetcher } from "@nestia/fetcher/lib/PlainFetcher"; + +/** + * @controller HealthController.get + * @path GET /health + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +export async function get(connection: IConnection): Promise { + return PlainFetcher.fetch(connection, { + ...get.METADATA, + template: get.METADATA.path, + path: get.path(), + }); +} +export namespace get { + export const METADATA = { + method: "GET", + path: "/health", + request: null, + response: { + type: "application/json", + encrypted: false, + }, + status: 200, + } as const; + + export const path = () => "/health"; +} diff --git a/test/features/route-manual-validate-log/src/api/functional/index.ts b/test/features/route-manual-validate-log/src/api/functional/index.ts new file mode 100644 index 000000000..3c05c315c --- /dev/null +++ b/test/features/route-manual-validate-log/src/api/functional/index.ts @@ -0,0 +1,8 @@ +/** + * @packageDocumentation + * @module api.functional + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +//================================================================ +export * as bbs from "./bbs"; +export * as health from "./health"; diff --git a/test/features/route-manual-validate-log/src/api/index.ts b/test/features/route-manual-validate-log/src/api/index.ts new file mode 100644 index 000000000..1705f43c8 --- /dev/null +++ b/test/features/route-manual-validate-log/src/api/index.ts @@ -0,0 +1,4 @@ +import * as api from "./module"; + +export * from "./module"; +export default api; diff --git a/test/features/route-manual-validate-log/src/api/module.ts b/test/features/route-manual-validate-log/src/api/module.ts new file mode 100644 index 000000000..2137b4473 --- /dev/null +++ b/test/features/route-manual-validate-log/src/api/module.ts @@ -0,0 +1,6 @@ +export type * from "./IConnection"; +export type * from "./Primitive"; +export type * from "./Resolved"; +export * from "./HttpError"; + +export * as functional from "./functional"; diff --git a/test/features/route-manual-validate-log/src/api/structures/IBbsArticle.ts b/test/features/route-manual-validate-log/src/api/structures/IBbsArticle.ts new file mode 100644 index 000000000..f741b6bcf --- /dev/null +++ b/test/features/route-manual-validate-log/src/api/structures/IBbsArticle.ts @@ -0,0 +1,11 @@ +import { tags } from "typia"; + +export interface IBbsArticle { + id: string & tags.Format<"uuid">; + title: string; + body: string; + thumbnail: + | null + | (string & tags.Format<"uri"> & tags.ContentMediaType<"image/*">); + created_at: string & tags.Format<"date-time">; +} diff --git a/test/features/route-manual-validate-log/src/controllers/BbsArticleController.ts b/test/features/route-manual-validate-log/src/controllers/BbsArticleController.ts new file mode 100644 index 000000000..370cbf1f3 --- /dev/null +++ b/test/features/route-manual-validate-log/src/controllers/BbsArticleController.ts @@ -0,0 +1,21 @@ +import { TypedParam, TypedRoute } from "@nestia/core"; +import { Controller } from "@nestjs/common"; +import { tags } from "typia"; + +import { IBbsArticle } from "@api/lib/structures/IBbsArticle"; + +@Controller("bbs/articles") +export class BbsArticlesController { + @TypedRoute.Get(":id") + public async at( + @TypedParam("id") id: string & tags.Format<"uuid">, + ): Promise { + return { + id, + title: "Hello, world!", + body: "This is a test article.", + thumbnail: null, + created_at: "wrong-data", + }; + } +} diff --git a/test/features/route-manual-validate-log/src/controllers/HealthController.ts b/test/features/route-manual-validate-log/src/controllers/HealthController.ts new file mode 100644 index 000000000..f7e06aef4 --- /dev/null +++ b/test/features/route-manual-validate-log/src/controllers/HealthController.ts @@ -0,0 +1,8 @@ +import core from "@nestia/core"; +import { Controller } from "@nestjs/common"; + +@Controller("health") +export class HealthController { + @core.TypedRoute.Get() + public get(): void {} +} diff --git a/test/features/route-manual-validate-log/src/test/features/api/test_api_bbs_article_at.ts b/test/features/route-manual-validate-log/src/test/features/api/test_api_bbs_article_at.ts new file mode 100644 index 000000000..781b53fc2 --- /dev/null +++ b/test/features/route-manual-validate-log/src/test/features/api/test_api_bbs_article_at.ts @@ -0,0 +1,34 @@ +import { TypedRoute } from "@nestia/core"; +import { TestValidator } from "@nestia/e2e"; +import typia from "typia"; +import { v4 } from "uuid"; + +import api from "@api"; +import { IBbsArticle } from "@api/lib/structures/IBbsArticle"; + +export const test_api_bbs_article_at = async ( + connection: api.IConnection, +): Promise => { + const logs: TypedRoute.IValidateErrorLog[] = []; + TypedRoute.setValidateErrorLogger((l) => logs.push(l)); + + const id: string = v4(); + const article: IBbsArticle = await api.functional.bbs.articles.at( + connection, + id, + ); + TestValidator.error("wrong data")(() => typia.assert(article)); + TestValidator.equals("logs")(logs)([ + { + errors: [ + { + path: "$input.created_at", + expected: `string & Format<"date-time">`, + value: "wrong-data", + }, + ], + method: "GET", + path: `/bbs/articles/${id}`, + }, + ]); +}; diff --git a/test/features/route-manual-validate-log/src/test/features/api/test_api_health_check.ts b/test/features/route-manual-validate-log/src/test/features/api/test_api_health_check.ts new file mode 100644 index 000000000..4dceb4cec --- /dev/null +++ b/test/features/route-manual-validate-log/src/test/features/api/test_api_health_check.ts @@ -0,0 +1,5 @@ +import api from "@api"; + +export const test_api_monitor_health_check = ( + connection: api.IConnection, +): Promise => api.functional.health.get(connection); diff --git a/test/features/route-manual-validate-log/src/test/index.ts b/test/features/route-manual-validate-log/src/test/index.ts new file mode 100644 index 000000000..31a7190ab --- /dev/null +++ b/test/features/route-manual-validate-log/src/test/index.ts @@ -0,0 +1,52 @@ +import { DynamicExecutor } from "@nestia/e2e"; +import chalk from "chalk"; + +import { Backend } from "../Backend"; + +async function main(): Promise { + const server: Backend = new Backend(); + await server.open(); + + const report: DynamicExecutor.IReport = await DynamicExecutor.validate({ + extension: __filename.substring(__filename.length - 2), + prefix: "test", + parameters: () => [ + { + host: "http://127.0.0.1:37000", + encryption: { + key: "A".repeat(32), + iv: "B".repeat(16), + }, + }, + ], + location: `${__dirname}/features`, + onComplete: (exec) => { + const trace = (str: string) => + console.log(` - ${chalk.green(exec.name)}: ${str}`); + if (exec.error === null) { + const elapsed: number = + new Date(exec.completed_at).getTime() - + new Date(exec.started_at).getTime(); + trace(`${chalk.yellow(elapsed.toLocaleString())} ms`); + } else trace(chalk.red(exec.error.name)); + }, + }); + await server.close(); + + const exceptions: Error[] = report.executions + .filter((exec) => exec.error !== null) + .map((exec) => exec.error!); + if (exceptions.length === 0) { + console.log("Success"); + console.log("Elapsed time", report.time.toLocaleString(), `ms`); + } else { + for (const exp of exceptions) console.log(exp); + console.log("Failed"); + console.log("Elapsed time", report.time.toLocaleString(), `ms`); + process.exit(-1); + } +} +main().catch((exp) => { + console.log(exp); + process.exit(-1); +}); diff --git a/test/features/route-manual-validate-log/swagger.json b/test/features/route-manual-validate-log/swagger.json new file mode 100644 index 000000000..e92f2ae6a --- /dev/null +++ b/test/features/route-manual-validate-log/swagger.json @@ -0,0 +1 @@ +{"openapi":"3.1.0","servers":[{"url":"https://github.com/samchon/nestia","description":"insert your server url"}],"info":{"version":"3.15.0-dev.20240926","title":"@samchon/nestia-test","description":"Test program of Nestia","license":{"name":"MIT"}},"paths":{"/bbs/articles/{id}":{"get":{"tags":[],"parameters":[{"name":"id","in":"path","schema":{"type":"string","format":"uuid"},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IBbsArticle"}}}}}}},"/health":{"get":{"tags":[],"parameters":[],"responses":{"200":{"description":"","content":{"application/json":{}}}}}}},"components":{"schemas":{"IBbsArticle":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"body":{"type":"string"},"thumbnail":{"oneOf":[{"type":"null"},{"type":"string","format":"uri","contentMediaType":"image/*"}]},"created_at":{"type":"string","format":"date-time"}},"required":["id","title","body","thumbnail","created_at"]}},"securitySchemes":{"bearer":{"type":"apiKey"}}},"tags":[],"x-samchon-emended":true} \ No newline at end of file diff --git a/test/features/route-manual-validate-log/tsconfig.json b/test/features/route-manual-validate-log/tsconfig.json new file mode 100644 index 000000000..1a65dafd1 --- /dev/null +++ b/test/features/route-manual-validate-log/tsconfig.json @@ -0,0 +1,102 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */// "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + "paths": { + "@api": ["./src/api"], + "@api/lib/*": ["./src/api/*"], + }, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./bin", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. *//* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true, /* Skip type checking all .d.ts files. */ + "plugins": [ + { "transform": "typescript-transform-paths" }, + { "transform": "typia/lib/transform" }, + { + "transform": "@nestia/core/lib/transform", + "stringify": "validate.log", + }, + ], + }, + "include": ["src"], + } \ No newline at end of file diff --git a/test/package.json b/test/package.json index 07a482b85..9f42bd58f 100644 --- a/test/package.json +++ b/test/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@samchon/nestia-test", - "version": "3.14.1", + "version": "3.15.0-dev.20240926", "description": "Test program of Nestia", "main": "index.js", "scripts": { @@ -26,7 +26,7 @@ }, "homepage": "https://nestia.io", "devDependencies": { - "@nestia/sdk": "^3.14.1", + "@nestia/sdk": "../packages/sdk/nestia-sdk-3.15.0-dev.20240926.tgz", "@nestjs/swagger": "^7.4.2", "@samchon/openapi": "^1.0.6", "@types/express": "^4.17.17", @@ -40,9 +40,9 @@ }, "dependencies": { "@fastify/multipart": "^8.1.0", - "@nestia/core": "^3.14.1", + "@nestia/core": "../packages/core/nestia-core-3.15.0-dev.20240926.tgz", "@nestia/e2e": "^0.7.0", - "@nestia/fetcher": "^3.14.1", + "@nestia/fetcher": "../packages/fetcher/nestia-fetcher-3.15.0-dev.20240926.tgz", "@nestjs/common": "^10.4.4", "@nestjs/core": "^10.4.4", "@nestjs/platform-express": "^10.4.4", diff --git a/test/template/success/src/api/functional/health/index.ts b/test/template/success/src/api/functional/health/index.ts deleted file mode 100644 index 311cea08a..000000000 --- a/test/template/success/src/api/functional/health/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @packageDocumentation - * @module api.functional.health - * @nestia Generated by Nestia - https://github.com/samchon/nestia - */ -//================================================================ -import { Fetcher } from "@nestia/fetcher"; -import type { IConnection } from "@nestia/fetcher"; - -/** - * @controller HealthController.get() - * @path GET /health - * @nestia Generated by Nestia - https://github.com/samchon/nestia - */ -export function get(connection: IConnection): Promise { - return Fetcher.fetch(connection, get.ENCRYPTED, get.METHOD, get.path()); -} -export namespace get { - export const METHOD = "GET" as const; - export const PATH: string = "/health"; - export const ENCRYPTED: Fetcher.IEncrypted = { - request: false, - response: false, - }; - - export function path(): string { - return `/health`; - } -} diff --git a/test/template/success/src/api/functional/performance/index.ts b/test/template/success/src/api/functional/performance/index.ts deleted file mode 100644 index 9d122ab17..000000000 --- a/test/template/success/src/api/functional/performance/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @packageDocumentation - * @module api.functional.performance - * @nestia Generated by Nestia - https://github.com/samchon/nestia - */ -//================================================================ -import { Fetcher } from "@nestia/fetcher"; -import type { IConnection } from "@nestia/fetcher"; - -import type { IPerformance } from "../../structures/IPerformance"; - -/** - * @controller PerformanceController.get() - * @path GET /performance - * @nestia Generated by Nestia - https://github.com/samchon/nestia - */ -export function get(connection: IConnection): Promise { - return Fetcher.fetch(connection, get.ENCRYPTED, get.METHOD, get.path()); -} -export namespace get { - export type Output = IPerformance; - - export const METHOD = "GET" as const; - export const PATH: string = "/performance"; - export const ENCRYPTED: Fetcher.IEncrypted = { - request: false, - response: false, - }; - - export function path(): string { - return `/performance`; - } -} diff --git a/test/template/success/src/api/structures/IPerformance.ts b/test/template/success/src/api/structures/IPerformance.ts deleted file mode 100644 index 22b8f2840..000000000 --- a/test/template/success/src/api/structures/IPerformance.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Performance info. - * - * @author Samchon - */ -export interface IPerformance { - cpu: NodeJS.CpuUsage; - memory: NodeJS.MemoryUsage; - resource: NodeJS.ResourceUsage; -} diff --git a/test/template/success/src/api/structures/ISystem.ts b/test/template/success/src/api/structures/ISystem.ts deleted file mode 100644 index 0e135bede..000000000 --- a/test/template/success/src/api/structures/ISystem.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * System Information. - * - * @author Jeongho Nam - */ -export interface ISystem { - /** - * Random Unique ID. - */ - uid: number; - - /** - * `process.argv` - */ - arguments: string[]; - - /** - * Git commit info. - */ - commit: ISystem.ICommit; - - /** - * `package.json` - */ - package: ISystem.IPackage; - - /** - * Creation time of this server. - */ - created_at: string; -} - -export namespace ISystem { - /** - * Git commit info. - */ - export interface ICommit { - shortHash: string; - branch: string; - hash: string; - subject: string; - sanitizedSubject: string; - body: string; - author: ICommit.IUser; - committer: ICommit.IUser; - authored_at: string; - commited_at: string; - notes?: string; - tags: string[]; - } - export namespace ICommit { - /** - * Git user account info. - */ - export interface IUser { - name: string; - email: string; - } - } - - /** - * NPM package info. - */ - export interface IPackage { - name: string; - version: string; - description: string; - main?: string; - typings?: string; - scripts: Record; - repository: { type: "git"; url: string }; - author: string; - license: string; - bugs: { url: string }; - homepage: string; - devDependencies?: Record; - dependencies: Record; - publishConfig?: { registry: string }; - files?: string[]; - } -} diff --git a/test/template/success/src/controllers/PerformanceController.ts b/test/template/success/src/controllers/PerformanceController.ts deleted file mode 100644 index 616548cf8..000000000 --- a/test/template/success/src/controllers/PerformanceController.ts +++ /dev/null @@ -1,16 +0,0 @@ -import core from "@nestia/core"; -import { Controller } from "@nestjs/common"; - -import { IPerformance } from "@api/lib/structures/IPerformance"; - -@Controller("performance") -export class PerformanceController { - @core.TypedRoute.Get() - public async get(): Promise { - return { - cpu: process.cpuUsage(), - memory: process.memoryUsage(), - resource: process.resourceUsage(), - }; - } -} diff --git a/test/template/success/src/test/index.ts b/test/template/success/src/test/index.ts index 3e74f8d31..31a7190ab 100644 --- a/test/template/success/src/test/index.ts +++ b/test/template/success/src/test/index.ts @@ -1,4 +1,5 @@ import { DynamicExecutor } from "@nestia/e2e"; +import chalk from "chalk"; import { Backend } from "../Backend"; @@ -18,7 +19,18 @@ async function main(): Promise { }, }, ], - })(`${__dirname}/features`); + location: `${__dirname}/features`, + onComplete: (exec) => { + const trace = (str: string) => + console.log(` - ${chalk.green(exec.name)}: ${str}`); + if (exec.error === null) { + const elapsed: number = + new Date(exec.completed_at).getTime() - + new Date(exec.started_at).getTime(); + trace(`${chalk.yellow(elapsed.toLocaleString())} ms`); + } else trace(chalk.red(exec.error.name)); + }, + }); await server.close(); const exceptions: Error[] = report.executions From a954a6a4799b6adadb7287c4350895dc4f44b17e Mon Sep 17 00:00:00 2001 From: Jeongho Nam Date: Thu, 26 Sep 2024 15:10:16 +0900 Subject: [PATCH 2/4] Update setup.mdx --- website/pages/docs/setup.mdx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/website/pages/docs/setup.mdx b/website/pages/docs/setup.mdx index 573576a2a..ac047d116 100644 --- a/website/pages/docs/setup.mdx +++ b/website/pages/docs/setup.mdx @@ -441,6 +441,9 @@ As `@nestia/core` and `typia` are generating optimal validation and JSON seriali - [`validateEquals`](https://typia.io/docs/validators/validate/#validateequals-function) - [`assertPrune`](https://typia.io/docs/misc/#prune-functions) or [`validatePrune`](https://typia.io/docs/misc/#prune-functions): prune extra properties with type checking - [`assertClone`](https://typia.io/docs/misc/#clone-functions) or [`validateClone`](https://typia.io/docs/misc/#clone-functions): deep clone with type checking for faster pruning + - Only for `stringify` + - [`validate.log`](https://typia.io/docs/validators/validate/): check response data, but do not throw error and just log it + - [`validateEquals.log`](https://typia.io/docs/validators/validate/#validateequals-function): check response data, but do not throw error and just log it Also, never forget to configure `strict` (or `strictNullChecks`) as `true`. It is essential option for modern TypeScript development. From f5d6d387fc32fc3a5d3e849ab60aaf82f744acf5 Mon Sep 17 00:00:00 2001 From: Jeongho Nam Date: Thu, 26 Sep 2024 15:12:42 +0900 Subject: [PATCH 3/4] Upgrade `typia` and `@samchon/openapi` dependencies --- benchmark/package.json | 2 +- packages/benchmark/package.json | 2 +- packages/core/package.json | 6 +++--- packages/e2e/package.json | 2 +- packages/fetcher/package.json | 2 +- packages/migrate/package.json | 4 ++-- packages/sdk/package.json | 6 +++--- .../distribute-assert-json/packages/api/package.json | 2 +- test/features/distribute-assert/packages/api/package.json | 2 +- test/features/distribute-json/packages/api/package.json | 2 +- test/features/distribute/packages/api/package.json | 2 +- test/package.json | 4 ++-- website/package.json | 4 ++-- 13 files changed, 20 insertions(+), 20 deletions(-) diff --git a/benchmark/package.json b/benchmark/package.json index ff72f30ad..dc8004b37 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -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", diff --git a/packages/benchmark/package.json b/packages/benchmark/package.json index 3691b0edb..ebf6f2cb1 100644 --- a/packages/benchmark/package.json +++ b/packages/benchmark/package.json @@ -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": { diff --git a/packages/core/package.json b/packages/core/package.json index d0449cd39..a7abb6b8e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -39,7 +39,7 @@ "@nestia/fetcher": "../fetcher/nestia-fetcher-3.15.0-dev.20240926.tgz", "@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", @@ -49,7 +49,7 @@ "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": { @@ -58,7 +58,7 @@ "@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", diff --git a/packages/e2e/package.json b/packages/e2e/package.json index ad24ed5cd..dba8e0a83 100644 --- a/packages/e2e/package.json +++ b/packages/e2e/package.json @@ -41,7 +41,7 @@ "ts-patch": "^3.2.1", "typescript": "^5.5.3", "typescript-transform-paths": "^3.4.7", - "typia": "^6.10.0" + "typia": "^6.11.0" }, "files": [ "lib", diff --git a/packages/fetcher/package.json b/packages/fetcher/package.json index e23bb89f1..bc734f706 100644 --- a/packages/fetcher/package.json +++ b/packages/fetcher/package.json @@ -26,7 +26,7 @@ }, "homepage": "https://nestia.io", "dependencies": { - "@samchon/openapi": "^1.0.2" + "@samchon/openapi": "^1.1.0" }, "peerDependencies": { "typescript": ">= 4.8.0" diff --git a/packages/migrate/package.json b/packages/migrate/package.json index 63935a541..eb246827f 100644 --- a/packages/migrate/package.json +++ b/packages/migrate/package.json @@ -65,13 +65,13 @@ "typescript-transform-paths": "^3.4.6" }, "dependencies": { - "@samchon/openapi": "^1.0.2", + "@samchon/openapi": "^1.1.0", "commander": "10.0.0", "inquirer": "8.2.5", "prettier": "^3.2.5", "tstl": "^3.0.0", "typescript": "^5.5.4", - "typia": "^6.10.0" + "typia": "^6.11.0" }, "files": [ "lib", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index a2d7c8698..ea6e153c9 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -34,7 +34,7 @@ "dependencies": { "@nestia/core": "../core/nestia-core-3.15.0-dev.20240926.tgz", "@nestia/fetcher": "../fetcher/nestia-fetcher-3.15.0-dev.20240926.tgz", - "@samchon/openapi": "^1.0.2", + "@samchon/openapi": "^1.1.0", "cli": "^1.0.1", "get-function-location": "^2.0.0", "glob": "^7.2.0", @@ -44,7 +44,7 @@ "tsconfck": "^2.1.2", "tsconfig-paths": "^4.1.1", "tstl": "^3.0.0", - "typia": "^6.10.0" + "typia": "^6.11.0" }, "peerDependencies": { "@nestia/core": ">=3.15.0-dev.20240926", @@ -53,7 +53,7 @@ "@nestjs/core": ">=7.0.1", "reflect-metadata": ">=0.1.12", "ts-node": ">=10.6.0", - "typia": ">=6.10.0 <7.0.0" + "typia": ">=6.11.0 <7.0.0" }, "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.3.0", diff --git a/test/features/distribute-assert-json/packages/api/package.json b/test/features/distribute-assert-json/packages/api/package.json index d6a1c5fc1..e6a365c41 100644 --- a/test/features/distribute-assert-json/packages/api/package.json +++ b/test/features/distribute-assert-json/packages/api/package.json @@ -33,6 +33,6 @@ }, "dependencies": { "@nestia/fetcher": "^3.13.0-dev.2024910", - "typia": "^6.10.0" + "typia": "^6.11.0" } } \ No newline at end of file diff --git a/test/features/distribute-assert/packages/api/package.json b/test/features/distribute-assert/packages/api/package.json index d6a1c5fc1..e6a365c41 100644 --- a/test/features/distribute-assert/packages/api/package.json +++ b/test/features/distribute-assert/packages/api/package.json @@ -33,6 +33,6 @@ }, "dependencies": { "@nestia/fetcher": "^3.13.0-dev.2024910", - "typia": "^6.10.0" + "typia": "^6.11.0" } } \ No newline at end of file diff --git a/test/features/distribute-json/packages/api/package.json b/test/features/distribute-json/packages/api/package.json index d6a1c5fc1..e6a365c41 100644 --- a/test/features/distribute-json/packages/api/package.json +++ b/test/features/distribute-json/packages/api/package.json @@ -33,6 +33,6 @@ }, "dependencies": { "@nestia/fetcher": "^3.13.0-dev.2024910", - "typia": "^6.10.0" + "typia": "^6.11.0" } } \ No newline at end of file diff --git a/test/features/distribute/packages/api/package.json b/test/features/distribute/packages/api/package.json index d6a1c5fc1..e6a365c41 100644 --- a/test/features/distribute/packages/api/package.json +++ b/test/features/distribute/packages/api/package.json @@ -33,6 +33,6 @@ }, "dependencies": { "@nestia/fetcher": "^3.13.0-dev.2024910", - "typia": "^6.10.0" + "typia": "^6.11.0" } } \ No newline at end of file diff --git a/test/package.json b/test/package.json index 9f42bd58f..b838e3981 100644 --- a/test/package.json +++ b/test/package.json @@ -28,7 +28,7 @@ "devDependencies": { "@nestia/sdk": "../packages/sdk/nestia-sdk-3.15.0-dev.20240926.tgz", "@nestjs/swagger": "^7.4.2", - "@samchon/openapi": "^1.0.6", + "@samchon/openapi": "^1.1.0", "@types/express": "^4.17.17", "@types/node": "20.11.16", "@types/uuid": "^9.0.8", @@ -49,7 +49,7 @@ "@nestjs/platform-fastify": "^10.4.4", "tgrid": "^1.0.3", "tstl": "^3.0.0", - "typia": "6.10.4", + "typia": "6.11.0", "uuid": "^9.0.1" } } \ No newline at end of file diff --git a/website/package.json b/website/package.json index d1c7e5711..88dfec847 100644 --- a/website/package.json +++ b/website/package.json @@ -24,7 +24,7 @@ "@mui/material": "5.15.6", "@mui/system": "5.15.6", "@nestia/migrate": "^0.18.2", - "@samchon/openapi": "^1.0.2", + "@samchon/openapi": "^1.1.0", "@stackblitz/sdk": "^1.9.0", "js-yaml": "^4.1.0", "next": "14.2.5", @@ -34,7 +34,7 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-mui-fileuploader": "^0.5.2", - "typia": "^6.10.0" + "typia": "^6.11.0" }, "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.3.0", From f2b72293880781bf9412980ba52bf14bd223a007 Mon Sep 17 00:00:00 2001 From: Jeongho Nam Date: Thu, 26 Sep 2024 16:38:41 +0900 Subject: [PATCH 4/4] Documentation about `validate.log` --- .github/workflows/build.yml | 10 ++--- .github/workflows/migrate.yaml | 27 +++++++++++++ package.json | 2 +- packages/core/package.json | 6 +-- packages/fetcher/package.json | 2 +- packages/sdk/package.json | 10 ++--- test/package.json | 8 ++-- website/pages/docs/core/TypedBody.mdx | 56 ++++++++++++++------------ website/pages/docs/core/TypedRoute.mdx | 51 +++++++++++++++++++++++ 9 files changed, 125 insertions(+), 47 deletions(-) create mode 100644 .github/workflows/migrate.yaml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8ca41be33..fb4fe4648 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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: @@ -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 \ No newline at end of file diff --git a/.github/workflows/migrate.yaml b/.github/workflows/migrate.yaml new file mode 100644 index 000000000..ae1c8eb1f --- /dev/null +++ b/.github/workflows/migrate.yaml @@ -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 \ No newline at end of file diff --git a/package.json b/package.json index 2e39a09ba..08f57d893 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@nestia/station", - "version": "3.15.0-dev.20240926", + "version": "3.15.0", "description": "Nestia station", "scripts": { "build": "node build/index.js", diff --git a/packages/core/package.json b/packages/core/package.json index a7abb6b8e..9fc09d47a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@nestia/core", - "version": "3.15.0-dev.20240926", + "version": "3.15.0", "description": "Super-fast validation decorators of NestJS", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -36,7 +36,7 @@ }, "homepage": "https://nestia.io", "dependencies": { - "@nestia/fetcher": "../fetcher/nestia-fetcher-3.15.0-dev.20240926.tgz", + "@nestia/fetcher": "^3.15.0", "@nestjs/common": ">=7.0.1", "@nestjs/core": ">=7.0.1", "@samchon/openapi": "^1.1.0", @@ -53,7 +53,7 @@ "ws": "^7.5.3" }, "peerDependencies": { - "@nestia/fetcher": ">=3.15.0-dev.20240926", + "@nestia/fetcher": ">=3.15.0", "@nestjs/common": ">=7.0.1", "@nestjs/core": ">=7.0.1", "reflect-metadata": ">=0.1.12", diff --git a/packages/fetcher/package.json b/packages/fetcher/package.json index bc734f706..cbd8446d1 100644 --- a/packages/fetcher/package.json +++ b/packages/fetcher/package.json @@ -1,6 +1,6 @@ { "name": "@nestia/fetcher", - "version": "3.15.0-dev.20240926", + "version": "3.15.0", "description": "Fetcher library of Nestia SDK", "main": "lib/index.js", "typings": "lib/index.d.ts", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index ea6e153c9..64ab8d29c 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@nestia/sdk", - "version": "3.15.0-dev.20240926", + "version": "3.15.0", "description": "Nestia SDK and Swagger generator", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -32,8 +32,8 @@ }, "homepage": "https://nestia.io", "dependencies": { - "@nestia/core": "../core/nestia-core-3.15.0-dev.20240926.tgz", - "@nestia/fetcher": "../fetcher/nestia-fetcher-3.15.0-dev.20240926.tgz", + "@nestia/core": "^3.15.0", + "@nestia/fetcher": "^3.15.0", "@samchon/openapi": "^1.1.0", "cli": "^1.0.1", "get-function-location": "^2.0.0", @@ -47,8 +47,8 @@ "typia": "^6.11.0" }, "peerDependencies": { - "@nestia/core": ">=3.15.0-dev.20240926", - "@nestia/fetcher": ">=3.15.0-dev.20240926", + "@nestia/core": ">=3.15.0", + "@nestia/fetcher": ">=3.15.0", "@nestjs/common": ">=7.0.1", "@nestjs/core": ">=7.0.1", "reflect-metadata": ">=0.1.12", diff --git a/test/package.json b/test/package.json index b838e3981..325cdc971 100644 --- a/test/package.json +++ b/test/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@samchon/nestia-test", - "version": "3.15.0-dev.20240926", + "version": "3.15.0", "description": "Test program of Nestia", "main": "index.js", "scripts": { @@ -26,7 +26,7 @@ }, "homepage": "https://nestia.io", "devDependencies": { - "@nestia/sdk": "../packages/sdk/nestia-sdk-3.15.0-dev.20240926.tgz", + "@nestia/sdk": "^3.15.0", "@nestjs/swagger": "^7.4.2", "@samchon/openapi": "^1.1.0", "@types/express": "^4.17.17", @@ -40,9 +40,9 @@ }, "dependencies": { "@fastify/multipart": "^8.1.0", - "@nestia/core": "../packages/core/nestia-core-3.15.0-dev.20240926.tgz", + "@nestia/core": "^3.15.0", "@nestia/e2e": "^0.7.0", - "@nestia/fetcher": "../packages/fetcher/nestia-fetcher-3.15.0-dev.20240926.tgz", + "@nestia/fetcher": "^3.15.0", "@nestjs/common": "^10.4.4", "@nestjs/core": "^10.4.4", "@nestjs/platform-express": "^10.4.4", diff --git a/website/pages/docs/core/TypedBody.mdx b/website/pages/docs/core/TypedBody.mdx index 676ba8710..49051c54e 100644 --- a/website/pages/docs/core/TypedBody.mdx +++ b/website/pages/docs/core/TypedBody.mdx @@ -678,27 +678,46 @@ exports.checkSpecialTag = checkSpecialTag; +## `EncryptedBody` +Encrypted request body decorator function. + +`@EncryptedBody()` is a decorator function similar with `@TypedBody()`, but it encrypts the request body through AES-128/256-CBC algorithm like below. Therefore, it would be slower than `@TypedBody()`, but it guarantees the security of request body data. + + - AES-128/256 + - CBC mode + - PKCS #5 Padding + - Base64 Encoding + +For reference, such encryption spec is not supported in the [Swagger-UI](../sdk/swagger). Instead, [SDK (Software Development Kit)](../sdk/sdk) generated by `@nestia/sdk` supports it. Thus, you have to build and distribute the SDK library to the client developers when using such encryption decorators. + + + + ## Configuration -```json filename="tsconfig.json" showLineNumbers {9} copy +```json filename="tsconfig.json" showLineNumbers {8} { - "strict": true, - "strictNullChecks": true, "compilerOptions": { + "strict": true, "plugins": [ { "transform": "typia/lib/transform" }, - { + { "transform": "@nestia/core/lib/transform", - "validate": "valiateEquals", - "stringify": "assert", - }, - ], - }, + "validate": "assert", + // "assert" + // "is" + // "validate" + // "assertEquals" + // "equals" + // "validateEquals" + } + ] + } } ``` -Change type validation function to other one. +Change type validation function to another one. -If you configure `validate` property of plugin defined in the `tsconfig.json` file, you can change the `@TypedBody()` to use another validation function instead of using the default [`typia.assert`](https://typia.io/docs/validators/assert) function. For example, if you want to use [`typia.validateEquals()`](https://typia.io/docs/validators/validate/#validateequals-function) function instead, then set the `validate` property to `validateEquals`. +If you configure `validate` property of plugin defined in the `tsconfig.json` file, you can change the `@TypedBody()` to use another validation function instead of the default [`typia.assert`](https://typia.io/docs/validators/assert) function. For example, if you want to use [`typia.validateEquals()`](https://typia.io/docs/validators/validate/#validateequals-function) function instead, then set the `validate` property to `validateEquals`. Below is the list of available validation functions. @@ -711,21 +730,6 @@ Below is the list of available validation functions. -## `EncryptedBody` -Encrypted request body decorator function. - -`@EncryptedBody()` is a decorator function similar with `@TypedBody()`, but it encrypts the request body through AES-128/256-CBC algorithm like below. Therefore, it would be slower than `@TypedBody()`, but it guarantees the security of request body data. - - - AES-128/256 - - CBC mode - - PKCS #5 Padding - - Base64 Encoding - -For reference, such encryption spec is not supported in the [Swagger-UI](../sdk/swagger). Instead, [SDK (Software Development Kit)](../sdk/sdk) generated by `@nestia/sdk` supports it. Thus, you have to build and distribute the SDK library to the client developers when using such encryption decorators. - - - - ## Benchmark Super-fast and super-safe. diff --git a/website/pages/docs/core/TypedRoute.mdx b/website/pages/docs/core/TypedRoute.mdx index 2f7ba731b..c76aac89e 100644 --- a/website/pages/docs/core/TypedRoute.mdx +++ b/website/pages/docs/core/TypedRoute.mdx @@ -425,6 +425,7 @@ Such optimization is called AOT (Ahead of Time) compilation, and it is the secre + ## Special Tags You can enhance validation logic, of `TypedRoute`, through comment tags. @@ -540,6 +541,56 @@ For reference, such encryption spec is not supported in the [Swagger-UI](../sdk/ +## Configuration +```json filename="tsconfig.json" showLineNumbers {8} +{ + "compilerOptions": { + "strict": true, + "plugins": [ + { "transform": "typia/lib/transform" }, + { + "transform": "@nestia/core/lib/transform", + "stringify": "assert", + // "stringify": typia.stringify without validation + // "assert": typia.assertStringify + // "is": typia.isStringify + // "validate": typia.validateStringify + // "validate.log": do not throw error, just log + // "validateEquals".log: do not throw error, just log + } + ] + } +} +``` + +Change JSON serializer to another one. + +If you configure `stringify` property of plugin defined in the `tsconfig.json` file, you can change the `@TypedRoute` module to utilize another JSON serialization function instead of the default [`typia.json.assertStringify()`](https://typia.io/docs/json/stringify/) function. For example, if you change the property to `"validate"`, the JSON serialization function of `@TypedRoute` module be changed to [`typia.json.validateStringify()`](https://typia.io/docs/json/stringify/) function. + +Here is the list of available options. + + - `stringify`: [`typia.json.stringify()`](https://typia.io/docs/json/stringify/) + - `assert`: [`typia.json.assertStringify()`](https://typia.io/docs/json/stringify/) + - `is`: [`typia.json.isStringify()`](https://typia.io/docs/json/stringify/) + - `validate`: [`typia.json.validateStringify()`](https://typia.io/docs/json/stringify/) + - `null`: just use `JSON.stringify()` function without validation + - `validate.log`: do not throw error, but just log by [`typia.validate()`](https://typia.io/docs/validators/validate/) + - `validateEquals.log`: do not throw error, but just log by [`typia.validateEquals()`](https://typia.io/docs/validators/validate/#validateequals-function) + +By the way, this is not a recommended way, but you can skip the response type validation for. If you set the `stringify` property to `null`, the response type validation will be skipped and just `JSON.stringify()` function be used. + +Also, `validate.log` and `validateEquals.log` perform the validation, but do not throw 500 internal server error. When type error be detected, it seralizes response data by `JSON.stringify()` function, and logs the error message to the console or somewhere you've specified. It is useful when you want to know the error message, but do not want to throw 500 internal server error to the client application. + +```typescript +TypedRoute.setValidateErrorLogger((err: TypedRoute.IValidateErrorLog) => { + // you can customize the validation error logging + console.error(err); +}); +``` + + + + ## Benchmark ### JSON Comparing JSON serialization speed, [`typia`](https://typia.io/docs/json/stringify) is maximum 200x faster than `class-transformer`.