@deepkit/http 1.0.1-alpha.59 → 1.0.1-alpha.61

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.
Files changed (142) hide show
  1. package/coverage/clover.xml +1086 -0
  2. package/coverage/coverage-final.json +12 -0
  3. package/coverage/lcov-report/base.css +224 -0
  4. package/coverage/lcov-report/block-navigation.js +79 -0
  5. package/coverage/lcov-report/favicon.png +0 -0
  6. package/coverage/lcov-report/index.html +126 -0
  7. package/coverage/lcov-report/prettify.css +1 -0
  8. package/coverage/lcov-report/prettify.js +2 -0
  9. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  10. package/coverage/lcov-report/sorter.js +170 -0
  11. package/coverage/lcov-report/src/controllers.ts.html +113 -0
  12. package/coverage/lcov-report/src/decorator.ts.html +1280 -0
  13. package/coverage/lcov-report/src/filter.ts.html +734 -0
  14. package/coverage/lcov-report/src/http.ts.html +2192 -0
  15. package/coverage/lcov-report/src/index.html +246 -0
  16. package/coverage/lcov-report/src/kernel.ts.html +260 -0
  17. package/coverage/lcov-report/src/middleware.ts.html +428 -0
  18. package/coverage/lcov-report/src/model.ts.html +937 -0
  19. package/coverage/lcov-report/src/module.config.ts.html +89 -0
  20. package/coverage/lcov-report/src/module.ts.html +245 -0
  21. package/coverage/lcov-report/src/router.ts.html +2318 -0
  22. package/coverage/lcov-report/tests/index.html +111 -0
  23. package/coverage/lcov-report/tests/utils.ts.html +176 -0
  24. package/coverage/lcov.info +2225 -0
  25. package/dist/cjs/src/controllers.d.ts +4 -4
  26. package/dist/cjs/src/controllers.js +3 -0
  27. package/dist/cjs/src/controllers.js.map +1 -1
  28. package/dist/cjs/src/decorator.d.ts +29 -94
  29. package/dist/cjs/src/decorator.js +53 -118
  30. package/dist/cjs/src/decorator.js.map +1 -1
  31. package/dist/cjs/src/filter.d.ts +1 -0
  32. package/dist/cjs/src/filter.js +11 -17
  33. package/dist/cjs/src/filter.js.map +1 -1
  34. package/dist/cjs/src/http.d.ts +5 -3
  35. package/dist/cjs/src/http.js +91 -55
  36. package/dist/cjs/src/http.js.map +1 -1
  37. package/dist/cjs/src/kernel.d.ts +3 -3
  38. package/dist/cjs/src/kernel.js +5 -25
  39. package/dist/cjs/src/kernel.js.map +1 -1
  40. package/dist/cjs/src/logger.d.ts +2 -2
  41. package/dist/cjs/src/logger.js +4 -8
  42. package/dist/cjs/src/logger.js.map +1 -1
  43. package/dist/cjs/src/middleware.d.ts +5 -1
  44. package/dist/cjs/src/middleware.js +15 -3
  45. package/dist/cjs/src/middleware.js.map +1 -1
  46. package/dist/cjs/src/model.d.ts +60 -0
  47. package/dist/cjs/src/model.js +95 -24
  48. package/dist/cjs/src/model.js.map +1 -1
  49. package/dist/cjs/src/module.config.d.ts +3 -4
  50. package/dist/cjs/src/module.config.js +8 -6
  51. package/dist/cjs/src/module.config.js.map +1 -1
  52. package/dist/cjs/src/module.d.ts +2 -3
  53. package/dist/cjs/src/module.js +4 -2
  54. package/dist/cjs/src/module.js.map +1 -1
  55. package/dist/cjs/src/router.d.ts +24 -53
  56. package/dist/cjs/src/router.js +140 -159
  57. package/dist/cjs/src/router.js.map +1 -1
  58. package/dist/cjs/src/static-serving.d.ts +1 -0
  59. package/dist/cjs/src/static-serving.js +38 -34
  60. package/dist/cjs/src/static-serving.js.map +1 -1
  61. package/dist/cjs/src/utils.js +1 -0
  62. package/dist/cjs/tests/filter.spec.js +61 -47
  63. package/dist/cjs/tests/filter.spec.js.map +1 -1
  64. package/dist/cjs/tests/middleware.spec.d.ts +1 -1
  65. package/dist/cjs/tests/middleware.spec.js +157 -145
  66. package/dist/cjs/tests/middleware.spec.js.map +1 -1
  67. package/dist/cjs/tests/model.spec.js +4 -4
  68. package/dist/cjs/tests/model.spec.js.map +1 -1
  69. package/dist/cjs/tests/module.spec.js +4 -3
  70. package/dist/cjs/tests/module.spec.js.map +1 -1
  71. package/dist/cjs/tests/router.spec.d.ts +1 -1
  72. package/dist/cjs/tests/router.spec.js +298 -333
  73. package/dist/cjs/tests/router.spec.js.map +1 -1
  74. package/dist/cjs/tests/utils.js +5 -2
  75. package/dist/cjs/tests/utils.js.map +1 -1
  76. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  77. package/dist/esm/src/controllers.d.ts +4 -4
  78. package/dist/esm/src/controllers.js +3 -0
  79. package/dist/esm/src/controllers.js.map +1 -1
  80. package/dist/esm/src/decorator.d.ts +29 -94
  81. package/dist/esm/src/decorator.js +49 -114
  82. package/dist/esm/src/decorator.js.map +1 -1
  83. package/dist/esm/src/filter.d.ts +1 -0
  84. package/dist/esm/src/filter.js +11 -18
  85. package/dist/esm/src/filter.js.map +1 -1
  86. package/dist/esm/src/http.d.ts +5 -3
  87. package/dist/esm/src/http.js +89 -55
  88. package/dist/esm/src/http.js.map +1 -1
  89. package/dist/esm/src/kernel.d.ts +3 -3
  90. package/dist/esm/src/kernel.js +6 -27
  91. package/dist/esm/src/kernel.js.map +1 -1
  92. package/dist/esm/src/logger.d.ts +2 -2
  93. package/dist/esm/src/logger.js +4 -9
  94. package/dist/esm/src/logger.js.map +1 -1
  95. package/dist/esm/src/middleware.d.ts +5 -1
  96. package/dist/esm/src/middleware.js +12 -0
  97. package/dist/esm/src/middleware.js.map +1 -1
  98. package/dist/esm/src/model.d.ts +60 -0
  99. package/dist/esm/src/model.js +91 -23
  100. package/dist/esm/src/model.js.map +1 -1
  101. package/dist/esm/src/module.config.d.ts +3 -4
  102. package/dist/esm/src/module.config.js +6 -5
  103. package/dist/esm/src/module.config.js.map +1 -1
  104. package/dist/esm/src/module.d.ts +2 -3
  105. package/dist/esm/src/module.js +5 -3
  106. package/dist/esm/src/module.js.map +1 -1
  107. package/dist/esm/src/router.d.ts +24 -53
  108. package/dist/esm/src/router.js +136 -155
  109. package/dist/esm/src/router.js.map +1 -1
  110. package/dist/esm/src/static-serving.d.ts +1 -0
  111. package/dist/esm/src/static-serving.js +29 -25
  112. package/dist/esm/src/static-serving.js.map +1 -1
  113. package/dist/esm/src/utils.js +1 -0
  114. package/dist/esm/tests/filter.spec.js +14 -0
  115. package/dist/esm/tests/filter.spec.js.map +1 -1
  116. package/dist/esm/tests/middleware.spec.d.ts +1 -1
  117. package/dist/esm/tests/middleware.spec.js +60 -48
  118. package/dist/esm/tests/middleware.spec.js.map +1 -1
  119. package/dist/esm/tests/module.spec.js +1 -0
  120. package/dist/esm/tests/module.spec.js.map +1 -1
  121. package/dist/esm/tests/router.spec.d.ts +1 -1
  122. package/dist/esm/tests/router.spec.js +167 -202
  123. package/dist/esm/tests/router.spec.js.map +1 -1
  124. package/dist/esm/tests/utils.js +4 -1
  125. package/dist/esm/tests/utils.js.map +1 -1
  126. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  127. package/package.json +14 -15
  128. package/src/controllers.ts +3 -3
  129. package/src/decorator.ts +42 -131
  130. package/src/filter.ts +0 -2
  131. package/src/http.ts +35 -36
  132. package/src/kernel.ts +4 -5
  133. package/src/logger.ts +2 -4
  134. package/src/middleware.ts +2 -1
  135. package/src/model.ts +77 -5
  136. package/src/module.config.ts +3 -6
  137. package/src/module.ts +2 -2
  138. package/src/router.ts +121 -161
  139. package/src/static-serving.ts +2 -4
  140. package/tests/middleware.spec.ts +0 -1
  141. package/tests/router.spec.ts +131 -133
  142. package/tsconfig.json +2 -1
