-
Notifications
You must be signed in to change notification settings - Fork 534
Expand file tree
/
Copy pathexpressTemplateService.ts
More file actions
149 lines (134 loc) · 6.16 KB
/
Copy pathexpressTemplateService.ts
File metadata and controls
149 lines (134 loc) · 6.16 KB
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
import { Request as ExRequest, Response as ExResponse, NextFunction as ExNext } from 'express';
import { Controller } from '../../../interfaces/controller';
import { FieldErrors } from '../../templateHelpers';
import { TsoaRoute } from '../../tsoa-route';
import { ValidateError } from '../../templateHelpers';
import { TemplateService } from '../templateService';
import { Readable } from 'node:stream';
type ExpressApiHandlerParameters = {
methodName: string;
controller: Controller | object;
response: ExResponse;
next: ExNext;
validatedArgs: any[];
successStatus?: number;
};
type ExpressValidationArgsParameters = {
args: Record<string, TsoaRoute.ParameterSchema>;
request: ExRequest;
response: ExResponse;
};
type ExpressReturnHandlerParameters = {
response: ExResponse;
headers: any;
statusCode?: number;
data?: any;
};
export class ExpressTemplateService extends TemplateService<ExpressApiHandlerParameters, ExpressValidationArgsParameters, ExpressReturnHandlerParameters> {
async apiHandler(params: ExpressApiHandlerParameters) {
const { methodName, controller, response, validatedArgs, successStatus, next } = params;
try {
const data = await this.buildPromise(methodName, controller, validatedArgs);
let statusCode = successStatus;
let headers;
if (this.isController(controller)) {
headers = controller.getHeaders();
statusCode = controller.getStatus() || statusCode;
}
this.returnHandler({ response, headers, statusCode, data });
} catch (error) {
return next(error);
}
}
getValidatedArgs(params: ExpressValidationArgsParameters): any[] {
const { args, request, response } = params;
const fieldErrors: FieldErrors = {};
const values = Object.values(args).map(param => {
const name = param.name;
switch (param.in) {
case 'request':
return request;
case 'request-prop': {
const descriptor = Object.getOwnPropertyDescriptor(request, name);
const value = descriptor ? descriptor.value : undefined;
return this.validationService.ValidateParam(param, value, name, fieldErrors, false, undefined);
}
case 'query':
return this.validationService.ValidateParam(param, request.query[name], name, fieldErrors, false, undefined);
case 'queries':
return this.validationService.ValidateParam(param, request.query, name, fieldErrors, false, undefined);
case 'path':
return this.validationService.ValidateParam(param, request.params[name], name, fieldErrors, false, undefined);
case 'header':
return this.validationService.ValidateParam(param, request.header(name), name, fieldErrors, false, undefined);
case 'body': {
const body = Object.keys(request.body ?? {}).length === 0 ? undefined : request.body
const bodyFieldErrors: FieldErrors = {};
const bodyArgs = this.validationService.ValidateParam(param, body, name, bodyFieldErrors, true, undefined);
Object.keys(bodyFieldErrors).forEach(key => {
fieldErrors[key] = { message: bodyFieldErrors[key].message };
});
return bodyArgs;
}
case 'body-prop': {
const bodyPropFieldErrors: FieldErrors = {};
const bodyPropArgs = this.validationService.ValidateParam(param, request.body?.[name], name, bodyPropFieldErrors, true, 'body.');
Object.keys(bodyPropFieldErrors).forEach(key => {
fieldErrors[key] = { message: bodyPropFieldErrors[key].message };
});
return bodyPropArgs;
}
case 'formData': {
const files = Object.values(args).filter(p => p.dataType === 'file' || (p.dataType === 'array' && p.array && p.array.dataType === 'file'));
if ((param.dataType === 'file' || (param.dataType === 'array' && param.array && param.array.dataType === 'file')) && files.length > 0) {
const requestFiles = request.files as { [fileName: string]: Express.Multer.File[] } | undefined;
const fileArgs = this.validationService.ValidateParam(param, requestFiles?.[name], name, fieldErrors, false, undefined);
if (param.dataType === 'array') {
return fileArgs;
}
return Array.isArray(fileArgs) && fileArgs.length === 1 ? fileArgs[0] : fileArgs;
}
return this.validationService.ValidateParam(param, request.body?.[name], name, fieldErrors, false, undefined);
}
case 'res':
return (status: number | undefined, data: any, headers: any) => {
this.returnHandler({ response, headers, statusCode: status, data });
};
}
});
if (Object.keys(fieldErrors).length > 0) {
throw new ValidateError(fieldErrors, '');
}
return values;
}
protected returnHandler(params: ExpressReturnHandlerParameters) {
const { response, statusCode, data } = params;
let { headers } = params;
headers = headers || {};
if (response.headersSent) {
return;
}
Object.keys(headers).forEach((name: string) => {
response.set(name, headers[name]);
});
// Check if the response is marked to be JSON
const isJsonResponse = response.get('Content-Type')?.includes('json') || false;
if (data && typeof data.pipe === 'function' && data.readable && typeof data._read === 'function') {
response.status(statusCode || 200);
(data as Readable).pipe(response);
} else if (data !== undefined && (data !== null || isJsonResponse)) {
// allow null response when it is a json response
if (typeof data === 'number' || isJsonResponse) {
// express treats number data as status code so use the json method instead
// or if the response was marked as json then use the json so for example strings are quoted
response.status(statusCode || 200).json(data);
} else {
// do not use json for every type since internally the send will invoke json if needed
// but for string data it will not quote it, so we can send string as plain/text data
response.status(statusCode || 200).send(data);
}
} else {
response.status(statusCode || 204).end();
}
}
}