@e22m4u/ts-rest-router 0.5.5 → 0.6.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/dist/cjs/index.cjs +55 -80
- package/dist/esm/controller-registry.js +11 -3
- package/dist/esm/data-schema-types.d.ts +15 -0
- package/dist/esm/debuggable-service.d.ts +3 -17
- package/dist/esm/debuggable-service.js +7 -24
- package/dist/esm/decorators/before-action/before-action-reflector.spec.js +15 -15
- package/dist/esm/decorators/request-data/request-data-decorator.d.ts +11 -12
- package/dist/esm/decorators/request-data/request-data-decorator.js +20 -12
- package/dist/esm/decorators/request-data/request-data-decorator.spec.js +183 -1
- package/dist/esm/decorators/request-data/request-data-metadata.d.ts +2 -2
- package/dist/esm/decorators/response-body/response-body-decorator.d.ts +2 -3
- package/dist/esm/decorators/response-body/response-body-decorator.js +7 -7
- package/dist/esm/decorators/response-body/response-body-decorator.spec.js +21 -1
- package/dist/esm/decorators/response-body/response-body-metadata.d.ts +2 -2
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/utils/index.d.ts +0 -2
- package/dist/esm/utils/index.js +0 -2
- package/package.json +10 -10
- package/src/controller-registry.spec.ts +174 -1
- package/src/controller-registry.ts +11 -7
- package/src/data-schema-types.ts +18 -0
- package/src/debuggable-service.spec.ts +0 -8
- package/src/debuggable-service.ts +7 -28
- package/src/decorators/before-action/before-action-reflector.spec.ts +15 -15
- package/src/decorators/request-data/request-data-decorator.spec.ts +174 -1
- package/src/decorators/request-data/request-data-decorator.ts +22 -13
- package/src/decorators/request-data/request-data-metadata.ts +2 -2
- package/src/decorators/response-body/response-body-decorator.spec.ts +17 -1
- package/src/decorators/response-body/response-body-decorator.ts +9 -11
- package/src/decorators/response-body/response-body-metadata.ts +2 -2
- package/src/index.ts +3 -1
- package/src/utils/index.ts +0 -2
- package/dist/esm/utils/create-error.d.ts +0 -10
- package/dist/esm/utils/create-error.js +0 -13
- package/dist/esm/utils/create-error.spec.js +0 -8
- package/dist/esm/utils/to-camel-case.d.ts +0 -6
- package/dist/esm/utils/to-camel-case.js +0 -11
- package/dist/esm/utils/to-camel-case.spec.d.ts +0 -1
- package/dist/esm/utils/to-camel-case.spec.js +0 -10
- package/src/utils/create-error.spec.ts +0 -9
- package/src/utils/create-error.ts +0 -19
- package/src/utils/to-camel-case.spec.ts +0 -11
- package/src/utils/to-camel-case.ts +0 -11
- /package/dist/esm/{utils/create-error.spec.d.ts → data-schema-types.js} +0 -0
|
@@ -164,7 +164,7 @@ describe('requestData', function () {
|
|
|
164
164
|
schema: { type: DataType.STRING },
|
|
165
165
|
});
|
|
166
166
|
});
|
|
167
|
-
it('
|
|
167
|
+
it('sets a given DataSchema to the target metadata', function () {
|
|
168
168
|
const schema = { type: DataType.STRING, required: true };
|
|
169
169
|
class Target {
|
|
170
170
|
myMethod(prop) { }
|
|
@@ -181,6 +181,23 @@ describe('requestData', function () {
|
|
|
181
181
|
schema,
|
|
182
182
|
});
|
|
183
183
|
});
|
|
184
|
+
it('sets a given DataSchemaFactory to the target metadata', function () {
|
|
185
|
+
const factory = () => ({ type: DataType.STRING, required: true });
|
|
186
|
+
class Target {
|
|
187
|
+
myMethod(prop) { }
|
|
188
|
+
}
|
|
189
|
+
__decorate([
|
|
190
|
+
__param(0, requestBody(factory)),
|
|
191
|
+
__metadata("design:type", Function),
|
|
192
|
+
__metadata("design:paramtypes", [Object]),
|
|
193
|
+
__metadata("design:returntype", void 0)
|
|
194
|
+
], Target.prototype, "myMethod", null);
|
|
195
|
+
const res = RequestDataReflector.getMetadata(Target, 'myMethod');
|
|
196
|
+
expect(res.get(0)).to.be.eql({
|
|
197
|
+
source: RequestDataSource.BODY,
|
|
198
|
+
schema: factory,
|
|
199
|
+
});
|
|
200
|
+
});
|
|
184
201
|
});
|
|
185
202
|
});
|
|
186
203
|
describe('request data piece by a given property key', function () {
|
|
@@ -263,6 +280,39 @@ describe('requestData', function () {
|
|
|
263
280
|
property: propertyKey,
|
|
264
281
|
});
|
|
265
282
|
});
|
|
283
|
+
it('sets a given DataSchemaFactory to the target metadata', function () {
|
|
284
|
+
const container = {};
|
|
285
|
+
const factory = sc => {
|
|
286
|
+
expect(sc).to.be.eq(container);
|
|
287
|
+
return { type: DataType.STRING, required: true };
|
|
288
|
+
};
|
|
289
|
+
const propertyKey = 'myPropertyKey';
|
|
290
|
+
class Target {
|
|
291
|
+
myMethod(prop) { }
|
|
292
|
+
}
|
|
293
|
+
__decorate([
|
|
294
|
+
__param(0, requestParam(propertyKey, factory)),
|
|
295
|
+
__metadata("design:type", Function),
|
|
296
|
+
__metadata("design:paramtypes", [Object]),
|
|
297
|
+
__metadata("design:returntype", void 0)
|
|
298
|
+
], Target.prototype, "myMethod", null);
|
|
299
|
+
const mdMap = RequestDataReflector.getMetadata(Target, 'myMethod');
|
|
300
|
+
const md = mdMap.get(0);
|
|
301
|
+
expect(md.source).to.be.eq(RequestDataSource.PARAMS);
|
|
302
|
+
expect(md.schema).to.be.a('function');
|
|
303
|
+
expect(md.property).to.be.eq(propertyKey);
|
|
304
|
+
const res1 = md.schema;
|
|
305
|
+
const res2 = res1(container);
|
|
306
|
+
expect(res2).to.be.eql({
|
|
307
|
+
type: DataType.OBJECT,
|
|
308
|
+
properties: {
|
|
309
|
+
[propertyKey]: {
|
|
310
|
+
type: DataType.STRING,
|
|
311
|
+
required: true,
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
});
|
|
266
316
|
});
|
|
267
317
|
describe('query', function () {
|
|
268
318
|
it('sets a given "propertyKey" to the target metadata', function () {
|
|
@@ -343,6 +393,39 @@ describe('requestData', function () {
|
|
|
343
393
|
property: propertyKey,
|
|
344
394
|
});
|
|
345
395
|
});
|
|
396
|
+
it('sets a given DataSchemaFactory to the target metadata', function () {
|
|
397
|
+
const container = {};
|
|
398
|
+
const factory = sc => {
|
|
399
|
+
expect(sc).to.be.eq(container);
|
|
400
|
+
return { type: DataType.STRING, required: true };
|
|
401
|
+
};
|
|
402
|
+
const propertyKey = 'myPropertyKey';
|
|
403
|
+
class Target {
|
|
404
|
+
myMethod(prop) { }
|
|
405
|
+
}
|
|
406
|
+
__decorate([
|
|
407
|
+
__param(0, requestQuery(propertyKey, factory)),
|
|
408
|
+
__metadata("design:type", Function),
|
|
409
|
+
__metadata("design:paramtypes", [Object]),
|
|
410
|
+
__metadata("design:returntype", void 0)
|
|
411
|
+
], Target.prototype, "myMethod", null);
|
|
412
|
+
const mdMap = RequestDataReflector.getMetadata(Target, 'myMethod');
|
|
413
|
+
const md = mdMap.get(0);
|
|
414
|
+
expect(md.source).to.be.eq(RequestDataSource.QUERY);
|
|
415
|
+
expect(md.schema).to.be.a('function');
|
|
416
|
+
expect(md.property).to.be.eq(propertyKey);
|
|
417
|
+
const res1 = md.schema;
|
|
418
|
+
const res2 = res1(container);
|
|
419
|
+
expect(res2).to.be.eql({
|
|
420
|
+
type: DataType.OBJECT,
|
|
421
|
+
properties: {
|
|
422
|
+
[propertyKey]: {
|
|
423
|
+
type: DataType.STRING,
|
|
424
|
+
required: true,
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
});
|
|
346
429
|
});
|
|
347
430
|
describe('header', function () {
|
|
348
431
|
it('sets a given "propertyKey" to the target metadata', function () {
|
|
@@ -423,6 +506,39 @@ describe('requestData', function () {
|
|
|
423
506
|
property: propertyKey,
|
|
424
507
|
});
|
|
425
508
|
});
|
|
509
|
+
it('sets a given DataSchemaFactory to the target metadata', function () {
|
|
510
|
+
const container = {};
|
|
511
|
+
const factory = sc => {
|
|
512
|
+
expect(sc).to.be.eq(container);
|
|
513
|
+
return { type: DataType.STRING, required: true };
|
|
514
|
+
};
|
|
515
|
+
const propertyKey = 'myPropertyKey';
|
|
516
|
+
class Target {
|
|
517
|
+
myMethod(prop) { }
|
|
518
|
+
}
|
|
519
|
+
__decorate([
|
|
520
|
+
__param(0, requestHeader(propertyKey, factory)),
|
|
521
|
+
__metadata("design:type", Function),
|
|
522
|
+
__metadata("design:paramtypes", [Object]),
|
|
523
|
+
__metadata("design:returntype", void 0)
|
|
524
|
+
], Target.prototype, "myMethod", null);
|
|
525
|
+
const mdMap = RequestDataReflector.getMetadata(Target, 'myMethod');
|
|
526
|
+
const md = mdMap.get(0);
|
|
527
|
+
expect(md.source).to.be.eq(RequestDataSource.HEADERS);
|
|
528
|
+
expect(md.schema).to.be.a('function');
|
|
529
|
+
expect(md.property).to.be.eq(propertyKey);
|
|
530
|
+
const res1 = md.schema;
|
|
531
|
+
const res2 = res1(container);
|
|
532
|
+
expect(res2).to.be.eql({
|
|
533
|
+
type: DataType.OBJECT,
|
|
534
|
+
properties: {
|
|
535
|
+
[propertyKey]: {
|
|
536
|
+
type: DataType.STRING,
|
|
537
|
+
required: true,
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
});
|
|
541
|
+
});
|
|
426
542
|
});
|
|
427
543
|
describe('cookie', function () {
|
|
428
544
|
it('sets a given "propertyKey" to the target metadata', function () {
|
|
@@ -503,6 +619,39 @@ describe('requestData', function () {
|
|
|
503
619
|
property: propertyKey,
|
|
504
620
|
});
|
|
505
621
|
});
|
|
622
|
+
it('sets a given DataSchemaFactory to the target metadata', function () {
|
|
623
|
+
const container = {};
|
|
624
|
+
const factory = sc => {
|
|
625
|
+
expect(sc).to.be.eq(container);
|
|
626
|
+
return { type: DataType.STRING, required: true };
|
|
627
|
+
};
|
|
628
|
+
const propertyKey = 'myPropertyKey';
|
|
629
|
+
class Target {
|
|
630
|
+
myMethod(prop) { }
|
|
631
|
+
}
|
|
632
|
+
__decorate([
|
|
633
|
+
__param(0, requestCookie(propertyKey, factory)),
|
|
634
|
+
__metadata("design:type", Function),
|
|
635
|
+
__metadata("design:paramtypes", [Object]),
|
|
636
|
+
__metadata("design:returntype", void 0)
|
|
637
|
+
], Target.prototype, "myMethod", null);
|
|
638
|
+
const mdMap = RequestDataReflector.getMetadata(Target, 'myMethod');
|
|
639
|
+
const md = mdMap.get(0);
|
|
640
|
+
expect(md.source).to.be.eq(RequestDataSource.COOKIE);
|
|
641
|
+
expect(md.schema).to.be.a('function');
|
|
642
|
+
expect(md.property).to.be.eq(propertyKey);
|
|
643
|
+
const res1 = md.schema;
|
|
644
|
+
const res2 = res1(container);
|
|
645
|
+
expect(res2).to.be.eql({
|
|
646
|
+
type: DataType.OBJECT,
|
|
647
|
+
properties: {
|
|
648
|
+
[propertyKey]: {
|
|
649
|
+
type: DataType.STRING,
|
|
650
|
+
required: true,
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
});
|
|
654
|
+
});
|
|
506
655
|
});
|
|
507
656
|
describe('field', function () {
|
|
508
657
|
it('sets a given "propertyKey" to the target metadata', function () {
|
|
@@ -583,6 +732,39 @@ describe('requestData', function () {
|
|
|
583
732
|
property: propertyKey,
|
|
584
733
|
});
|
|
585
734
|
});
|
|
735
|
+
it('sets a given DataSchemaFactory to the target metadata', function () {
|
|
736
|
+
const container = {};
|
|
737
|
+
const factory = sc => {
|
|
738
|
+
expect(sc).to.be.eq(container);
|
|
739
|
+
return { type: DataType.STRING, required: true };
|
|
740
|
+
};
|
|
741
|
+
const propertyKey = 'myPropertyKey';
|
|
742
|
+
class Target {
|
|
743
|
+
myMethod(prop) { }
|
|
744
|
+
}
|
|
745
|
+
__decorate([
|
|
746
|
+
__param(0, requestField(propertyKey, factory)),
|
|
747
|
+
__metadata("design:type", Function),
|
|
748
|
+
__metadata("design:paramtypes", [Object]),
|
|
749
|
+
__metadata("design:returntype", void 0)
|
|
750
|
+
], Target.prototype, "myMethod", null);
|
|
751
|
+
const mdMap = RequestDataReflector.getMetadata(Target, 'myMethod');
|
|
752
|
+
const md = mdMap.get(0);
|
|
753
|
+
expect(md.source).to.be.eq(RequestDataSource.BODY);
|
|
754
|
+
expect(md.schema).to.be.a('function');
|
|
755
|
+
expect(md.property).to.be.eq(propertyKey);
|
|
756
|
+
const res1 = md.schema;
|
|
757
|
+
const res2 = res1(container);
|
|
758
|
+
expect(res2).to.be.eql({
|
|
759
|
+
type: DataType.OBJECT,
|
|
760
|
+
properties: {
|
|
761
|
+
[propertyKey]: {
|
|
762
|
+
type: DataType.STRING,
|
|
763
|
+
required: true,
|
|
764
|
+
},
|
|
765
|
+
},
|
|
766
|
+
});
|
|
767
|
+
});
|
|
586
768
|
});
|
|
587
769
|
});
|
|
588
770
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MetadataKey } from '@e22m4u/ts-reflector';
|
|
2
|
-
import {
|
|
2
|
+
import { DataSchemaOrFactory } from '../../data-schema-types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Request data source.
|
|
5
5
|
*/
|
|
@@ -15,7 +15,7 @@ export declare enum RequestDataSource {
|
|
|
15
15
|
*/
|
|
16
16
|
export type RequestDataMetadata = {
|
|
17
17
|
source: RequestDataSource;
|
|
18
|
-
schema?:
|
|
18
|
+
schema?: DataSchemaOrFactory;
|
|
19
19
|
property?: string;
|
|
20
20
|
};
|
|
21
21
|
/**
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { Prototype } from '../../types.js';
|
|
2
|
-
import {
|
|
3
|
-
import { DataSchema } from '@e22m4u/ts-data-schema';
|
|
2
|
+
import { DataSchemaInput } from '../../data-schema-types.js';
|
|
4
3
|
/**
|
|
5
4
|
* Response body decorator.
|
|
6
5
|
*
|
|
7
6
|
* @param schemaOrType
|
|
8
7
|
*/
|
|
9
|
-
export declare function responseBody<T extends object>(
|
|
8
|
+
export declare function responseBody<T extends object>(schemaInput?: DataSchemaInput): (target: Prototype<T>, propertyKey: string, descriptor: PropertyDescriptor) => void;
|
|
@@ -6,18 +6,18 @@ import { ResponseBodyReflector } from './response-body-reflector.js';
|
|
|
6
6
|
*
|
|
7
7
|
* @param schemaOrType
|
|
8
8
|
*/
|
|
9
|
-
export function responseBody(
|
|
9
|
+
export function responseBody(schemaInput) {
|
|
10
10
|
return function (target, propertyKey, descriptor) {
|
|
11
11
|
const decoratorType = getDecoratorTargetType(target, propertyKey, descriptor);
|
|
12
12
|
if (decoratorType !== DecoratorTargetType.INSTANCE_METHOD)
|
|
13
13
|
throw new Error('@responseBody decorator is only supported on an instance method.');
|
|
14
|
-
let
|
|
15
|
-
if (typeof
|
|
16
|
-
|
|
14
|
+
let schemaOrFactory;
|
|
15
|
+
if (typeof schemaInput === 'function' || typeof schemaInput === 'object') {
|
|
16
|
+
schemaOrFactory = schemaInput;
|
|
17
17
|
}
|
|
18
|
-
else if (typeof
|
|
19
|
-
|
|
18
|
+
else if (typeof schemaInput === 'string') {
|
|
19
|
+
schemaOrFactory = { type: schemaInput };
|
|
20
20
|
}
|
|
21
|
-
ResponseBodyReflector.setMetadata(
|
|
21
|
+
ResponseBodyReflector.setMetadata(schemaOrFactory ? { schema: schemaOrFactory } : {}, target.constructor, propertyKey);
|
|
22
22
|
};
|
|
23
23
|
}
|
|
@@ -38,7 +38,7 @@ describe('responseBody', function () {
|
|
|
38
38
|
const res = ResponseBodyReflector.getMetadata(Target);
|
|
39
39
|
expect(res.get('myMethod')).to.be.eql({ schema: { type: DataType.STRING } });
|
|
40
40
|
});
|
|
41
|
-
it('sets the given
|
|
41
|
+
it('sets the given DataSchema to the target metadata', function () {
|
|
42
42
|
const schema = {
|
|
43
43
|
type: DataType.OBJECT,
|
|
44
44
|
properties: {
|
|
@@ -58,4 +58,24 @@ describe('responseBody', function () {
|
|
|
58
58
|
const res = ResponseBodyReflector.getMetadata(Target);
|
|
59
59
|
expect(res.get('myMethod')).to.be.eql({ schema });
|
|
60
60
|
});
|
|
61
|
+
it('sets the given DataSchemaFactory to the target metadata', function () {
|
|
62
|
+
const factory = () => ({
|
|
63
|
+
type: DataType.OBJECT,
|
|
64
|
+
properties: {
|
|
65
|
+
foo: { type: DataType.STRING },
|
|
66
|
+
bar: { type: DataType.NUMBER },
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
class Target {
|
|
70
|
+
myMethod() { }
|
|
71
|
+
}
|
|
72
|
+
__decorate([
|
|
73
|
+
responseBody(factory),
|
|
74
|
+
__metadata("design:type", Function),
|
|
75
|
+
__metadata("design:paramtypes", []),
|
|
76
|
+
__metadata("design:returntype", void 0)
|
|
77
|
+
], Target.prototype, "myMethod", null);
|
|
78
|
+
const res = ResponseBodyReflector.getMetadata(Target);
|
|
79
|
+
expect(res.get('myMethod')).to.be.eql({ schema: factory });
|
|
80
|
+
});
|
|
61
81
|
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { MetadataKey } from '@e22m4u/ts-reflector';
|
|
2
|
-
import {
|
|
2
|
+
import { DataSchemaOrFactory } from '../../data-schema-types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Response body metadata.
|
|
5
5
|
*/
|
|
6
6
|
export type ResponseBodyMetadata = {
|
|
7
|
-
schema?:
|
|
7
|
+
schema?: DataSchemaOrFactory;
|
|
8
8
|
};
|
|
9
9
|
/**
|
|
10
10
|
* Response body metadata map.
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export * from '
|
|
1
|
+
export * from '@e22m4u/js-trie-router';
|
|
2
2
|
export * from './rest-router.js';
|
|
3
3
|
export * from './errors/index.js';
|
|
4
4
|
export * from './decorators/index.js';
|
|
5
|
+
export * from './data-schema-types.js';
|
|
5
6
|
export * from './controller-registry.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export * from '
|
|
1
|
+
export * from '@e22m4u/js-trie-router';
|
|
2
2
|
export * from './rest-router.js';
|
|
3
3
|
export * from './errors/index.js';
|
|
4
4
|
export * from './decorators/index.js';
|
|
5
|
+
export * from './data-schema-types.js';
|
|
5
6
|
export * from './controller-registry.js';
|
package/dist/esm/utils/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@e22m4u/ts-rest-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Декларативный REST-маршрутизатор на основе контроллеров для TypeScript",
|
|
5
5
|
"author": "Mikhail Evstropov <e22m4u@yandex.ru>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,34 +43,34 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@e22m4u/js-debug": "~0.3.1",
|
|
45
45
|
"@e22m4u/js-format": "~0.2.0",
|
|
46
|
-
"@e22m4u/js-service": "~0.4.
|
|
46
|
+
"@e22m4u/js-service": "~0.4.2",
|
|
47
47
|
"@e22m4u/js-trie-router": "~0.3.1",
|
|
48
|
-
"@e22m4u/ts-data-schema": "~0.4.
|
|
49
|
-
"@e22m4u/ts-reflector": "~0.1.
|
|
48
|
+
"@e22m4u/ts-data-schema": "~0.4.4",
|
|
49
|
+
"@e22m4u/ts-reflector": "~0.1.8",
|
|
50
50
|
"http-errors": "~2.0.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@commitlint/cli": "~20.1.0",
|
|
54
54
|
"@commitlint/config-conventional": "~20.0.0",
|
|
55
|
-
"@eslint/js": "~9.
|
|
55
|
+
"@eslint/js": "~9.37.0",
|
|
56
56
|
"@types/chai": "~5.2.2",
|
|
57
57
|
"@types/debug": "~4.1.12",
|
|
58
58
|
"@types/http-errors": "~2.0.5",
|
|
59
59
|
"@types/mocha": "~10.0.10",
|
|
60
|
-
"@types/node": "~24.
|
|
60
|
+
"@types/node": "~24.7.0",
|
|
61
61
|
"c8": "~10.1.3",
|
|
62
62
|
"chai": "~6.2.0",
|
|
63
63
|
"esbuild": "~0.25.10",
|
|
64
|
-
"eslint": "~9.
|
|
64
|
+
"eslint": "~9.37.0",
|
|
65
65
|
"eslint-config-prettier": "~10.1.8",
|
|
66
66
|
"eslint-plugin-chai-expect": "~3.1.0",
|
|
67
|
-
"eslint-plugin-mocha": "~11.
|
|
67
|
+
"eslint-plugin-mocha": "~11.2.0",
|
|
68
68
|
"husky": "~9.1.7",
|
|
69
|
-
"mocha": "~11.7.
|
|
69
|
+
"mocha": "~11.7.4",
|
|
70
70
|
"prettier": "~3.6.2",
|
|
71
71
|
"rimraf": "~6.0.1",
|
|
72
72
|
"tsx": "~4.20.6",
|
|
73
73
|
"typescript": "~5.9.3",
|
|
74
|
-
"typescript-eslint": "~8.
|
|
74
|
+
"typescript-eslint": "~8.46.0"
|
|
75
75
|
}
|
|
76
76
|
}
|
|
@@ -33,9 +33,11 @@ import {
|
|
|
33
33
|
} from './decorators/index.js';
|
|
34
34
|
|
|
35
35
|
import {expect} from 'chai';
|
|
36
|
+
import {Service} from '@e22m4u/js-service';
|
|
36
37
|
import {DataType} from '@e22m4u/ts-data-schema';
|
|
38
|
+
import {ServiceContainer} from '@e22m4u/js-service';
|
|
39
|
+
import {DataSchemaFactory} from './data-schema-types.js';
|
|
37
40
|
import {ControllerRegistry} from './controller-registry.js';
|
|
38
|
-
import {Service, ServiceContainer} from '@e22m4u/js-service';
|
|
39
41
|
|
|
40
42
|
const PRE_HANDLER_1 = () => undefined;
|
|
41
43
|
const PRE_HANDLER_2 = () => undefined;
|
|
@@ -1039,5 +1041,176 @@ describe('ControllerRegistry', function () {
|
|
|
1039
1041
|
await handler(ctx1);
|
|
1040
1042
|
expect(counter).to.be.eq(1);
|
|
1041
1043
|
});
|
|
1044
|
+
|
|
1045
|
+
describe('data schema', function () {
|
|
1046
|
+
describe('@requestBody', function () {
|
|
1047
|
+
describe('DataType', function () {
|
|
1048
|
+
it('should cast request data by the specified type', async function () {
|
|
1049
|
+
let invoked = 0;
|
|
1050
|
+
class MyController {
|
|
1051
|
+
myAction(
|
|
1052
|
+
@requestBody(DataType.NUMBER)
|
|
1053
|
+
body: number,
|
|
1054
|
+
) {
|
|
1055
|
+
expect(body).to.be.eq(10);
|
|
1056
|
+
invoked++;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
const req = createRequestMock();
|
|
1060
|
+
const res = createResponseMock();
|
|
1061
|
+
const S = new ControllerRegistry();
|
|
1062
|
+
const ctx = new RequestContext(S.container, req, res);
|
|
1063
|
+
ctx.body = '10';
|
|
1064
|
+
const handler = S['createRouteHandler'](MyController, 'myAction');
|
|
1065
|
+
await handler(ctx);
|
|
1066
|
+
expect(invoked).to.be.eq(1);
|
|
1067
|
+
});
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
describe('DataSchema', function () {
|
|
1071
|
+
it('should apply default value to request data', async function () {
|
|
1072
|
+
let invoked = 0;
|
|
1073
|
+
class MyController {
|
|
1074
|
+
myAction(
|
|
1075
|
+
@requestBody({
|
|
1076
|
+
type: DataType.STRING,
|
|
1077
|
+
default: 'OK',
|
|
1078
|
+
})
|
|
1079
|
+
body: string,
|
|
1080
|
+
) {
|
|
1081
|
+
expect(body).to.be.eq('OK');
|
|
1082
|
+
invoked++;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
const req = createRequestMock();
|
|
1086
|
+
const res = createResponseMock();
|
|
1087
|
+
const S = new ControllerRegistry();
|
|
1088
|
+
const ctx = new RequestContext(S.container, req, res);
|
|
1089
|
+
const handler = S['createRouteHandler'](MyController, 'myAction');
|
|
1090
|
+
await handler(ctx);
|
|
1091
|
+
expect(invoked).to.be.eq(1);
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
it('should cast request data by the specified type', async function () {
|
|
1095
|
+
let invoked = 0;
|
|
1096
|
+
class MyController {
|
|
1097
|
+
myAction(
|
|
1098
|
+
@requestBody({type: DataType.NUMBER})
|
|
1099
|
+
body: number,
|
|
1100
|
+
) {
|
|
1101
|
+
expect(body).to.be.eq(10);
|
|
1102
|
+
invoked++;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
const req = createRequestMock();
|
|
1106
|
+
const res = createResponseMock();
|
|
1107
|
+
const S = new ControllerRegistry();
|
|
1108
|
+
const ctx = new RequestContext(S.container, req, res);
|
|
1109
|
+
ctx.body = '10';
|
|
1110
|
+
const handler = S['createRouteHandler'](MyController, 'myAction');
|
|
1111
|
+
await handler(ctx);
|
|
1112
|
+
expect(invoked).to.be.eq(1);
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
it('should validate request data by the given schema', async function () {
|
|
1116
|
+
class MyController {
|
|
1117
|
+
myAction(
|
|
1118
|
+
@requestBody({
|
|
1119
|
+
type: DataType.OBJECT,
|
|
1120
|
+
required: true,
|
|
1121
|
+
})
|
|
1122
|
+
body: object,
|
|
1123
|
+
) {
|
|
1124
|
+
throw new Error('Must not to be invoked');
|
|
1125
|
+
return body;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
const req = createRequestMock();
|
|
1130
|
+
const res = createResponseMock();
|
|
1131
|
+
const S = new ControllerRegistry();
|
|
1132
|
+
const ctx = new RequestContext(S.container, req, res);
|
|
1133
|
+
const handler = S['createRouteHandler'](MyController, 'myAction');
|
|
1134
|
+
const throwable = () => handler(ctx);
|
|
1135
|
+
expect(throwable).to.throw(/is required, but undefined was given/);
|
|
1136
|
+
});
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
describe('DataSchemaFactory', function () {
|
|
1140
|
+
it('should apply default value to request data', async function () {
|
|
1141
|
+
let invoked = 0;
|
|
1142
|
+
const S = new ControllerRegistry();
|
|
1143
|
+
const factory: DataSchemaFactory = sc => {
|
|
1144
|
+
expect(sc).to.be.eq(S.container);
|
|
1145
|
+
return {type: DataType.STRING, default: 'OK'};
|
|
1146
|
+
};
|
|
1147
|
+
class MyController {
|
|
1148
|
+
myAction(
|
|
1149
|
+
@requestBody(factory)
|
|
1150
|
+
body: string,
|
|
1151
|
+
) {
|
|
1152
|
+
expect(body).to.be.eq('OK');
|
|
1153
|
+
invoked++;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
const req = createRequestMock();
|
|
1157
|
+
const res = createResponseMock();
|
|
1158
|
+
const ctx = new RequestContext(S.container, req, res);
|
|
1159
|
+
const handler = S['createRouteHandler'](MyController, 'myAction');
|
|
1160
|
+
await handler(ctx);
|
|
1161
|
+
expect(invoked).to.be.eq(1);
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
it('should cast request data by the specified type', async function () {
|
|
1165
|
+
let invoked = 0;
|
|
1166
|
+
const S = new ControllerRegistry();
|
|
1167
|
+
const factory: DataSchemaFactory = sc => {
|
|
1168
|
+
expect(sc).to.be.eq(S.container);
|
|
1169
|
+
return {type: DataType.NUMBER};
|
|
1170
|
+
};
|
|
1171
|
+
class MyController {
|
|
1172
|
+
myAction(
|
|
1173
|
+
@requestBody(factory)
|
|
1174
|
+
body: number,
|
|
1175
|
+
) {
|
|
1176
|
+
expect(body).to.be.eq(10);
|
|
1177
|
+
invoked++;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
const req = createRequestMock();
|
|
1181
|
+
const res = createResponseMock();
|
|
1182
|
+
const ctx = new RequestContext(S.container, req, res);
|
|
1183
|
+
ctx.body = '10';
|
|
1184
|
+
const handler = S['createRouteHandler'](MyController, 'myAction');
|
|
1185
|
+
await handler(ctx);
|
|
1186
|
+
expect(invoked).to.be.eq(1);
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
it('should validate request data by the given schema', async function () {
|
|
1190
|
+
const S = new ControllerRegistry();
|
|
1191
|
+
const factory: DataSchemaFactory = sc => {
|
|
1192
|
+
expect(sc).to.be.eq(S.container);
|
|
1193
|
+
return {type: DataType.OBJECT, required: true};
|
|
1194
|
+
};
|
|
1195
|
+
class MyController {
|
|
1196
|
+
myAction(
|
|
1197
|
+
@requestBody(factory)
|
|
1198
|
+
body: object,
|
|
1199
|
+
) {
|
|
1200
|
+
throw new Error('Must not to be invoked');
|
|
1201
|
+
return body;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
const req = createRequestMock();
|
|
1206
|
+
const res = createResponseMock();
|
|
1207
|
+
const ctx = new RequestContext(S.container, req, res);
|
|
1208
|
+
const handler = S['createRouteHandler'](MyController, 'myAction');
|
|
1209
|
+
const throwable = () => handler(ctx);
|
|
1210
|
+
expect(throwable).to.throw(/is required, but undefined was given/);
|
|
1211
|
+
});
|
|
1212
|
+
});
|
|
1213
|
+
});
|
|
1214
|
+
});
|
|
1042
1215
|
});
|
|
1043
1216
|
});
|
|
@@ -2,6 +2,7 @@ import {AnyObject} from './types.js';
|
|
|
2
2
|
import {Constructor} from './types.js';
|
|
3
3
|
import {Errorf} from '@e22m4u/js-format';
|
|
4
4
|
import {TrieRouter} from '@e22m4u/js-trie-router';
|
|
5
|
+
import {DataSchema} from '@e22m4u/ts-data-schema';
|
|
5
6
|
import {RouteHandler} from '@e22m4u/js-trie-router';
|
|
6
7
|
import {DataValidator} from '@e22m4u/ts-data-schema';
|
|
7
8
|
import {DataTypeCaster} from '@e22m4u/ts-data-schema';
|
|
@@ -461,22 +462,25 @@ export class ControllerRegistry extends DebuggableService {
|
|
|
461
462
|
// по умолчанию, выполняется конвертация входящего
|
|
462
463
|
// значения и валидация согласно схеме
|
|
463
464
|
if (requestDataMd.schema) {
|
|
465
|
+
let dataSchema: DataSchema;
|
|
466
|
+
if (typeof requestDataMd.schema === 'function') {
|
|
467
|
+
dataSchema = requestDataMd.schema(this.container);
|
|
468
|
+
debug('Data schema extracted from factory function.');
|
|
469
|
+
} else {
|
|
470
|
+
dataSchema = requestDataMd.schema;
|
|
471
|
+
}
|
|
464
472
|
data = defaultsApplier.applyDefaultValuesIfNeeded(
|
|
465
473
|
data,
|
|
466
|
-
|
|
474
|
+
dataSchema,
|
|
467
475
|
requestDataMd.source,
|
|
468
476
|
);
|
|
469
477
|
debug('Default values applied.');
|
|
470
|
-
data = dataTypeCaster.cast(data,
|
|
478
|
+
data = dataTypeCaster.cast(data, dataSchema, {
|
|
471
479
|
noTypeCastError: true,
|
|
472
480
|
sourcePath: requestDataMd.source,
|
|
473
481
|
});
|
|
474
482
|
debug('Data type casting applied.');
|
|
475
|
-
dataValidator.validate(
|
|
476
|
-
data,
|
|
477
|
-
requestDataMd.schema,
|
|
478
|
-
requestDataMd.source,
|
|
479
|
-
);
|
|
483
|
+
dataValidator.validate(data, dataSchema, requestDataMd.source);
|
|
480
484
|
debug('Data validation passed.');
|
|
481
485
|
}
|
|
482
486
|
// если свойство данных не определено,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {DataType} from '@e22m4u/ts-data-schema';
|
|
2
|
+
import {DataSchema} from '@e22m4u/ts-data-schema';
|
|
3
|
+
import {ServiceContainer} from '@e22m4u/js-service';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Data schema factory.
|
|
7
|
+
*/
|
|
8
|
+
export type DataSchemaFactory = (container: ServiceContainer) => DataSchema;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Data schema or factory.
|
|
12
|
+
*/
|
|
13
|
+
export type DataSchemaOrFactory = DataSchema | DataSchemaFactory;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Data schema input.
|
|
17
|
+
*/
|
|
18
|
+
export type DataSchemaInput = DataSchemaOrFactory | DataType;
|