File size: 10,927 Bytes
cfbbc1a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RouterExecutionContext = void 0;
const common_1 = require("@nestjs/common");
const constants_1 = require("@nestjs/common/constants");
const route_paramtypes_enum_1 = require("@nestjs/common/enums/route-paramtypes.enum");
const shared_utils_1 = require("@nestjs/common/utils/shared.utils");
const guards_1 = require("../guards");
const context_utils_1 = require("../helpers/context-utils");
const handler_metadata_storage_1 = require("../helpers/handler-metadata-storage");
const constants_2 = require("../injector/constants");
const router_response_controller_1 = require("./router-response-controller");
class RouterExecutionContext {
    constructor(paramsFactory, pipesContextCreator, pipesConsumer, guardsContextCreator, guardsConsumer, interceptorsContextCreator, interceptorsConsumer, applicationRef) {
        this.paramsFactory = paramsFactory;
        this.pipesContextCreator = pipesContextCreator;
        this.pipesConsumer = pipesConsumer;
        this.guardsContextCreator = guardsContextCreator;
        this.guardsConsumer = guardsConsumer;
        this.interceptorsContextCreator = interceptorsContextCreator;
        this.interceptorsConsumer = interceptorsConsumer;
        this.applicationRef = applicationRef;
        this.handlerMetadataStorage = new handler_metadata_storage_1.HandlerMetadataStorage();
        this.contextUtils = new context_utils_1.ContextUtils();
        this.responseController = new router_response_controller_1.RouterResponseController(applicationRef);
    }
    create(instance, callback, methodName, moduleKey, requestMethod, contextId = constants_2.STATIC_CONTEXT, inquirerId) {
        const contextType = 'http';
        const { argsLength, fnHandleResponse, isSseHandler, paramtypes, getParamsMetadata, httpStatusCode, responseHeaders, hasCustomHeaders, } = this.getMetadata(instance, callback, methodName, moduleKey, requestMethod, contextType);
        const paramsOptions = this.contextUtils.mergeParamsMetatypes(getParamsMetadata(moduleKey, contextId, inquirerId), paramtypes);
        const pipes = this.pipesContextCreator.create(instance, callback, moduleKey, contextId, inquirerId);
        const guards = this.guardsContextCreator.create(instance, callback, moduleKey, contextId, inquirerId);
        const interceptors = this.interceptorsContextCreator.create(instance, callback, moduleKey, contextId, inquirerId);
        const fnCanActivate = this.createGuardsFn(guards, instance, callback, contextType);
        const fnApplyPipes = this.createPipesFn(pipes, paramsOptions);
        const handler = (args, req, res, next) => async () => {
            fnApplyPipes && (await fnApplyPipes(args, req, res, next));
            return callback.apply(instance, args);
        };
        return async (req, res, next) => {
            const args = this.contextUtils.createNullArray(argsLength);
            fnCanActivate && (await fnCanActivate([req, res, next]));
            this.responseController.setStatus(res, httpStatusCode);
            hasCustomHeaders &&
                this.responseController.setHeaders(res, responseHeaders);
            const resultOrDeferred = this.interceptorsConsumer.intercept(interceptors, [req, res, next], instance, callback, handler(args, req, res, next), contextType);
            const result = isSseHandler ? resultOrDeferred : await resultOrDeferred;
            await fnHandleResponse(result, res, req);
        };
    }
    getMetadata(instance, callback, methodName, moduleKey, requestMethod, contextType) {
        const cacheMetadata = this.handlerMetadataStorage.get(instance, methodName);
        if (cacheMetadata) {
            return cacheMetadata;
        }
        const metadata = this.contextUtils.reflectCallbackMetadata(instance, methodName, constants_1.ROUTE_ARGS_METADATA) || {};
        const keys = Object.keys(metadata);
        const argsLength = this.contextUtils.getArgumentsLength(keys, metadata);
        const paramtypes = this.contextUtils.reflectCallbackParamtypes(instance, methodName);
        const contextFactory = this.contextUtils.getContextFactory(contextType, instance, callback);
        const getParamsMetadata = (moduleKey, contextId = constants_2.STATIC_CONTEXT, inquirerId) => this.exchangeKeysForValues(keys, metadata, moduleKey, contextId, inquirerId, contextFactory);
        const paramsMetadata = getParamsMetadata(moduleKey);
        const isResponseHandled = this.isResponseHandled(instance, methodName, paramsMetadata);
        const httpRedirectResponse = this.reflectRedirect(callback);
        const fnHandleResponse = this.createHandleResponseFn(callback, isResponseHandled, httpRedirectResponse);
        const isSseHandler = !!this.reflectSse(callback);
        const httpCode = this.reflectHttpStatusCode(callback);
        const httpStatusCode = httpCode
            ? httpCode
            : this.responseController.getStatusByMethod(requestMethod);
        const responseHeaders = this.reflectResponseHeaders(callback);
        const hasCustomHeaders = !(0, shared_utils_1.isEmpty)(responseHeaders);
        const handlerMetadata = {
            argsLength,
            fnHandleResponse,
            isSseHandler,
            paramtypes,
            getParamsMetadata,
            httpStatusCode,
            hasCustomHeaders,
            responseHeaders,
        };
        this.handlerMetadataStorage.set(instance, methodName, handlerMetadata);
        return handlerMetadata;
    }
    reflectRedirect(callback) {
        return Reflect.getMetadata(constants_1.REDIRECT_METADATA, callback);
    }
    reflectHttpStatusCode(callback) {
        return Reflect.getMetadata(constants_1.HTTP_CODE_METADATA, callback);
    }
    reflectRenderTemplate(callback) {
        return Reflect.getMetadata(constants_1.RENDER_METADATA, callback);
    }
    reflectResponseHeaders(callback) {
        return Reflect.getMetadata(constants_1.HEADERS_METADATA, callback) || [];
    }
    reflectSse(callback) {
        return Reflect.getMetadata(constants_1.SSE_METADATA, callback);
    }
    exchangeKeysForValues(keys, metadata, moduleContext, contextId = constants_2.STATIC_CONTEXT, inquirerId, contextFactory) {
        this.pipesContextCreator.setModuleContext(moduleContext);
        return keys.map(key => {
            const { index, data, pipes: pipesCollection } = metadata[key];
            const pipes = this.pipesContextCreator.createConcreteContext(pipesCollection, contextId, inquirerId);
            const type = this.contextUtils.mapParamType(key);
            if (key.includes(constants_1.CUSTOM_ROUTE_ARGS_METADATA)) {
                const { factory } = metadata[key];
                const customExtractValue = this.contextUtils.getCustomFactory(factory, data, contextFactory);
                return { index, extractValue: customExtractValue, type, data, pipes };
            }
            const numericType = Number(type);
            const extractValue = (req, res, next) => this.paramsFactory.exchangeKeyForValue(numericType, data, {
                req: req,
                res,
                next,
            });
            return { index, extractValue, type: numericType, data, pipes };
        });
    }
    async getParamValue(value, { metatype, type, data, }, pipes) {
        if (!(0, shared_utils_1.isEmpty)(pipes)) {
            return this.pipesConsumer.apply(value, { metatype, type, data }, pipes);
        }
        return value;
    }
    isPipeable(type) {
        return (type === route_paramtypes_enum_1.RouteParamtypes.BODY ||
            type === route_paramtypes_enum_1.RouteParamtypes.RAW_BODY ||
            type === route_paramtypes_enum_1.RouteParamtypes.QUERY ||
            type === route_paramtypes_enum_1.RouteParamtypes.PARAM ||
            type === route_paramtypes_enum_1.RouteParamtypes.FILE ||
            type === route_paramtypes_enum_1.RouteParamtypes.FILES ||
            (0, shared_utils_1.isString)(type));
    }
    createGuardsFn(guards, instance, callback, contextType) {
        const canActivateFn = async (args) => {
            const canActivate = await this.guardsConsumer.tryActivate(guards, args, instance, callback, contextType);
            if (!canActivate) {
                throw new common_1.ForbiddenException(guards_1.FORBIDDEN_MESSAGE);
            }
        };
        return guards.length ? canActivateFn : null;
    }
    createPipesFn(pipes, paramsOptions) {
        const pipesFn = async (args, req, res, next) => {
            const resolveParamValue = async (param) => {
                const { index, extractValue, type, data, metatype, pipes: paramPipes, } = param;
                const value = extractValue(req, res, next);
                args[index] = this.isPipeable(type)
                    ? await this.getParamValue(value, { metatype, type, data }, pipes.concat(paramPipes))
                    : value;
            };
            await Promise.all(paramsOptions.map(resolveParamValue));
        };
        return paramsOptions.length ? pipesFn : null;
    }
    createHandleResponseFn(callback, isResponseHandled, redirectResponse, httpStatusCode) {
        const renderTemplate = this.reflectRenderTemplate(callback);
        if (renderTemplate) {
            return async (result, res) => {
                return await this.responseController.render(result, res, renderTemplate);
            };
        }
        if (redirectResponse && (0, shared_utils_1.isString)(redirectResponse.url)) {
            return async (result, res) => {
                await this.responseController.redirect(result, res, redirectResponse);
            };
        }
        const isSseHandler = !!this.reflectSse(callback);
        if (isSseHandler) {
            return async (result, res, req) => {
                const rawResponse = res.raw ?? res;
                await this.responseController.sse(result, rawResponse, req.raw || req, {
                    additionalHeaders: res.getHeaders?.(),
                    statusCode: res.statusCode ??
                        rawResponse.statusCode,
                });
            };
        }
        return async (result, res) => {
            result = await this.responseController.transformToResult(result);
            !isResponseHandled &&
                (await this.responseController.apply(result, res, httpStatusCode));
            return res;
        };
    }
    isResponseHandled(instance, methodName, paramsMetadata) {
        const hasResponseOrNextDecorator = paramsMetadata.some(({ type }) => type === route_paramtypes_enum_1.RouteParamtypes.RESPONSE || type === route_paramtypes_enum_1.RouteParamtypes.NEXT);
        const isPassthroughEnabled = this.contextUtils.reflectPassthrough(instance, methodName);
        return hasResponseOrNextDecorator && !isPassthroughEnabled;
    }
}
exports.RouterExecutionContext = RouterExecutionContext;