@webpieces/http-filters 0.2.12 → 0.2.14
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/package.json +2 -2
- package/src/Filter.d.ts +62 -89
- package/src/Filter.js +77 -45
- package/src/Filter.js.map +1 -1
- package/src/FilterChain.d.ts +8 -8
- package/src/FilterChain.js +23 -18
- package/src/FilterChain.js.map +1 -1
- package/src/index.d.ts +1 -3
- package/src/index.js +3 -14
- package/src/index.js.map +1 -1
- package/src/filters/ContextFilter.d.ts +0 -12
- package/src/filters/ContextFilter.js +0 -36
- package/src/filters/ContextFilter.js.map +0 -1
- package/src/filters/JsonFilter.d.ts +0 -95
- package/src/filters/JsonFilter.js +0 -202
- package/src/filters/JsonFilter.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/http-filters",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.14",
|
|
4
4
|
"description": "Filter chain infrastructure for cross-cutting concerns",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -21,6 +21,6 @@
|
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@webpieces/core-context": "0.2.
|
|
24
|
+
"@webpieces/core-context": "0.2.14"
|
|
25
25
|
}
|
|
26
26
|
}
|
package/src/Filter.d.ts
CHANGED
|
@@ -1,112 +1,85 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* WpResponse - Wraps controller responses for the filter chain.
|
|
3
|
+
*
|
|
4
|
+
* Generic type parameter TResult represents the controller's return type.
|
|
5
|
+
* The filter chain uses WpResponse<unknown> because it handles all response types uniformly.
|
|
6
|
+
*
|
|
7
|
+
* JsonFilter is responsible for:
|
|
8
|
+
* 1. Serializing WpResponse.response to JSON
|
|
9
|
+
* 2. Writing the JSON to the HTTP response body
|
|
4
10
|
*/
|
|
5
|
-
export declare class
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* The request path
|
|
12
|
-
*/
|
|
13
|
-
path: string;
|
|
14
|
-
/**
|
|
15
|
-
* The method name being invoked on the controller
|
|
16
|
-
*/
|
|
17
|
-
methodName: string;
|
|
11
|
+
export declare class WpResponse<TResult = unknown> {
|
|
12
|
+
response?: TResult;
|
|
13
|
+
statusCode: number;
|
|
14
|
+
headers: Map<string, string>;
|
|
15
|
+
constructor(response?: TResult, statusCode?: number);
|
|
18
16
|
/**
|
|
19
|
-
*
|
|
20
|
-
* Filters can modify this array (e.g., JsonFilter deserializes request body into params[0])
|
|
17
|
+
* Set a response header.
|
|
21
18
|
*/
|
|
22
|
-
|
|
19
|
+
setHeader(name: string, value: string): WpResponse<TResult>;
|
|
23
20
|
/**
|
|
24
|
-
*
|
|
21
|
+
* Create an error response wrapper.
|
|
25
22
|
*/
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* The response object (if applicable)
|
|
29
|
-
*/
|
|
30
|
-
response?: any;
|
|
31
|
-
/**
|
|
32
|
-
* Additional metadata
|
|
33
|
-
*/
|
|
34
|
-
metadata?: Map<string, any>;
|
|
35
|
-
constructor(httpMethod: string, path: string, methodName: string, params: any[], request?: any, response?: any, metadata?: Map<string, any>);
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Action returned by filters and controllers.
|
|
39
|
-
* Can represent different types of responses.
|
|
40
|
-
*/
|
|
41
|
-
export declare class Action {
|
|
42
|
-
type: 'json' | 'html' | 'redirect' | 'error';
|
|
43
|
-
data?: any;
|
|
44
|
-
statusCode?: number;
|
|
45
|
-
headers?: Record<string, string>;
|
|
46
|
-
constructor(type: 'json' | 'html' | 'redirect' | 'error', data?: any, statusCode?: number, headers?: Record<string, string>);
|
|
23
|
+
static error<T = unknown>(message: string, statusCode?: number): WpResponse<T>;
|
|
47
24
|
}
|
|
48
25
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
26
|
+
* Service interface - Similar to Java WebPieces Service<REQ, RESP>.
|
|
27
|
+
* Represents any component that can process a request and return a response.
|
|
28
|
+
*
|
|
29
|
+
* Used for:
|
|
30
|
+
* - Final controller invocation
|
|
31
|
+
* - Wrapping filters as services in the chain
|
|
32
|
+
* - Functional composition of filters
|
|
52
33
|
*/
|
|
53
|
-
export
|
|
34
|
+
export interface Service<REQ, RESP> {
|
|
54
35
|
/**
|
|
55
|
-
*
|
|
56
|
-
* @
|
|
36
|
+
* Invoke the service with the given metadata.
|
|
37
|
+
* @param meta - Request metadata
|
|
38
|
+
* @returns Promise of the response
|
|
57
39
|
*/
|
|
58
|
-
|
|
40
|
+
invoke(meta: REQ): Promise<RESP>;
|
|
59
41
|
}
|
|
60
42
|
/**
|
|
61
|
-
* Filter
|
|
62
|
-
* Similar to Java WebPieces RouteFilter.
|
|
43
|
+
* Filter abstract class - Similar to Java WebPieces Filter<REQ, RESP>.
|
|
63
44
|
*
|
|
64
|
-
* Filters are
|
|
65
|
-
*
|
|
45
|
+
* Filters are STATELESS and can handle N concurrent requests.
|
|
46
|
+
* They wrap the execution of subsequent filters and the controller.
|
|
66
47
|
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* export class LoggingFilter implements Filter {
|
|
71
|
-
* priority = 100;
|
|
48
|
+
* Key principles:
|
|
49
|
+
* - STATELESS: No instance variables for request data
|
|
50
|
+
* - COMPOSABLE: Use chain() methods for functional composition
|
|
72
51
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* console.log(`Response: ${action.statusCode}`);
|
|
77
|
-
* return action;
|
|
78
|
-
* }
|
|
79
|
-
* }
|
|
80
|
-
* ```
|
|
52
|
+
* For HTTP filters, use Filter<MethodMeta, WpResponse<unknown>>:
|
|
53
|
+
* - MethodMeta: Standardized request metadata (defined in http-server)
|
|
54
|
+
* - WpResponse<unknown>: Wraps any controller response
|
|
81
55
|
*/
|
|
82
|
-
export
|
|
83
|
-
/**
|
|
84
|
-
* Priority of this filter.
|
|
85
|
-
* Higher numbers execute first.
|
|
86
|
-
* Typical values:
|
|
87
|
-
* - 140: Context setup
|
|
88
|
-
* - 120: Request attributes
|
|
89
|
-
* - 90: Metrics
|
|
90
|
-
* - 80: Logging
|
|
91
|
-
* - 60: JSON serialization
|
|
92
|
-
* - 40: Transactions
|
|
93
|
-
* - 0: Controller
|
|
94
|
-
*/
|
|
95
|
-
priority: number;
|
|
56
|
+
export declare abstract class Filter<REQ, RESP> {
|
|
96
57
|
/**
|
|
97
58
|
* Filter method that wraps the next filter/controller.
|
|
98
59
|
*
|
|
99
60
|
* @param meta - Metadata about the method being invoked
|
|
100
|
-
* @param
|
|
101
|
-
* @returns Promise of the
|
|
61
|
+
* @param nextFilter - Next filter/controller as a Service
|
|
62
|
+
* @returns Promise of the response
|
|
63
|
+
*/
|
|
64
|
+
abstract filter(meta: REQ, nextFilter: Service<REQ, RESP>): Promise<RESP>;
|
|
65
|
+
/**
|
|
66
|
+
* Chain this filter with another filter.
|
|
67
|
+
* Returns a new Filter that composes both filters.
|
|
68
|
+
*
|
|
69
|
+
* Similar to Java: filter1.chain(filter2)
|
|
70
|
+
*
|
|
71
|
+
* @param nextFilter - The filter to execute after this one
|
|
72
|
+
* @returns Composed filter
|
|
102
73
|
*/
|
|
103
|
-
|
|
74
|
+
chain(nextFilter: Filter<REQ, RESP>): Filter<REQ, RESP>;
|
|
75
|
+
/**
|
|
76
|
+
* Chain this filter with a final service (controller).
|
|
77
|
+
* Returns a Service that can be invoked.
|
|
78
|
+
*
|
|
79
|
+
* Similar to Java: filter.chain(service)
|
|
80
|
+
*
|
|
81
|
+
* @param svc - The final service (controller) to execute
|
|
82
|
+
* @returns Service wrapping the entire filter chain
|
|
83
|
+
*/
|
|
84
|
+
chainService(svc: Service<REQ, RESP>): Service<REQ, RESP>;
|
|
104
85
|
}
|
|
105
|
-
/**
|
|
106
|
-
* Helper to create a JSON action response.
|
|
107
|
-
*/
|
|
108
|
-
export declare function jsonAction(data: any, statusCode?: number): Action;
|
|
109
|
-
/**
|
|
110
|
-
* Helper to create an error action response.
|
|
111
|
-
*/
|
|
112
|
-
export declare function errorAction(error: Error | string, statusCode?: number): Action;
|
package/src/Filter.js
CHANGED
|
@@ -1,57 +1,89 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.jsonAction = jsonAction;
|
|
5
|
-
exports.errorAction = errorAction;
|
|
3
|
+
exports.Filter = exports.WpResponse = void 0;
|
|
6
4
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* WpResponse - Wraps controller responses for the filter chain.
|
|
6
|
+
*
|
|
7
|
+
* Generic type parameter TResult represents the controller's return type.
|
|
8
|
+
* The filter chain uses WpResponse<unknown> because it handles all response types uniformly.
|
|
9
|
+
*
|
|
10
|
+
* JsonFilter is responsible for:
|
|
11
|
+
* 1. Serializing WpResponse.response to JSON
|
|
12
|
+
* 2. Writing the JSON to the HTTP response body
|
|
9
13
|
*/
|
|
10
|
-
class
|
|
11
|
-
constructor(
|
|
12
|
-
this.httpMethod = httpMethod;
|
|
13
|
-
this.path = path;
|
|
14
|
-
this.methodName = methodName;
|
|
15
|
-
this.params = params;
|
|
16
|
-
this.request = request;
|
|
14
|
+
class WpResponse {
|
|
15
|
+
constructor(response, statusCode = 200) {
|
|
17
16
|
this.response = response;
|
|
18
|
-
this.metadata = metadata;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
exports.MethodMeta = MethodMeta;
|
|
22
|
-
/**
|
|
23
|
-
* Action returned by filters and controllers.
|
|
24
|
-
* Can represent different types of responses.
|
|
25
|
-
*/
|
|
26
|
-
class Action {
|
|
27
|
-
constructor(type, data, statusCode, headers) {
|
|
28
|
-
this.type = type;
|
|
29
|
-
this.data = data;
|
|
30
17
|
this.statusCode = statusCode;
|
|
31
|
-
this.headers =
|
|
18
|
+
this.headers = new Map();
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Set a response header.
|
|
22
|
+
*/
|
|
23
|
+
setHeader(name, value) {
|
|
24
|
+
this.headers.set(name, value);
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create an error response wrapper.
|
|
29
|
+
*/
|
|
30
|
+
static error(message, statusCode = 500) {
|
|
31
|
+
const wrapper = new WpResponse(undefined, statusCode);
|
|
32
|
+
wrapper.setHeader('X-Error', message);
|
|
33
|
+
return wrapper;
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
|
-
exports.
|
|
35
|
-
/**
|
|
36
|
-
* Next filter class.
|
|
37
|
-
* This is a class instead of a function type to make it easier to trace
|
|
38
|
-
* who is calling what in the debugger/IDE.
|
|
39
|
-
*/
|
|
40
|
-
class NextFilter {
|
|
41
|
-
}
|
|
42
|
-
exports.NextFilter = NextFilter;
|
|
43
|
-
/**
|
|
44
|
-
* Helper to create a JSON action response.
|
|
45
|
-
*/
|
|
46
|
-
function jsonAction(data, statusCode = 200) {
|
|
47
|
-
return new Action('json', data, statusCode);
|
|
48
|
-
}
|
|
36
|
+
exports.WpResponse = WpResponse;
|
|
49
37
|
/**
|
|
50
|
-
*
|
|
38
|
+
* Filter abstract class - Similar to Java WebPieces Filter<REQ, RESP>.
|
|
39
|
+
*
|
|
40
|
+
* Filters are STATELESS and can handle N concurrent requests.
|
|
41
|
+
* They wrap the execution of subsequent filters and the controller.
|
|
42
|
+
*
|
|
43
|
+
* Key principles:
|
|
44
|
+
* - STATELESS: No instance variables for request data
|
|
45
|
+
* - COMPOSABLE: Use chain() methods for functional composition
|
|
46
|
+
*
|
|
47
|
+
* For HTTP filters, use Filter<MethodMeta, WpResponse<unknown>>:
|
|
48
|
+
* - MethodMeta: Standardized request metadata (defined in http-server)
|
|
49
|
+
* - WpResponse<unknown>: Wraps any controller response
|
|
51
50
|
*/
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
class Filter {
|
|
52
|
+
/**
|
|
53
|
+
* Chain this filter with another filter.
|
|
54
|
+
* Returns a new Filter that composes both filters.
|
|
55
|
+
*
|
|
56
|
+
* Similar to Java: filter1.chain(filter2)
|
|
57
|
+
*
|
|
58
|
+
* @param nextFilter - The filter to execute after this one
|
|
59
|
+
* @returns Composed filter
|
|
60
|
+
*/
|
|
61
|
+
chain(nextFilter) {
|
|
62
|
+
const self = this;
|
|
63
|
+
return new class extends Filter {
|
|
64
|
+
async filter(meta, nextService) {
|
|
65
|
+
// Call outer filter, passing next filter wrapped as a Service
|
|
66
|
+
return self.filter(meta, {
|
|
67
|
+
invoke: (m) => nextFilter.filter(m, nextService)
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Chain this filter with a final service (controller).
|
|
74
|
+
* Returns a Service that can be invoked.
|
|
75
|
+
*
|
|
76
|
+
* Similar to Java: filter.chain(service)
|
|
77
|
+
*
|
|
78
|
+
* @param svc - The final service (controller) to execute
|
|
79
|
+
* @returns Service wrapping the entire filter chain
|
|
80
|
+
*/
|
|
81
|
+
chainService(svc) {
|
|
82
|
+
const self = this;
|
|
83
|
+
return {
|
|
84
|
+
invoke: (meta) => self.filter(meta, svc)
|
|
85
|
+
};
|
|
86
|
+
}
|
|
56
87
|
}
|
|
88
|
+
exports.Filter = Filter;
|
|
57
89
|
//# sourceMappingURL=Filter.js.map
|
package/src/Filter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Filter.js","sourceRoot":"","sources":["../../../../../packages/http/http-filters/src/Filter.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"Filter.js","sourceRoot":"","sources":["../../../../../packages/http/http-filters/src/Filter.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;GASG;AACH,MAAa,UAAU;IAKrB,YAAY,QAAkB,EAAE,aAAqB,GAAG;QACtD,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,IAAI,GAAG,EAAE,CAAC;IAC3B,CAAC;IAED;;OAEG;IACH,SAAS,CAAC,IAAY,EAAE,KAAa;QACnC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,KAAK,CAAc,OAAe,EAAE,aAAqB,GAAG;QACjE,MAAM,OAAO,GAAG,IAAI,UAAU,CAAI,SAAS,EAAE,UAAU,CAAC,CAAC;QACzD,OAAO,CAAC,SAAS,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACtC,OAAO,OAAO,CAAC;IACjB,CAAC;CACF;AA3BD,gCA2BC;AAoBD;;;;;;;;;;;;;GAaG;AACH,MAAsB,MAAM;IAiB1B;;;;;;;;OAQG;IACH,KAAK,CAAC,UAA6B;QACjC,MAAM,IAAI,GAAG,IAAI,CAAC;QAElB,OAAO,IAAI,KAAM,SAAQ,MAAiB;YACxC,KAAK,CAAC,MAAM,CACV,IAAS,EACT,WAA+B;gBAE/B,8DAA8D;gBAC9D,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;oBACvB,MAAM,EAAE,CAAC,CAAM,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,CAAC;iBACtD,CAAC,CAAC;YACL,CAAC;SACF,CAAC;IACJ,CAAC;IAED;;;;;;;;OAQG;IACH,YAAY,CAAC,GAAuB;QAClC,MAAM,IAAI,GAAG,IAAI,CAAC;QAElB,OAAO;YACL,MAAM,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC;SAC9C,CAAC;IACJ,CAAC;CACF;AA1DD,wBA0DC","sourcesContent":["/**\n * WpResponse - Wraps controller responses for the filter chain.\n *\n * Generic type parameter TResult represents the controller's return type.\n * The filter chain uses WpResponse<unknown> because it handles all response types uniformly.\n *\n * JsonFilter is responsible for:\n * 1. Serializing WpResponse.response to JSON\n * 2. Writing the JSON to the HTTP response body\n */\nexport class WpResponse<TResult = unknown> {\n response?: TResult;\n statusCode: number;\n headers: Map<string, string>;\n\n constructor(response?: TResult, statusCode: number = 200) {\n this.response = response;\n this.statusCode = statusCode;\n this.headers = new Map();\n }\n\n /**\n * Set a response header.\n */\n setHeader(name: string, value: string): WpResponse<TResult> {\n this.headers.set(name, value);\n return this;\n }\n\n /**\n * Create an error response wrapper.\n */\n static error<T = unknown>(message: string, statusCode: number = 500): WpResponse<T> {\n const wrapper = new WpResponse<T>(undefined, statusCode);\n wrapper.setHeader('X-Error', message);\n return wrapper;\n }\n}\n\n/**\n * Service interface - Similar to Java WebPieces Service<REQ, RESP>.\n * Represents any component that can process a request and return a response.\n *\n * Used for:\n * - Final controller invocation\n * - Wrapping filters as services in the chain\n * - Functional composition of filters\n */\nexport interface Service<REQ, RESP> {\n /**\n * Invoke the service with the given metadata.\n * @param meta - Request metadata\n * @returns Promise of the response\n */\n invoke(meta: REQ): Promise<RESP>;\n}\n\n/**\n * Filter abstract class - Similar to Java WebPieces Filter<REQ, RESP>.\n *\n * Filters are STATELESS and can handle N concurrent requests.\n * They wrap the execution of subsequent filters and the controller.\n *\n * Key principles:\n * - STATELESS: No instance variables for request data\n * - COMPOSABLE: Use chain() methods for functional composition\n *\n * For HTTP filters, use Filter<MethodMeta, WpResponse<unknown>>:\n * - MethodMeta: Standardized request metadata (defined in http-server)\n * - WpResponse<unknown>: Wraps any controller response\n */\nexport abstract class Filter<REQ, RESP> {\n\n //priority is determined by how it is chained only here\n //DO NOT add priority here\n\n /**\n * Filter method that wraps the next filter/controller.\n *\n * @param meta - Metadata about the method being invoked\n * @param nextFilter - Next filter/controller as a Service\n * @returns Promise of the response\n */\n abstract filter(\n meta: REQ,\n nextFilter: Service<REQ, RESP>\n ): Promise<RESP>;\n\n /**\n * Chain this filter with another filter.\n * Returns a new Filter that composes both filters.\n *\n * Similar to Java: filter1.chain(filter2)\n *\n * @param nextFilter - The filter to execute after this one\n * @returns Composed filter\n */\n chain(nextFilter: Filter<REQ, RESP>): Filter<REQ, RESP> {\n const self = this;\n\n return new class extends Filter<REQ, RESP> {\n async filter(\n meta: REQ,\n nextService: Service<REQ, RESP>\n ): Promise<RESP> {\n // Call outer filter, passing next filter wrapped as a Service\n return self.filter(meta, {\n invoke: (m: REQ) => nextFilter.filter(m, nextService)\n });\n }\n };\n }\n\n /**\n * Chain this filter with a final service (controller).\n * Returns a Service that can be invoked.\n *\n * Similar to Java: filter.chain(service)\n *\n * @param svc - The final service (controller) to execute\n * @returns Service wrapping the entire filter chain\n */\n chainService(svc: Service<REQ, RESP>): Service<REQ, RESP> {\n const self = this;\n\n return {\n invoke: (meta: REQ) => self.filter(meta, svc)\n };\n }\n}\n"]}
|
package/src/FilterChain.d.ts
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import { Filter
|
|
1
|
+
import { Filter } from './Filter';
|
|
2
2
|
/**
|
|
3
3
|
* FilterChain - Manages execution of filters in priority order.
|
|
4
4
|
* Similar to Java servlet filter chains.
|
|
5
5
|
*
|
|
6
6
|
* Filters are sorted by priority (highest first) and each filter
|
|
7
|
-
* calls
|
|
7
|
+
* calls nextFilter.invoke() to invoke the next filter in the chain.
|
|
8
8
|
*
|
|
9
9
|
* The final "filter" in the chain is the controller method itself.
|
|
10
10
|
*/
|
|
11
|
-
export declare class FilterChain {
|
|
11
|
+
export declare class FilterChain<REQ, RESP> {
|
|
12
12
|
private filters;
|
|
13
|
-
constructor(filters: Filter[]);
|
|
13
|
+
constructor(filters: Filter<REQ, RESP>[]);
|
|
14
14
|
/**
|
|
15
15
|
* Execute the filter chain.
|
|
16
16
|
*
|
|
17
|
-
* @param meta -
|
|
17
|
+
* @param meta - Request metadata
|
|
18
18
|
* @param finalHandler - The controller method to execute at the end
|
|
19
|
-
* @returns Promise of the
|
|
19
|
+
* @returns Promise of the response
|
|
20
20
|
*/
|
|
21
|
-
execute(meta:
|
|
21
|
+
execute(meta: REQ, finalHandler: () => Promise<RESP>): Promise<RESP>;
|
|
22
22
|
/**
|
|
23
23
|
* Get all filters in the chain (sorted by priority).
|
|
24
24
|
*/
|
|
25
|
-
getFilters(): Filter[];
|
|
25
|
+
getFilters(): Filter<REQ, RESP>[];
|
|
26
26
|
/**
|
|
27
27
|
* Get the number of filters in the chain.
|
|
28
28
|
*/
|
package/src/FilterChain.js
CHANGED
|
@@ -1,44 +1,49 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.FilterChain = void 0;
|
|
4
|
-
const Filter_1 = require("./Filter");
|
|
5
4
|
/**
|
|
6
5
|
* FilterChain - Manages execution of filters in priority order.
|
|
7
6
|
* Similar to Java servlet filter chains.
|
|
8
7
|
*
|
|
9
8
|
* Filters are sorted by priority (highest first) and each filter
|
|
10
|
-
* calls
|
|
9
|
+
* calls nextFilter.invoke() to invoke the next filter in the chain.
|
|
11
10
|
*
|
|
12
11
|
* The final "filter" in the chain is the controller method itself.
|
|
13
12
|
*/
|
|
14
13
|
class FilterChain {
|
|
15
14
|
constructor(filters) {
|
|
16
|
-
//
|
|
17
|
-
|
|
15
|
+
// Filters are already sorted by priority from FilterMatcher
|
|
16
|
+
// No need to sort again (priority is in FilterDefinition, not Filter)
|
|
17
|
+
this.filters = filters;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* Execute the filter chain.
|
|
21
21
|
*
|
|
22
|
-
* @param meta -
|
|
22
|
+
* @param meta - Request metadata
|
|
23
23
|
* @param finalHandler - The controller method to execute at the end
|
|
24
|
-
* @returns Promise of the
|
|
24
|
+
* @returns Promise of the response
|
|
25
25
|
*/
|
|
26
26
|
async execute(meta, finalHandler) {
|
|
27
|
-
let index = 0;
|
|
28
27
|
const filters = this.filters;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
// Create Service adapter that recursively calls filters
|
|
29
|
+
const createServiceForIndex = (currentIndex) => {
|
|
30
|
+
return {
|
|
31
|
+
invoke: async (m) => {
|
|
32
|
+
if (currentIndex < filters.length) {
|
|
33
|
+
const filter = filters[currentIndex];
|
|
34
|
+
const nextService = createServiceForIndex(currentIndex + 1);
|
|
35
|
+
return filter.filter(m, nextService);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
// All filters executed, now execute the controller
|
|
39
|
+
return finalHandler();
|
|
40
|
+
}
|
|
34
41
|
}
|
|
35
|
-
|
|
36
|
-
// All filters have been executed, now execute the controller
|
|
37
|
-
return finalHandler();
|
|
38
|
-
}
|
|
39
|
-
}
|
|
42
|
+
};
|
|
40
43
|
};
|
|
41
|
-
|
|
44
|
+
// Start execution with first filter
|
|
45
|
+
const service = createServiceForIndex(0);
|
|
46
|
+
return service.invoke(meta);
|
|
42
47
|
}
|
|
43
48
|
/**
|
|
44
49
|
* Get all filters in the chain (sorted by priority).
|
package/src/FilterChain.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilterChain.js","sourceRoot":"","sources":["../../../../../packages/http/http-filters/src/FilterChain.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"FilterChain.js","sourceRoot":"","sources":["../../../../../packages/http/http-filters/src/FilterChain.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;GAQG;AACH,MAAa,WAAW;IAGtB,YAAY,OAA4B;QACtC,4DAA4D;QAC5D,sEAAsE;QACtE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,CACX,IAAS,EACT,YAAiC;QAEjC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAE7B,wDAAwD;QACxD,MAAM,qBAAqB,GAAG,CAAC,YAAoB,EAAsB,EAAE;YACzE,OAAO;gBACL,MAAM,EAAE,KAAK,EAAE,CAAM,EAAiB,EAAE;oBACtC,IAAI,YAAY,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;wBAClC,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;wBACrC,MAAM,WAAW,GAAG,qBAAqB,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC;wBAC5D,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;oBACvC,CAAC;yBAAM,CAAC;wBACN,mDAAmD;wBACnD,OAAO,YAAY,EAAE,CAAC;oBACxB,CAAC;gBACH,CAAC;aACF,CAAC;QACJ,CAAC,CAAC;QAEF,oCAAoC;QACpC,MAAM,OAAO,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACzC,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED;;OAEG;IACH,UAAU;QACR,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;IAED;;OAEG;IACH,IAAI;QACF,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;IAC7B,CAAC;CACF;AAxDD,kCAwDC","sourcesContent":["import { Filter, Service } from './Filter';\n\n/**\n * FilterChain - Manages execution of filters in priority order.\n * Similar to Java servlet filter chains.\n *\n * Filters are sorted by priority (highest first) and each filter\n * calls nextFilter.invoke() to invoke the next filter in the chain.\n *\n * The final \"filter\" in the chain is the controller method itself.\n */\nexport class FilterChain<REQ, RESP> {\n private filters: Filter<REQ, RESP>[];\n\n constructor(filters: Filter<REQ, RESP>[]) {\n // Filters are already sorted by priority from FilterMatcher\n // No need to sort again (priority is in FilterDefinition, not Filter)\n this.filters = filters;\n }\n\n /**\n * Execute the filter chain.\n *\n * @param meta - Request metadata\n * @param finalHandler - The controller method to execute at the end\n * @returns Promise of the response\n */\n async execute(\n meta: REQ,\n finalHandler: () => Promise<RESP>\n ): Promise<RESP> {\n const filters = this.filters;\n\n // Create Service adapter that recursively calls filters\n const createServiceForIndex = (currentIndex: number): Service<REQ, RESP> => {\n return {\n invoke: async (m: REQ): Promise<RESP> => {\n if (currentIndex < filters.length) {\n const filter = filters[currentIndex];\n const nextService = createServiceForIndex(currentIndex + 1);\n return filter.filter(m, nextService);\n } else {\n // All filters executed, now execute the controller\n return finalHandler();\n }\n }\n };\n };\n\n // Start execution with first filter\n const service = createServiceForIndex(0);\n return service.invoke(meta);\n }\n\n /**\n * Get all filters in the chain (sorted by priority).\n */\n getFilters(): Filter<REQ, RESP>[] {\n return [...this.filters];\n }\n\n /**\n * Get the number of filters in the chain.\n */\n size(): number {\n return this.filters.length;\n }\n}\n"]}
|
package/src/index.d.ts
CHANGED
|
@@ -1,4 +1,2 @@
|
|
|
1
|
-
export { Filter,
|
|
1
|
+
export { Filter, WpResponse, Service, } from './Filter';
|
|
2
2
|
export { FilterChain } from './FilterChain';
|
|
3
|
-
export { ContextFilter } from './filters/ContextFilter';
|
|
4
|
-
export { JsonFilter, JsonFilterConfig, FILTER_TYPES, ValidationException, HttpException, } from './filters/JsonFilter';
|
package/src/index.js
CHANGED
|
@@ -1,20 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.FilterChain = exports.WpResponse = exports.Filter = void 0;
|
|
4
4
|
var Filter_1 = require("./Filter");
|
|
5
|
-
Object.defineProperty(exports, "
|
|
6
|
-
Object.defineProperty(exports, "
|
|
7
|
-
Object.defineProperty(exports, "NextFilter", { enumerable: true, get: function () { return Filter_1.NextFilter; } });
|
|
8
|
-
Object.defineProperty(exports, "jsonAction", { enumerable: true, get: function () { return Filter_1.jsonAction; } });
|
|
9
|
-
Object.defineProperty(exports, "errorAction", { enumerable: true, get: function () { return Filter_1.errorAction; } });
|
|
5
|
+
Object.defineProperty(exports, "Filter", { enumerable: true, get: function () { return Filter_1.Filter; } });
|
|
6
|
+
Object.defineProperty(exports, "WpResponse", { enumerable: true, get: function () { return Filter_1.WpResponse; } });
|
|
10
7
|
var FilterChain_1 = require("./FilterChain");
|
|
11
8
|
Object.defineProperty(exports, "FilterChain", { enumerable: true, get: function () { return FilterChain_1.FilterChain; } });
|
|
12
|
-
var ContextFilter_1 = require("./filters/ContextFilter");
|
|
13
|
-
Object.defineProperty(exports, "ContextFilter", { enumerable: true, get: function () { return ContextFilter_1.ContextFilter; } });
|
|
14
|
-
var JsonFilter_1 = require("./filters/JsonFilter");
|
|
15
|
-
Object.defineProperty(exports, "JsonFilter", { enumerable: true, get: function () { return JsonFilter_1.JsonFilter; } });
|
|
16
|
-
Object.defineProperty(exports, "JsonFilterConfig", { enumerable: true, get: function () { return JsonFilter_1.JsonFilterConfig; } });
|
|
17
|
-
Object.defineProperty(exports, "FILTER_TYPES", { enumerable: true, get: function () { return JsonFilter_1.FILTER_TYPES; } });
|
|
18
|
-
Object.defineProperty(exports, "ValidationException", { enumerable: true, get: function () { return JsonFilter_1.ValidationException; } });
|
|
19
|
-
Object.defineProperty(exports, "HttpException", { enumerable: true, get: function () { return JsonFilter_1.HttpException; } });
|
|
20
9
|
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-filters/src/index.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-filters/src/index.ts"],"names":[],"mappings":";;;AAAA,mCAIkB;AAHhB,gGAAA,MAAM,OAAA;AACN,oGAAA,UAAU,OAAA;AAIZ,6CAA4C;AAAnC,0GAAA,WAAW,OAAA","sourcesContent":["export {\n Filter,\n WpResponse,\n Service,\n} from './Filter';\n\nexport { FilterChain } from './FilterChain';\n"]}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { Filter, MethodMeta, Action, NextFilter } from '../Filter';
|
|
2
|
-
/**
|
|
3
|
-
* ContextFilter - Sets up AsyncLocalStorage context for each request.
|
|
4
|
-
* Priority: 140 (executes first)
|
|
5
|
-
*
|
|
6
|
-
* This filter ensures that all subsequent filters and the controller
|
|
7
|
-
* execute within a context that can store request-scoped data.
|
|
8
|
-
*/
|
|
9
|
-
export declare class ContextFilter implements Filter {
|
|
10
|
-
priority: number;
|
|
11
|
-
filter(meta: MethodMeta, next: NextFilter): Promise<Action>;
|
|
12
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ContextFilter = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
const inversify_1 = require("inversify");
|
|
6
|
-
const http_routing_1 = require("@webpieces/http-routing");
|
|
7
|
-
const core_context_1 = require("@webpieces/core-context");
|
|
8
|
-
/**
|
|
9
|
-
* ContextFilter - Sets up AsyncLocalStorage context for each request.
|
|
10
|
-
* Priority: 140 (executes first)
|
|
11
|
-
*
|
|
12
|
-
* This filter ensures that all subsequent filters and the controller
|
|
13
|
-
* execute within a context that can store request-scoped data.
|
|
14
|
-
*/
|
|
15
|
-
let ContextFilter = class ContextFilter {
|
|
16
|
-
constructor() {
|
|
17
|
-
this.priority = 140;
|
|
18
|
-
}
|
|
19
|
-
async filter(meta, next) {
|
|
20
|
-
// Run the rest of the filter chain within a new context
|
|
21
|
-
return core_context_1.RequestContext.run(async () => {
|
|
22
|
-
// Store request metadata in context for other filters to access
|
|
23
|
-
core_context_1.RequestContext.put('METHOD_META', meta);
|
|
24
|
-
core_context_1.RequestContext.put('REQUEST_PATH', meta.path);
|
|
25
|
-
core_context_1.RequestContext.put('HTTP_METHOD', meta.httpMethod);
|
|
26
|
-
return await next.execute();
|
|
27
|
-
//RequestContext is auto cleared when done.
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
exports.ContextFilter = ContextFilter;
|
|
32
|
-
exports.ContextFilter = ContextFilter = tslib_1.__decorate([
|
|
33
|
-
(0, http_routing_1.provideSingleton)(),
|
|
34
|
-
(0, inversify_1.injectable)()
|
|
35
|
-
], ContextFilter);
|
|
36
|
-
//# sourceMappingURL=ContextFilter.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ContextFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-filters/src/filters/ContextFilter.ts"],"names":[],"mappings":";;;;AAAA,yCAAuC;AACvC,0DAA2D;AAC3D,0DAAyD;AAGzD;;;;;;GAMG;AAGI,IAAM,aAAa,GAAnB,MAAM,aAAa;IAAnB;QACL,aAAQ,GAAG,GAAG,CAAC;IAcjB,CAAC;IAZC,KAAK,CAAC,MAAM,CAAC,IAAgB,EAAE,IAAgB;QAC7C,wDAAwD;QACxD,OAAO,6BAAc,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE;YACnC,gEAAgE;YAChE,6BAAc,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;YACxC,6BAAc,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YAC9C,6BAAc,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;YAEnD,OAAO,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YAC5B,2CAA2C;QAC7C,CAAC,CAAC,CAAC;IACL,CAAC;CACF,CAAA;AAfY,sCAAa;wBAAb,aAAa;IAFzB,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;GACA,aAAa,CAezB","sourcesContent":["import { injectable } from 'inversify';\nimport { provideSingleton } from '@webpieces/http-routing';\nimport { RequestContext } from '@webpieces/core-context';\nimport { Filter, MethodMeta, Action, NextFilter } from '../Filter';\n\n/**\n * ContextFilter - Sets up AsyncLocalStorage context for each request.\n * Priority: 140 (executes first)\n *\n * This filter ensures that all subsequent filters and the controller\n * execute within a context that can store request-scoped data.\n */\n@provideSingleton()\n@injectable()\nexport class ContextFilter implements Filter {\n priority = 140;\n\n async filter(meta: MethodMeta, next: NextFilter): Promise<Action> {\n // Run the rest of the filter chain within a new context\n return RequestContext.run(async () => {\n // Store request metadata in context for other filters to access\n RequestContext.put('METHOD_META', meta);\n RequestContext.put('REQUEST_PATH', meta.path);\n RequestContext.put('HTTP_METHOD', meta.httpMethod);\n\n return await next.execute();\n //RequestContext is auto cleared when done.\n });\n }\n}\n"]}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { Filter, MethodMeta, Action, NextFilter } from '../Filter';
|
|
2
|
-
/**
|
|
3
|
-
* DI tokens for http-filters.
|
|
4
|
-
*/
|
|
5
|
-
export declare const FILTER_TYPES: {
|
|
6
|
-
JsonFilterConfig: symbol;
|
|
7
|
-
};
|
|
8
|
-
/**
|
|
9
|
-
* Configuration for JsonFilter.
|
|
10
|
-
* Register this in your DI container to customize JsonFilter behavior.
|
|
11
|
-
*
|
|
12
|
-
* Example:
|
|
13
|
-
* ```typescript
|
|
14
|
-
* export const MyModule = new ContainerModule((bind) => {
|
|
15
|
-
* bind(FILTER_TYPES.JsonFilterConfig).toConstantValue(
|
|
16
|
-
* new JsonFilterConfig(true, true) // validation enabled, logging enabled
|
|
17
|
-
* );
|
|
18
|
-
* });
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
export declare class JsonFilterConfig {
|
|
22
|
-
/**
|
|
23
|
-
* Whether to enable validation using class-validator.
|
|
24
|
-
* Default: true
|
|
25
|
-
*/
|
|
26
|
-
validationEnabled: boolean;
|
|
27
|
-
/**
|
|
28
|
-
* Whether to log requests and responses.
|
|
29
|
-
* Default: false
|
|
30
|
-
*/
|
|
31
|
-
loggingEnabled: boolean;
|
|
32
|
-
constructor(validationEnabled?: boolean, loggingEnabled?: boolean);
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* JsonFilter - Handles JSON deserialization and serialization.
|
|
36
|
-
* Priority: 60
|
|
37
|
-
*
|
|
38
|
-
* Similar to Java WebPieces JacksonCatchAllFilter.
|
|
39
|
-
*
|
|
40
|
-
* Responsibilities:
|
|
41
|
-
* 1. Deserialize request body to DTO (if request has body)
|
|
42
|
-
* 2. Validate DTO using class-validator (if enabled)
|
|
43
|
-
* 3. Execute next filter/controller
|
|
44
|
-
* 4. Serialize response to JSON
|
|
45
|
-
* 5. Handle errors and translate to JSON error responses
|
|
46
|
-
*
|
|
47
|
-
* Configuration:
|
|
48
|
-
* JsonFilter uses constructor injection to receive JsonFilterConfig.
|
|
49
|
-
* The default config has validation enabled and logging disabled.
|
|
50
|
-
* To customize, bind JsonFilterConfig in your DI module.
|
|
51
|
-
*/
|
|
52
|
-
export declare class JsonFilter implements Filter {
|
|
53
|
-
private config;
|
|
54
|
-
priority: number;
|
|
55
|
-
constructor(config: JsonFilterConfig);
|
|
56
|
-
filter(meta: MethodMeta, next: NextFilter): Promise<Action>;
|
|
57
|
-
/**
|
|
58
|
-
* Process the request: deserialize and validate.
|
|
59
|
-
*/
|
|
60
|
-
private processRequest;
|
|
61
|
-
/**
|
|
62
|
-
* Validate a DTO using class-validator.
|
|
63
|
-
*/
|
|
64
|
-
private validateDto;
|
|
65
|
-
/**
|
|
66
|
-
* Format validation errors into a readable format.
|
|
67
|
-
*/
|
|
68
|
-
private formatValidationErrors;
|
|
69
|
-
/**
|
|
70
|
-
* Handle errors and translate to JSON error responses.
|
|
71
|
-
*/
|
|
72
|
-
private handleError;
|
|
73
|
-
/**
|
|
74
|
-
* Log the incoming request.
|
|
75
|
-
*/
|
|
76
|
-
private logRequest;
|
|
77
|
-
/**
|
|
78
|
-
* Log the outgoing response.
|
|
79
|
-
*/
|
|
80
|
-
private logResponse;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Exception thrown when validation fails.
|
|
84
|
-
*/
|
|
85
|
-
export declare class ValidationException extends Error {
|
|
86
|
-
violations: string[];
|
|
87
|
-
constructor(violations: string[]);
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* HTTP exception with status code.
|
|
91
|
-
*/
|
|
92
|
-
export declare class HttpException extends Error {
|
|
93
|
-
statusCode: number;
|
|
94
|
-
constructor(message: string, statusCode: number);
|
|
95
|
-
}
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.HttpException = exports.ValidationException = exports.JsonFilter = exports.JsonFilterConfig = exports.FILTER_TYPES = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
const inversify_1 = require("inversify");
|
|
6
|
-
const http_routing_1 = require("@webpieces/http-routing");
|
|
7
|
-
const class_validator_1 = require("class-validator");
|
|
8
|
-
const Filter_1 = require("../Filter");
|
|
9
|
-
const core_util_1 = require("@webpieces/core-util");
|
|
10
|
-
/**
|
|
11
|
-
* DI tokens for http-filters.
|
|
12
|
-
*/
|
|
13
|
-
exports.FILTER_TYPES = {
|
|
14
|
-
JsonFilterConfig: Symbol.for('JsonFilterConfig'),
|
|
15
|
-
};
|
|
16
|
-
/**
|
|
17
|
-
* Configuration for JsonFilter.
|
|
18
|
-
* Register this in your DI container to customize JsonFilter behavior.
|
|
19
|
-
*
|
|
20
|
-
* Example:
|
|
21
|
-
* ```typescript
|
|
22
|
-
* export const MyModule = new ContainerModule((bind) => {
|
|
23
|
-
* bind(FILTER_TYPES.JsonFilterConfig).toConstantValue(
|
|
24
|
-
* new JsonFilterConfig(true, true) // validation enabled, logging enabled
|
|
25
|
-
* );
|
|
26
|
-
* });
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
let JsonFilterConfig = class JsonFilterConfig {
|
|
30
|
-
constructor(validationEnabled = true, loggingEnabled = false) {
|
|
31
|
-
this.validationEnabled = validationEnabled;
|
|
32
|
-
this.loggingEnabled = loggingEnabled;
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
exports.JsonFilterConfig = JsonFilterConfig;
|
|
36
|
-
exports.JsonFilterConfig = JsonFilterConfig = tslib_1.__decorate([
|
|
37
|
-
(0, inversify_1.injectable)(),
|
|
38
|
-
tslib_1.__metadata("design:paramtypes", [Boolean, Boolean])
|
|
39
|
-
], JsonFilterConfig);
|
|
40
|
-
/**
|
|
41
|
-
* JsonFilter - Handles JSON deserialization and serialization.
|
|
42
|
-
* Priority: 60
|
|
43
|
-
*
|
|
44
|
-
* Similar to Java WebPieces JacksonCatchAllFilter.
|
|
45
|
-
*
|
|
46
|
-
* Responsibilities:
|
|
47
|
-
* 1. Deserialize request body to DTO (if request has body)
|
|
48
|
-
* 2. Validate DTO using class-validator (if enabled)
|
|
49
|
-
* 3. Execute next filter/controller
|
|
50
|
-
* 4. Serialize response to JSON
|
|
51
|
-
* 5. Handle errors and translate to JSON error responses
|
|
52
|
-
*
|
|
53
|
-
* Configuration:
|
|
54
|
-
* JsonFilter uses constructor injection to receive JsonFilterConfig.
|
|
55
|
-
* The default config has validation enabled and logging disabled.
|
|
56
|
-
* To customize, bind JsonFilterConfig in your DI module.
|
|
57
|
-
*/
|
|
58
|
-
let JsonFilter = class JsonFilter {
|
|
59
|
-
constructor(config) {
|
|
60
|
-
this.config = config;
|
|
61
|
-
this.priority = 60;
|
|
62
|
-
// Config is injected from DI container
|
|
63
|
-
}
|
|
64
|
-
async filter(meta, next) {
|
|
65
|
-
try {
|
|
66
|
-
// Deserialize and validate request if there's a body
|
|
67
|
-
await this.processRequest(meta);
|
|
68
|
-
if (this.config.loggingEnabled) {
|
|
69
|
-
this.logRequest(meta);
|
|
70
|
-
}
|
|
71
|
-
// Execute next filter/controller
|
|
72
|
-
const action = await next.execute();
|
|
73
|
-
if (this.config.loggingEnabled) {
|
|
74
|
-
this.logResponse(action);
|
|
75
|
-
}
|
|
76
|
-
// Ensure response is JSON
|
|
77
|
-
if (action.type !== 'json' && action.type !== 'error') {
|
|
78
|
-
return (0, Filter_1.jsonAction)(action.data);
|
|
79
|
-
}
|
|
80
|
-
return action;
|
|
81
|
-
}
|
|
82
|
-
catch (err) {
|
|
83
|
-
const error = (0, core_util_1.toError)(err);
|
|
84
|
-
// Translate error to JSON response
|
|
85
|
-
return this.handleError(error, meta);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Process the request: deserialize and validate.
|
|
90
|
-
*/
|
|
91
|
-
async processRequest(meta) {
|
|
92
|
-
// If there's request data and a parameter type, deserialize it
|
|
93
|
-
if (meta.request?.body && meta.params.length === 0) {
|
|
94
|
-
const body = meta.request.body;
|
|
95
|
-
// For now, we'll just pass the body as-is
|
|
96
|
-
// In a real implementation, we'd use the parameter type from decorators
|
|
97
|
-
// to properly deserialize and validate
|
|
98
|
-
// If we have type information, we can do proper transformation
|
|
99
|
-
// For this MVP, we'll store the body in params[0]
|
|
100
|
-
meta.params[0] = body;
|
|
101
|
-
// If validation is enabled and we have a class instance, validate it
|
|
102
|
-
if (this.config.validationEnabled && body.constructor !== Object) {
|
|
103
|
-
await this.validateDto(body);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Validate a DTO using class-validator.
|
|
109
|
-
*/
|
|
110
|
-
async validateDto(dto) {
|
|
111
|
-
const errors = await (0, class_validator_1.validate)(dto);
|
|
112
|
-
if (errors.length > 0) {
|
|
113
|
-
const messages = this.formatValidationErrors(errors);
|
|
114
|
-
throw new ValidationException(messages);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Format validation errors into a readable format.
|
|
119
|
-
*/
|
|
120
|
-
formatValidationErrors(errors) {
|
|
121
|
-
const messages = [];
|
|
122
|
-
for (const error of errors) {
|
|
123
|
-
if (error.constraints) {
|
|
124
|
-
const constraints = Object.values(error.constraints);
|
|
125
|
-
messages.push(...constraints);
|
|
126
|
-
}
|
|
127
|
-
if (error.children && error.children.length > 0) {
|
|
128
|
-
const childMessages = this.formatValidationErrors(error.children);
|
|
129
|
-
messages.push(...childMessages);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return messages;
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Handle errors and translate to JSON error responses.
|
|
136
|
-
*/
|
|
137
|
-
handleError(error, meta) {
|
|
138
|
-
if (error instanceof ValidationException) {
|
|
139
|
-
return (0, Filter_1.errorAction)({
|
|
140
|
-
error: 'Validation failed',
|
|
141
|
-
violations: error.violations,
|
|
142
|
-
}, 400);
|
|
143
|
-
}
|
|
144
|
-
if (error instanceof HttpException) {
|
|
145
|
-
return (0, Filter_1.errorAction)({
|
|
146
|
-
error: error.message,
|
|
147
|
-
code: error.statusCode,
|
|
148
|
-
}, error.statusCode);
|
|
149
|
-
}
|
|
150
|
-
// Log unexpected errors
|
|
151
|
-
console.error('Unexpected error in filter chain:', error);
|
|
152
|
-
return (0, Filter_1.errorAction)('Internal server error', 500);
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Log the incoming request.
|
|
156
|
-
*/
|
|
157
|
-
logRequest(meta) {
|
|
158
|
-
console.log(`[JsonFilter] ${meta.httpMethod} ${meta.path}`);
|
|
159
|
-
if (meta.params.length > 0) {
|
|
160
|
-
console.log('[JsonFilter] Request body:', JSON.stringify(meta.params[0], null, 2));
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Log the outgoing response.
|
|
165
|
-
*/
|
|
166
|
-
logResponse(action) {
|
|
167
|
-
console.log(`[JsonFilter] Response: ${action.statusCode}`);
|
|
168
|
-
if (action.data) {
|
|
169
|
-
console.log('[JsonFilter] Response body:', JSON.stringify(action.data, null, 2));
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
exports.JsonFilter = JsonFilter;
|
|
174
|
-
exports.JsonFilter = JsonFilter = tslib_1.__decorate([
|
|
175
|
-
(0, http_routing_1.provideSingleton)(),
|
|
176
|
-
(0, inversify_1.injectable)(),
|
|
177
|
-
tslib_1.__param(0, (0, inversify_1.inject)(exports.FILTER_TYPES.JsonFilterConfig)),
|
|
178
|
-
tslib_1.__metadata("design:paramtypes", [JsonFilterConfig])
|
|
179
|
-
], JsonFilter);
|
|
180
|
-
/**
|
|
181
|
-
* Exception thrown when validation fails.
|
|
182
|
-
*/
|
|
183
|
-
class ValidationException extends Error {
|
|
184
|
-
constructor(violations) {
|
|
185
|
-
super('Validation failed');
|
|
186
|
-
this.violations = violations;
|
|
187
|
-
this.name = 'ValidationException';
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
exports.ValidationException = ValidationException;
|
|
191
|
-
/**
|
|
192
|
-
* HTTP exception with status code.
|
|
193
|
-
*/
|
|
194
|
-
class HttpException extends Error {
|
|
195
|
-
constructor(message, statusCode) {
|
|
196
|
-
super(message);
|
|
197
|
-
this.statusCode = statusCode;
|
|
198
|
-
this.name = 'HttpException';
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
exports.HttpException = HttpException;
|
|
202
|
-
//# sourceMappingURL=JsonFilter.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"JsonFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-filters/src/filters/JsonFilter.ts"],"names":[],"mappings":"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","sourcesContent":["import { injectable, inject } from 'inversify';\nimport { provideSingleton } from '@webpieces/http-routing';\nimport { plainToInstance } from 'class-transformer';\nimport { validate, ValidationError } from 'class-validator';\nimport { Filter, MethodMeta, Action, NextFilter, jsonAction, errorAction } from '../Filter';\nimport { RequestContext } from '@webpieces/core-context';\nimport { toError } from '@webpieces/core-util';\n\n/**\n * DI tokens for http-filters.\n */\nexport const FILTER_TYPES = {\n JsonFilterConfig: Symbol.for('JsonFilterConfig'),\n};\n\n/**\n * Configuration for JsonFilter.\n * Register this in your DI container to customize JsonFilter behavior.\n *\n * Example:\n * ```typescript\n * export const MyModule = new ContainerModule((bind) => {\n * bind(FILTER_TYPES.JsonFilterConfig).toConstantValue(\n * new JsonFilterConfig(true, true) // validation enabled, logging enabled\n * );\n * });\n * ```\n */\n@injectable()\nexport class JsonFilterConfig {\n /**\n * Whether to enable validation using class-validator.\n * Default: true\n */\n validationEnabled: boolean;\n\n /**\n * Whether to log requests and responses.\n * Default: false\n */\n loggingEnabled: boolean;\n\n constructor(\n validationEnabled: boolean = true,\n loggingEnabled: boolean = false\n ) {\n this.validationEnabled = validationEnabled;\n this.loggingEnabled = loggingEnabled;\n }\n}\n\n/**\n * JsonFilter - Handles JSON deserialization and serialization.\n * Priority: 60\n *\n * Similar to Java WebPieces JacksonCatchAllFilter.\n *\n * Responsibilities:\n * 1. Deserialize request body to DTO (if request has body)\n * 2. Validate DTO using class-validator (if enabled)\n * 3. Execute next filter/controller\n * 4. Serialize response to JSON\n * 5. Handle errors and translate to JSON error responses\n *\n * Configuration:\n * JsonFilter uses constructor injection to receive JsonFilterConfig.\n * The default config has validation enabled and logging disabled.\n * To customize, bind JsonFilterConfig in your DI module.\n */\n@provideSingleton()\n@injectable()\nexport class JsonFilter implements Filter {\n priority = 60;\n\n constructor(@inject(FILTER_TYPES.JsonFilterConfig) private config: JsonFilterConfig) {\n // Config is injected from DI container\n }\n\n async filter(meta: MethodMeta, next: NextFilter): Promise<Action> {\n try {\n // Deserialize and validate request if there's a body\n await this.processRequest(meta);\n\n if (this.config.loggingEnabled) {\n this.logRequest(meta);\n }\n\n // Execute next filter/controller\n const action = await next.execute();\n\n if (this.config.loggingEnabled) {\n this.logResponse(action);\n }\n\n // Ensure response is JSON\n if (action.type !== 'json' && action.type !== 'error') {\n return jsonAction(action.data);\n }\n\n return action;\n } catch (err: any) {\n const error = toError(err);\n // Translate error to JSON response\n return this.handleError(error, meta);\n }\n }\n\n /**\n * Process the request: deserialize and validate.\n */\n private async processRequest(meta: MethodMeta): Promise<void> {\n // If there's request data and a parameter type, deserialize it\n if (meta.request?.body && meta.params.length === 0) {\n const body = meta.request.body;\n\n // For now, we'll just pass the body as-is\n // In a real implementation, we'd use the parameter type from decorators\n // to properly deserialize and validate\n\n // If we have type information, we can do proper transformation\n // For this MVP, we'll store the body in params[0]\n meta.params[0] = body;\n\n // If validation is enabled and we have a class instance, validate it\n if (this.config.validationEnabled && body.constructor !== Object) {\n await this.validateDto(body);\n }\n }\n }\n\n /**\n * Validate a DTO using class-validator.\n */\n private async validateDto(dto: any): Promise<void> {\n const errors = await validate(dto);\n\n if (errors.length > 0) {\n const messages = this.formatValidationErrors(errors);\n throw new ValidationException(messages);\n }\n }\n\n /**\n * Format validation errors into a readable format.\n */\n private formatValidationErrors(errors: ValidationError[]): string[] {\n const messages: string[] = [];\n\n for (const error of errors) {\n if (error.constraints) {\n const constraints = Object.values(error.constraints);\n messages.push(...constraints);\n }\n\n if (error.children && error.children.length > 0) {\n const childMessages = this.formatValidationErrors(error.children);\n messages.push(...childMessages);\n }\n }\n\n return messages;\n }\n\n /**\n * Handle errors and translate to JSON error responses.\n */\n private handleError(error: any, meta: MethodMeta): Action {\n if (error instanceof ValidationException) {\n return errorAction(\n {\n error: 'Validation failed',\n violations: error.violations,\n } as any,\n 400\n );\n }\n\n if (error instanceof HttpException) {\n return errorAction(\n {\n error: error.message,\n code: error.statusCode,\n } as any,\n error.statusCode\n );\n }\n\n // Log unexpected errors\n console.error('Unexpected error in filter chain:', error);\n\n return errorAction(\n 'Internal server error',\n 500\n );\n }\n\n /**\n * Log the incoming request.\n */\n private logRequest(meta: MethodMeta): void {\n console.log(`[JsonFilter] ${meta.httpMethod} ${meta.path}`);\n if (meta.params.length > 0) {\n console.log('[JsonFilter] Request body:', JSON.stringify(meta.params[0], null, 2));\n }\n }\n\n /**\n * Log the outgoing response.\n */\n private logResponse(action: Action): void {\n console.log(`[JsonFilter] Response: ${action.statusCode}`);\n if (action.data) {\n console.log('[JsonFilter] Response body:', JSON.stringify(action.data, null, 2));\n }\n }\n}\n\n/**\n * Exception thrown when validation fails.\n */\nexport class ValidationException extends Error {\n constructor(public violations: string[]) {\n super('Validation failed');\n this.name = 'ValidationException';\n }\n}\n\n/**\n * HTTP exception with status code.\n */\nexport class HttpException extends Error {\n constructor(message: string, public statusCode: number) {\n super(message);\n this.name = 'HttpException';\n }\n}\n"]}
|