@e22m4u/ts-rest-router 0.2.0 → 0.2.1
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/README.md +129 -34
- package/dist/cjs/index.cjs +6 -2
- package/dist/esm/controller-registry.js +9 -4
- package/dist/esm/decorators/after-action/after-action-reflector.spec.js +0 -1
- package/dist/esm/decorators/before-action/before-action-reflector.spec.js +0 -1
- package/dist/esm/decorators/request-context/request-context-reflector.spec.js +0 -1
- package/dist/esm/decorators/request-data/request-data-reflector.spec.js +0 -1
- package/dist/esm/decorators/rest-action/rest-action-reflector.spec.js +0 -1
- package/dist/esm/decorators/rest-controller/rest-controller-reflector.spec.js +0 -1
- package/eslint.config.js +1 -1
- package/package.json +15 -15
- package/src/controller-registry.spec.ts +85 -27
- package/src/controller-registry.ts +13 -4
- package/src/debuggable-service.spec.ts +0 -1
- package/src/decorators/after-action/after-action-reflector.spec.ts +0 -1
- package/src/decorators/before-action/before-action-reflector.spec.ts +0 -1
- package/src/decorators/request-context/request-context-reflector.spec.ts +0 -1
- package/src/decorators/request-data/request-data-reflector.spec.ts +0 -1
- package/src/decorators/rest-action/rest-action-reflector.spec.ts +0 -1
- package/src/decorators/rest-controller/rest-controller-reflector.spec.ts +0 -1
package/README.md
CHANGED
@@ -10,6 +10,16 @@ REST маршрутизатор на основе контроллеров дл
|
|
10
10
|
- Валидация входящих данных.
|
11
11
|
- Поддержка всех HTTP методов (GET, POST, PUT, PATCH и DELETE).
|
12
12
|
|
13
|
+
## Содержание
|
14
|
+
|
15
|
+
- [Установка](#установка)
|
16
|
+
- [Поддержка декораторов](#поддержка-декораторов)
|
17
|
+
- [Базовый пример](#базовый-пример)
|
18
|
+
- [Валидация](#валидация)
|
19
|
+
- [Декораторы](#декораторы)
|
20
|
+
- [Отладка](#отладка)
|
21
|
+
- [Тесты](#тесты)
|
22
|
+
|
13
23
|
## Установка
|
14
24
|
|
15
25
|
```bash
|
@@ -28,7 +38,7 @@ npm install @e22m4u/ts-rest-router
|
|
28
38
|
}
|
29
39
|
```
|
30
40
|
|
31
|
-
##
|
41
|
+
## Базовый пример
|
32
42
|
|
33
43
|
Создание контроллера и методов.
|
34
44
|
|
@@ -39,24 +49,25 @@ import {postAction} from '@e22m4u/ts-rest-router';
|
|
39
49
|
import {requestField} from '@e22m4u/ts-rest-router';
|
40
50
|
import {restController} from '@e22m4u/ts-rest-router';
|
41
51
|
|
42
|
-
|
43
|
-
|
44
|
-
|
52
|
+
// объявление контроллера
|
53
|
+
// и базового пути /users
|
54
|
+
@restController('users')
|
55
|
+
class UserController {
|
56
|
+
// объявление метода POST /users/login
|
57
|
+
// (использует базовый путь контроллера)
|
58
|
+
@postAction('login')
|
45
59
|
async login(
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
username: string,
|
51
|
-
@requestField('password', { // поле "password" в теле запроса
|
52
|
-
type: DataType.STRING, // тип параметра допускает только строки
|
53
|
-
required: true, // параметр является обязательным
|
54
|
-
})
|
55
|
-
password: string,
|
60
|
+
// инъекция значений указанных полей
|
61
|
+
// извлеизвлекаемых из тела запроса
|
62
|
+
@requestField('username') username?: string,
|
63
|
+
@requestField('password') password?: string,
|
56
64
|
) {
|
57
|
-
|
58
|
-
|
59
|
-
|
65
|
+
// так как метод возвращает объект,
|
66
|
+
// результат будет представлен как
|
67
|
+
// "Content-Type: application/json"
|
68
|
+
return {
|
69
|
+
id: 1,
|
70
|
+
firstName: 'John',
|
60
71
|
lastName: 'Doe',
|
61
72
|
};
|
62
73
|
}
|
@@ -84,6 +95,90 @@ server.listen('8080', '0.0.0.0', () => {
|
|
84
95
|
});
|
85
96
|
```
|
86
97
|
|
98
|
+
## Валидация
|
99
|
+
|
100
|
+
Указанные ниже декораторы используются для инъекции соответствующих параметров
|
101
|
+
запроса в качестве аргументов метода контроллера. Каждый из указанных декораторов
|
102
|
+
имеет параметр `schemaOrType`, в котором определяется тип ожидаемого значения
|
103
|
+
или схема для проверки данных.
|
104
|
+
|
105
|
+
- `@requestParam(name: string, schemaOrType?: DataSchema | DataType)`
|
106
|
+
*- извлечение URL параметра по названию;*
|
107
|
+
- `@requestQuery(name: string, schemaOrType?: DataSchema | DataType)`
|
108
|
+
*- извлечение query параметра по названию;*
|
109
|
+
- `@requestBody(schemaOrType?: DataSchema | DataType)`
|
110
|
+
*- извлечение тела запроса;*
|
111
|
+
- `@requestField(name: string, schemaOrType?: DataSchema | DataType)`
|
112
|
+
*- извлечение свойства из тела запроса;*
|
113
|
+
- `@requestHeader(name: string, schemaOrType?: DataSchema | DataType)`
|
114
|
+
*- извлечение заголовка запроса по названию;*
|
115
|
+
- `@requestCookie(name: string, schemaOrType?: DataSchema | DataType)`
|
116
|
+
*- извлечение cookie запроса по названию;*
|
117
|
+
|
118
|
+
Проверка входящих данных выполняется встроенным модулем
|
119
|
+
[@e22m4u/ts-data-schema](https://www.npmjs.com/package/@e22m4u/ts-data-schema)
|
120
|
+
(не требует установки). Ниже приводятся константы для определения допустимых
|
121
|
+
типов извлекаемого значения.
|
122
|
+
|
123
|
+
- `DataType.ANY` - принимает любой тип
|
124
|
+
- `DataType.STRING` - строковые значения
|
125
|
+
- `DataType.NUMBER` - числовые значения
|
126
|
+
- `DataType.BOOLEAN` - логические значения
|
127
|
+
- `DataType.ARRAY` - массивы
|
128
|
+
- `DataType.OBJECT` - объекты (не экземпляры)
|
129
|
+
|
130
|
+
Для определения дополнительных условий, используется объект `DataSchema`,
|
131
|
+
с помощью которого можно определить структуру ожидаемого объекта, допустимые
|
132
|
+
элементы массива, функции-валидаторы и другие ограничения входящих данных.
|
133
|
+
|
134
|
+
```ts
|
135
|
+
type DataSchema = {
|
136
|
+
type: DataType;
|
137
|
+
items?: DataSchema;
|
138
|
+
properties?: {[key: string]: DataSchema};
|
139
|
+
required?: boolean;
|
140
|
+
validate?: CallableValidator | CallableValidator[];
|
141
|
+
default?: unknown;
|
142
|
+
}
|
143
|
+
```
|
144
|
+
|
145
|
+
Пример проверки передаваемого объекта методом POST:
|
146
|
+
|
147
|
+
```ts
|
148
|
+
import {DataType} from '@e22m4u/ts-rest-router';
|
149
|
+
import {getAction} from '@e22m4u/ts-rest-router';
|
150
|
+
import {postAction} from '@e22m4u/ts-rest-router';
|
151
|
+
import {requestField} from '@e22m4u/ts-rest-router';
|
152
|
+
import {restController} from '@e22m4u/ts-rest-router';
|
153
|
+
|
154
|
+
@restController('users')
|
155
|
+
class UserController {
|
156
|
+
@postAction() // POST /users
|
157
|
+
async create(
|
158
|
+
@requestBody({ // декоратор тела запроса
|
159
|
+
type: DataType.OBJECT, // в теле запроса ожидается объект
|
160
|
+
properties: {
|
161
|
+
name: { // схема свойства "name"
|
162
|
+
type: DataType.STRING, // свойство должно содержать строку
|
163
|
+
required: true, // свойство не может содержать undefined или null
|
164
|
+
validate: v => v.length > 2, // проверка длины строки
|
165
|
+
},
|
166
|
+
age: { // схема свойства "age"
|
167
|
+
type: DataType.NUMBER, // свойство должно являться числом
|
168
|
+
}
|
169
|
+
},
|
170
|
+
})
|
171
|
+
body: {name: string, age?: number},
|
172
|
+
) {
|
173
|
+
return {
|
174
|
+
id: 1,
|
175
|
+
name: body.name,
|
176
|
+
age: body.age,
|
177
|
+
};
|
178
|
+
}
|
179
|
+
}
|
180
|
+
```
|
181
|
+
|
87
182
|
## Декораторы
|
88
183
|
|
89
184
|
Контроллер и методы:
|
@@ -125,16 +220,16 @@ server.listen('8080', '0.0.0.0', () => {
|
|
125
220
|
```ts
|
126
221
|
@restController()
|
127
222
|
class UserController {
|
128
|
-
//
|
223
|
+
// ...
|
129
224
|
}
|
130
225
|
```
|
131
226
|
|
132
|
-
Определение
|
227
|
+
Определение базового пути.
|
133
228
|
|
134
229
|
```ts
|
135
|
-
@restController('
|
230
|
+
@restController('users')
|
136
231
|
class UserController {
|
137
|
-
//
|
232
|
+
// ...
|
138
233
|
}
|
139
234
|
```
|
140
235
|
|
@@ -142,12 +237,12 @@ class UserController {
|
|
142
237
|
|
143
238
|
```ts
|
144
239
|
@restController({
|
145
|
-
path: '
|
240
|
+
path: 'api', // базовый путь
|
146
241
|
before: [authMiddleware], // middleware до обработки запроса
|
147
242
|
after: [loggerMiddleware], // middleware после обработки запроса
|
148
243
|
})
|
149
244
|
class UserController {
|
150
|
-
//
|
245
|
+
// ...
|
151
246
|
}
|
152
247
|
```
|
153
248
|
|
@@ -156,9 +251,9 @@ class UserController {
|
|
156
251
|
Определение метода GET.
|
157
252
|
|
158
253
|
```ts
|
159
|
-
@restController('
|
160
|
-
class UserController {
|
161
|
-
@getAction('
|
254
|
+
@restController('users')
|
255
|
+
class UserController {
|
256
|
+
@getAction('whoAmI') // маршрут GET /users/whoAmI
|
162
257
|
async whoAmI() {
|
163
258
|
return { // если метод возвращает объект,
|
164
259
|
name: 'John', // то результат будет представлен
|
@@ -171,9 +266,9 @@ class UserController { // класс контроллера
|
|
171
266
|
Дополнительные параметры декоратора.
|
172
267
|
|
173
268
|
```ts
|
174
|
-
@restController('
|
175
|
-
class UserController {
|
176
|
-
@getAction('
|
269
|
+
@restController('users')
|
270
|
+
class UserController {
|
271
|
+
@getAction('whoAmI', { // маршрут GET /users/whoAmI
|
177
272
|
before: [authMiddleware], // middleware до обработки запроса
|
178
273
|
after: [loggerMiddleware], // middleware после обработки запроса
|
179
274
|
})
|
@@ -193,12 +288,12 @@ class UserController { // класс контроллера
|
|
193
288
|
```ts
|
194
289
|
import {RequestContext} from '@e22m4u/js-trie-router';
|
195
290
|
|
196
|
-
@restController('
|
197
|
-
class UserController {
|
198
|
-
@getAction('
|
291
|
+
@restController('users')
|
292
|
+
class UserController {
|
293
|
+
@getAction(':id')
|
199
294
|
findById(
|
200
|
-
@requestContext() //
|
201
|
-
ctx: RequestContext, // в качестве
|
295
|
+
@requestContext() // инъекция контекста запроса
|
296
|
+
ctx: RequestContext, // в качестве аргумента
|
202
297
|
) {
|
203
298
|
console.log(ctx.req); // IncomingMessage
|
204
299
|
console.log(ctx.res); // ServerResponse
|
package/dist/cjs/index.cjs
CHANGED
@@ -548,6 +548,7 @@ function httpResponse() {
|
|
548
548
|
__name(httpResponse, "httpResponse");
|
549
549
|
|
550
550
|
// dist/esm/controller-registry.js
|
551
|
+
var import_ts_data_schema4 = require("@e22m4u/ts-data-schema");
|
551
552
|
var _ControllerRegistry = class _ControllerRegistry extends DebuggableService {
|
552
553
|
/**
|
553
554
|
* Controllers.
|
@@ -828,6 +829,7 @@ var _ControllerRegistry = class _ControllerRegistry extends DebuggableService {
|
|
828
829
|
const requestContextMetadataMap = RequestContextReflector.getMetadata(controllerCtor, actionName);
|
829
830
|
const requestDataMetadataMap = RequestDataReflector.getMetadata(controllerCtor, actionName);
|
830
831
|
const argsNumber = controllerCtor.prototype[actionName].length;
|
832
|
+
const defaultsApplier = this.getService(import_ts_data_schema4.DefaultValuesApplier);
|
831
833
|
const dataTypeCaster = this.getService(import_ts_data_schema3.DataTypeCaster);
|
832
834
|
const dataValidator = this.getService(import_ts_data_schema2.DataValidator);
|
833
835
|
return (requestContext2) => {
|
@@ -872,13 +874,15 @@ var _ControllerRegistry = class _ControllerRegistry extends DebuggableService {
|
|
872
874
|
}
|
873
875
|
debug("Request data source is %v.", requestDataMd.source);
|
874
876
|
if (requestDataMd.schema) {
|
877
|
+
data = defaultsApplier.applyDefaultValuesIfNeeded(data, requestDataMd.schema, requestDataMd.source);
|
878
|
+
debug("Default values applied.");
|
875
879
|
data = dataTypeCaster.cast(data, requestDataMd.schema, {
|
876
880
|
noTypeCastError: true,
|
877
881
|
sourcePath: requestDataMd.source
|
878
882
|
});
|
879
|
-
debug("Data type casting
|
883
|
+
debug("Data type casting applied.");
|
880
884
|
dataValidator.validate(data, requestDataMd.schema, requestDataMd.source);
|
881
|
-
debug("Data validation
|
885
|
+
debug("Data validation passed.");
|
882
886
|
}
|
883
887
|
if (requestDataMd.property == null) {
|
884
888
|
debug("Request data property is not specified.");
|
@@ -8,6 +8,7 @@ import { DebuggableService } from './debuggable-service.js';
|
|
8
8
|
import { RestActionReflector } from './decorators/index.js';
|
9
9
|
import { RequestDataReflector } from './decorators/index.js';
|
10
10
|
import { AfterActionReflector } from './decorators/index.js';
|
11
|
+
import { DefaultValuesApplier } from '@e22m4u/ts-data-schema';
|
11
12
|
import { BeforeActionReflector } from './decorators/index.js';
|
12
13
|
import { RestControllerReflector } from './decorators/index.js';
|
13
14
|
import { RequestContextReflector } from './decorators/index.js';
|
@@ -320,6 +321,7 @@ export class ControllerRegistry extends DebuggableService {
|
|
320
321
|
const requestContextMetadataMap = RequestContextReflector.getMetadata(controllerCtor, actionName);
|
321
322
|
const requestDataMetadataMap = RequestDataReflector.getMetadata(controllerCtor, actionName);
|
322
323
|
const argsNumber = controllerCtor.prototype[actionName].length;
|
324
|
+
const defaultsApplier = this.getService(DefaultValuesApplier);
|
323
325
|
const dataTypeCaster = this.getService(DataTypeCaster);
|
324
326
|
const dataValidator = this.getService(DataValidator);
|
325
327
|
return (requestContext) => {
|
@@ -378,16 +380,19 @@ export class ControllerRegistry extends DebuggableService {
|
|
378
380
|
break;
|
379
381
|
}
|
380
382
|
debug('Request data source is %v.', requestDataMd.source);
|
381
|
-
// при наличии схемы данных
|
382
|
-
//
|
383
|
+
// при наличии схемы данных применяются значения
|
384
|
+
// по умолчанию, выполняется конвертация входящего
|
385
|
+
// значения и валидация согласно схеме
|
383
386
|
if (requestDataMd.schema) {
|
387
|
+
data = defaultsApplier.applyDefaultValuesIfNeeded(data, requestDataMd.schema, requestDataMd.source);
|
388
|
+
debug('Default values applied.');
|
384
389
|
data = dataTypeCaster.cast(data, requestDataMd.schema, {
|
385
390
|
noTypeCastError: true,
|
386
391
|
sourcePath: requestDataMd.source,
|
387
392
|
});
|
388
|
-
debug('Data type casting
|
393
|
+
debug('Data type casting applied.');
|
389
394
|
dataValidator.validate(data, requestDataMd.schema, requestDataMd.source);
|
390
|
-
debug('Data validation
|
395
|
+
debug('Data validation passed.');
|
391
396
|
}
|
392
397
|
// если свойство данных не определено,
|
393
398
|
// то используем весь объекта данных
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import { expect } from 'chai';
|
2
|
-
import { describe } from 'mocha';
|
3
2
|
import { Reflector } from '@e22m4u/ts-reflector';
|
4
3
|
import { RequestContextReflector } from './request-context-reflector.js';
|
5
4
|
import { REQUEST_CONTEXT_METADATA_KEY } from './request-context-metadata.js';
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import { expect } from 'chai';
|
2
|
-
import { describe } from 'mocha';
|
3
2
|
import { Reflector } from '@e22m4u/ts-reflector';
|
4
3
|
import { RestControllerReflector } from './rest-controller-reflector.js';
|
5
4
|
import { REST_CONTROLLER_METADATA_KEY } from './rest-controller-metadata.js';
|
package/eslint.config.js
CHANGED
@@ -31,7 +31,7 @@ export default [
|
|
31
31
|
rules: {
|
32
32
|
...eslintJs.configs.recommended.rules,
|
33
33
|
...eslintPrettierConfig.rules,
|
34
|
-
...eslintMochaPlugin.configs.
|
34
|
+
...eslintMochaPlugin.configs.recommended.rules,
|
35
35
|
...eslintChaiExpectPlugin.configs['recommended-flat'].rules,
|
36
36
|
...eslintTypescript.configs.recommended.reduce(
|
37
37
|
(rules, config) => ({...rules, ...config.rules}),
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@e22m4u/ts-rest-router",
|
3
|
-
"version": "0.2.
|
3
|
+
"version": "0.2.1",
|
4
4
|
"description": "REST маршрутизатор на основе контроллеров для TypeScript",
|
5
5
|
"author": "e22m4u <e22m4u@yandex.ru>",
|
6
6
|
"license": "MIT",
|
@@ -41,36 +41,36 @@
|
|
41
41
|
"prepare": "husky"
|
42
42
|
},
|
43
43
|
"dependencies": {
|
44
|
-
"@e22m4u/js-debug": "~0.0
|
44
|
+
"@e22m4u/js-debug": "~0.1.0",
|
45
45
|
"@e22m4u/js-format": "~0.1.0",
|
46
46
|
"@e22m4u/js-service": "~0.2.0",
|
47
47
|
"@e22m4u/js-trie-router": "~0.0.1",
|
48
|
-
"@e22m4u/ts-data-schema": "~0.
|
48
|
+
"@e22m4u/ts-data-schema": "~0.2.1",
|
49
49
|
"@e22m4u/ts-reflector": "~0.1.0",
|
50
50
|
"http-errors": "~2.0.0"
|
51
51
|
},
|
52
52
|
"devDependencies": {
|
53
|
-
"@commitlint/cli": "~19.8.
|
54
|
-
"@commitlint/config-conventional": "~19.8.
|
55
|
-
"@eslint/js": "~9.
|
56
|
-
"@types/chai": "~5.2.
|
53
|
+
"@commitlint/cli": "~19.8.1",
|
54
|
+
"@commitlint/config-conventional": "~19.8.1",
|
55
|
+
"@eslint/js": "~9.26.0",
|
56
|
+
"@types/chai": "~5.2.2",
|
57
57
|
"@types/debug": "~4.1.12",
|
58
58
|
"@types/http-errors": "~2.0.4",
|
59
59
|
"@types/mocha": "~10.0.10",
|
60
|
-
"@types/node": "~22.
|
60
|
+
"@types/node": "~22.15.17",
|
61
61
|
"c8": "~10.1.3",
|
62
62
|
"chai": "~5.2.0",
|
63
|
-
"esbuild": "~0.25.
|
64
|
-
"eslint": "~9.
|
65
|
-
"eslint-config-prettier": "~10.1.
|
63
|
+
"esbuild": "~0.25.4",
|
64
|
+
"eslint": "~9.26.0",
|
65
|
+
"eslint-config-prettier": "~10.1.5",
|
66
66
|
"eslint-plugin-chai-expect": "~3.1.0",
|
67
|
-
"eslint-plugin-mocha": "~
|
67
|
+
"eslint-plugin-mocha": "~11.0.0",
|
68
68
|
"husky": "~9.1.7",
|
69
|
-
"mocha": "~11.
|
69
|
+
"mocha": "~11.2.2",
|
70
70
|
"prettier": "~3.5.3",
|
71
71
|
"rimraf": "~6.0.1",
|
72
|
-
"tsx": "~4.19.
|
72
|
+
"tsx": "~4.19.4",
|
73
73
|
"typescript": "~5.8.3",
|
74
|
-
"typescript-eslint": "~8.
|
74
|
+
"typescript-eslint": "~8.32.0"
|
75
75
|
}
|
76
76
|
}
|
@@ -1,32 +1,38 @@
|
|
1
1
|
/* eslint mocha/no-sibling-hooks: 0 */
|
2
|
+
import {
|
3
|
+
createRequestMock,
|
4
|
+
createResponseMock,
|
5
|
+
HookName,
|
6
|
+
HttpMethod,
|
7
|
+
ParsedCookie,
|
8
|
+
ParsedHeaders,
|
9
|
+
ParsedParams,
|
10
|
+
ParsedQuery,
|
11
|
+
RequestContext,
|
12
|
+
RequestParser,
|
13
|
+
RouteRegistry,
|
14
|
+
TrieRouter,
|
15
|
+
} from '@e22m4u/js-trie-router';
|
16
|
+
import {
|
17
|
+
afterAction,
|
18
|
+
beforeAction,
|
19
|
+
getAction,
|
20
|
+
postAction,
|
21
|
+
requestBody,
|
22
|
+
requestCookie,
|
23
|
+
requestCookies,
|
24
|
+
requestField,
|
25
|
+
requestHeader,
|
26
|
+
requestHeaders,
|
27
|
+
requestParam,
|
28
|
+
requestParams,
|
29
|
+
requestQueries,
|
30
|
+
requestQuery,
|
31
|
+
restController,
|
32
|
+
} from './decorators/index.js';
|
33
|
+
|
2
34
|
import {expect} from 'chai';
|
3
|
-
import {
|
4
|
-
import {getAction} from './decorators/index.js';
|
5
|
-
import {postAction} from './decorators/index.js';
|
6
|
-
import {TrieRouter} from '@e22m4u/js-trie-router';
|
7
|
-
import {HttpMethod} from '@e22m4u/js-trie-router';
|
8
|
-
import {requestBody} from './decorators/index.js';
|
9
|
-
import {afterAction} from './decorators/index.js';
|
10
|
-
import {requestQuery} from './decorators/index.js';
|
11
|
-
import {requestParam} from './decorators/index.js';
|
12
|
-
import {requestField} from './decorators/index.js';
|
13
|
-
import {beforeAction} from './decorators/index.js';
|
14
|
-
import {ParsedQuery} from '@e22m4u/js-trie-router';
|
15
|
-
import {ParsedCookie} from '@e22m4u/js-trie-router';
|
16
|
-
import {ParsedParams} from '@e22m4u/js-trie-router';
|
17
|
-
import {requestCookie} from './decorators/index.js';
|
18
|
-
import {requestParams} from './decorators/index.js';
|
19
|
-
import {requestHeader} from './decorators/index.js';
|
20
|
-
import {requestCookies} from './decorators/index.js';
|
21
|
-
import {requestQueries} from './decorators/index.js';
|
22
|
-
import {requestHeaders} from './decorators/index.js';
|
23
|
-
import {restController} from './decorators/index.js';
|
24
|
-
import {ParsedHeaders} from '@e22m4u/js-trie-router';
|
25
|
-
import {RouteRegistry} from '@e22m4u/js-trie-router';
|
26
|
-
import {RequestParser} from '@e22m4u/js-trie-router';
|
27
|
-
import {RequestContext} from '@e22m4u/js-trie-router';
|
28
|
-
import {createRequestMock} from '@e22m4u/js-trie-router';
|
29
|
-
import {createResponseMock} from '@e22m4u/js-trie-router';
|
35
|
+
import {DataType} from '@e22m4u/ts-data-schema';
|
30
36
|
import {ControllerRegistry} from './controller-registry.js';
|
31
37
|
|
32
38
|
const PRE_HANDLER_1 = () => undefined;
|
@@ -911,4 +917,56 @@ describe('ControllerRegistry', function () {
|
|
911
917
|
expect(S.hasController(MyController)).to.be.true;
|
912
918
|
});
|
913
919
|
});
|
920
|
+
|
921
|
+
describe('createRouteHandler', function () {
|
922
|
+
it('uses default values from schema as copy', async function () {
|
923
|
+
let invoked = false;
|
924
|
+
const defaultValue = {foo: 'bar'};
|
925
|
+
class MyController {
|
926
|
+
myAction(
|
927
|
+
@requestBody({
|
928
|
+
type: DataType.OBJECT,
|
929
|
+
default: defaultValue,
|
930
|
+
})
|
931
|
+
body: object,
|
932
|
+
) {
|
933
|
+
expect(body).to.be.not.eq(defaultValue);
|
934
|
+
expect(body).to.be.eql(defaultValue);
|
935
|
+
invoked = true;
|
936
|
+
}
|
937
|
+
}
|
938
|
+
const S = new ControllerRegistry();
|
939
|
+
const req = createRequestMock();
|
940
|
+
const res = createResponseMock();
|
941
|
+
const ctx = new RequestContext(S.container, req, res);
|
942
|
+
const handler = S['createRouteHandler'](MyController, 'myAction');
|
943
|
+
await handler(ctx);
|
944
|
+
expect(invoked).to.be.true;
|
945
|
+
});
|
946
|
+
|
947
|
+
it('uses default values from factory function that defined in schema as copy', async function () {
|
948
|
+
let invoked = false;
|
949
|
+
const defaultValue = {foo: 'bar'};
|
950
|
+
class MyController {
|
951
|
+
myAction(
|
952
|
+
@requestBody({
|
953
|
+
type: DataType.OBJECT,
|
954
|
+
default: () => defaultValue,
|
955
|
+
})
|
956
|
+
body: object,
|
957
|
+
) {
|
958
|
+
expect(body).to.be.not.eq(defaultValue);
|
959
|
+
expect(body).to.be.eql(defaultValue);
|
960
|
+
invoked = true;
|
961
|
+
}
|
962
|
+
}
|
963
|
+
const S = new ControllerRegistry();
|
964
|
+
const req = createRequestMock();
|
965
|
+
const res = createResponseMock();
|
966
|
+
const ctx = new RequestContext(S.container, req, res);
|
967
|
+
const handler = S['createRouteHandler'](MyController, 'myAction');
|
968
|
+
await handler(ctx);
|
969
|
+
expect(invoked).to.be.true;
|
970
|
+
});
|
971
|
+
});
|
914
972
|
});
|
@@ -14,6 +14,7 @@ import {DebuggableService} from './debuggable-service.js';
|
|
14
14
|
import {RestActionReflector} from './decorators/index.js';
|
15
15
|
import {RequestDataReflector} from './decorators/index.js';
|
16
16
|
import {AfterActionReflector} from './decorators/index.js';
|
17
|
+
import {DefaultValuesApplier} from '@e22m4u/ts-data-schema';
|
17
18
|
import {BeforeActionReflector} from './decorators/index.js';
|
18
19
|
import {RestControllerReflector} from './decorators/index.js';
|
19
20
|
import {RequestContextReflector} from './decorators/index.js';
|
@@ -381,6 +382,7 @@ export class ControllerRegistry extends DebuggableService {
|
|
381
382
|
actionName,
|
382
383
|
);
|
383
384
|
const argsNumber = controllerCtor.prototype[actionName].length;
|
385
|
+
const defaultsApplier = this.getService(DefaultValuesApplier);
|
384
386
|
const dataTypeCaster = this.getService(DataTypeCaster);
|
385
387
|
const dataValidator = this.getService(DataValidator);
|
386
388
|
return (requestContext: RequestContext) => {
|
@@ -445,20 +447,27 @@ export class ControllerRegistry extends DebuggableService {
|
|
445
447
|
break;
|
446
448
|
}
|
447
449
|
debug('Request data source is %v.', requestDataMd.source);
|
448
|
-
// при наличии схемы данных
|
449
|
-
//
|
450
|
+
// при наличии схемы данных применяются значения
|
451
|
+
// по умолчанию, выполняется конвертация входящего
|
452
|
+
// значения и валидация согласно схеме
|
450
453
|
if (requestDataMd.schema) {
|
454
|
+
data = defaultsApplier.applyDefaultValuesIfNeeded(
|
455
|
+
data,
|
456
|
+
requestDataMd.schema,
|
457
|
+
requestDataMd.source,
|
458
|
+
);
|
459
|
+
debug('Default values applied.');
|
451
460
|
data = dataTypeCaster.cast(data, requestDataMd.schema, {
|
452
461
|
noTypeCastError: true,
|
453
462
|
sourcePath: requestDataMd.source,
|
454
463
|
});
|
455
|
-
debug('Data type casting
|
464
|
+
debug('Data type casting applied.');
|
456
465
|
dataValidator.validate(
|
457
466
|
data,
|
458
467
|
requestDataMd.schema,
|
459
468
|
requestDataMd.source,
|
460
469
|
);
|
461
|
-
debug('Data validation
|
470
|
+
debug('Data validation passed.');
|
462
471
|
}
|
463
472
|
// если свойство данных не определено,
|
464
473
|
// то используем весь объекта данных
|