package/src/router.ts CHANGED
@@ -7,31 +7,34 @@
7
7
  *
8
8
  * You should have received a copy of the MIT License along with this program.
9
9
  */
10
- import 'reflect-metadata';
11
- import { asyncOperation, ClassType, CompilerContext, getClassName, isClass, urlJoin } from '@deepkit/core';
10
+ import { asyncOperation, ClassType, CompilerContext, getClassName, isClass, isObject, urlJoin } from '@deepkit/core';
12
11
  import {
12
+ assertType,
13
13
  entity,
14
- getClassSchema,
15
- getPropertyXtoClassFunction,
16
- JitConverterOptions,
17
- jitValidateProperty,
18
- jsonSerializer,
19
- PropertySchema,
14
+ findMember,
15
+ getSerializeFunction,
16
+ getValidatorFunction,
17
+ metaAnnotation,
18
+ ReflectionClass,
19
+ ReflectionKind,
20
+ ReflectionParameter,
21
+ SerializationOptions,
22
+ serializer,
20
23
  Serializer,
21
- t,
22
- ValidationFailed,
23
- ValidationFailedItem
24
+ stringifyType,
25
+ Type,
26
+ typeToObject,
27
+ ValidationError
24
28
  } from '@deepkit/type';
25
29
  // @ts-ignore
26
30
  import formidable from 'formidable';
27
- import { IncomingMessage } from 'http';
28
31
  import querystring from 'querystring';
29
32
  import { httpClass } from './decorator';
