@deepkit/http 1.0.1-alpha.16 → 1.0.1-alpha.160
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/CHANGELOG.md +236 -0
- package/dist/cjs/index.d.ts +14 -10
- package/dist/cjs/index.js +19 -11
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/src/controllers.d.ts +10 -3
- package/dist/cjs/src/controllers.js +7 -3
- package/dist/cjs/src/controllers.js.map +1 -1
- package/dist/cjs/src/decorator.d.ts +144 -70
- package/dist/cjs/src/decorator.js +277 -102
- package/dist/cjs/src/decorator.js.map +1 -1
- package/dist/cjs/src/filter.d.ts +49 -0
- package/dist/cjs/src/filter.js +215 -0
- package/dist/cjs/src/filter.js.map +1 -0
- package/dist/cjs/src/http-app.d.ts +8 -0
- package/dist/cjs/src/http-app.js +16 -0
- package/dist/cjs/src/http-app.js.map +1 -0
- package/dist/cjs/src/http.d.ts +131 -32
- package/dist/cjs/src/http.js +523 -122
- package/dist/cjs/src/http.js.map +1 -1
- package/dist/cjs/src/kernel.d.ts +50 -9
- package/dist/cjs/src/kernel.js +109 -98
- package/dist/cjs/src/kernel.js.map +1 -1
- package/dist/cjs/src/logger.d.ts +3 -3
- package/dist/cjs/src/logger.js +9 -12
- package/dist/cjs/src/logger.js.map +1 -1
- package/dist/cjs/src/middleware.d.ts +61 -0
- package/dist/cjs/src/middleware.js +95 -0
- package/dist/cjs/src/middleware.js.map +1 -0
- package/dist/cjs/src/model.d.ts +270 -4
- package/dist/cjs/src/model.js +401 -3
- package/dist/cjs/src/model.js.map +1 -1
- package/dist/cjs/src/module.config.d.ts +149 -4
- package/dist/cjs/src/module.config.js +11 -6
- package/dist/cjs/src/module.config.js.map +1 -1
- package/dist/cjs/src/module.d.ts +14 -32
- package/dist/cjs/src/module.js +122 -7
- package/dist/cjs/src/module.js.map +1 -1
- package/dist/cjs/src/request-parser.d.ts +37 -0
- package/dist/cjs/src/request-parser.js +382 -0
- package/dist/cjs/src/request-parser.js.map +1 -0
- package/dist/cjs/src/router.d.ts +174 -75
- package/dist/cjs/src/router.js +469 -288
- package/dist/cjs/src/router.js.map +1 -1
- package/dist/cjs/src/static-serving.d.ts +32 -4
- package/dist/cjs/src/static-serving.js +115 -71
- package/dist/cjs/src/static-serving.js.map +1 -1
- package/dist/cjs/src/utils.js +2 -2
- package/dist/cjs/src/utils.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -2916
- package/dist/esm/index.d.ts +14 -10
- package/dist/esm/index.js +14 -10
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/src/controllers.d.ts +10 -3
- package/dist/esm/src/controllers.js +7 -3
- package/dist/esm/src/controllers.js.map +1 -1
- package/dist/esm/src/decorator.d.ts +144 -70
- package/dist/esm/src/decorator.js +272 -99
- package/dist/esm/src/decorator.js.map +1 -1
- package/dist/esm/src/filter.d.ts +49 -0
- package/dist/esm/src/filter.js +209 -0
- package/dist/esm/src/filter.js.map +1 -0
- package/dist/esm/src/http-app.d.ts +8 -0
- package/dist/esm/src/http-app.js +12 -0
- package/dist/esm/src/http-app.js.map +1 -0
- package/dist/esm/src/http.d.ts +131 -32
- package/dist/esm/src/http.js +504 -121
- package/dist/esm/src/http.js.map +1 -1
- package/dist/esm/src/kernel.d.ts +50 -9
- package/dist/esm/src/kernel.js +110 -100
- package/dist/esm/src/kernel.js.map +1 -1
- package/dist/esm/src/logger.d.ts +3 -3
- package/dist/esm/src/logger.js +7 -11
- package/dist/esm/src/logger.js.map +1 -1
- package/dist/esm/src/middleware.d.ts +61 -0
- package/dist/esm/src/middleware.js +90 -0
- package/dist/esm/src/middleware.js.map +1 -0
- package/dist/esm/src/model.d.ts +270 -4
- package/dist/esm/src/model.js +357 -1
- package/dist/esm/src/model.js.map +1 -1
- package/dist/esm/src/module.config.d.ts +149 -4
- package/dist/esm/src/module.config.js +9 -5
- package/dist/esm/src/module.config.js.map +1 -1
- package/dist/esm/src/module.d.ts +14 -32
- package/dist/esm/src/module.js +121 -7
- package/dist/esm/src/module.js.map +1 -1
- package/dist/esm/src/request-parser.d.ts +37 -0
- package/dist/esm/src/request-parser.js +372 -0
- package/dist/esm/src/request-parser.js.map +1 -0
- package/dist/esm/src/router.d.ts +174 -75
- package/dist/esm/src/router.js +463 -285
- package/dist/esm/src/router.js.map +1 -1
- package/dist/esm/src/static-serving.d.ts +32 -4
- package/dist/esm/src/static-serving.js +107 -65
- package/dist/esm/src/static-serving.js.map +1 -1
- package/dist/esm/src/utils.js +1 -0
- package/dist/esm/src/utils.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -2916
- package/index.ts +14 -10
- package/package.json +30 -19
- package/src/controllers.ts +4 -3
- package/src/decorator.ts +242 -104
- package/src/filter.ts +224 -0
- package/src/http-app.ts +11 -0
- package/src/http.ts +501 -100
- package/src/kernel.ts +128 -80
- package/src/logger.ts +4 -6
- package/src/middleware.ts +116 -0
- package/src/model.ts +516 -5
- package/src/module.config.ts +168 -5
- package/src/module.ts +120 -7
- package/src/request-parser.ts +430 -0
- package/src/router.ts +546 -317
- package/src/static-serving.ts +120 -50
- package/src/utils.ts +0 -2
- package/tsconfig.esm.json +7 -7
- package/tsconfig.json +18 -11
- package/tsconfig.spec.json +8 -0
- package/dist/cjs/tests/router.spec.d.ts +0 -1
- package/dist/cjs/tests/router.spec.js +0 -481
- package/dist/cjs/tests/router.spec.js.map +0 -1
- package/dist/esm/tests/router.spec.d.ts +0 -1
- package/dist/esm/tests/router.spec.js +0 -479
- package/dist/esm/tests/router.spec.js.map +0 -1
- package/tests/router.spec.ts +0 -347
package/src/router.ts
CHANGED
|
@@ -7,138 +7,189 @@
|
|
|
7
7
|
*
|
|
8
8
|
* You should have received a copy of the MIT License along with this program.
|
|
9
9
|
*/
|
|
10
|
+
import { ClassType, CompilerContext, getClassName, isArray, isClass, urlJoin } from '@deepkit/core';
|
|
11
|
+
import {
|
|
12
|
+
entity,
|
|
13
|
+
ReflectionClass,
|
|
14
|
+
ReflectionFunction,
|
|
15
|
+
ReflectionKind,
|
|
16
|
+
ReflectionParameter,
|
|
17
|
+
SerializationOptions,
|
|
18
|
+
serializer,
|
|
19
|
+
Serializer,
|
|
20
|
+
Type,
|
|
21
|
+
ValidationError,
|
|
22
|
+
} from '@deepkit/type';
|
|
23
|
+
import { getActions, HttpAction, httpClass, HttpController, HttpDecorator } from './decorator.js';
|
|
24
|
+
import { HttpRequest, HttpRequestPositionedParameters, HttpRequestQuery, HttpRequestResolvedParameters } from './model.js';
|
|
25
|
+
import { InjectorContext, InjectorModule } from '@deepkit/injector';
|
|
26
|
+
import { Logger, LoggerInterface } from '@deepkit/logger';
|
|
27
|
+
import { HttpControllers } from './controllers.js';
|
|
28
|
+
import { MiddlewareRegistry, MiddlewareRegistryEntry } from '@deepkit/app';
|
|
29
|
+
import { HttpMiddlewareConfig, HttpMiddlewareFn } from './middleware.js';
|
|
30
|
+
|
|
31
|
+
//@ts-ignore
|
|
32
|
+
import qs from 'qs';
|
|
33
|
+
import { HtmlResponse, JSONResponse, Response } from './http.js';
|
|
34
|
+
import { getRequestParserCodeForParameters, ParameterForRequestParser, parseRoutePathToRegex } from './request-parser.js';
|
|
35
|
+
import { HttpConfig } from './module.config.js';
|
|
36
|
+
|
|
37
|
+
export type RouteParameterResolverForInjector = ((injector: InjectorContext) => HttpRequestPositionedParameters | Promise<HttpRequestPositionedParameters>);
|
|
38
|
+
|
|
39
|
+
interface ResolvedController {
|
|
40
|
+
parameters: RouteParameterResolverForInjector;
|
|
41
|
+
routeConfig: RouteConfig;
|
|
42
|
+
uploadedFiles: { [name: string]: UploadedFile };
|
|
43
|
+
middlewares?: (injector: InjectorContext) => { fn: HttpMiddlewareFn, timeout?: number }[];
|
|
44
|
+
}
|
|
10
45
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
46
|
+
export const UploadedFileSymbol = Symbol('UploadedFile');
|
|
47
|
+
|
|
48
|
+
@entity.name('@deepkit/UploadedFile')
|
|
49
|
+
export class UploadedFile {
|
|
50
|
+
/**
|
|
51
|
+
* Validator to ensure the file was provided by the framework, and not the user spoofing it.
|
|
52
|
+
*/
|
|
53
|
+
validator!: typeof UploadedFileSymbol | null;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The size of the uploaded file in bytes.
|
|
57
|
+
*/
|
|
58
|
+
size!: number;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The local path this file is being written to. Will be deleted when request is handled.
|
|
62
|
+
*/
|
|
63
|
+
path!: string;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The name this file had according to the uploading client.
|
|
67
|
+
*/
|
|
68
|
+
name!: string | null;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The mime type of this file, according to the uploading client.
|
|
72
|
+
*/
|
|
73
|
+
type!: string | null;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* A Date object (or `null`) containing the time this file was last written to.
|
|
77
|
+
* Mostly here for compatibility with the [W3C File API Draft](http://dev.w3.org/2006/webapi/FileAPI/).
|
|
78
|
+
*/
|
|
79
|
+
lastModifiedDate!: Date | null;
|
|
80
|
+
|
|
81
|
+
// /**
|
|
82
|
+
// * If `options.hash` calculation was set, you can read the hex digest out of this var.
|
|
83
|
+
// */
|
|
84
|
+
// hash!: string | 'sha1' | 'md5' | 'sha256' | null;
|
|
39
85
|
}
|
|
40
86
|
|
|
41
|
-
|
|
87
|
+
serializer.typeGuards.getRegistry(1).registerClass(UploadedFile, (type, state) => {
|
|
88
|
+
state.setContext({ UploadedFileSymbol });
|
|
89
|
+
state.addSetterAndReportErrorIfInvalid('uploadSecurity', 'Not an uploaded file', `typeof ${state.accessor} === 'object' && ${state.accessor} !== null && ${state.accessor}.validator === UploadedFileSymbol`);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
export interface RouteFunctionControllerAction {
|
|
93
|
+
type: 'function';
|
|
94
|
+
//if not set, the root module is used
|
|
95
|
+
module?: InjectorModule<any>;
|
|
96
|
+
fn: (...args: any[]) => any;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface RouteClassControllerAction {
|
|
100
|
+
type: 'controller';
|
|
101
|
+
//if not set, the root module is used
|
|
102
|
+
module?: InjectorModule<any>;
|
|
42
103
|
controller: ClassType;
|
|
43
104
|
methodName: string;
|
|
44
105
|
}
|
|
45
106
|
|
|
46
|
-
export
|
|
47
|
-
type
|
|
48
|
-
|
|
49
|
-
* undefined = propertyName, '' === root, else given path
|
|
50
|
-
*/
|
|
51
|
-
typePath?: string;
|
|
52
|
-
|
|
53
|
-
optional: boolean;
|
|
54
|
-
name: string;
|
|
107
|
+
export function getRouteActionLabel(action: RouteClassControllerAction | RouteFunctionControllerAction): string {
|
|
108
|
+
if (action.type === 'controller') return `${getClassName(action.controller)}.${action.methodName}`;
|
|
109
|
+
return action.fn.name;
|
|
55
110
|
}
|
|
56
111
|
|
|
57
112
|
export class RouteConfig {
|
|
58
113
|
public baseUrl: string = '';
|
|
59
|
-
|
|
60
|
-
public
|
|
114
|
+
|
|
115
|
+
public responses: { statusCode: number, description: string, type?: Type }[] = [];
|
|
116
|
+
|
|
61
117
|
public description: string = '';
|
|
62
118
|
public groups: string[] = [];
|
|
63
119
|
public category: string = '';
|
|
64
120
|
|
|
121
|
+
public returnType?: Type;
|
|
122
|
+
|
|
123
|
+
public serializationOptions?: SerializationOptions;
|
|
124
|
+
public serializer?: Serializer;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* When assigned defines where this route came from.
|
|
128
|
+
*/
|
|
129
|
+
public module?: InjectorModule<any>;
|
|
130
|
+
|
|
131
|
+
resolverForToken: Map<any, ClassType> = new Map();
|
|
132
|
+
|
|
133
|
+
middlewares: { config: HttpMiddlewareConfig, module?: InjectorModule<any> }[] = [];
|
|
134
|
+
|
|
135
|
+
resolverForParameterName: Map<string, ClassType> = new Map();
|
|
136
|
+
|
|
65
137
|
/**
|
|
66
138
|
* An arbitrary data container the user can use to store app specific settings/values.
|
|
67
139
|
*/
|
|
68
140
|
data = new Map<any, any>();
|
|
69
141
|
|
|
70
|
-
public parameters: {
|
|
71
|
-
[name: string]: RouteParameterConfig
|
|
72
|
-
} = {};
|
|
73
|
-
|
|
74
142
|
constructor(
|
|
75
143
|
public readonly name: string,
|
|
76
|
-
public readonly
|
|
144
|
+
public readonly httpMethods: string[],
|
|
77
145
|
public readonly path: string,
|
|
78
|
-
public readonly action:
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
getFullPath(): string {
|
|
83
|
-
let path = this.baseUrl ? join(this.baseUrl, this.path) : this.path;
|
|
84
|
-
if (!path.startsWith('/')) path = '/' + path;
|
|
85
|
-
return path;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* When this class is injected into a route, then validation errors are not automatically thrown (using onHttpControllerValidationError event),
|
|
91
|
-
* but injected to the route itself. The user is then responsible to handle the errors.
|
|
92
|
-
*
|
|
93
|
-
* Note: The body parameter is still passed, however it might contain now invalid data. The BodyValidation tells what data is invalid.
|
|
94
|
-
*/
|
|
95
|
-
export class BodyValidation {
|
|
96
|
-
constructor(
|
|
97
|
-
public readonly errors: ValidationFailedItem[] = []
|
|
146
|
+
public readonly action: RouteClassControllerAction | RouteFunctionControllerAction,
|
|
147
|
+
public internal: boolean = false,
|
|
98
148
|
) {
|
|
99
149
|
}
|
|
100
150
|
|
|
101
|
-
|
|
102
|
-
return this.
|
|
151
|
+
getReflectionFunction(): ReflectionFunction {
|
|
152
|
+
return this.action.type === 'controller' ?
|
|
153
|
+
ReflectionClass.from(this.action.controller).getMethod(this.action.methodName)
|
|
154
|
+
: ReflectionFunction.from(this.action.fn);
|
|
103
155
|
}
|
|
104
156
|
|
|
105
|
-
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
getErrorsForPath(path: string): ValidationFailedItem[] {
|
|
112
|
-
return this.errors.filter(v => v.path === path);
|
|
157
|
+
getSchemaForResponse(statusCode: number): Type | undefined {
|
|
158
|
+
if (!this.responses.length) return;
|
|
159
|
+
for (const response of this.responses) {
|
|
160
|
+
if (response.statusCode === statusCode) return response.type;
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
113
163
|
}
|
|
114
164
|
|
|
115
|
-
|
|
116
|
-
|
|
165
|
+
getFullPath(): string {
|
|
166
|
+
let path = this.baseUrl ? urlJoin(this.baseUrl, this.path) : this.path;
|
|
167
|
+
if (!path.startsWith('/')) path = '/' + path;
|
|
168
|
+
return path;
|
|
117
169
|
}
|
|
118
170
|
}
|
|
119
171
|
|
|
120
|
-
class ParsedRoute {
|
|
172
|
+
export class ParsedRoute {
|
|
121
173
|
public regex?: string;
|
|
122
|
-
public customValidationErrorHandling?: ParsedRouteParameter;
|
|
123
174
|
|
|
124
175
|
public pathParameterNames: { [name: string]: number } = {};
|
|
125
176
|
|
|
126
|
-
protected parameters:
|
|
177
|
+
protected parameters: ParameterForRequestParser[] = [];
|
|
127
178
|
|
|
128
179
|
constructor(public routeConfig: RouteConfig) {
|
|
129
180
|
}
|
|
130
181
|
|
|
131
|
-
addParameter(property:
|
|
132
|
-
const parameter = new
|
|
182
|
+
addParameter(property: ReflectionParameter): ParameterForRequestParser {
|
|
183
|
+
const parameter = new ParameterForRequestParser(property);
|
|
133
184
|
this.parameters.push(parameter);
|
|
134
185
|
return parameter;
|
|
135
186
|
}
|
|
136
187
|
|
|
137
|
-
getParameters():
|
|
188
|
+
getParameters(): ParameterForRequestParser[] {
|
|
138
189
|
return this.parameters;
|
|
139
190
|
}
|
|
140
191
|
|
|
141
|
-
getParameter(name: string):
|
|
192
|
+
getParameter(name: string): ParameterForRequestParser {
|
|
142
193
|
for (const parameter of this.parameters) {
|
|
143
194
|
if (parameter.getName() === name) return parameter;
|
|
144
195
|
}
|
|
@@ -146,68 +197,16 @@ class ParsedRoute {
|
|
|
146
197
|
}
|
|
147
198
|
}
|
|
148
199
|
|
|
149
|
-
class ParsedRouteParameter {
|
|
150
|
-
regexPosition?: number;
|
|
151
|
-
|
|
152
|
-
constructor(
|
|
153
|
-
public property: PropertySchema,
|
|
154
|
-
public config?: RouteParameterConfig,
|
|
155
|
-
) {
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
get body() {
|
|
159
|
-
return this.config ? this.config.type === 'body' : false;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
get query() {
|
|
163
|
-
return this.config ? this.config.type === 'query' : false;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
get typePath() {
|
|
167
|
-
return this.config ? this.config.typePath : undefined;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
getName() {
|
|
171
|
-
return this.property.name;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
isPartOfPath(): boolean {
|
|
175
|
-
return this.regexPosition !== undefined;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function parseRoutePathToRegex(routeConfig: RouteConfig): { regex: string, parameterNames: { [name: string]: number } } {
|
|
180
|
-
const parameterNames: { [name: string]: number } = {};
|
|
181
|
-
let path = routeConfig.getFullPath();
|
|
182
|
-
|
|
183
|
-
let argumentIndex = 0;
|
|
184
|
-
path = path.replace(/:(\w+)/g, (a, name) => {
|
|
185
|
-
parameterNames[name] = argumentIndex;
|
|
186
|
-
argumentIndex++;
|
|
187
|
-
return routeConfig.parameterRegularExpressions[name] ? '(' + routeConfig.parameterRegularExpressions[name] + ')' : String.raw`([^/]+)`;
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
return { regex: path, parameterNames };
|
|
191
|
-
}
|
|
192
|
-
|
|
193
200
|
export function parseRouteControllerAction(routeConfig: RouteConfig): ParsedRoute {
|
|
194
|
-
const schema = getClassSchema(routeConfig.action.controller);
|
|
195
201
|
const parsedRoute = new ParsedRoute(routeConfig);
|
|
196
202
|
|
|
197
|
-
const
|
|
198
|
-
const parsedPath = parseRoutePathToRegex(routeConfig);
|
|
203
|
+
const parameters = routeConfig.getReflectionFunction().getParameters();
|
|
204
|
+
const parsedPath = parseRoutePathToRegex(routeConfig.getFullPath(), parameters);
|
|
199
205
|
parsedRoute.regex = parsedPath.regex;
|
|
200
206
|
parsedRoute.pathParameterNames = parsedPath.parameterNames;
|
|
201
207
|
|
|
202
|
-
for (const property of
|
|
203
|
-
const
|
|
204
|
-
const parsedParameter = parsedRoute.addParameter(property, decoratorData);
|
|
205
|
-
|
|
206
|
-
if (decoratorData && decoratorData.optional) property.isOptional = true;
|
|
207
|
-
|
|
208
|
-
if (property.type === 'class' && property.classType === BodyValidation) {
|
|
209
|
-
parsedRoute.customValidationErrorHandling = parsedParameter;
|
|
210
|
-
}
|
|
208
|
+
for (const property of parameters) {
|
|
209
|
+
const parsedParameter = parsedRoute.addParameter(property);
|
|
211
210
|
parsedParameter.regexPosition = parsedPath.parameterNames[property.name];
|
|
212
211
|
}
|
|
213
212
|
|
|
@@ -225,182 +224,433 @@ export interface RouteParameterResolver {
|
|
|
225
224
|
}
|
|
226
225
|
|
|
227
226
|
export interface RouteParameterResolverContext {
|
|
228
|
-
|
|
227
|
+
token: ClassType | string | symbol | any;
|
|
229
228
|
route: RouteConfig;
|
|
230
229
|
request: HttpRequest;
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* The parameter name (variable name).
|
|
233
|
+
*/
|
|
234
|
+
name: any;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* The raw parameter value from the path, if the parameter is defined in the path (e.g. /user/:name).
|
|
238
|
+
* If not in the path, you have to use `parameters.<name>` instead.
|
|
239
|
+
*/
|
|
240
|
+
value: any;
|
|
241
|
+
|
|
231
242
|
query: HttpRequestQuery;
|
|
232
243
|
parameters: HttpRequestResolvedParameters;
|
|
244
|
+
type: ReflectionParameter;
|
|
233
245
|
}
|
|
234
246
|
|
|
235
|
-
|
|
236
|
-
|
|
247
|
+
function filterMiddlewaresForRoute(middlewareRawConfigs: MiddlewareRegistryEntry[], routeConfig: RouteConfig, fullPath: string): {
|
|
248
|
+
config: HttpMiddlewareConfig,
|
|
249
|
+
module: InjectorModule<any>
|
|
250
|
+
}[] {
|
|
251
|
+
const middlewares = middlewareRawConfigs.slice(0);
|
|
252
|
+
middlewares.push(...routeConfig.middlewares as any[]);
|
|
237
253
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
254
|
+
const middlewareConfigs = middlewares.filter((v) => {
|
|
255
|
+
if (!(v.config instanceof HttpMiddlewareConfig)) return false;
|
|
256
|
+
|
|
257
|
+
if (v.config.controllers.length && routeConfig.action.type === 'controller' && !v.config.controllers.includes(routeConfig.action.controller)) {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (v.config.excludeControllers.length && routeConfig.action.type === 'controller' && v.config.excludeControllers.includes(routeConfig.action.controller)) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (v.config.modules.length && (!routeConfig.module || !v.config.modules.includes(routeConfig.module))) {
|
|
266
|
+
if (!routeConfig.module) return false;
|
|
267
|
+
for (const module of v.config.modules) {
|
|
268
|
+
if (routeConfig.module.id !== module.id) return false;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (v.config.selfModule && v.module.id !== routeConfig.module?.id) return false;
|
|
273
|
+
|
|
274
|
+
if (v.config.routeNames.length) {
|
|
275
|
+
let found: boolean = false;
|
|
276
|
+
for (const name of v.config.routeNames) {
|
|
277
|
+
if (name.includes('*')) {
|
|
278
|
+
const regex = new RegExp('^' + name.replace(/\*/g, '.*') + '$');
|
|
279
|
+
if (regex.test(routeConfig.name)) {
|
|
280
|
+
found = true;
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
} else if (name === routeConfig.name) {
|
|
284
|
+
found = true;
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (!found) return false;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (v.config.excludeRouteNames.length) {
|
|
292
|
+
for (const name of v.config.excludeRouteNames) {
|
|
293
|
+
if (name.includes('*')) {
|
|
294
|
+
const regex = new RegExp('^' + name.replace(/\*/g, '.*') + '$');
|
|
295
|
+
if (regex.test(routeConfig.name)) return false;
|
|
296
|
+
} else if (name == routeConfig.name) {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
for (const route of v.config.routes) {
|
|
303
|
+
if (route.httpMethod && !routeConfig.httpMethods.includes(route.httpMethod)) return false;
|
|
304
|
+
|
|
305
|
+
if (route.category && route.category !== routeConfig.category) return false;
|
|
306
|
+
if (route.excludeCategory && route.excludeCategory === routeConfig.category) return false;
|
|
307
|
+
|
|
308
|
+
if (route.group && !routeConfig.groups.includes(route.group)) return false;
|
|
309
|
+
if (route.excludeGroup && routeConfig.groups.includes(route.excludeGroup)) return false;
|
|
310
|
+
|
|
311
|
+
if (route.path || route.pathRegExp) {
|
|
312
|
+
if (!route.pathRegExp && route.path) route.pathRegExp = new RegExp('^' + route.path.replace(/\*/g, '.*') + '$');
|
|
313
|
+
if (route.pathRegExp && !route.pathRegExp.test(fullPath)) return false;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return true;
|
|
318
|
+
}) as { config: HttpMiddlewareConfig, module: any }[];
|
|
319
|
+
|
|
320
|
+
middlewareConfigs.sort((a, b) => {
|
|
321
|
+
return a.config.order - b.config.order;
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
return middlewareConfigs;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export interface HttpRouterFunctionOptions {
|
|
328
|
+
path: string;
|
|
329
|
+
name?: string;
|
|
330
|
+
methods?: string[];
|
|
331
|
+
description?: string;
|
|
332
|
+
category?: string;
|
|
333
|
+
groups?: string[];
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* An arbitrary data container the user can use to store app specific settings/values.
|
|
337
|
+
*/
|
|
338
|
+
data?: Record<any, any>;
|
|
339
|
+
|
|
340
|
+
baseUrl?: string;
|
|
341
|
+
middlewares?: (() => HttpMiddlewareConfig)[];
|
|
342
|
+
|
|
343
|
+
serializer?: Serializer;
|
|
344
|
+
serializationOptions?: SerializationOptions;
|
|
345
|
+
|
|
346
|
+
resolverForToken?: Map<any, ClassType>;
|
|
347
|
+
resolverForParameterName?: Map<string, ClassType>;
|
|
348
|
+
|
|
349
|
+
responses?: { statusCode: number, description: string, type?: Type }[];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function convertOptions(methods: string[], pathOrOptions: string | HttpRouterFunctionOptions, defaultOptions: Partial<HttpRouterFunctionOptions>): HttpRouterFunctionOptions {
|
|
353
|
+
const options = 'string' === typeof pathOrOptions ? { path: pathOrOptions } : pathOrOptions;
|
|
354
|
+
if (options.methods) return options;
|
|
355
|
+
return { ...options, methods };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Annotated types like HTMLResponse/JSONResponse are not used for serialization.
|
|
360
|
+
*/
|
|
361
|
+
function filterValidReturnType(type: Type): Type | undefined {
|
|
362
|
+
if (type.kind === ReflectionKind.class && (type.classType === HtmlResponse || type.classType === JSONResponse || type.classType === Response)) return;
|
|
363
|
+
return type;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export abstract class HttpRouterRegistryFunctionRegistrar {
|
|
367
|
+
protected defaultOptions: Partial<HttpRouterFunctionOptions> = {};
|
|
368
|
+
|
|
369
|
+
abstract addRoute(routeConfig: RouteConfig): void;
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Returns a new registrar object with default options that apply to each registered route through this registrar.
|
|
373
|
+
*
|
|
374
|
+
* ```typescript
|
|
375
|
+
* const registry: HttpRouterRegistry = ...;
|
|
376
|
+
*
|
|
377
|
+
* const secretRegistry = registry.forOptions({groups: ['secret']});
|
|
378
|
+
*
|
|
379
|
+
* secretRegistry.get('/admin/groups', () => {
|
|
380
|
+
* });
|
|
381
|
+
*
|
|
382
|
+
* secretRegistry.get('/admin/users', () => {
|
|
383
|
+
* });
|
|
384
|
+
* ```
|
|
385
|
+
*/
|
|
386
|
+
forOptions(options: Partial<HttpRouterFunctionOptions>): HttpRouterRegistryFunctionRegistrar {
|
|
387
|
+
const that = this;
|
|
388
|
+
return new class extends HttpRouterRegistryFunctionRegistrar {
|
|
389
|
+
defaultOptions = options;
|
|
390
|
+
|
|
391
|
+
addRoute(routeConfig: RouteConfig) {
|
|
392
|
+
that.addRoute(routeConfig);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
public any(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
|
|
398
|
+
this.register(convertOptions([], pathOrOptions, this.defaultOptions), callback);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
public add(decorator: HttpDecorator, callback: (...args: any[]) => any) {
|
|
402
|
+
const data = decorator(Object, '_');
|
|
403
|
+
const action = isArray(data) ? data.find(v => v instanceof HttpAction) : undefined;
|
|
404
|
+
if (!action) throw new Error('No HttpAction available');
|
|
405
|
+
|
|
406
|
+
const fn = ReflectionFunction.from(callback);
|
|
407
|
+
const routeConfig = createRouteConfigFromHttpAction({
|
|
408
|
+
type: 'function',
|
|
409
|
+
fn: callback,
|
|
410
|
+
}, action);
|
|
411
|
+
routeConfig.returnType = filterValidReturnType(fn.getReturnType());
|
|
412
|
+
this.addRoute(routeConfig);
|
|
243
413
|
}
|
|
244
414
|
|
|
245
|
-
|
|
246
|
-
this.
|
|
247
|
-
|
|
415
|
+
public get(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
|
|
416
|
+
this.register(convertOptions(['GET'], pathOrOptions, this.defaultOptions), callback);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
public post(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
|
|
420
|
+
this.register(convertOptions(['POST'], pathOrOptions, this.defaultOptions), callback);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
public put(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
|
|
424
|
+
this.register(convertOptions(['PUT'], pathOrOptions, this.defaultOptions), callback);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
public patch(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
|
|
428
|
+
this.register(convertOptions(['PATCH'], pathOrOptions, this.defaultOptions), callback);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
public delete(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
|
|
432
|
+
this.register(convertOptions(['DELETE'], pathOrOptions, this.defaultOptions), callback);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
public options(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
|
|
436
|
+
this.register(convertOptions(['OPTIONS'], pathOrOptions, this.defaultOptions), callback);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
public trace(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
|
|
440
|
+
this.register(convertOptions(['TRACE'], pathOrOptions, this.defaultOptions), callback);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
public head(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
|
|
444
|
+
this.register(convertOptions(['HEAD'], pathOrOptions, this.defaultOptions), callback);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
private register(options: HttpRouterFunctionOptions, callback: (...args: any[]) => any, module?: InjectorModule<any>) {
|
|
448
|
+
const fn = ReflectionFunction.from(callback);
|
|
449
|
+
|
|
450
|
+
const routeConfig = new RouteConfig(options.name || '', options.methods || [], options.path, {
|
|
451
|
+
type: 'function',
|
|
452
|
+
fn: callback,
|
|
453
|
+
});
|
|
454
|
+
routeConfig.module = module;
|
|
455
|
+
if (options.responses) routeConfig.responses = options.responses;
|
|
456
|
+
if (options.description) routeConfig.description = options.description;
|
|
457
|
+
if (options.category) routeConfig.category = options.category;
|
|
458
|
+
if (options.groups) routeConfig.groups = options.groups;
|
|
459
|
+
if (options.data) routeConfig.data = new Map(Object.entries(options.data));
|
|
460
|
+
if (options.baseUrl) routeConfig.baseUrl = options.baseUrl;
|
|
461
|
+
if (options.middlewares) {
|
|
462
|
+
routeConfig.middlewares = options.middlewares.map(v => {
|
|
463
|
+
return { config: v(), module };
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (options.resolverForToken) {
|
|
468
|
+
for (const item of options.resolverForToken) routeConfig.resolverForToken.set(...item);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (options.resolverForToken) {
|
|
472
|
+
for (const item of options.resolverForToken) routeConfig.resolverForToken.set(...item);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (options.resolverForParameterName) {
|
|
476
|
+
for (const item of options.resolverForParameterName) routeConfig.resolverForParameterName.set(...item);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
routeConfig.serializer = options.serializer;
|
|
480
|
+
routeConfig.serializationOptions = options.serializationOptions;
|
|
481
|
+
|
|
482
|
+
routeConfig.returnType = filterValidReturnType(fn.getReturnType());
|
|
483
|
+
this.addRoute(routeConfig);
|
|
248
484
|
}
|
|
249
485
|
}
|
|
250
486
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
487
|
+
function createRouteConfigFromHttpAction(routeAction: RouteClassControllerAction | RouteFunctionControllerAction, action: HttpAction, module?: InjectorModule<any>, controller?: HttpController) {
|
|
488
|
+
const routeConfig = new RouteConfig(action.name, action.httpMethods, action.path, routeAction);
|
|
489
|
+
routeConfig.responses = action.responses;
|
|
490
|
+
routeConfig.description = action.description;
|
|
491
|
+
routeConfig.category = action.category;
|
|
492
|
+
routeConfig.groups = action.groups;
|
|
493
|
+
routeConfig.data = new Map(action.data);
|
|
494
|
+
if (controller) {
|
|
495
|
+
routeConfig.baseUrl = controller.baseUrl;
|
|
496
|
+
|
|
497
|
+
routeConfig.middlewares = controller.middlewares.map(v => {
|
|
498
|
+
return { config: v(), module };
|
|
499
|
+
});
|
|
500
|
+
routeConfig.resolverForToken = new Map(controller.resolverForToken);
|
|
501
|
+
routeConfig.resolverForParameterName = new Map(controller.resolverForParameterName);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
routeConfig.middlewares.push(...action.middlewares.map(v => {
|
|
505
|
+
return { config: v(), module };
|
|
506
|
+
}));
|
|
507
|
+
|
|
508
|
+
for (const item of action.resolverForToken) routeConfig.resolverForToken.set(...item);
|
|
509
|
+
|
|
510
|
+
for (const item of action.resolverForParameterName) routeConfig.resolverForParameterName.set(...item);
|
|
511
|
+
|
|
512
|
+
routeConfig.serializer = action.serializer;
|
|
513
|
+
routeConfig.serializationOptions = action.serializationOptions;
|
|
514
|
+
return routeConfig;
|
|
515
|
+
}
|
|
255
516
|
|
|
517
|
+
export class HttpRouterRegistry extends HttpRouterRegistryFunctionRegistrar {
|
|
256
518
|
protected routes: RouteConfig[] = [];
|
|
519
|
+
private buildId: number = 1;
|
|
257
520
|
|
|
258
|
-
|
|
521
|
+
public getBuildId(): number {
|
|
522
|
+
return this.buildId;
|
|
523
|
+
}
|
|
259
524
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
525
|
+
public getRoutes(): RouteConfig[] {
|
|
526
|
+
return this.routes;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
public addRouteForController(controller: ClassType, module: InjectorModule<any>) {
|
|
530
|
+
const controllerData = httpClass._fetch(controller);
|
|
531
|
+
if (!controllerData) throw new Error(`Http controller class ${getClassName(controller)} has no @http.controller decorator.`);
|
|
532
|
+
const schema = ReflectionClass.from(controller);
|
|
533
|
+
|
|
534
|
+
for (const action of getActions(controller)) {
|
|
535
|
+
const routeAction: RouteClassControllerAction = {
|
|
536
|
+
type: 'controller',
|
|
537
|
+
controller,
|
|
538
|
+
module,
|
|
539
|
+
methodName: action.methodName
|
|
540
|
+
};
|
|
541
|
+
const routeConfig = createRouteConfigFromHttpAction(routeAction, action, module, controllerData);
|
|
542
|
+
|
|
543
|
+
routeConfig.module = module;
|
|
544
|
+
|
|
545
|
+
if (schema.hasMethod(action.methodName)) routeConfig.returnType = filterValidReturnType(schema.getMethod(action.methodName).getReturnType());
|
|
546
|
+
this.addRoute(routeConfig);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
public addRoute(routeConfig: RouteConfig) {
|
|
551
|
+
this.routes.push(routeConfig);
|
|
552
|
+
this.buildId++;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
export class HttpRouter {
|
|
557
|
+
protected fn?: (request: HttpRequest) => ResolvedController | undefined;
|
|
558
|
+
protected buildId: number = 0;
|
|
559
|
+
protected resolveFn?: (name: string, parameters: { [name: string]: any }) => string;
|
|
266
560
|
|
|
267
561
|
constructor(
|
|
268
562
|
controllers: HttpControllers,
|
|
269
|
-
private logger:
|
|
270
|
-
|
|
563
|
+
private logger: LoggerInterface,
|
|
564
|
+
private config: HttpConfig,
|
|
565
|
+
private middlewareRegistry: MiddlewareRegistry = new MiddlewareRegistry,
|
|
566
|
+
private registry: HttpRouterRegistry = new HttpRouterRegistry,
|
|
271
567
|
) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
568
|
+
for (const controller of controllers.controllers) {
|
|
569
|
+
this.addRouteForController(controller.controller, controller.module);
|
|
570
|
+
}
|
|
275
571
|
}
|
|
276
572
|
|
|
277
573
|
getRoutes(): RouteConfig[] {
|
|
278
|
-
return this.
|
|
574
|
+
return this.registry.getRoutes();
|
|
279
575
|
}
|
|
280
576
|
|
|
281
|
-
static forControllers(
|
|
282
|
-
|
|
577
|
+
static forControllers(
|
|
578
|
+
controllers: (ClassType | { module: InjectorModule<any>, controller: ClassType })[],
|
|
579
|
+
middlewareRegistry: MiddlewareRegistry = new MiddlewareRegistry(),
|
|
580
|
+
module: InjectorModule<any> = new InjectorModule(),
|
|
581
|
+
config: HttpConfig = new HttpConfig()
|
|
582
|
+
): HttpRouter {
|
|
583
|
+
return new this(new HttpControllers(controllers.map(v => {
|
|
584
|
+
return isClass(v) ? { controller: v, module } : v;
|
|
585
|
+
})), new Logger([], []), config, middlewareRegistry);
|
|
283
586
|
}
|
|
284
587
|
|
|
285
588
|
protected getRouteCode(compiler: CompilerContext, routeConfig: RouteConfig): string {
|
|
286
|
-
const routeConfigVar = compiler.reserveVariable('routeConfigVar', routeConfig);
|
|
287
589
|
const parsedRoute = parseRouteControllerAction(routeConfig);
|
|
288
590
|
const path = routeConfig.getFullPath();
|
|
289
|
-
const
|
|
591
|
+
const pathParamStarter = path.indexOf(':');
|
|
592
|
+
const prefix = path.substr(0, pathParamStarter);
|
|
290
593
|
|
|
291
594
|
const regexVar = compiler.reserveVariable('regex', new RegExp('^' + parsedRoute.regex + '$'));
|
|
292
|
-
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
parameterNames.push(`parameters.${parameter.property.name}`);
|
|
319
|
-
} else if (parameter.body) {
|
|
320
|
-
const bodyVar = compiler.reserveVariable('body');
|
|
321
|
-
|
|
322
|
-
const validatorVar = compiler.reserveVariable('argumentValidator', jitValidateProperty(parameter.property));
|
|
323
|
-
const converterVar = compiler.reserveVariable('argumentConverter', getPropertyXtoClassFunction(parameter.property, jsonSerializer));
|
|
324
|
-
|
|
325
|
-
enableParseBody = true;
|
|
326
|
-
parameterValidator.push(`
|
|
327
|
-
${bodyVar} = ${converterVar}(bodyFields);
|
|
328
|
-
${validatorVar}(${bodyVar}, ${JSON.stringify(parameter.typePath || '')}, bodyErrors);
|
|
329
|
-
`);
|
|
330
|
-
setParameters.push(`parameters.${parameter.property.name} = ${bodyVar};`);
|
|
331
|
-
parameterNames.push(`parameters.${parameter.property.name}`);
|
|
332
|
-
} else if (parameter.query) {
|
|
333
|
-
const converted = parameter.property.type === 'any' ? (v: any) => v : getPropertyXtoClassFunction(parameter.property, jsonSerializer);
|
|
334
|
-
const validator = parameter.property.type === 'any' ? (v: any) => undefined : jitValidateProperty(parameter.property);
|
|
335
|
-
const converterVar = compiler.reserveVariable('argumentConverter', converted);
|
|
336
|
-
const validatorVar = compiler.reserveVariable('argumentValidator', validator);
|
|
337
|
-
|
|
338
|
-
const queryPath = parameter.typePath === undefined ? parameter.property.name : parameter.typePath;
|
|
339
|
-
const accessor = queryPath ? `['` + (queryPath.replace(/\./g, `']['`)) + `']` : '';
|
|
340
|
-
const queryAccessor = queryPath ? `_query${accessor}` : '_query';
|
|
341
|
-
setParameters.push(`parameters.${parameter.property.name} = ${converterVar}(${queryAccessor});`);
|
|
342
|
-
parameterNames.push(`parameters.${parameter.property.name}`);
|
|
343
|
-
parameterValidator.push(`${validatorVar}(${queryAccessor}, ${JSON.stringify(parameter.typePath)}, validationErrors);`);
|
|
595
|
+
|
|
596
|
+
const hasParameters = pathParamStarter !== -1 || parsedRoute.getParameters().length > 0;
|
|
597
|
+
|
|
598
|
+
const fullPath = routeConfig.getFullPath();
|
|
599
|
+
const middlewareConfigs = filterMiddlewaresForRoute(this.middlewareRegistry.configs, routeConfig, fullPath);
|
|
600
|
+
|
|
601
|
+
const parameters = [...parsedRoute.getParameters()];
|
|
602
|
+
|
|
603
|
+
//look through all http listeners and check if they need any http parameters.
|
|
604
|
+
//the router then parses
|
|
605
|
+
|
|
606
|
+
let matcher = `_path.startsWith(${JSON.stringify(prefix)}) && (_match = _path.match(${regexVar}))`;
|
|
607
|
+
if (!hasParameters) {
|
|
608
|
+
matcher = `_path === ${JSON.stringify(path)}`;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
let middlewares = 'undefined';
|
|
612
|
+
if (middlewareConfigs.length) {
|
|
613
|
+
const middlewareItems: string[] = [];
|
|
614
|
+
for (const middlewareConfig of middlewareConfigs) {
|
|
615
|
+
const moduleVar = middlewareConfig.module ? ', ' + compiler.reserveVariable('module', middlewareConfig.module) : '';
|
|
616
|
+
|
|
617
|
+
for (const middleware of middlewareConfig.config.middlewares) {
|
|
618
|
+
if (isClass(middleware)) {
|
|
619
|
+
const classVar = compiler.reserveVariable('middlewareClassType', middleware);
|
|
620
|
+
middlewareItems.push(`{fn: function() {return _injector.get(${classVar}${moduleVar}).execute(...arguments) }, timeout: ${middlewareConfig.config.timeout}}`);
|
|
344
621
|
} else {
|
|
345
|
-
|
|
346
|
-
const classTypeVar = compiler.reserveVariable('classType', classType);
|
|
347
|
-
|
|
348
|
-
for (const resolverTag of this.parameterResolverTags) {
|
|
349
|
-
if (resolverTag.classTypes.includes(classType)) {
|
|
350
|
-
const resolverProvideTokenVar = compiler.reserveVariable('resolverProvideToken', resolverTag.provider.provide);
|
|
351
|
-
requiresAsyncParameters = true;
|
|
352
|
-
if (!setParametersFromPath) {
|
|
353
|
-
for (const i in parsedRoute.pathParameterNames) {
|
|
354
|
-
setParametersFromPath += `parameters.${i} = _match[${1 + parsedRoute.pathParameterNames[i]}];`;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
setParameters.push(`parameters.${parameter.property.name} = await _injector.get(${resolverProvideTokenVar}).resolve(
|
|
358
|
-
{classType: ${classTypeVar}, routeConfig: ${routeConfigVar}, request: request, query: _query, parameters: parameters}
|
|
359
|
-
);`);
|
|
360
|
-
parameterNames.push(`parameters.${parameter.property.name}`);
|
|
361
|
-
continue params;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
setParameters.push(`parameters.${parameter.property.name} = _injector.get(${classTypeVar});`);
|
|
366
|
-
parameterNames.push(`parameters.${parameter.property.name}`);
|
|
622
|
+
middlewareItems.push(`{fn: ${compiler.reserveVariable('middlewareFn', middleware)}, timeout: ${middlewareConfig.config.timeout}}`);
|
|
367
623
|
}
|
|
368
624
|
}
|
|
369
625
|
}
|
|
370
626
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
requiresAsyncParameters = true;
|
|
627
|
+
middlewares = `
|
|
628
|
+
function(_injector) {
|
|
629
|
+
return [${middlewareItems.join(', ')}];
|
|
630
|
+
}
|
|
631
|
+
`;
|
|
377
632
|
}
|
|
378
633
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
634
|
+
const parametersLoader = getRequestParserCodeForParameters(compiler, this.config.parser, parameters, {
|
|
635
|
+
module: routeConfig.module,
|
|
636
|
+
resolverForToken: routeConfig.resolverForToken,
|
|
637
|
+
resolverForParameterName: routeConfig.resolverForParameterName,
|
|
638
|
+
pathParameterNames: parsedRoute.pathParameterNames,
|
|
639
|
+
routeConfig
|
|
640
|
+
});
|
|
383
641
|
|
|
384
|
-
let
|
|
385
|
-
if (
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const parameters = {};
|
|
390
|
-
${setParametersFromPath}
|
|
391
|
-
${parseBodyLoading}
|
|
392
|
-
${parameterValidator.join('\n')}
|
|
393
|
-
${bodyValidationErrorHandling}
|
|
394
|
-
${setParameters.join('\n')}
|
|
395
|
-
if (validationErrors.length) throw ValidationFailed.from(validationErrors);
|
|
396
|
-
return [${parameterNames.join(',')}];
|
|
397
|
-
}`;
|
|
642
|
+
let methodCheck = '';
|
|
643
|
+
if (routeConfig.httpMethods.length) {
|
|
644
|
+
methodCheck = '(' + routeConfig.httpMethods.map(v => {
|
|
645
|
+
return `_method === '${v.toUpperCase()}'`;
|
|
646
|
+
}).join(' || ') + ') && ';
|
|
398
647
|
}
|
|
399
648
|
|
|
649
|
+
const routeConfigVar = compiler.reserveVariable('routeConfigVar', routeConfig);
|
|
400
650
|
return `
|
|
401
|
-
//=> ${path}
|
|
402
|
-
if (
|
|
403
|
-
return {routeConfig: ${routeConfigVar}, parameters: ${
|
|
651
|
+
//=> ${routeConfig.httpMethods.join(',')} ${path}
|
|
652
|
+
if (${methodCheck}${matcher}) {
|
|
653
|
+
return {routeConfig: ${routeConfigVar}, parameters: ${parametersLoader}, uploadedFiles: uploadedFiles, middlewares: ${middlewares}};
|
|
404
654
|
}
|
|
405
655
|
`;
|
|
406
656
|
}
|
|
@@ -415,11 +665,11 @@ export class Router {
|
|
|
415
665
|
|
|
416
666
|
const modify: string[] = [];
|
|
417
667
|
for (const parameter of parsedRoute.getParameters()) {
|
|
418
|
-
if (parameter.query) {
|
|
419
|
-
const queryPath = parameter.typePath === undefined ? parameter.
|
|
668
|
+
if (parameter.query || parameter.queries) {
|
|
669
|
+
const queryPath = parameter.typePath === undefined && parameter.query ? parameter.parameter.name : parameter.typePath || '';
|
|
420
670
|
|
|
421
|
-
if (parameter.
|
|
422
|
-
for (const property of parameter.
|
|
671
|
+
if (parameter.parameter.type.kind === ReflectionKind.class || parameter.parameter.type.kind === ReflectionKind.objectLiteral) {
|
|
672
|
+
for (const property of ReflectionClass.from(parameter.parameter.type).getProperties()) {
|
|
423
673
|
const accessor = `parameters.${parameter.getName()}?.${property.name}`;
|
|
424
674
|
const thisPath = queryPath ? queryPath + '.' + property.name : property.name;
|
|
425
675
|
modify.push(`${accessor} !== undefined && query.push(${JSON.stringify(dotToUrlPath(thisPath))} + '=' + encodeURIComponent(${accessor}))`);
|
|
@@ -442,49 +692,34 @@ export class Router {
|
|
|
442
692
|
}
|
|
443
693
|
|
|
444
694
|
public addRoute(routeConfig: RouteConfig) {
|
|
445
|
-
this.
|
|
446
|
-
this.fn = undefined;
|
|
695
|
+
this.registry.addRoute(routeConfig);
|
|
447
696
|
}
|
|
448
697
|
|
|
449
|
-
public addRouteForController(controller: ClassType) {
|
|
450
|
-
|
|
451
|
-
if (!data) return;
|
|
452
|
-
|
|
453
|
-
for (const action of data.getActions()) {
|
|
454
|
-
const routeConfig = new RouteConfig(action.name, action.httpMethod, action.path, {
|
|
455
|
-
controller,
|
|
456
|
-
methodName: action.methodName
|
|
457
|
-
});
|
|
458
|
-
routeConfig.parameterRegularExpressions = action.parameterRegularExpressions;
|
|
459
|
-
routeConfig.throws = action.throws;
|
|
460
|
-
routeConfig.description = action.description;
|
|
461
|
-
routeConfig.category = action.category;
|
|
462
|
-
routeConfig.groups = action.groups;
|
|
463
|
-
routeConfig.data = new Map(action.data);
|
|
464
|
-
routeConfig.baseUrl = data.baseUrl;
|
|
465
|
-
routeConfig.parameters = { ...action.parameters };
|
|
466
|
-
this.addRoute(routeConfig);
|
|
467
|
-
}
|
|
698
|
+
public addRouteForController(controller: ClassType, module: InjectorModule<any>) {
|
|
699
|
+
this.registry.addRouteForController(controller, module);
|
|
468
700
|
}
|
|
469
701
|
|
|
470
|
-
protected build():
|
|
702
|
+
protected build(): (request: HttpRequest) => ResolvedController | undefined {
|
|
703
|
+
this.buildId = this.registry.getBuildId();
|
|
471
704
|
const compiler = new CompilerContext;
|
|
472
|
-
compiler.context.set('
|
|
473
|
-
compiler.context.set('
|
|
474
|
-
compiler.context.set('parseQueryString', querystring.parse);
|
|
705
|
+
compiler.context.set('ValidationError', ValidationError);
|
|
706
|
+
compiler.context.set('qs', qs);
|
|
475
707
|
|
|
476
708
|
const code: string[] = [];
|
|
477
709
|
|
|
478
|
-
for (const route of this.
|
|
710
|
+
for (const route of this.getRoutes()) {
|
|
479
711
|
code.push(this.getRouteCode(compiler, route));
|
|
480
712
|
}
|
|
481
713
|
|
|
482
714
|
return compiler.build(`
|
|
483
|
-
|
|
484
|
-
const
|
|
715
|
+
let _match;
|
|
716
|
+
const _method = request.method || 'GET';
|
|
717
|
+
const _url = request.url || '/';
|
|
718
|
+
const _headers = request.headers || {};
|
|
485
719
|
const _qPosition = _url.indexOf('?');
|
|
720
|
+
let uploadedFiles = {};
|
|
486
721
|
const _path = _qPosition === -1 ? _url : _url.substr(0, _qPosition);
|
|
487
|
-
const _query = _qPosition === -1 ? {} :
|
|
722
|
+
const _query = _qPosition === -1 ? {} : qs.parse(_url.substr(_qPosition + 1));
|
|
488
723
|
${code.join('\n')}
|
|
489
724
|
`, 'request') as any;
|
|
490
725
|
}
|
|
@@ -493,7 +728,7 @@ export class Router {
|
|
|
493
728
|
const compiler = new CompilerContext;
|
|
494
729
|
const code: string[] = [];
|
|
495
730
|
|
|
496
|
-
for (const route of this.
|
|
731
|
+
for (const route of this.getRoutes()) {
|
|
497
732
|
code.push(this.getRouteUrlResolveCode(compiler, route));
|
|
498
733
|
}
|
|
499
734
|
|
|
@@ -514,21 +749,15 @@ export class Router {
|
|
|
514
749
|
}
|
|
515
750
|
|
|
516
751
|
public resolveRequest(request: HttpRequest): ResolvedController | undefined {
|
|
517
|
-
if (!this.fn) {
|
|
752
|
+
if (!this.fn || this.buildId !== this.registry.getBuildId()) {
|
|
518
753
|
this.fn = this.build();
|
|
519
754
|
}
|
|
520
755
|
|
|
521
|
-
return this.fn
|
|
756
|
+
return this.fn(request);
|
|
522
757
|
}
|
|
523
758
|
|
|
524
759
|
public resolve(method: string, url: string): ResolvedController | undefined {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
return url;
|
|
528
|
-
},
|
|
529
|
-
getMethod() {
|
|
530
|
-
return method;
|
|
531
|
-
},
|
|
532
|
-
} as any);
|
|
760
|
+
method = method.toUpperCase();
|
|
761
|
+
return this.resolveRequest({ url, method } as any);
|
|
533
762
|
}
|
|
534
763
|
}
|