@e22m4u/ts-rest-router 0.2.0 → 0.2.2
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 +87 -23
- 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/index.d.ts +1 -0
- package/dist/esm/decorators/index.js +1 -0
- package/dist/esm/decorators/request-context/request-context-reflector.spec.js +0 -1
- package/dist/esm/decorators/request-data/request-data-decorator.d.ts +11 -11
- package/dist/esm/decorators/request-data/request-data-decorator.js +3 -3
- package/dist/esm/decorators/request-data/request-data-reflector.spec.js +0 -1
- package/dist/esm/decorators/response-body/index.d.ts +3 -0
- package/dist/esm/decorators/response-body/index.js +3 -0
- package/dist/esm/decorators/response-body/response-body-decorator.d.ts +9 -0
- package/dist/esm/decorators/response-body/response-body-decorator.js +23 -0
- package/dist/esm/decorators/response-body/response-body-decorator.spec.d.ts +1 -0
- package/dist/esm/decorators/response-body/response-body-decorator.spec.js +61 -0
- package/dist/esm/decorators/response-body/response-body-metadata.d.ts +16 -0
- package/dist/esm/decorators/response-body/response-body-metadata.js +5 -0
- package/dist/esm/decorators/response-body/response-body-reflector.d.ts +22 -0
- package/dist/esm/decorators/response-body/response-body-reflector.js +29 -0
- package/dist/esm/decorators/response-body/response-body-reflector.spec.d.ts +1 -0
- package/dist/esm/decorators/response-body/response-body-reflector.spec.js +58 -0
- 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/index.ts +1 -0
- package/src/decorators/request-context/request-context-reflector.spec.ts +0 -1
- package/src/decorators/request-data/request-data-decorator.ts +3 -11
- package/src/decorators/request-data/request-data-reflector.spec.ts +0 -1
- package/src/decorators/response-body/index.ts +3 -0
- package/src/decorators/response-body/response-body-decorator.spec.ts +40 -0
- package/src/decorators/response-body/response-body-decorator.ts +43 -0
- package/src/decorators/response-body/response-body-metadata.ts +20 -0
- package/src/decorators/response-body/response-body-reflector.spec.ts +59 -0
- package/src/decorators/response-body/response-body-reflector.ts +41 -0
- package/src/decorators/rest-action/rest-action-reflector.spec.ts +0 -1
- package/src/decorators/rest-controller/rest-controller-reflector.spec.ts +0 -1
@@ -0,0 +1,23 @@
|
|
1
|
+
import { DecoratorTargetType } from '@e22m4u/ts-reflector';
|
2
|
+
import { getDecoratorTargetType } from '@e22m4u/ts-reflector';
|
3
|
+
import { ResponseBodyReflector } from './response-body-reflector.js';
|
4
|
+
/**
|
5
|
+
* Response body decorator.
|
6
|
+
*
|
7
|
+
* @param schemaOrType
|
8
|
+
*/
|
9
|
+
export function responseBody(schemaOrType) {
|
10
|
+
return function (target, propertyKey, descriptor) {
|
11
|
+
const decoratorType = getDecoratorTargetType(target, propertyKey, descriptor);
|
12
|
+
if (decoratorType !== DecoratorTargetType.INSTANCE_METHOD)
|
13
|
+
throw new Error('@responseBody decorator is only supported on an instance method.');
|
14
|
+
let schema;
|
15
|
+
if (typeof schemaOrType === 'object') {
|
16
|
+
schema = schemaOrType;
|
17
|
+
}
|
18
|
+
else if (typeof schemaOrType === 'string') {
|
19
|
+
schema = { type: schemaOrType };
|
20
|
+
}
|
21
|
+
ResponseBodyReflector.setMetadata(schema ? { schema } : {}, target.constructor, propertyKey);
|
22
|
+
};
|
23
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1,61 @@
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
6
|
+
};
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
9
|
+
};
|
10
|
+
import { expect } from 'chai';
|
11
|
+
import { DataType } from '@e22m4u/ts-data-schema';
|
12
|
+
import { responseBody } from './response-body-decorator.js';
|
13
|
+
import { ResponseBodyReflector } from './response-body-reflector.js';
|
14
|
+
describe('responseBody', function () {
|
15
|
+
it('does not require arguments', function () {
|
16
|
+
class Target {
|
17
|
+
myMethod() { }
|
18
|
+
}
|
19
|
+
__decorate([
|
20
|
+
responseBody(),
|
21
|
+
__metadata("design:type", Function),
|
22
|
+
__metadata("design:paramtypes", []),
|
23
|
+
__metadata("design:returntype", void 0)
|
24
|
+
], Target.prototype, "myMethod", null);
|
25
|
+
const res = ResponseBodyReflector.getMetadata(Target);
|
26
|
+
expect(res.get('myMethod')).to.be.eql({});
|
27
|
+
});
|
28
|
+
it('sets the given DataType to the target metadata', function () {
|
29
|
+
class Target {
|
30
|
+
myMethod() { }
|
31
|
+
}
|
32
|
+
__decorate([
|
33
|
+
responseBody(DataType.STRING),
|
34
|
+
__metadata("design:type", Function),
|
35
|
+
__metadata("design:paramtypes", []),
|
36
|
+
__metadata("design:returntype", void 0)
|
37
|
+
], Target.prototype, "myMethod", null);
|
38
|
+
const res = ResponseBodyReflector.getMetadata(Target);
|
39
|
+
expect(res.get('myMethod')).to.be.eql({ schema: { type: DataType.STRING } });
|
40
|
+
});
|
41
|
+
it('sets the given schema to the target metadata', function () {
|
42
|
+
const schema = {
|
43
|
+
type: DataType.OBJECT,
|
44
|
+
properties: {
|
45
|
+
foo: { type: DataType.STRING },
|
46
|
+
bar: { type: DataType.NUMBER },
|
47
|
+
},
|
48
|
+
};
|
49
|
+
class Target {
|
50
|
+
myMethod() { }
|
51
|
+
}
|
52
|
+
__decorate([
|
53
|
+
responseBody(schema),
|
54
|
+
__metadata("design:type", Function),
|
55
|
+
__metadata("design:paramtypes", []),
|
56
|
+
__metadata("design:returntype", void 0)
|
57
|
+
], Target.prototype, "myMethod", null);
|
58
|
+
const res = ResponseBodyReflector.getMetadata(Target);
|
59
|
+
expect(res.get('myMethod')).to.be.eql({ schema });
|
60
|
+
});
|
61
|
+
});
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import { MetadataKey } from '@e22m4u/ts-reflector';
|
2
|
+
import { DataSchema } from '@e22m4u/ts-data-schema';
|
3
|
+
/**
|
4
|
+
* Response body metadata.
|
5
|
+
*/
|
6
|
+
export type ResponseBodyMetadata = {
|
7
|
+
schema?: DataSchema;
|
8
|
+
};
|
9
|
+
/**
|
10
|
+
* Response body metadata map.
|
11
|
+
*/
|
12
|
+
export type ResponseBodyMetadataMap = Map<string, ResponseBodyMetadata>;
|
13
|
+
/**
|
14
|
+
* Response body metadata key.
|
15
|
+
*/
|
16
|
+
export declare const RESPONSE_BODY_METADATA_KEY: MetadataKey<ResponseBodyMetadataMap>;
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import { Constructor } from '../../types.js';
|
2
|
+
import { ResponseBodyMetadata } from './response-body-metadata.js';
|
3
|
+
import { ResponseBodyMetadataMap } from './response-body-metadata.js';
|
4
|
+
/**
|
5
|
+
* Response body reflector.
|
6
|
+
*/
|
7
|
+
export declare class ResponseBodyReflector {
|
8
|
+
/**
|
9
|
+
* Set metadata.
|
10
|
+
*
|
11
|
+
* @param metadata
|
12
|
+
* @param target
|
13
|
+
* @param propertyKey
|
14
|
+
*/
|
15
|
+
static setMetadata(metadata: ResponseBodyMetadata, target: Constructor, propertyKey: string): void;
|
16
|
+
/**
|
17
|
+
* Get metadata.
|
18
|
+
*
|
19
|
+
* @param target
|
20
|
+
*/
|
21
|
+
static getMetadata(target: Constructor): ResponseBodyMetadataMap;
|
22
|
+
}
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import { Reflector } from '@e22m4u/ts-reflector';
|
2
|
+
import { RESPONSE_BODY_METADATA_KEY } from './response-body-metadata.js';
|
3
|
+
/**
|
4
|
+
* Response body reflector.
|
5
|
+
*/
|
6
|
+
export class ResponseBodyReflector {
|
7
|
+
/**
|
8
|
+
* Set metadata.
|
9
|
+
*
|
10
|
+
* @param metadata
|
11
|
+
* @param target
|
12
|
+
* @param propertyKey
|
13
|
+
*/
|
14
|
+
static setMetadata(metadata, target, propertyKey) {
|
15
|
+
const oldMap = Reflector.getOwnMetadata(RESPONSE_BODY_METADATA_KEY, target);
|
16
|
+
const newMap = new Map(oldMap);
|
17
|
+
newMap.set(propertyKey, metadata);
|
18
|
+
Reflector.defineMetadata(RESPONSE_BODY_METADATA_KEY, newMap, target);
|
19
|
+
}
|
20
|
+
/**
|
21
|
+
* Get metadata.
|
22
|
+
*
|
23
|
+
* @param target
|
24
|
+
*/
|
25
|
+
static getMetadata(target) {
|
26
|
+
const metadata = Reflector.getOwnMetadata(RESPONSE_BODY_METADATA_KEY, target);
|
27
|
+
return metadata ?? new Map();
|
28
|
+
}
|
29
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import { expect } from 'chai';
|
2
|
+
import { Reflector } from '@e22m4u/ts-reflector';
|
3
|
+
import { ResponseBodyReflector } from './response-body-reflector.js';
|
4
|
+
import { RESPONSE_BODY_METADATA_KEY } from './response-body-metadata.js';
|
5
|
+
describe('ResponseBodyReflector', function () {
|
6
|
+
describe('setMetadata', function () {
|
7
|
+
it('sets a given value as target metadata', function () {
|
8
|
+
class Target {
|
9
|
+
}
|
10
|
+
const md1 = {};
|
11
|
+
const md2 = {};
|
12
|
+
ResponseBodyReflector.setMetadata(md1, Target, 'propertyKey1');
|
13
|
+
ResponseBodyReflector.setMetadata(md2, Target, 'propertyKey2');
|
14
|
+
const res = Reflector.getOwnMetadata(RESPONSE_BODY_METADATA_KEY, Target);
|
15
|
+
expect(res).to.be.instanceof(Map);
|
16
|
+
expect(res.get('propertyKey1')).to.be.eq(md1);
|
17
|
+
expect(res.get('propertyKey2')).to.be.eq(md2);
|
18
|
+
});
|
19
|
+
it('overrides existing metadata', function () {
|
20
|
+
class Target {
|
21
|
+
}
|
22
|
+
const md1 = {};
|
23
|
+
const md2 = {};
|
24
|
+
ResponseBodyReflector.setMetadata(md1, Target, 'propertyKey');
|
25
|
+
const res1 = Reflector.getOwnMetadata(RESPONSE_BODY_METADATA_KEY, Target);
|
26
|
+
expect(res1).to.be.instanceof(Map);
|
27
|
+
expect(res1.get('propertyKey')).to.be.eq(md1);
|
28
|
+
ResponseBodyReflector.setMetadata(md2, Target, 'propertyKey');
|
29
|
+
const res2 = Reflector.getOwnMetadata(RESPONSE_BODY_METADATA_KEY, Target);
|
30
|
+
expect(res2).to.be.instanceof(Map);
|
31
|
+
expect(res2.get('propertyKey')).to.be.eq(md2);
|
32
|
+
});
|
33
|
+
});
|
34
|
+
describe('getMetadata', function () {
|
35
|
+
it('returns an existing metadata of the target', function () {
|
36
|
+
class Target {
|
37
|
+
}
|
38
|
+
const md1 = {};
|
39
|
+
const md2 = {};
|
40
|
+
const mdMap = new Map([
|
41
|
+
['propertyKey1', md1],
|
42
|
+
['propertyKey2', md2],
|
43
|
+
]);
|
44
|
+
Reflector.defineMetadata(RESPONSE_BODY_METADATA_KEY, mdMap, Target);
|
45
|
+
const res = ResponseBodyReflector.getMetadata(Target);
|
46
|
+
expect(res).to.be.instanceof(Map);
|
47
|
+
expect(res.get('propertyKey1')).to.be.eq(md1);
|
48
|
+
expect(res.get('propertyKey2')).to.be.eq(md2);
|
49
|
+
});
|
50
|
+
it('returns an empty map if no metadata', function () {
|
51
|
+
class Target {
|
52
|
+
}
|
53
|
+
const res = ResponseBodyReflector.getMetadata(Target);
|
54
|
+
expect(res).to.be.instanceof(Map);
|
55
|
+
expect(res).to.be.empty;
|
56
|
+
});
|
57
|
+
});
|
58
|
+
});
|
@@ -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.2",
|
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.28.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.29",
|
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.5",
|
64
|
+
"eslint": "~9.28.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.1.0",
|
68
68
|
"husky": "~9.1.7",
|
69
|
-
"mocha": "~11.
|
69
|
+
"mocha": "~11.5.0",
|
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.33.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
|
// то используем весь объекта данных
|
package/src/decorators/index.ts
CHANGED
@@ -2,5 +2,6 @@ export * from './rest-action/index.js';
|
|
2
2
|
export * from './request-data/index.js';
|
3
3
|
export * from './after-action/index.js';
|
4
4
|
export * from './before-action/index.js';
|
5
|
+
export * from './response-body/index.js';
|
5
6
|
export * from './rest-controller/index.js';
|
6
7
|
export * from './request-context/index.js';
|
@@ -20,16 +20,8 @@ export type RequestDataOptions = RequestDataMetadata;
|
|
20
20
|
* @param options
|
21
21
|
*/
|
22
22
|
export function requestData<T extends object>(options: RequestDataOptions) {
|
23
|
-
return function (
|
24
|
-
target
|
25
|
-
propertyKey: string,
|
26
|
-
indexOrDescriptor: number,
|
27
|
-
) {
|
28
|
-
const decoratorType = getDecoratorTargetType(
|
29
|
-
target,
|
30
|
-
propertyKey,
|
31
|
-
indexOrDescriptor,
|
32
|
-
);
|
23
|
+
return function (target: Prototype<T>, propertyKey: string, index: number) {
|
24
|
+
const decoratorType = getDecoratorTargetType(target, propertyKey, index);
|
33
25
|
if (decoratorType !== DecoratorTargetType.INSTANCE_METHOD_PARAMETER)
|
34
26
|
throw new Error(
|
35
27
|
'@requestData decorator is only supported ' +
|
@@ -38,7 +30,7 @@ export function requestData<T extends object>(options: RequestDataOptions) {
|
|
38
30
|
RequestDataReflector.setMetadata(
|
39
31
|
options,
|
40
32
|
target.constructor as Constructor<T>,
|
41
|
-
|
33
|
+
index,
|
42
34
|
propertyKey,
|
43
35
|
);
|
44
36
|
};
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import {expect} from 'chai';
|
2
|
+
import {DataType} from '@e22m4u/ts-data-schema';
|
3
|
+
import {responseBody} from './response-body-decorator.js';
|
4
|
+
import {ResponseBodyReflector} from './response-body-reflector.js';
|
5
|
+
|
6
|
+
describe('responseBody', function () {
|
7
|
+
it('does not require arguments', function () {
|
8
|
+
class Target {
|
9
|
+
@responseBody()
|
10
|
+
myMethod() {}
|
11
|
+
}
|
12
|
+
const res = ResponseBodyReflector.getMetadata(Target);
|
13
|
+
expect(res.get('myMethod')).to.be.eql({});
|
14
|
+
});
|
15
|
+
|
16
|
+
it('sets the given DataType to the target metadata', function () {
|
17
|
+
class Target {
|
18
|
+
@responseBody(DataType.STRING)
|
19
|
+
myMethod() {}
|
20
|
+
}
|
21
|
+
const res = ResponseBodyReflector.getMetadata(Target);
|
22
|
+
expect(res.get('myMethod')).to.be.eql({schema: {type: DataType.STRING}});
|
23
|
+
});
|
24
|
+
|
25
|
+
it('sets the given schema to the target metadata', function () {
|
26
|
+
const schema = {
|
27
|
+
type: DataType.OBJECT,
|
28
|
+
properties: {
|
29
|
+
foo: {type: DataType.STRING},
|
30
|
+
bar: {type: DataType.NUMBER},
|
31
|
+
},
|
32
|
+
};
|
33
|
+
class Target {
|
34
|
+
@responseBody(schema)
|
35
|
+
myMethod() {}
|
36
|
+
}
|
37
|
+
const res = ResponseBodyReflector.getMetadata(Target);
|
38
|
+
expect(res.get('myMethod')).to.be.eql({schema});
|
39
|
+
});
|
40
|
+
});
|
@@ -0,0 +1,43 @@
|
|
1
|
+
import {Prototype} from '../../types.js';
|
2
|
+
import {Constructor} from '../../types.js';
|
3
|
+
import {DataType} from '@e22m4u/ts-data-schema';
|
4
|
+
import {DataSchema} from '@e22m4u/ts-data-schema';
|
5
|
+
import {DecoratorTargetType} from '@e22m4u/ts-reflector';
|
6
|
+
import {getDecoratorTargetType} from '@e22m4u/ts-reflector';
|
7
|
+
import {ResponseBodyReflector} from './response-body-reflector.js';
|
8
|
+
|
9
|
+
/**
|
10
|
+
* Response body decorator.
|
11
|
+
*
|
12
|
+
* @param schemaOrType
|
13
|
+
*/
|
14
|
+
export function responseBody<T extends object>(
|
15
|
+
schemaOrType?: DataSchema | DataType,
|
16
|
+
) {
|
17
|
+
return function (
|
18
|
+
target: Prototype<T>,
|
19
|
+
propertyKey: string,
|
20
|
+
descriptor: PropertyDescriptor,
|
21
|
+
) {
|
22
|
+
const decoratorType = getDecoratorTargetType(
|
23
|
+
target,
|
24
|
+
propertyKey,
|
25
|
+
descriptor,
|
26
|
+
);
|
27
|
+
if (decoratorType !== DecoratorTargetType.INSTANCE_METHOD)
|
28
|
+
throw new Error(
|
29
|
+
'@responseBody decorator is only supported on an instance method.',
|
30
|
+
);
|
31
|
+
let schema: DataSchema | undefined;
|
32
|
+
if (typeof schemaOrType === 'object') {
|
33
|
+
schema = schemaOrType;
|
34
|
+
} else if (typeof schemaOrType === 'string') {
|
35
|
+
schema = {type: schemaOrType};
|
36
|
+
}
|
37
|
+
ResponseBodyReflector.setMetadata(
|
38
|
+
schema ? {schema} : {},
|
39
|
+
target.constructor as Constructor<T>,
|
40
|
+
propertyKey,
|
41
|
+
);
|
42
|
+
};
|
43
|
+
}
|