30
- import { HttpRequest, HttpRequestQuery, HttpRequestResolvedParameters } from './model';
31
- import { injectable, InjectOptions, InjectorContext, TagRegistry } from '@deepkit/injector';
32
- import { Logger } from '@deepkit/logger';
33
+ import { BodyValidationError, getRegExp, HttpRequest, HttpRequestQuery, HttpRequestResolvedParameters, ValidatedBody } from './model';
34
+ import { InjectorContext, InjectorModule, TagRegistry } from '@deepkit/injector';
35
+ import { Logger, LoggerInterface } from '@deepkit/logger';
33
36
  import { HttpControllers } from './controllers';
34
- import { AppModule, MiddlewareRegistry, MiddlewareRegistryEntry } from '@deepkit/app';
37
+ import { MiddlewareRegistry, MiddlewareRegistryEntry } from '@deepkit/app';
35
38
  import { HttpMiddlewareConfig, HttpMiddlewareFn } from './middleware';
36
39
 
37
40
  export type RouteParameterResolverForInjector = ((injector: InjectorContext) => any[] | Promise<any[]>);
@@ -48,38 +51,38 @@ export class UploadedFile {
48
51
  /**
49
52
  * The size of the uploaded file in bytes.
50
53
  */
51
- @t.required size!: number;
54
+ size!: number;
52
55
 
53
56
  /**
54
57
  * The path this file is being written to.
55
58
  */
56
- @t.required path!: string;
59
+ path!: string;
57
60
 
58
61
  /**
59
62
  * The name this file had according to the uploading client.
60
63
  */
61
- @t.string.required.nullable name!: string | null;
64
+ name!: string | null;
62
65
 
63
66
  /**
64
67
  * The mime type of this file, according to the uploading client.
65
68
  */
66
- @t.string.required.nullable type!: string | null;
69
+ type!: string | null;
67
70
 
68
71
  /**
69
72
  * A Date object (or `null`) containing the time this file was last written to.
70
73
  * Mostly here for compatibility with the [W3C File API Draft](http://dev.w3.org/2006/webapi/FileAPI/).
71
74
  */
72
- @t.date.required.nullable lastModifiedDate!: Date | null;
75
+ lastModifiedDate!: Date | null;
73
76
 
74
77
  // /**
75
78
  // * If `options.hash` calculation was set, you can read the hex digest out of this var.
76
79
  // */
77
- // @t.string.required.nullable hash!: string | 'sha1' | 'md5' | 'sha256' | null;
80
+ // hash!: string | 'sha1' | 'md5' | 'sha256' | null;
78
81
  }
79
82
 
80
83
  export interface RouteControllerAction {
81
84
  //if not set, the root module is used
82
- module?: AppModule<any>;
85
+ module?: InjectorModule<any>;
83
86
  controller: ClassType;
84
87
  methodName: string;
85
88
  }
@@ -88,43 +91,28 @@ function getRouterControllerActionName(action: RouteControllerAction): string {
88
91
  return `${getClassName(action.controller)}.${action.methodName}`;
89
92
  }
90
93
 
