@nestia/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/lib/decorators/EncryptedBody.d.ts +22 -0
- package/lib/decorators/EncryptedBody.js +125 -0
- package/lib/decorators/EncryptedBody.js.map +1 -0
- package/lib/decorators/EncryptedController.d.ts +31 -0
- package/lib/decorators/EncryptedController.js +42 -0
- package/lib/decorators/EncryptedController.js.map +1 -0
- package/lib/decorators/EncryptedModule.d.ts +48 -0
- package/lib/decorators/EncryptedModule.js +270 -0
- package/lib/decorators/EncryptedModule.js.map +1 -0
- package/lib/decorators/EncryptedRoute.d.ts +78 -0
- package/lib/decorators/EncryptedRoute.js +191 -0
- package/lib/decorators/EncryptedRoute.js.map +1 -0
- package/lib/decorators/PlainBody.d.ts +22 -0
- package/lib/decorators/PlainBody.js +83 -0
- package/lib/decorators/PlainBody.js.map +1 -0
- package/lib/decorators/TypedBody.d.ts +14 -0
- package/lib/decorators/TypedBody.js +93 -0
- package/lib/decorators/TypedBody.js.map +1 -0
- package/lib/decorators/TypedParam.d.ts +25 -0
- package/lib/decorators/TypedParam.js +62 -0
- package/lib/decorators/TypedParam.js.map +1 -0
- package/lib/decorators/TypedRoute.d.ts +71 -0
- package/lib/decorators/TypedRoute.js +159 -0
- package/lib/decorators/TypedRoute.js.map +1 -0
- package/lib/decorators/internal/EncryptedConstant.d.ts +1 -0
- package/lib/decorators/internal/EncryptedConstant.js +8 -0
- package/lib/decorators/internal/EncryptedConstant.js.map +1 -0
- package/lib/decorators/internal/get_path_and_stringify.d.ts +1 -0
- package/lib/decorators/internal/get_path_and_stringify.js +75 -0
- package/lib/decorators/internal/get_path_and_stringify.js.map +1 -0
- package/lib/decorators/internal/headers_to_object.d.ts +1 -0
- package/lib/decorators/internal/headers_to_object.js +53 -0
- package/lib/decorators/internal/headers_to_object.js.map +1 -0
- package/lib/decorators/internal/route_error.d.ts +1 -0
- package/lib/decorators/internal/route_error.js +90 -0
- package/lib/decorators/internal/route_error.js.map +1 -0
- package/lib/decorators/internal/validate_request_body.d.ts +2 -0
- package/lib/decorators/internal/validate_request_body.js +56 -0
- package/lib/decorators/internal/validate_request_body.js.map +1 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +32 -0
- package/lib/index.js.map +1 -0
- package/lib/module.d.ts +9 -0
- package/lib/module.js +26 -0
- package/lib/module.js.map +1 -0
- package/lib/options/INestiaTransformOptions.d.ts +5 -0
- package/lib/options/INestiaTransformOptions.js +3 -0
- package/lib/options/INestiaTransformOptions.js.map +1 -0
- package/lib/options/INestiaTransformProject.d.ts +5 -0
- package/lib/options/INestiaTransformProject.js +3 -0
- package/lib/options/INestiaTransformProject.js.map +1 -0
- package/lib/options/IRequestBodyValidator.d.ts +16 -0
- package/lib/options/IRequestBodyValidator.js +3 -0
- package/lib/options/IRequestBodyValidator.js.map +1 -0
- package/lib/options/IResponseBodyStringifier.d.ts +20 -0
- package/lib/options/IResponseBodyStringifier.js +3 -0
- package/lib/options/IResponseBodyStringifier.js.map +1 -0
- package/lib/transform.d.ts +3 -0
- package/lib/transform.js +21 -0
- package/lib/transform.js.map +1 -0
- package/lib/transformers/BodyTransformer.d.ts +5 -0
- package/lib/transformers/BodyTransformer.js +69 -0
- package/lib/transformers/BodyTransformer.js.map +1 -0
- package/lib/transformers/FileTransformer.d.ts +5 -0
- package/lib/transformers/FileTransformer.js +33 -0
- package/lib/transformers/FileTransformer.js.map +1 -0
- package/lib/transformers/MethodTransformer.d.ts +5 -0
- package/lib/transformers/MethodTransformer.js +55 -0
- package/lib/transformers/MethodTransformer.js.map +1 -0
- package/lib/transformers/NodeTransformer.d.ts +5 -0
- package/lib/transformers/NodeTransformer.js +21 -0
- package/lib/transformers/NodeTransformer.js.map +1 -0
- package/lib/transformers/ParameterTransformer.d.ts +5 -0
- package/lib/transformers/ParameterTransformer.js +31 -0
- package/lib/transformers/ParameterTransformer.js.map +1 -0
- package/lib/transformers/RouteTransformer.d.ts +5 -0
- package/lib/transformers/RouteTransformer.js +115 -0
- package/lib/transformers/RouteTransformer.js.map +1 -0
- package/lib/typings/Creator.d.ts +3 -0
- package/lib/typings/Creator.js +3 -0
- package/lib/typings/Creator.js.map +1 -0
- package/lib/utils/ExceptionManager.d.ts +64 -0
- package/lib/utils/ExceptionManager.js +113 -0
- package/lib/utils/ExceptionManager.js.map +1 -0
- package/lib/utils/Singleton.d.ts +1 -0
- package/lib/utils/Singleton.js +24 -0
- package/lib/utils/Singleton.js.map +1 -0
- package/package.json +66 -0
- package/src/decorators/EncryptedBody.ts +102 -0
- package/src/decorators/EncryptedController.ts +43 -0
- package/src/decorators/EncryptedModule.ts +127 -0
- package/src/decorators/EncryptedRoute.ts +200 -0
- package/src/decorators/PlainBody.ts +38 -0
- package/src/decorators/TypedBody.ts +49 -0
- package/src/decorators/TypedParam.ts +70 -0
- package/src/decorators/TypedRoute.ts +149 -0
- package/src/decorators/internal/EncryptedConstant.ts +4 -0
- package/src/decorators/internal/get_path_and_stringify.ts +76 -0
- package/src/decorators/internal/headers_to_object.ts +13 -0
- package/src/decorators/internal/route_error.ts +41 -0
- package/src/decorators/internal/validate_request_body.ts +58 -0
- package/src/index.ts +5 -0
- package/src/module.ts +9 -0
- package/src/options/INestiaTransformOptions.ts +6 -0
- package/src/options/INestiaTransformProject.ts +6 -0
- package/src/options/IRequestBodyValidator.ts +20 -0
- package/src/options/IResponseBodyStringifier.ts +25 -0
- package/src/transform.ts +20 -0
- package/src/transformers/BodyTransformer.ts +106 -0
- package/src/transformers/FileTransformer.ts +49 -0
- package/src/transformers/MethodTransformer.ts +91 -0
- package/src/transformers/NodeTransformer.ts +18 -0
- package/src/transformers/ParameterTransformer.ts +45 -0
- package/src/transformers/RouteTransformer.ts +134 -0
- package/src/typings/Creator.ts +3 -0
- package/src/utils/ExceptionManager.ts +126 -0
- package/src/utils/Singleton.ts +20 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BadRequestException,
|
|
3
|
+
ExecutionContext,
|
|
4
|
+
createParamDecorator,
|
|
5
|
+
} from "@nestjs/common";
|
|
6
|
+
import type express from "express";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* URL parameter decorator with type.
|
|
10
|
+
*
|
|
11
|
+
* `TypedParam` is a decorator function getting specific typed parameter from the HTTP
|
|
12
|
+
* request URL. It's almost same with the {@link nest.Param}, but `TypedParam` can specify
|
|
13
|
+
* the parameter type manually. Beside, the {@link nest.Param} always parses all of the
|
|
14
|
+
* parameters as string type.
|
|
15
|
+
*
|
|
16
|
+
* ```typescript
|
|
17
|
+
* \@TypedRoute.Get("shopping/sales/:section/:id/:paused")
|
|
18
|
+
* public async pause
|
|
19
|
+
* (
|
|
20
|
+
* \@TypedParam("section", "string") section: string,
|
|
21
|
+
* \@TypedParam("id", "number") id: number,
|
|
22
|
+
* \@TypedParam("paused", "boolean", true) paused: boolean | null
|
|
23
|
+
* ): Promise<void>;
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @param name URL Parameter name
|
|
27
|
+
* @param type Type of the URL parameter
|
|
28
|
+
* @returns Parameter decorator
|
|
29
|
+
*
|
|
30
|
+
* @author Jeongho Nam - https://github.com/samchon
|
|
31
|
+
*/
|
|
32
|
+
export function TypedParam(
|
|
33
|
+
name: string,
|
|
34
|
+
type: "boolean" | "number" | "string" | "uuid" = "string",
|
|
35
|
+
nullable: boolean = false,
|
|
36
|
+
) {
|
|
37
|
+
return createParamDecorator(function TypedParam(
|
|
38
|
+
{}: any,
|
|
39
|
+
ctx: ExecutionContext,
|
|
40
|
+
) {
|
|
41
|
+
const request: express.Request = ctx.switchToHttp().getRequest();
|
|
42
|
+
const str: string = request.params[name];
|
|
43
|
+
|
|
44
|
+
if (nullable === true && str === "null") return null;
|
|
45
|
+
else if (type === "boolean") {
|
|
46
|
+
if (str === "true" || str === "1") return true;
|
|
47
|
+
else if (str === "false" || str === "0") return false;
|
|
48
|
+
else
|
|
49
|
+
throw new BadRequestException(
|
|
50
|
+
`Value of the URL parameter '${name}' is not a boolean.`,
|
|
51
|
+
);
|
|
52
|
+
} else if (type === "number") {
|
|
53
|
+
const value: number = Number(str);
|
|
54
|
+
if (isNaN(value))
|
|
55
|
+
throw new BadRequestException(
|
|
56
|
+
`Value of the URL parameter "${name}" is not a number.`,
|
|
57
|
+
);
|
|
58
|
+
return value;
|
|
59
|
+
} else if (type === "uuid") {
|
|
60
|
+
if (UUID_PATTERN.test(str) === false)
|
|
61
|
+
throw new BadRequestException(
|
|
62
|
+
`Value of the URL parameter "${name}" is not a valid UUID.`,
|
|
63
|
+
);
|
|
64
|
+
return str;
|
|
65
|
+
} else return str;
|
|
66
|
+
})(name);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const UUID_PATTERN =
|
|
70
|
+
/^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CallHandler,
|
|
3
|
+
Delete,
|
|
4
|
+
ExecutionContext,
|
|
5
|
+
Get,
|
|
6
|
+
NestInterceptor,
|
|
7
|
+
Patch,
|
|
8
|
+
Post,
|
|
9
|
+
Put,
|
|
10
|
+
UseInterceptors,
|
|
11
|
+
applyDecorators,
|
|
12
|
+
} from "@nestjs/common";
|
|
13
|
+
import { HttpArgumentsHost } from "@nestjs/common/interfaces";
|
|
14
|
+
import express from "express";
|
|
15
|
+
import { Observable, catchError, map } from "rxjs";
|
|
16
|
+
import {
|
|
17
|
+
assertStringify,
|
|
18
|
+
isStringify,
|
|
19
|
+
stringify,
|
|
20
|
+
validateStringify,
|
|
21
|
+
} from "typia";
|
|
22
|
+
|
|
23
|
+
import { IResponseBodyStringifier } from "../options/IResponseBodyStringifier";
|
|
24
|
+
import { get_path_and_stringify } from "./internal/get_path_and_stringify";
|
|
25
|
+
import { route_error } from "./internal/route_error";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Safe router decorator functions.
|
|
29
|
+
*
|
|
30
|
+
* `TypedRoute` is a module containing router decorator functions which can boost up
|
|
31
|
+
* JSON string conversion speed about 5x times faster, through
|
|
32
|
+
* [`typia.stringify()`](https://github.com/samchon/typia#fastest-json-string-conversion).
|
|
33
|
+
*
|
|
34
|
+
* Also, router functions in `TypedRoute` can convert custom error classes to the
|
|
35
|
+
* regular {@link nest.HttpException} class automatically, through
|
|
36
|
+
* {@link ExceptionManager}.
|
|
37
|
+
*
|
|
38
|
+
* @author Jeongho Nam - https://github.com/samchon
|
|
39
|
+
*/
|
|
40
|
+
export namespace TypedRoute {
|
|
41
|
+
/**
|
|
42
|
+
* Router decorator function for the GET method.
|
|
43
|
+
*
|
|
44
|
+
* @param path Path of the HTTP request
|
|
45
|
+
* @returns Method decorator
|
|
46
|
+
*/
|
|
47
|
+
export const Get = Generator("Get");
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Router decorator function for the POST method.
|
|
51
|
+
*
|
|
52
|
+
* @param path Path of the HTTP request
|
|
53
|
+
* @returns Method decorator
|
|
54
|
+
*/
|
|
55
|
+
export const Post = Generator("Post");
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Router decorator function for the PATH method.
|
|
59
|
+
*
|
|
60
|
+
* @param path Path of the HTTP request
|
|
61
|
+
* @returns Method decorator
|
|
62
|
+
*/
|
|
63
|
+
export const Patch = Generator("Patch");
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Router decorator function for the PUT method.
|
|
67
|
+
*
|
|
68
|
+
* @param path Path of the HTTP request
|
|
69
|
+
* @returns Method decorator
|
|
70
|
+
*/
|
|
71
|
+
export const Put = Generator("Put");
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Router decorator function for the DELETE method.
|
|
75
|
+
*
|
|
76
|
+
* @param path Path of the HTTP request
|
|
77
|
+
* @returns Method decorator
|
|
78
|
+
*/
|
|
79
|
+
export const Delete = Generator("Delete");
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @internal
|
|
83
|
+
*/
|
|
84
|
+
function Generator(method: "Get" | "Post" | "Put" | "Patch" | "Delete") {
|
|
85
|
+
function route(path?: string | string[]): MethodDecorator;
|
|
86
|
+
function route<T>(
|
|
87
|
+
stringify?: IResponseBodyStringifier<T>,
|
|
88
|
+
): MethodDecorator;
|
|
89
|
+
function route<T>(
|
|
90
|
+
path: string | string[],
|
|
91
|
+
stringify?: IResponseBodyStringifier<T>,
|
|
92
|
+
): MethodDecorator;
|
|
93
|
+
|
|
94
|
+
function route(...args: any[]): MethodDecorator {
|
|
95
|
+
const [path, stringify] = get_path_and_stringify(
|
|
96
|
+
`TypedRoute.${method}`,
|
|
97
|
+
)(...args);
|
|
98
|
+
return applyDecorators(
|
|
99
|
+
ROUTERS[method](path),
|
|
100
|
+
UseInterceptors(new TypedRouteInterceptor(stringify)),
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return route;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
for (const method of [
|
|
107
|
+
assertStringify,
|
|
108
|
+
isStringify,
|
|
109
|
+
stringify,
|
|
110
|
+
validateStringify,
|
|
111
|
+
]) {
|
|
112
|
+
Object.assign(TypedRoute.Get, method);
|
|
113
|
+
Object.assign(TypedRoute.Delete, method);
|
|
114
|
+
Object.assign(TypedRoute.Post, method);
|
|
115
|
+
Object.assign(TypedRoute.Put, method);
|
|
116
|
+
Object.assign(TypedRoute.Patch, method);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @internal
|
|
121
|
+
*/
|
|
122
|
+
class TypedRouteInterceptor implements NestInterceptor {
|
|
123
|
+
public constructor(private readonly stringify: (input: any) => string) {}
|
|
124
|
+
|
|
125
|
+
public intercept(
|
|
126
|
+
context: ExecutionContext,
|
|
127
|
+
next: CallHandler,
|
|
128
|
+
): Observable<any> {
|
|
129
|
+
const http: HttpArgumentsHost = context.switchToHttp();
|
|
130
|
+
const response: express.Response = http.getResponse();
|
|
131
|
+
response.header("Content-Type", "application/json");
|
|
132
|
+
|
|
133
|
+
return next.handle().pipe(
|
|
134
|
+
map((value) => this.stringify(value)),
|
|
135
|
+
catchError((err) => route_error(http.getRequest(), err)),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @internal
|
|
142
|
+
*/
|
|
143
|
+
const ROUTERS = {
|
|
144
|
+
Get,
|
|
145
|
+
Post,
|
|
146
|
+
Patch,
|
|
147
|
+
Put,
|
|
148
|
+
Delete,
|
|
149
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { InternalServerErrorException } from "@nestjs/common";
|
|
2
|
+
import { IValidation, TypeGuardError } from "typia";
|
|
3
|
+
|
|
4
|
+
import { IResponseBodyStringifier } from "../../options/IResponseBodyStringifier";
|
|
5
|
+
|
|
6
|
+
export const get_path_and_stringify =
|
|
7
|
+
(method: string) =>
|
|
8
|
+
(
|
|
9
|
+
...args: any[]
|
|
10
|
+
): [string | string[] | undefined, (input: any) => string] => {
|
|
11
|
+
const path: string | string[] | null | undefined =
|
|
12
|
+
args[0] === undefined ||
|
|
13
|
+
typeof args[0] === "string" ||
|
|
14
|
+
Array.isArray(args[0])
|
|
15
|
+
? args[0]
|
|
16
|
+
: null;
|
|
17
|
+
const functor: IResponseBodyStringifier<any> | undefined =
|
|
18
|
+
path === null ? args[0] : args[1];
|
|
19
|
+
return [path ?? undefined, take(method)(functor)];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const take =
|
|
23
|
+
(method: string) =>
|
|
24
|
+
<T>(functor?: IResponseBodyStringifier<T>) => {
|
|
25
|
+
if (functor === undefined)
|
|
26
|
+
throw new Error(
|
|
27
|
+
`Error on nestia.core.${method}(): no stringify function provided.`,
|
|
28
|
+
);
|
|
29
|
+
else if (functor.type === "stringify") return functor.stringify;
|
|
30
|
+
else if (functor.type === "assert") return assert(functor.assert);
|
|
31
|
+
else if (functor.type === "is") return is(functor.is);
|
|
32
|
+
else if (functor.type === "validate") return validate(functor.validate);
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Error on nestia.core.${method}(): invalid typed stringify function.`,
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const assert =
|
|
39
|
+
<T>(closure: (data: T) => string) =>
|
|
40
|
+
(data: T) => {
|
|
41
|
+
try {
|
|
42
|
+
return closure(data);
|
|
43
|
+
} catch (exp) {
|
|
44
|
+
if (exp instanceof TypeGuardError) {
|
|
45
|
+
throw new InternalServerErrorException({
|
|
46
|
+
path: exp.path,
|
|
47
|
+
reason: exp.message,
|
|
48
|
+
expected: exp.expected,
|
|
49
|
+
message: MESSAGE,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
throw exp;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const is =
|
|
57
|
+
<T>(closure: (data: T) => string | null) =>
|
|
58
|
+
(data: T) => {
|
|
59
|
+
const result: string | null = closure(data);
|
|
60
|
+
if (result === null) throw new InternalServerErrorException(MESSAGE);
|
|
61
|
+
return result;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const validate =
|
|
65
|
+
<T>(closure: (data: T) => IValidation<string>) =>
|
|
66
|
+
(data: T) => {
|
|
67
|
+
const result: IValidation<string> = closure(data);
|
|
68
|
+
if (result.success === false)
|
|
69
|
+
throw new InternalServerErrorException({
|
|
70
|
+
errors: result.errors,
|
|
71
|
+
message: MESSAGE,
|
|
72
|
+
});
|
|
73
|
+
return result.data;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const MESSAGE = "Response body data is not following the promised type.";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import http from "http";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export function headers_to_object(
|
|
7
|
+
headers: http.IncomingHttpHeaders,
|
|
8
|
+
): Record<string, string> {
|
|
9
|
+
const output: Record<string, string> = {};
|
|
10
|
+
for (const [key, value] of Object.entries(headers))
|
|
11
|
+
output[key] = value instanceof Array ? value[0] : value || "";
|
|
12
|
+
return output;
|
|
13
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { HttpException } from "@nestjs/common";
|
|
2
|
+
import express from "express";
|
|
3
|
+
import { Observable, throwError } from "rxjs";
|
|
4
|
+
|
|
5
|
+
import { ExceptionManager } from "../../utils/ExceptionManager";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export function route_error(
|
|
11
|
+
request: express.Request,
|
|
12
|
+
error: any,
|
|
13
|
+
): Observable<never> {
|
|
14
|
+
error = (() => {
|
|
15
|
+
// HTTP-ERROR
|
|
16
|
+
if (error instanceof HttpException) return error;
|
|
17
|
+
|
|
18
|
+
// CUSTOM-REGISTERED ERROR
|
|
19
|
+
for (const [creator, closure] of ExceptionManager.tuples)
|
|
20
|
+
if (error instanceof creator) return closure(error);
|
|
21
|
+
|
|
22
|
+
// MAYBE INTERNAL ERROR
|
|
23
|
+
return error;
|
|
24
|
+
})();
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
error.method = request.method;
|
|
28
|
+
error.path = request.path;
|
|
29
|
+
} catch {}
|
|
30
|
+
|
|
31
|
+
setTimeout(() => {
|
|
32
|
+
for (const listener of ExceptionManager.listeners) {
|
|
33
|
+
try {
|
|
34
|
+
const res: any | Promise<any> = listener(error);
|
|
35
|
+
if (typeof res === "object" && typeof res.catch === "function")
|
|
36
|
+
res.catch(() => {});
|
|
37
|
+
} catch {}
|
|
38
|
+
}
|
|
39
|
+
}, 0);
|
|
40
|
+
return throwError(() => error);
|
|
41
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { BadRequestException } from "@nestjs/common";
|
|
2
|
+
import { IValidation, TypeGuardError } from "typia";
|
|
3
|
+
|
|
4
|
+
import { IRequestBodyValidator } from "../../options/IRequestBodyValidator";
|
|
5
|
+
|
|
6
|
+
export const validate_request_body =
|
|
7
|
+
(method: string) =>
|
|
8
|
+
<T>(validator?: IRequestBodyValidator<T>) => {
|
|
9
|
+
if (!validator)
|
|
10
|
+
throw new Error(
|
|
11
|
+
`Error on nestia.core.${method}(): no transformer.`,
|
|
12
|
+
);
|
|
13
|
+
else if (validator.type === "assert") return assert(validator.assert);
|
|
14
|
+
else if (validator.type === "is") return is(validator.is);
|
|
15
|
+
else if (validator.type === "validate")
|
|
16
|
+
return validate(validator.validate);
|
|
17
|
+
throw new Error(
|
|
18
|
+
`Error on nestia.core.${method}(): invalid typed validator.`,
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const assert =
|
|
23
|
+
<T>(closure: (data: T) => T) =>
|
|
24
|
+
(data: T) => {
|
|
25
|
+
try {
|
|
26
|
+
closure(data);
|
|
27
|
+
} catch (exp) {
|
|
28
|
+
if (exp instanceof TypeGuardError) {
|
|
29
|
+
throw new BadRequestException({
|
|
30
|
+
path: exp.path,
|
|
31
|
+
reason: exp.message,
|
|
32
|
+
expected: exp.expected,
|
|
33
|
+
message: MESSAGE,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
throw exp;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const is =
|
|
41
|
+
<T>(closure: (data: T) => boolean) =>
|
|
42
|
+
(data: T) => {
|
|
43
|
+
const success: boolean = closure(data);
|
|
44
|
+
if (success === false) throw new BadRequestException(MESSAGE);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const validate =
|
|
48
|
+
<T>(closure: (data: T) => IValidation<T>) =>
|
|
49
|
+
(data: T) => {
|
|
50
|
+
const result: IValidation<T> = closure(data);
|
|
51
|
+
if (result.success === false)
|
|
52
|
+
throw new BadRequestException({
|
|
53
|
+
errors: result.errors,
|
|
54
|
+
message: MESSAGE,
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const MESSAGE = "Request body data is not following the promised type.";
|
package/src/index.ts
ADDED
package/src/module.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./decorators/EncryptedBody";
|
|
2
|
+
export * from "./decorators/EncryptedController";
|
|
3
|
+
export * from "./decorators/EncryptedModule";
|
|
4
|
+
export * from "./decorators/EncryptedRoute";
|
|
5
|
+
export * from "./utils/ExceptionManager";
|
|
6
|
+
export * from "./decorators/PlainBody";
|
|
7
|
+
export * from "./decorators/TypedBody";
|
|
8
|
+
export * from "./decorators/TypedParam";
|
|
9
|
+
export * from "./decorators/TypedRoute";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { IValidation } from "typia";
|
|
2
|
+
|
|
3
|
+
export type IRequestBodyValidator<T> =
|
|
4
|
+
| IRequestBodyValidator.IAssert<T>
|
|
5
|
+
| IRequestBodyValidator.IIs<T>
|
|
6
|
+
| IRequestBodyValidator.IValidate<T>;
|
|
7
|
+
export namespace IRequestBodyValidator {
|
|
8
|
+
export interface IAssert<T> {
|
|
9
|
+
type: "assert";
|
|
10
|
+
assert: (input: T) => T;
|
|
11
|
+
}
|
|
12
|
+
export interface IIs<T> {
|
|
13
|
+
type: "is";
|
|
14
|
+
is: (input: T) => boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface IValidate<T> {
|
|
17
|
+
type: "validate";
|
|
18
|
+
validate: (input: T) => IValidation<T>;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { IValidation } from "typia";
|
|
2
|
+
|
|
3
|
+
export type IResponseBodyStringifier<T> =
|
|
4
|
+
| IResponseBodyStringifier.IStringify<T>
|
|
5
|
+
| IResponseBodyStringifier.IIs<T>
|
|
6
|
+
| IResponseBodyStringifier.IAssert<T>
|
|
7
|
+
| IResponseBodyStringifier.IValidate<T>;
|
|
8
|
+
export namespace IResponseBodyStringifier {
|
|
9
|
+
export interface IStringify<T> {
|
|
10
|
+
type: "stringify";
|
|
11
|
+
stringify: (input: T) => string;
|
|
12
|
+
}
|
|
13
|
+
export interface IIs<T> {
|
|
14
|
+
type: "is";
|
|
15
|
+
is: (input: T) => string | null;
|
|
16
|
+
}
|
|
17
|
+
export interface IAssert<T> {
|
|
18
|
+
type: "assert";
|
|
19
|
+
assert: (input: T) => string;
|
|
20
|
+
}
|
|
21
|
+
export interface IValidate<T> {
|
|
22
|
+
type: "validate";
|
|
23
|
+
validate: (input: T) => IValidation<string>;
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/transform.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { IProject } from "typia/lib/transformers/IProject";
|
|
3
|
+
|
|
4
|
+
import { INestiaTransformOptions } from "./options/INestiaTransformOptions";
|
|
5
|
+
import { FileTransformer } from "./transformers/FileTransformer";
|
|
6
|
+
|
|
7
|
+
export default function transform(
|
|
8
|
+
program: ts.Program,
|
|
9
|
+
options?: INestiaTransformOptions,
|
|
10
|
+
): ts.TransformerFactory<ts.SourceFile> {
|
|
11
|
+
const project: IProject = {
|
|
12
|
+
program,
|
|
13
|
+
compilerOptions: program.getCompilerOptions(),
|
|
14
|
+
checker: program.getTypeChecker(),
|
|
15
|
+
printer: ts.createPrinter(),
|
|
16
|
+
options: options || {},
|
|
17
|
+
};
|
|
18
|
+
return (context) => (file) =>
|
|
19
|
+
FileTransformer.transform(project, context, file);
|
|
20
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
import { AssertProgrammer } from "typia/lib/programmers/AssertProgrammer";
|
|
4
|
+
import { IsProgrammer } from "typia/lib/programmers/IsProgrammer";
|
|
5
|
+
import { ValidateProgrammer } from "typia/lib/programmers/ValidateProgrammer";
|
|
6
|
+
import { IProject } from "typia/lib/transformers/IProject";
|
|
7
|
+
|
|
8
|
+
import { INestiaTransformProject } from "../options/INestiaTransformProject";
|
|
9
|
+
|
|
10
|
+
export namespace BodyTransformer {
|
|
11
|
+
export function transform(
|
|
12
|
+
project: INestiaTransformProject,
|
|
13
|
+
type: ts.Type,
|
|
14
|
+
decorator: ts.Decorator,
|
|
15
|
+
): ts.Decorator {
|
|
16
|
+
if (!ts.isCallExpression(decorator.expression)) return decorator;
|
|
17
|
+
return ts.factory.createDecorator(
|
|
18
|
+
validate(project, type, decorator.expression),
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function validate(
|
|
23
|
+
project: INestiaTransformProject,
|
|
24
|
+
type: ts.Type,
|
|
25
|
+
expression: ts.CallExpression,
|
|
26
|
+
): ts.LeftHandSideExpression {
|
|
27
|
+
// CHECK SIGNATURE
|
|
28
|
+
const signature: ts.Signature | undefined =
|
|
29
|
+
project.checker.getResolvedSignature(expression);
|
|
30
|
+
if (!signature || !signature.declaration) return expression;
|
|
31
|
+
|
|
32
|
+
// CHECK TO BE TRANSFORMED
|
|
33
|
+
const validate: boolean = (() => {
|
|
34
|
+
// CHECK FILENAME
|
|
35
|
+
const location: string = path.resolve(
|
|
36
|
+
signature.declaration.getSourceFile().fileName,
|
|
37
|
+
);
|
|
38
|
+
if (
|
|
39
|
+
LIB_PATHS.every((str) => location.indexOf(str) === -1) &&
|
|
40
|
+
SRC_PATHS.every((str) => location !== str)
|
|
41
|
+
)
|
|
42
|
+
return false;
|
|
43
|
+
|
|
44
|
+
// CHECK DUPLICATED TRANSFORMATION
|
|
45
|
+
return expression.arguments.length === 0;
|
|
46
|
+
})();
|
|
47
|
+
if (validate === false) return expression;
|
|
48
|
+
|
|
49
|
+
// CHECK TYPE NODE
|
|
50
|
+
const typeNode: ts.TypeNode | undefined =
|
|
51
|
+
project.checker.typeToTypeNode(type, undefined, undefined);
|
|
52
|
+
if (typeNode === undefined) return expression;
|
|
53
|
+
|
|
54
|
+
//----
|
|
55
|
+
// TRANSFORMATION
|
|
56
|
+
//----
|
|
57
|
+
// GENERATE VALIDATION PLAN
|
|
58
|
+
const parameter = (
|
|
59
|
+
key: string,
|
|
60
|
+
programmer: (
|
|
61
|
+
project: IProject,
|
|
62
|
+
modulo: ts.LeftHandSideExpression,
|
|
63
|
+
) => (type: ts.Type) => ts.ArrowFunction,
|
|
64
|
+
) =>
|
|
65
|
+
ts.factory.createObjectLiteralExpression([
|
|
66
|
+
ts.factory.createPropertyAssignment(
|
|
67
|
+
ts.factory.createIdentifier("type"),
|
|
68
|
+
ts.factory.createStringLiteral(key),
|
|
69
|
+
),
|
|
70
|
+
ts.factory.createPropertyAssignment(
|
|
71
|
+
ts.factory.createIdentifier(key),
|
|
72
|
+
programmer(project, expression.expression)(type),
|
|
73
|
+
),
|
|
74
|
+
]);
|
|
75
|
+
const validator: ts.ObjectLiteralExpression = (() => {
|
|
76
|
+
if (project.options.validate === "is")
|
|
77
|
+
return parameter("is", IsProgrammer.generate);
|
|
78
|
+
else if (project.options.validate === "validate")
|
|
79
|
+
return parameter("validate", ValidateProgrammer.generate);
|
|
80
|
+
return parameter("assert", AssertProgrammer.generate);
|
|
81
|
+
})();
|
|
82
|
+
|
|
83
|
+
// UPDATE DECORATOR FUNCTION CALL
|
|
84
|
+
return ts.factory.updateCallExpression(
|
|
85
|
+
expression,
|
|
86
|
+
expression.expression,
|
|
87
|
+
expression.typeArguments,
|
|
88
|
+
[validator],
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const CLASSES = ["EncryptedBody", "TypedBody"];
|
|
93
|
+
const LIB_PATHS = CLASSES.map((cla) =>
|
|
94
|
+
path.join(
|
|
95
|
+
"node_modules",
|
|
96
|
+
"@nestia",
|
|
97
|
+
"core",
|
|
98
|
+
"lib",
|
|
99
|
+
"decorators",
|
|
100
|
+
`${cla}.d.ts`,
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
const SRC_PATHS = CLASSES.map((cla) =>
|
|
104
|
+
path.resolve(path.join(__dirname, "..", "decorators", `${cla}.ts`)),
|
|
105
|
+
);
|
|
106
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
|
|
3
|
+
import { INestiaTransformProject } from "../options/INestiaTransformProject";
|
|
4
|
+
import { NodeTransformer } from "./NodeTransformer";
|
|
5
|
+
|
|
6
|
+
export namespace FileTransformer {
|
|
7
|
+
export function transform(
|
|
8
|
+
project: INestiaTransformProject,
|
|
9
|
+
context: ts.TransformationContext,
|
|
10
|
+
file: ts.SourceFile,
|
|
11
|
+
): ts.SourceFile {
|
|
12
|
+
// ITERATE NODES
|
|
13
|
+
return ts.visitEachChild(
|
|
14
|
+
file,
|
|
15
|
+
(node) => iterate_node(project, context, node),
|
|
16
|
+
context,
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function iterate_node(
|
|
21
|
+
project: INestiaTransformProject,
|
|
22
|
+
context: ts.TransformationContext,
|
|
23
|
+
node: ts.Node,
|
|
24
|
+
): ts.Node {
|
|
25
|
+
return ts.visitEachChild(
|
|
26
|
+
try_transform_node(project, node),
|
|
27
|
+
(child) => iterate_node(project, context, child),
|
|
28
|
+
context,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function try_transform_node(
|
|
33
|
+
project: INestiaTransformProject,
|
|
34
|
+
node: ts.Node,
|
|
35
|
+
): ts.Node {
|
|
36
|
+
try {
|
|
37
|
+
return NodeTransformer.transform(project, node);
|
|
38
|
+
} catch (exp) {
|
|
39
|
+
if (!(exp instanceof Error)) throw exp;
|
|
40
|
+
|
|
41
|
+
const file: ts.SourceFile = node.getSourceFile();
|
|
42
|
+
const { line, character } = file.getLineAndCharacterOfPosition(
|
|
43
|
+
node.pos,
|
|
44
|
+
);
|
|
45
|
+
exp.message += ` - ${file.fileName}.${line + 1}:${character + 1}`;
|
|
46
|
+
throw exp;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|