-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathexecution_container.ts
102 lines (89 loc) · 3.18 KB
/
execution_container.ts
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
// Copyright 2020 Liam Tan. All rights reserved. MIT license.
import { RouterContext, Status } from "./deps.ts";
import {
ExecutionResult,
ControllerMetadata,
RouteDefinition,
RouteArgument,
RequestLifetime,
} from "./types.ts";
import { HttpException, InternalServerErrorException } from "./http_exception.ts";
import DIContainer from "./dependency_container.ts";
import * as transform from "./transform.ts";
/** class that executes a controller action with context */
export class ExecutionContainer {
#controllerMeta: ControllerMetadata;
#key: string;
constructor(meta: ControllerMetadata, key: string) {
this.#controllerMeta = meta;
this.#key = key;
}
async execute(route: RouteDefinition, context: RouterContext): Promise<ExecutionResult> {
const { buildRouteArgumentsFromMeta, executeBeforeFns, getStatus } = transform;
const result: ExecutionResult = {
success: false,
body: {},
status: Status.OK,
};
const lifetime: RequestLifetime = DIContainer.newRequestLifetime();
const argDefinitions: Array<RouteArgument> = this.#controllerMeta.args;
// Using the controller metadata and data from context, build controller args
const args: Array<any> = await buildRouteArgumentsFromMeta(
argDefinitions,
route,
context,
lifetime
);
// execute any defined before actions. If any fails, this will
// return true. If it does return true, context response as
// been set so return early and skip controller action
try {
await executeBeforeFns(
this.#controllerMeta.beforeFns.get(String(route.methodName)) ?? [],
context
);
} catch (e) {
const [errorStatus, errorBody] = this.#handleError(e);
result.body = errorBody;
result.status = errorStatus;
return result;
}
try {
const instance = lifetime.resolve(this.#key);
// execute action here.
const controllerResponse: any = await instance[String(route.methodName)](...args);
// Body has manually been set
if (!controllerResponse && context.response.body) {
result.status =
context.response.status ?? getStatus(route, this.#controllerMeta.defaultResponseCodes);
result.body = context.response.body;
} // No body set and no response, 204
else if (!controllerResponse && !context.response.body) {
result.status = 204;
result.body = null;
} // Return value from controller action
else {
result.status = getStatus(route, this.#controllerMeta.defaultResponseCodes);
result.body = controllerResponse;
}
result.success = true;
} catch (error) {
const [errorStatus, errorBody] = this.#handleError(error);
result.status = errorStatus;
result.body = errorBody;
}
// End request lifetime when execution container is finished
lifetime.end();
return result;
}
/**
* Helper method for gracefully handling execution errors
*/
#handleError = (error: any): Array<any> => {
if (!(error instanceof HttpException)) {
console.error(error);
error = new InternalServerErrorException();
}
return [error.getError().status, error.getError()];
};
}