91
- export interface RouteParameterConfig {
92
- type?: 'body' | 'query' | 'queries';
93
- /**
94
- * undefined = propertyName, '' === root, else given path
95
- */
96
- typePath?: string;
97
-
98
- optional: boolean;
99
- name: string;
100
- }
101
-
102
94
  export class RouteConfig {
103
95
  public baseUrl: string = '';
104
- public parameterRegularExpressions: { [name: string]: any } = {};
105
96
 
106
- public responses: { statusCode: number, description: string, type?: PropertySchema }[] = [];
97
+ public responses: { statusCode: number, description: string, type?: Type }[] = [];
107
98
 
108
99
  public description: string = '';
109
100
  public groups: string[] = [];
110
101
  public category: string = '';
111
102
 
112
- /**
113
- * This is set when the route action has a manual return type defined using @t.
114
- */
115
- public returnSchema?: PropertySchema;
103
+ public returnType?: Type;
116
104
 
117
- public serializationOptions?: JitConverterOptions;
105
+ public serializationOptions?: SerializationOptions;
118
106
  public serializer?: Serializer;
119
107
 
120
108
  /**
121
109
  * When assigned defines where this route came from.
122
110
  */
123
- public module?: AppModule<any>;
111
+ public module?: InjectorModule<any>;
124
112
 
125
113
  resolverForToken: Map<any, ClassType> = new Map();
126
114
 
127
- middlewares: { config: HttpMiddlewareConfig, module: AppModule<any> }[] = [];
115
+ middlewares: { config: HttpMiddlewareConfig, module: InjectorModule<any> }[] = [];
128
116
 
129
117
  resolverForParameterName: Map<string, ClassType> = new Map();
130
118
 
@@ -133,10 +121,6 @@ export class RouteConfig {
133
121
  */
134
122
  data = new Map<any, any>();
135
123
 
136
- public parameters: {
137
- [name: string]: RouteParameterConfig
138
- } = {};
139
-
140
124
  constructor(
141
125
  public readonly name: string,
142
126
  public readonly httpMethods: string[],
@@ -146,7 +130,7 @@ export class RouteConfig {
146
130
  ) {
147
131
  }
148
132
 
149
- getSchemaForResponse(statusCode: number): PropertySchema | undefined {
133
+ getSchemaForResponse(statusCode: number): Type | undefined {
150
134
  if (!this.responses.length) return;
151
135
  for (const response of this.responses) {
152
136
  if (response.statusCode === statusCode) return response.type;
@@ -161,40 +145,8 @@ export class RouteConfig {
161
145
  }
162
146
  }
163
147
 
164
- /**
165
- * When this class is injected into a route, then validation errors are not automatically thrown (using onHttpControllerValidationError event),
166
- * but injected to the route itself. The user is then responsible to handle the errors.
167
- *
168
- * Note: The body parameter is still passed, however it might contain now invalid data. The BodyValidation tells what data is invalid.
169
- */
170
- export class BodyValidation {
171
- constructor(
172
- public readonly errors: ValidationFailedItem[] = []
173
- ) {
174
- }
175
-
176
- hasErrors(prefix?: string): boolean {
177
- return this.getErrors(prefix).length > 0;
178
- }
179
-
180
- getErrors(prefix?: string): ValidationFailedItem[] {
181
- if (prefix) return this.errors.filter(v => v.path.startsWith(prefix));
182
-
183
- return this.errors;
184
- }
185
-
186
- getErrorsForPath(path: string): ValidationFailedItem[] {
187
- return this.errors.filter(v => v.path === path);
188
- }
189
-
190
- getErrorMessageForPath(path: string): string {
191
- return this.getErrorsForPath(path).map(v => v.message).join(', ');
192
- }
193
- }
194
-
195
148
  class ParsedRoute {
196
149
  public regex?: string;
197
- public customValidationErrorHandling?: ParsedRouteParameter;
198
150
 
199
151
  public pathParameterNames: { [name: string]: number } = {};
200
152
 
@@ -203,8 +155,8 @@ class ParsedRoute {
203
155
  constructor(public routeConfig: RouteConfig) {
204
156
  }
205
157
 
206
- addParameter(property: PropertySchema, config?: RouteParameterConfig): ParsedRouteParameter {
207
- const parameter = new ParsedRouteParameter(property, config);
158
+ addParameter(property: ReflectionParameter): ParsedRouteParameter {
159
+ const parameter = new ParsedRouteParameter(property);
208
160
  this.parameters.push(parameter);
209
161
  return parameter;
210
162
  }
@@ -225,29 +177,46 @@ class ParsedRouteParameter {
225
177
  regexPosition?: number;
226
178
 
227
179
  constructor(
228
- public property: PropertySchema,
229
- public config?: RouteParameterConfig,
180
+ public parameter: ReflectionParameter,
230
181
  ) {
231
182
  }
232
183
 
233
184
  get body() {
234
- return this.config ? this.config.type === 'body' : false;
185
+ return metaAnnotation.getForName(this.parameter.type, 'httpBody') !== undefined;
186
+ }
187
+
188
+ get bodyValidation() {
189
+ return metaAnnotation.getForName(this.parameter.type, 'httpBodyValidation') !== undefined;
190
+ }
191
+
192
+ getType(): Type {
193
+ if (this.bodyValidation) {
194
+ assertType(this.parameter.type, ReflectionKind.class);
195
+ const valueType = findMember('value', this.parameter.type);
196
+ if (!valueType || valueType.kind !== ReflectionKind.property) throw new Error(`No property value found at ${stringifyType(this.parameter.type)}`);
197
+ return valueType.type as Type;
198
+ }
199
+ return this.parameter.type;
235
200
  }
236
201
 
237
202
  get query() {
238
- return this.config ? this.config.type === 'query' : false;
203
+ return metaAnnotation.getForName(this.parameter.type, 'httpQuery') !== undefined;
239
204
  }
240
205
 
241
206
  get queries() {
242
- return this.config ? this.config.type === 'queries' : false;
207
+ return metaAnnotation.getForName(this.parameter.type, 'httpQueries') !== undefined;
243
208
  }
244
209
 
245
- get typePath() {
246
- return this.config ? this.config.typePath : undefined;
210
+ get typePath(): string | undefined {
211
+ const typeOptions = metaAnnotation.getForName(this.parameter.type, 'httpQueries') || metaAnnotation.getForName(this.parameter.type, 'httpQuery');
212
+ if (!typeOptions) return;
213
+ const options = typeToObject(typeOptions[0]);
214
+ if (isObject(options)) return options.name;
215
+ return;
247
216
  }
248
217
 
249
218
  getName() {
250
- return this.property.name;
219
+ return this.parameter.name;
251
220
  }
252
221
 
253
222
  isPartOfPath(): boolean {
@@ -259,34 +228,34 @@ function parseRoutePathToRegex(routeConfig: RouteConfig): { regex: string, param
259
228
  const parameterNames: { [name: string]: number } = {};
260
229
  let path = routeConfig.getFullPath();
261
230
 
231
+ const method = ReflectionClass.from(routeConfig.action.controller).getMethod(routeConfig.action.methodName);
232
+
262
233
  let argumentIndex = 0;
263
234
  path = path.replace(/:(\w+)/g, (a, name) => {
264
235
  parameterNames[name] = argumentIndex;
265
236
  argumentIndex++;
266
- return routeConfig.parameterRegularExpressions[name] ? '(' + routeConfig.parameterRegularExpressions[name] + ')' : String.raw`([^/]+)`;
237
+ const parameter = method.getParameterOrUndefined(name);
238
+ if (parameter) {
239
+ const regExp = getRegExp(parameter.type);
240
+ if (regExp) return '(' + regExp + ')';
241
+ }
242
+ return String.raw`([^/]+)`;
267
243
  });
268
244
 
269
245
  return { regex: path, parameterNames };
270
246
  }
271
247
 
272
248
  export function parseRouteControllerAction(routeConfig: RouteConfig): ParsedRoute {
273
- const schema = getClassSchema(routeConfig.action.controller);
249
+ const schema = ReflectionClass.from(routeConfig.action.controller);
274
250
  const parsedRoute = new ParsedRoute(routeConfig);
275
251
 
276
- const methodArgumentProperties = schema.getMethodProperties(routeConfig.action.methodName);
252
+ const methodArgumentProperties = schema.getMethodParameters(routeConfig.action.methodName);
277
253
  const parsedPath = parseRoutePathToRegex(routeConfig);
278
254
  parsedRoute.regex = parsedPath.regex;
279
255
  parsedRoute.pathParameterNames = parsedPath.parameterNames;
280
256
 
281
257
  for (const property of methodArgumentProperties) {
282
- const decoratorData = routeConfig.parameters[property.name];
283
- const parsedParameter = parsedRoute.addParameter(property, decoratorData);
284
-
285
- if (decoratorData && decoratorData.optional) property.isOptional = true;
286
-
287
- if (property.type === 'class' && property.classType === BodyValidation) {
288
- parsedRoute.customValidationErrorHandling = parsedParameter;
289
- }
258
+ const parsedParameter = parsedRoute.addParameter(property);
290
259
  parsedParameter.regexPosition = parsedPath.parameterNames[property.name];
291
260
  }
292
261
 
@@ -323,9 +292,9 @@ export interface RouteParameterResolverContext {
323
292
  parameters: HttpRequestResolvedParameters;
324
293
  }
325
294
 
326
- function filterMiddlewaresForRoute(middlewareRawConfigs: MiddlewareRegistryEntry[], routeConfig: RouteConfig, fullPath: string): { config: HttpMiddlewareConfig, module: AppModule<any> }[] {
295
+ function filterMiddlewaresForRoute(middlewareRawConfigs: MiddlewareRegistryEntry[], routeConfig: RouteConfig, fullPath: string): { config: HttpMiddlewareConfig, module: InjectorModule<any> }[] {
327
296
  const middlewares = middlewareRawConfigs.slice(0);
328
- middlewares.push(...routeConfig.middlewares);
297
+ middlewares.push(...routeConfig.middlewares as any[]);
329
298
 
330
299
  const middlewareConfigs = middlewares.filter((v) => {
331
300
  if (!(v.config instanceof HttpMiddlewareConfig)) return false;
@@ -385,7 +354,7 @@ function filterMiddlewaresForRoute(middlewareRawConfigs: MiddlewareRegistryEntry
385
354
  }
386
355
 
387
356
  return true;
388
- }) as { config: HttpMiddlewareConfig, module: AppModule<any> }[];
357
+ }) as { config: HttpMiddlewareConfig, module: any }[];
389
358
 
390
359
  middlewareConfigs.sort((a, b) => {
391
360
  return a.config.order - b.config.order;
@@ -394,7 +363,6 @@ function filterMiddlewaresForRoute(middlewareRawConfigs: MiddlewareRegistryEntry
394
363
  return middlewareConfigs;
395
364
  }
396
365
 
397
- @injectable
398
366
  export class Router {
399
367
  protected fn?: (request: HttpRequest) => ResolvedController | undefined;
400
368
  protected resolveFn?: (name: string, parameters: { [name: string]: any }) => string;
@@ -424,7 +392,7 @@ export class Router {
424
392
 
425
393
  constructor(
426
394
  controllers: HttpControllers,
427
- private logger: Logger,
395
+ private logger: LoggerInterface,
428
396
  tagRegistry: TagRegistry,
429
397
  private middlewareRegistry: MiddlewareRegistry = new MiddlewareRegistry,
430
398
  ) {
@@ -438,10 +406,10 @@ export class Router {
438
406
  }
439
407
 
440
408
  static forControllers(
441
- controllers: (ClassType | { module: AppModule<any>, controller: ClassType })[],
409
+ controllers: (ClassType | { module: InjectorModule<any>, controller: ClassType })[],
442
410
  tagRegistry: TagRegistry = new TagRegistry(),
443
411
  middlewareRegistry: MiddlewareRegistry = new MiddlewareRegistry(),
444
- module: AppModule<any> = new AppModule({})
412
+ module: InjectorModule<any> = new InjectorModule()
445
413
  ): Router {
446
414
  return new this(new HttpControllers(controllers.map(v => {
447
415
  return isClass(v) ? { controller: v, module } : v;
@@ -458,7 +426,7 @@ export class Router {
458
426
  const setParameters: string[] = [];
459
427
  const parameterNames: string[] = [];
460
428
  const parameterValidator: string[] = [];
461
- let bodyValidationErrorHandling = `if (bodyErrors.length) throw ValidationFailed.from(bodyErrors);`;
429
+ let bodyValidationErrorHandling = `if (bodyErrors.length) throw ValidationError.from(bodyErrors);`;
462
430
 
463
431
  let enableParseBody = false;
464
432
  const hasParameters = parsedRoute.getParameters().length > 0;
@@ -469,52 +437,53 @@ export class Router {
469
437
  const middlewareConfigs = filterMiddlewaresForRoute(this.middlewareRegistry.configs, routeConfig, fullPath);
470
438
 
471
439
  for (const parameter of parsedRoute.getParameters()) {
472
- if (parsedRoute.customValidationErrorHandling === parameter) {
473
- compiler.context.set('BodyValidation', BodyValidation);
474
- bodyValidationErrorHandling = '';
475
- setParameters.push(`parameters.${parameter.property.name} = new BodyValidation(bodyErrors);`);
476
- parameterNames.push(`parameters.${parameter.property.name}`);
477
- } else if (parameter.body) {
478
- const validatorVar = compiler.reserveVariable('argumentValidator', jitValidateProperty(parameter.property));
479
- const converterVar = compiler.reserveVariable('argumentConverter', getPropertyXtoClassFunction(parameter.property, jsonSerializer));
440
+ if (parameter.body || parameter.bodyValidation) {
441
+ const type = parameter.getType();
442
+ const validatorVar = compiler.reserveVariable('argumentValidator', getValidatorFunction(undefined, type));
443
+ const converterVar = compiler.reserveVariable('argumentConverter', getSerializeFunction(type, serializer.deserializeRegistry));
480
444
 
481
445
  enableParseBody = true;
482
- setParameters.push(`parameters.${parameter.property.name} = ${converterVar}(bodyFields);`);
483
- parameterValidator.push(`${validatorVar}(parameters.${parameter.property.name}, ${JSON.stringify(parameter.typePath || '')}, bodyErrors);`);
484
- parameterNames.push(`parameters.${parameter.property.name}`);
446
+ setParameters.push(`parameters.${parameter.parameter.name} = ${converterVar}(bodyFields, {loosely: true});`);
447
+ parameterValidator.push(`${validatorVar}(parameters.${parameter.parameter.name}, {errors: bodyErrors});`);
448
+ if (parameter.bodyValidation) {
449
+ compiler.context.set('BodyValidation', ValidatedBody);
450
+ compiler.context.set('BodyValidationError', BodyValidationError);
451
+ parameterNames.push(`new BodyValidation(new BodyValidationError(bodyErrors), bodyErrors.length === 0 ? parameters.${parameter.parameter.name} : undefined)`);
452
+ bodyValidationErrorHandling = '';
453
+ } else {
454
+ parameterNames.push(`parameters.${parameter.parameter.name}`);
455
+ }
485
456
  } else if (parameter.query || parameter.queries) {
486
- const converted = parameter.property.type === 'any' ? (v: any) => v : getPropertyXtoClassFunction(parameter.property, jsonSerializer);
487
- const validator = parameter.property.type === 'any' ? (v: any) => undefined : jitValidateProperty(parameter.property);
457
+ const converted = getSerializeFunction(parameter.parameter.parameter, serializer.deserializeRegistry, undefined, parameter.getName());
458
+ const validator = getValidatorFunction(undefined, parameter.parameter.parameter,);
488
459
  const converterVar = compiler.reserveVariable('argumentConverter', converted);
489
460
  const validatorVar = compiler.reserveVariable('argumentValidator', validator);
490
461
 
491
- const queryPath = parameter.typePath === undefined ? parameter.property.name : parameter.typePath;
462
+ const queryPath = parameter.typePath === undefined && parameter.query ? parameter.parameter.name : parameter.typePath;
492
463
  const accessor = queryPath ? `['` + (queryPath.replace(/\./g, `']['`)) + `']` : '';
493
464
  const queryAccessor = queryPath ? `_query${accessor}` : '_query';
494
465
 
495
- setParameters.push(`parameters.${parameter.property.name} = ${converterVar}(${queryAccessor});`);
496
- parameterNames.push(`parameters.${parameter.property.name}`);
497
- parameterValidator.push(`${validatorVar}(parameters.${parameter.property.name}, ${JSON.stringify(parameter.typePath)}, validationErrors);`);
466
+ setParameters.push(`parameters.${parameter.parameter.name} = ${converterVar}(${queryAccessor}, {loosely: true});`);
467
+ parameterNames.push(`parameters.${parameter.parameter.name}`);
468
+ parameterValidator.push(`${validatorVar}(parameters.${parameter.parameter.name}, {errors: validationErrors}, ${JSON.stringify(parameter.typePath || parameter.getName())});`);
498
469
  } else {
499
- parameterNames.push(`parameters.${parameter.property.name}`);
470
+ parameterNames.push(`parameters.${parameter.parameter.name}`);
500
471
 
501
472
  if (parameter.isPartOfPath()) {
502
- if (parameter.property.type !== 'class') {
503
- const converted = parameter.property.type === 'any' ? (v: any) => v : getPropertyXtoClassFunction(parameter.property, jsonSerializer);
473
+ if (parameter.parameter.type.kind !== ReflectionKind.class) {
474
+ const converted = getSerializeFunction(parameter.parameter.parameter, serializer.deserializeRegistry, undefined, parameter.getName());
504
475
  const converterVar = compiler.reserveVariable('argumentConverter', converted);
505
- setParameters.push(`parameters.${parameter.property.name} = ${converterVar}(_match[${1 + (parameter.regexPosition || 0)}]);`);
476
+ setParameters.push(`parameters.${parameter.parameter.name} = ${converterVar}(_match[${1 + (parameter.regexPosition || 0)}], {loosely: true});`);
506
477
 
507
- const validator = parameter.property.type === 'any' ? (v: any) => undefined : jitValidateProperty(parameter.property);
478
+ const validator = getValidatorFunction(undefined, parameter.parameter.parameter);
508
479
  const validatorVar = compiler.reserveVariable('argumentValidator', validator);
509
- parameterValidator.push(`${validatorVar}(parameters.${parameter.property.name}, ${JSON.stringify(parameter.getName())}, validationErrors);`);
480
+ parameterValidator.push(`${validatorVar}(parameters.${parameter.parameter.name}, {errors: validationErrors}, ${JSON.stringify(parameter.getName())});`);
510
481
  } else {
511
- setParameters.push(`parameters.${parameter.property.name} = _match[${1 + (parameter.regexPosition || 0)}];`);
482
+ setParameters.push(`parameters.${parameter.parameter.name} = _match[${1 + (parameter.regexPosition || 0)}];`);
512
483
  }
513
484
  }
514
485
 
515
- const injectorOptions = parameter.property.data['deepkit/inject'] as InjectOptions | undefined;
516
-
517
- const injectorToken = injectorOptions && injectorOptions.token ? injectorOptions.token : (parameter.property.type === 'class' ? parameter.property.getResolvedClassType() : undefined);
486
+ const injectorToken = parameter.parameter.type.kind === ReflectionKind.class ? parameter.parameter.type.classType : undefined;
518
487
  const injectorTokenVar = compiler.reserveVariable('classType', injectorToken);
519
488
  const parameterResolverFoundVar = compiler.reserveVariable('parameterResolverFound', false);
520
489
 
@@ -542,29 +511,22 @@ export class Router {
542
511
  ${instance} = ${injector}.get(${resolverProvideTokenVar}${moduleVar});
543
512
  if (!${parameterResolverFoundVar}) {
544
513
  ${parameterResolverFoundVar} = true;
545
- parameters.${parameter.property.name} = await ${instance}.resolve({
514
+ parameters.${parameter.parameter.name} = await ${instance}.resolve({
546
515
  token: ${injectorTokenVar},
547
516
  routeConfig: ${routeConfigVar},
548
517
  request: request,
549
- name: ${JSON.stringify(parameter.property.name)},
550
- value: parameters.${parameter.property.name},
518
+ name: ${JSON.stringify(parameter.parameter.name)},
519
+ value: parameters.${parameter.parameter.name},
551
520
  query: _query,
552
521
  parameters: parameters
553
522
  });
554
523
  }`);
555
- } else {
556
- if (!parameter.isPartOfPath() && parameter.property.type !== 'class' && (!injectorOptions || !injectorOptions.token)) {
557
- throw new Error(
558
- `Route parameter '${parameter.property.name}' of ${getRouterControllerActionName(routeConfig.action)} is not a ClassType nor is a @inject(T) set. It can not be requested from the DI container like that.` +
559
- `If its a query parameter use '@http.query() ${parameter.property.name}' or if its a body '@http.body() ${parameter.property.name}'.`
560
- );
561
- }
562
524
  }
563
525
 
564
526
  if (!parameter.isPartOfPath()) {
565
- let injectorGet = `parameters.${parameter.property.name} = ${injector}.get(${injectorTokenVar});`;
566
- if (injectorOptions && injectorOptions.optional) {
567
- injectorGet = `try {parameters.${parameter.property.name} = ${injector}.get(${injectorTokenVar}); } catch (e) {}`;
527
+ let injectorGet = `parameters.${parameter.parameter.name} = ${injector}.get(${injectorTokenVar});`;
528
+ if (parameter.parameter.isOptional()) {
529
+ injectorGet = `try {parameters.${parameter.parameter.name} = ${injector}.get(${injectorTokenVar}); } catch (e) {}`;
568
530
  }
569
531
  setParameters.push(`if (!${parameterResolverFoundVar}) ${injectorGet}`);
570
532
  }
@@ -588,7 +550,7 @@ export class Router {
588
550
  if (middlewareConfigs.length) {
589
551
  const middlewareItems: string[] = [];
590
552
  for (const middlewareConfig of middlewareConfigs) {
591
- const moduleVar = middlewareConfig.module ? ', ' + compiler.reserveVariable('module', middlewareConfig.module): '';
553
+ const moduleVar = middlewareConfig.module ? ', ' + compiler.reserveVariable('module', middlewareConfig.module) : '';
592
554
 
593
555
  for (const middleware of middlewareConfig.config.middlewares) {
594
556
  if (isClass(middleware)) {
@@ -618,7 +580,7 @@ export class Router {
618
580
  ${setParameters.join('\n')}
619
581
  ${parameterValidator.join('\n')}
620
582
  ${bodyValidationErrorHandling}
621
- if (validationErrors.length) throw ValidationFailed.from(validationErrors);
583
+ if (validationErrors.length) throw new ValidationError(validationErrors);
622
584
  return [${parameterNames.join(',')}];
623
585
  }`;
624
586
  }
@@ -649,10 +611,10 @@ export class Router {
649
611
  const modify: string[] = [];
650
612
  for (const parameter of parsedRoute.getParameters()) {
651
613
  if (parameter.query || parameter.queries) {
652
- const queryPath = parameter.typePath === undefined ? parameter.property.name : parameter.typePath;
614
+ const queryPath = parameter.typePath === undefined && parameter.query ? parameter.parameter.name : parameter.typePath || '';
653
615
 
654
- if (parameter.property.type === 'class') {
655
- for (const property of parameter.property.getResolvedClassSchema().getProperties()) {
616
+ if (parameter.parameter.type.kind === ReflectionKind.class || parameter.parameter.type.kind === ReflectionKind.objectLiteral) {
617
+ for (const property of ReflectionClass.from(parameter.parameter.type).getProperties()) {
656
618
  const accessor = `parameters.${parameter.getName()}?.${property.name}`;
657
619
  const thisPath = queryPath ? queryPath + '.' + property.name : property.name;
658
620
  modify.push(`${accessor} !== undefined && query.push(${JSON.stringify(dotToUrlPath(thisPath))} + '=' + encodeURIComponent(${accessor}))`);
@@ -679,10 +641,10 @@ export class Router {
679
641
  this.fn = undefined;
680
642
  }
681
643
 
682
- public addRouteForController(controller: ClassType, module: AppModule<any>) {
644
+ public addRouteForController(controller: ClassType, module: InjectorModule<any>) {
683
645
  const data = httpClass._fetch(controller);
684
646
  if (!data) throw new Error(`Http controller class ${getClassName(controller)} has no @http.controller decorator.`);
685
- const schema = getClassSchema(controller);
647
+ const schema = ReflectionClass.from(controller);
686
648
 
687
649
  for (const action of data.getActions()) {
688
650
  const routeConfig = new RouteConfig(action.name, action.httpMethods, action.path, {
@@ -691,7 +653,6 @@ export class Router {
691
653
  methodName: action.methodName
692
654
  });
693
655
  routeConfig.module = module;
694
- routeConfig.parameterRegularExpressions = action.parameterRegularExpressions;
695
656
  routeConfig.responses = action.responses;
696
657
  routeConfig.description = action.description;
697
658
  routeConfig.category = action.category;
@@ -714,18 +675,17 @@ export class Router {
714
675
  routeConfig.resolverForParameterName = new Map(data.resolverForParameterName);
715
676
  for (const item of action.resolverForParameterName) routeConfig.resolverForParameterName.set(...item);
716
677
 
717
- routeConfig.parameters = { ...action.parameters };
718
678
  routeConfig.serializationOptions = action.serializationOptions;
719
679
  routeConfig.serializer = action.serializer;
720
680
  routeConfig.serializer = action.serializer;
721
- if (schema.hasMethod(action.methodName)) routeConfig.returnSchema = schema.getMethod(action.methodName);
681
+ if (schema.hasMethod(action.methodName)) routeConfig.returnType = schema.getMethod(action.methodName).getReturnType();
722
682
  this.addRoute(routeConfig);
723
683
  }
724
684
  }
725
685
 
726
686
  protected build(): (request: HttpRequest) => ResolvedController | undefined {
727
687
  const compiler = new CompilerContext;
728
- compiler.context.set('ValidationFailed', ValidationFailed);
688
+ compiler.context.set('ValidationError', ValidationError);
729
689
  compiler.context.set('qs', require('qs'));
730
690
 
731
691
  const code: string[] = [];
@@ -780,6 +740,6 @@ export class Router {
780
740
 
781
741
  public resolve(method: string, url: string): ResolvedController | undefined {
782
742
  method = method.toUpperCase();
783
- return this.resolveRequest({url, method} as any);
743
+ return this.resolveRequest({ url, method } as any);
784
744
  }
785
745
  }
@@ -15,14 +15,12 @@ import { HtmlResponse, httpWorkflow } from './http';
15
15
  import { AppModule } from '@deepkit/app';
16
16
  import { normalizeDirectory } from './utils';
17
17
  import { ClassType, urlJoin } from '@deepkit/core';
18
- import { injectable } from '@deepkit/injector';
19
18
  import { HttpRequest, HttpResponse } from './model';
20
19
  import send from 'send';
21
20
  import { eventDispatcher } from '@deepkit/event';
22
21
  import { RouteConfig, Router } from './router';
23
22
 
24
23
  export function serveStaticListener(module: AppModule<any>, path: string, localPath: string = path): ClassType {
25
- @injectable
26
24
  class HttpRequestStaticServingListener {
27
25
  serve(path: string, request: HttpRequest, response: HttpResponse) {
28
26
  return new Promise((resolve, reject) => {
@@ -133,7 +131,7 @@ export function registerStaticHttpController(module: AppModule<any>, options: St
133
131
  methodName: 'serveIndex'
134
132
  });
135
133
  route1.groups = groups;
136
- module.setupGlobalProvider(Router).addRoute(route1);
134
+ module.setupGlobalProvider<Router>().addRoute(route1);
137
135
 
138
136
  const route2 = new RouteConfig('static', ['GET'], normalizeDirectory(options.path).slice(0, -1), {
139
137
  controller: StaticController,
@@ -141,7 +139,7 @@ export function registerStaticHttpController(module: AppModule<any>, options: St
141
139
  methodName: 'serveIndex'
142
140
  });
143
141
  route2.groups = groups;
144
- module.setupGlobalProvider(Router).addRoute(route2);
142
+ module.setupGlobalProvider<Router>().addRoute(route2);
145
143
 
146
144
  module.addProvider(StaticController);
147
145
  module.addListener(serveStaticListener(module, normalizeDirectory(options.path), options.localPath));
@@ -1,5 +1,4 @@
1
1
  import { expect, test } from '@jest/globals';
2
- import 'reflect-metadata';
3
2
  import { http } from '../src/decorator';
4
3
  import { createHttpKernel } from './utils';
5
4
  import { HttpMiddleware, httpMiddleware } from '../src/middleware';