@opra/core 0.26.4 → 0.27.0
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/cjs/execution-context.host.js +0 -1
- package/cjs/http/http-adapter-host.js +190 -169
- package/cjs/platform-adapter.host.js +20 -4
- package/cjs/request-context.js +1 -1
- package/esm/execution-context.host.js +0 -1
- package/esm/http/http-adapter-host.js +191 -170
- package/esm/platform-adapter.host.js +21 -5
- package/esm/request-context.js +1 -1
- package/i18n/en/error.json +2 -1
- package/package.json +4 -4
- package/types/augmentation/collection.augmentation.d.ts +32 -18
- package/types/augmentation/container.augmentation.d.ts +4 -3
- package/types/augmentation/singleton.augmentation.d.ts +20 -12
- package/types/augmentation/storage.augmentation.d.ts +16 -9
- package/types/execution-context.d.ts +0 -1
- package/types/execution-context.host.d.ts +0 -1
- package/types/http/http-adapter-host.d.ts +4 -4
- package/types/platform-adapter.host.d.ts +8 -3
- package/types/request.d.ts +2 -4
- package/types/request.host.d.ts +2 -0
- package/types/response.d.ts +1 -1
|
@@ -4,7 +4,8 @@ exports.HttpAdapterHost = void 0;
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const promises_1 = tslib_1.__importDefault(require("fs/promises"));
|
|
6
6
|
const os_1 = tslib_1.__importDefault(require("os"));
|
|
7
|
-
const
|
|
7
|
+
const type_is_1 = tslib_1.__importDefault(require("type-is"));
|
|
8
|
+
const vg = tslib_1.__importStar(require("valgen"));
|
|
8
9
|
const common_1 = require("@opra/common");
|
|
9
10
|
const execution_context_host_js_1 = require("../execution-context.host.js");
|
|
10
11
|
const platform_adapter_host_js_1 = require("../platform-adapter.host.js");
|
|
@@ -32,54 +33,47 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
32
33
|
async handleHttp(incoming, outgoing) {
|
|
33
34
|
const context = new execution_context_host_js_1.ExecutionContextHost(this.api, this.platform, { http: { incoming, outgoing } });
|
|
34
35
|
try {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
/* istanbul ignore next */
|
|
37
|
+
if (!this._api)
|
|
38
|
+
throw new common_1.InternalServerError(`${Object.getPrototypeOf(this).constructor.name} has not been initialized yet`);
|
|
39
|
+
outgoing.setHeader(common_1.HttpHeaderCodes.X_Opra_Version, common_1.OpraSchema.SpecVersion);
|
|
40
|
+
// Expose headers if cors enabled
|
|
41
|
+
if (outgoing.getHeader(common_1.HttpHeaderCodes.Access_Control_Allow_Origin)) {
|
|
42
|
+
// Expose X-Opra-* headers
|
|
43
|
+
outgoing.appendHeader(common_1.HttpHeaderCodes.Access_Control_Expose_Headers, Object.values(common_1.HttpHeaderCodes)
|
|
44
|
+
.filter(k => k.toLowerCase().startsWith('x-opra-')));
|
|
45
|
+
}
|
|
46
|
+
const { parsedUrl } = incoming;
|
|
47
|
+
if (!parsedUrl.path.length) {
|
|
48
|
+
if (incoming.method === 'GET') {
|
|
49
|
+
outgoing.setHeader('content-type', 'application/json');
|
|
50
|
+
outgoing.end(JSON.stringify(this.api.exportSchema({ webSafe: true })));
|
|
51
|
+
return;
|
|
45
52
|
}
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
outgoing.setHeader('content-type', 'application/json');
|
|
50
|
-
outgoing.end(JSON.stringify(this.api.exportSchema({ webSafe: true })));
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
// Process Batch
|
|
54
|
-
if (incoming.method === 'POST' && incoming.headers['content-type'] === 'multipart/mixed') {
|
|
55
|
-
// todo Process Batch
|
|
56
|
-
}
|
|
57
|
-
throw new common_1.BadRequestError();
|
|
53
|
+
// Process Batch
|
|
54
|
+
if (incoming.method === 'POST' && incoming.headers['content-type'] === 'multipart/mixed') {
|
|
55
|
+
// todo Process Batch
|
|
58
56
|
}
|
|
59
|
-
|
|
60
|
-
let requestProcessed = false;
|
|
61
|
-
const next = async () => {
|
|
62
|
-
const interceptor = this._interceptors[i++];
|
|
63
|
-
if (interceptor) {
|
|
64
|
-
await interceptor(context, next);
|
|
65
|
-
await next();
|
|
66
|
-
}
|
|
67
|
-
else if (!requestProcessed) {
|
|
68
|
-
requestProcessed = true;
|
|
69
|
-
await this.handleExecution(context);
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
await next();
|
|
73
|
-
}
|
|
74
|
-
catch (error) {
|
|
75
|
-
context.errors.push((0, common_1.wrapException)(error));
|
|
76
|
-
}
|
|
77
|
-
// If no response returned to the client we send an error
|
|
78
|
-
if (!outgoing.writableEnded) {
|
|
79
|
-
if (!context.errors.length)
|
|
80
|
-
context.errors.push(new common_1.BadRequestError(`Server can not process this request`));
|
|
81
|
-
await this.handleError(context);
|
|
57
|
+
throw new common_1.BadRequestError();
|
|
82
58
|
}
|
|
59
|
+
let i = 0;
|
|
60
|
+
let requestProcessed = false;
|
|
61
|
+
const next = async () => {
|
|
62
|
+
const interceptor = this._interceptors[i++];
|
|
63
|
+
if (interceptor) {
|
|
64
|
+
await interceptor(context, next);
|
|
65
|
+
await next();
|
|
66
|
+
}
|
|
67
|
+
else if (!requestProcessed) {
|
|
68
|
+
requestProcessed = true;
|
|
69
|
+
await this.handleExecution(context);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
await next();
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (!outgoing.writableEnded)
|
|
76
|
+
await this.sendErrorResponse(context, [error]);
|
|
83
77
|
}
|
|
84
78
|
finally {
|
|
85
79
|
await context.emitAsync('finish');
|
|
@@ -87,22 +81,33 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
87
81
|
}
|
|
88
82
|
async handleExecution(executionContext) {
|
|
89
83
|
// Parse incoming message and create Request object
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
84
|
+
let request;
|
|
85
|
+
try {
|
|
86
|
+
request = await this.parseRequest(executionContext);
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
if (e instanceof common_1.OpraException)
|
|
90
|
+
throw e;
|
|
91
|
+
if (e instanceof vg.ValidationError) {
|
|
92
|
+
throw new common_1.BadRequestError({
|
|
93
|
+
message: (0, common_1.translate)('error:RESPONSE_VALIDATION,', 'Response validation failed'),
|
|
94
|
+
code: 'RESPONSE_VALIDATION',
|
|
95
|
+
details: e.issues
|
|
96
|
+
}, e);
|
|
97
|
+
}
|
|
98
|
+
throw new common_1.BadRequestError(e);
|
|
98
99
|
}
|
|
99
100
|
try {
|
|
101
|
+
const { outgoing } = executionContext.switchToHttp();
|
|
102
|
+
const response = new response_host_js_1.ResponseHost({ http: outgoing });
|
|
103
|
+
const context = request_context_js_1.RequestContext.from(executionContext, request, response);
|
|
104
|
+
await this.executeRequest(context);
|
|
100
105
|
await this.sendResponse(context);
|
|
101
106
|
}
|
|
102
107
|
catch (e) {
|
|
103
108
|
if (e instanceof common_1.OpraException)
|
|
104
109
|
throw e;
|
|
105
|
-
if (e instanceof
|
|
110
|
+
if (e instanceof vg.ValidationError) {
|
|
106
111
|
throw new common_1.InternalServerError({
|
|
107
112
|
message: (0, common_1.translate)('error:RESPONSE_VALIDATION,', 'Response validation failed'),
|
|
108
113
|
code: 'RESPONSE_VALIDATION',
|
|
@@ -150,7 +155,7 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
150
155
|
return request;
|
|
151
156
|
}
|
|
152
157
|
if (resource instanceof common_1.Storage)
|
|
153
|
-
request = await this._parseRequestStorage(executionContext, resource,
|
|
158
|
+
request = await this._parseRequestStorage(executionContext, resource, searchParams);
|
|
154
159
|
else if (urlPath.length === 1) { // Collection and Singleton resources should be last element in path
|
|
155
160
|
if (resource instanceof common_1.Collection)
|
|
156
161
|
request = await this._parseRequestCollection(executionContext, resource, urlPath, searchParams);
|
|
@@ -167,14 +172,12 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
167
172
|
}
|
|
168
173
|
async _parseRequestAction(executionContext, resource, urlPath, searchParams) {
|
|
169
174
|
const p = urlPath[0];
|
|
170
|
-
const { controller, endpoint, handler } = await this.
|
|
175
|
+
const { controller, endpoint, handler } = await this.getActionHandler(resource, p.resource);
|
|
171
176
|
const { incoming } = executionContext.switchToHttp();
|
|
172
177
|
const contentId = incoming.headers['content-id'];
|
|
173
|
-
const params = this.parseParameters(endpoint.parameters,
|
|
178
|
+
const params = this.parseParameters(endpoint.parameters, searchParams);
|
|
174
179
|
return new request_host_js_1.RequestHost({
|
|
175
180
|
endpoint,
|
|
176
|
-
operation: 'action',
|
|
177
|
-
action: endpoint.name,
|
|
178
181
|
controller,
|
|
179
182
|
handler,
|
|
180
183
|
http: incoming,
|
|
@@ -194,11 +197,10 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
194
197
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'create');
|
|
195
198
|
const jsonReader = (0, json_body_loader_js_1.jsonBodyLoader)({ limit: endpoint.inputMaxContentSize }, endpoint);
|
|
196
199
|
let data = await jsonReader(incoming);
|
|
197
|
-
data = endpoint.
|
|
198
|
-
const params = this.parseParameters(endpoint.parameters,
|
|
200
|
+
data = endpoint.decodeInput(data, { coerce: true });
|
|
201
|
+
const params = this.parseParameters(endpoint.parameters, searchParams);
|
|
199
202
|
return new request_host_js_1.RequestHost({
|
|
200
203
|
endpoint,
|
|
201
|
-
operation: 'create',
|
|
202
204
|
controller,
|
|
203
205
|
handler,
|
|
204
206
|
http: incoming,
|
|
@@ -217,10 +219,9 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
217
219
|
case 'DELETE': {
|
|
218
220
|
if (p.key != null) {
|
|
219
221
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'delete');
|
|
220
|
-
const params = this.parseParameters(endpoint.parameters,
|
|
222
|
+
const params = this.parseParameters(endpoint.parameters, searchParams);
|
|
221
223
|
return new request_host_js_1.RequestHost({
|
|
222
224
|
endpoint,
|
|
223
|
-
operation: 'delete',
|
|
224
225
|
controller,
|
|
225
226
|
handler,
|
|
226
227
|
http: incoming,
|
|
@@ -230,10 +231,9 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
230
231
|
});
|
|
231
232
|
}
|
|
232
233
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'deleteMany');
|
|
233
|
-
const params = this.parseParameters(endpoint.parameters,
|
|
234
|
+
const params = this.parseParameters(endpoint.parameters, searchParams);
|
|
234
235
|
return new request_host_js_1.RequestHost({
|
|
235
236
|
endpoint,
|
|
236
|
-
operation: 'deleteMany',
|
|
237
237
|
controller,
|
|
238
238
|
handler,
|
|
239
239
|
http: incoming,
|
|
@@ -247,10 +247,9 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
247
247
|
case 'GET': {
|
|
248
248
|
if (p.key != null) {
|
|
249
249
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'get');
|
|
250
|
-
const params = this.parseParameters(endpoint.parameters,
|
|
250
|
+
const params = this.parseParameters(endpoint.parameters, searchParams);
|
|
251
251
|
return new request_host_js_1.RequestHost({
|
|
252
252
|
endpoint,
|
|
253
|
-
operation: 'get',
|
|
254
253
|
controller,
|
|
255
254
|
handler,
|
|
256
255
|
http: incoming,
|
|
@@ -265,10 +264,9 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
265
264
|
});
|
|
266
265
|
}
|
|
267
266
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'findMany');
|
|
268
|
-
const params = this.parseParameters(endpoint.parameters,
|
|
267
|
+
const params = this.parseParameters(endpoint.parameters, searchParams);
|
|
269
268
|
return new request_host_js_1.RequestHost({
|
|
270
269
|
endpoint,
|
|
271
|
-
operation: 'findMany',
|
|
272
270
|
controller,
|
|
273
271
|
handler,
|
|
274
272
|
http: incoming,
|
|
@@ -288,11 +286,10 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
288
286
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'update');
|
|
289
287
|
const jsonReader = (0, json_body_loader_js_1.jsonBodyLoader)({ limit: endpoint.inputMaxContentSize }, endpoint);
|
|
290
288
|
let data = await jsonReader(incoming);
|
|
291
|
-
data = endpoint.
|
|
292
|
-
const params = this.parseParameters(endpoint.parameters,
|
|
289
|
+
data = endpoint.decodeInput(data, { coerce: true });
|
|
290
|
+
const params = this.parseParameters(endpoint.parameters, searchParams);
|
|
293
291
|
return new request_host_js_1.RequestHost({
|
|
294
292
|
endpoint,
|
|
295
|
-
operation: 'update',
|
|
296
293
|
controller,
|
|
297
294
|
handler,
|
|
298
295
|
http: incoming,
|
|
@@ -310,11 +307,10 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
310
307
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'updateMany');
|
|
311
308
|
const jsonReader = (0, json_body_loader_js_1.jsonBodyLoader)({ limit: endpoint.inputMaxContentSize }, endpoint);
|
|
312
309
|
let data = await jsonReader(incoming);
|
|
313
|
-
data = endpoint.
|
|
314
|
-
const params = this.parseParameters(endpoint.parameters,
|
|
310
|
+
data = endpoint.decodeInput(data, { coerce: true });
|
|
311
|
+
const params = this.parseParameters(endpoint.parameters, searchParams);
|
|
315
312
|
return new request_host_js_1.RequestHost({
|
|
316
313
|
endpoint,
|
|
317
|
-
operation: 'updateMany',
|
|
318
314
|
controller,
|
|
319
315
|
handler,
|
|
320
316
|
http: incoming,
|
|
@@ -336,17 +332,15 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
336
332
|
if ((incoming.method === 'POST' || incoming.method === 'PATCH') && !incoming.is('json'))
|
|
337
333
|
throw new common_1.BadRequestError({ message: 'Unsupported Content-Type' });
|
|
338
334
|
const contentId = incoming.headers['content-id'];
|
|
339
|
-
const p = urlPath[0];
|
|
340
335
|
switch (incoming.method) {
|
|
341
336
|
case 'POST': {
|
|
342
337
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'create');
|
|
343
338
|
const jsonReader = (0, json_body_loader_js_1.jsonBodyLoader)({ limit: endpoint.inputMaxContentSize }, endpoint);
|
|
344
339
|
let data = await jsonReader(incoming);
|
|
345
|
-
data = endpoint.
|
|
346
|
-
const params = this.parseParameters(endpoint.parameters,
|
|
340
|
+
data = endpoint.decodeInput(data, { coerce: true });
|
|
341
|
+
const params = this.parseParameters(endpoint.parameters, searchParams);
|
|
347
342
|
return new request_host_js_1.RequestHost({
|
|
348
343
|
endpoint,
|
|
349
|
-
operation: 'create',
|
|
350
344
|
controller,
|
|
351
345
|
handler,
|
|
352
346
|
http: incoming,
|
|
@@ -362,10 +356,9 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
362
356
|
}
|
|
363
357
|
case 'DELETE': {
|
|
364
358
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'delete');
|
|
365
|
-
const params = this.parseParameters(endpoint.parameters,
|
|
359
|
+
const params = this.parseParameters(endpoint.parameters, searchParams);
|
|
366
360
|
return new request_host_js_1.RequestHost({
|
|
367
361
|
endpoint,
|
|
368
|
-
operation: 'delete',
|
|
369
362
|
controller,
|
|
370
363
|
handler,
|
|
371
364
|
http: incoming,
|
|
@@ -375,10 +368,9 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
375
368
|
}
|
|
376
369
|
case 'GET': {
|
|
377
370
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'get');
|
|
378
|
-
const params = this.parseParameters(endpoint.parameters,
|
|
371
|
+
const params = this.parseParameters(endpoint.parameters, searchParams);
|
|
379
372
|
return new request_host_js_1.RequestHost({
|
|
380
373
|
endpoint,
|
|
381
|
-
operation: 'get',
|
|
382
374
|
controller,
|
|
383
375
|
handler,
|
|
384
376
|
http: incoming,
|
|
@@ -395,11 +387,10 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
395
387
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'update');
|
|
396
388
|
const jsonReader = (0, json_body_loader_js_1.jsonBodyLoader)({ limit: endpoint.inputMaxContentSize }, endpoint);
|
|
397
389
|
let data = await jsonReader(incoming);
|
|
398
|
-
data = endpoint.
|
|
399
|
-
const params = this.parseParameters(endpoint.parameters,
|
|
390
|
+
data = endpoint.decodeInput(data, { coerce: true });
|
|
391
|
+
const params = this.parseParameters(endpoint.parameters, searchParams);
|
|
400
392
|
return new request_host_js_1.RequestHost({
|
|
401
393
|
endpoint,
|
|
402
|
-
operation: 'update',
|
|
403
394
|
controller,
|
|
404
395
|
handler,
|
|
405
396
|
http: incoming,
|
|
@@ -418,17 +409,15 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
418
409
|
message: `Singleton resource doesn't accept http "${incoming.method}" method`
|
|
419
410
|
});
|
|
420
411
|
}
|
|
421
|
-
async _parseRequestStorage(executionContext, resource,
|
|
412
|
+
async _parseRequestStorage(executionContext, resource, searchParams) {
|
|
422
413
|
const { incoming } = executionContext.switchToHttp();
|
|
423
414
|
const contentId = incoming.headers['content-id'];
|
|
424
|
-
const p = urlPath[0];
|
|
425
415
|
switch (incoming.method) {
|
|
426
416
|
case 'GET': {
|
|
427
417
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'get');
|
|
428
|
-
const params = this.parseParameters(endpoint.parameters,
|
|
418
|
+
const params = this.parseParameters(endpoint.parameters, searchParams);
|
|
429
419
|
return new request_host_js_1.RequestHost({
|
|
430
420
|
endpoint,
|
|
431
|
-
operation: 'get',
|
|
432
421
|
controller,
|
|
433
422
|
handler,
|
|
434
423
|
http: incoming,
|
|
@@ -439,10 +428,9 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
439
428
|
}
|
|
440
429
|
case 'DELETE': {
|
|
441
430
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'delete');
|
|
442
|
-
const params = this.parseParameters(endpoint.parameters,
|
|
431
|
+
const params = this.parseParameters(endpoint.parameters, searchParams);
|
|
443
432
|
return new request_host_js_1.RequestHost({
|
|
444
433
|
endpoint,
|
|
445
|
-
operation: 'delete',
|
|
446
434
|
controller,
|
|
447
435
|
handler,
|
|
448
436
|
http: incoming,
|
|
@@ -453,7 +441,7 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
453
441
|
}
|
|
454
442
|
case 'POST': {
|
|
455
443
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'post');
|
|
456
|
-
const params = this.parseParameters(endpoint.parameters,
|
|
444
|
+
const params = this.parseParameters(endpoint.parameters, searchParams);
|
|
457
445
|
await promises_1.default.mkdir(this._tempDir, { recursive: true });
|
|
458
446
|
const multipartIterator = new multipart_helper_js_1.MultipartIterator(incoming, {
|
|
459
447
|
...endpoint.options,
|
|
@@ -468,7 +456,6 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
468
456
|
});
|
|
469
457
|
return new request_host_js_1.RequestHost({
|
|
470
458
|
endpoint,
|
|
471
|
-
operation: 'post',
|
|
472
459
|
controller,
|
|
473
460
|
handler,
|
|
474
461
|
http: incoming,
|
|
@@ -483,13 +470,15 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
483
470
|
message: `Storage resource doesn't accept http "${incoming.method}" method`
|
|
484
471
|
});
|
|
485
472
|
}
|
|
486
|
-
parseParameters(paramDefs,
|
|
473
|
+
parseParameters(paramDefs, searchParams) {
|
|
487
474
|
const out = {};
|
|
488
475
|
// Parse known parameters
|
|
489
476
|
for (const [k, prm] of paramDefs.entries()) {
|
|
490
477
|
const decode = prm.getDecoder();
|
|
491
478
|
let v = searchParams?.getAll(k);
|
|
492
479
|
try {
|
|
480
|
+
if (!v.length && prm.default != null)
|
|
481
|
+
v = [prm.default];
|
|
493
482
|
if (!prm.isArray) {
|
|
494
483
|
v = v[0];
|
|
495
484
|
v = decode(v, { coerce: true });
|
|
@@ -522,38 +511,51 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
522
511
|
async executeRequest(context) {
|
|
523
512
|
const { request } = context;
|
|
524
513
|
const { response } = context;
|
|
525
|
-
const { resource, handler } = request;
|
|
514
|
+
const { endpoint, resource, handler } = request;
|
|
526
515
|
// Call endpoint handler method
|
|
527
516
|
let value;
|
|
528
517
|
try {
|
|
529
518
|
value = await handler.call(request.controller, context);
|
|
530
519
|
if (response.value == null)
|
|
531
520
|
response.value = value;
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
if (
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
affected = value;
|
|
538
|
-
if (typeof value === 'boolean')
|
|
539
|
-
affected = value ? 1 : 0;
|
|
540
|
-
if (typeof value === 'object')
|
|
541
|
-
affected = value.affected || value.affectedRows ||
|
|
542
|
-
(operation === 'updateMany' ? value.updated : value.deleted);
|
|
543
|
-
response.value = affected;
|
|
521
|
+
// Normalize response value
|
|
522
|
+
if (endpoint.kind === 'operation') {
|
|
523
|
+
if (resource instanceof common_1.Storage && endpoint.name === 'post') {
|
|
524
|
+
// Count file parts
|
|
525
|
+
value = context.request.parts.items.reduce((n, item) => item.file ? n + 1 : n, 0);
|
|
544
526
|
}
|
|
545
|
-
|
|
527
|
+
if (resource instanceof common_1.Collection || resource instanceof common_1.Singleton || resource instanceof common_1.Storage) {
|
|
528
|
+
const operationName = endpoint.name;
|
|
529
|
+
if (operationName === 'delete' || operationName === 'deleteMany' ||
|
|
530
|
+
operationName === 'updateMany' || operationName === 'post') {
|
|
531
|
+
let affected = 0;
|
|
532
|
+
if (typeof value === 'number')
|
|
533
|
+
affected = value;
|
|
534
|
+
else if (typeof value === 'boolean')
|
|
535
|
+
affected = value ? 1 : 0;
|
|
536
|
+
else if (typeof value === 'object')
|
|
537
|
+
affected = value.affected || value.affectedRows ||
|
|
538
|
+
(operationName === 'updateMany' ? value.updated : value.deleted);
|
|
539
|
+
response.value = affected;
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
if (resource instanceof common_1.Storage)
|
|
543
|
+
return;
|
|
546
544
|
// "get" and "update" endpoints must return the entity instance, otherwise it means resource not found
|
|
547
|
-
if (value == null && (
|
|
545
|
+
if (value == null && (operationName === 'get' || operationName === 'update'))
|
|
548
546
|
throw new common_1.ResourceNotFoundError(resource.name, request.key);
|
|
549
547
|
// "findMany" endpoint should return array of entity instances
|
|
550
|
-
if (
|
|
551
|
-
value = value == null ? [] : Array.isArray(value) ? value : [value];
|
|
548
|
+
if (operationName === 'findMany')
|
|
549
|
+
value = (value == null ? [] : Array.isArray(value) ? value : [value]);
|
|
552
550
|
else
|
|
553
551
|
value = value == null ? {} : Array.isArray(value) ? value[0] : value;
|
|
552
|
+
value = endpoint.encodeReturning(value, { coerce: true });
|
|
554
553
|
response.value = value;
|
|
554
|
+
return;
|
|
555
555
|
}
|
|
556
556
|
}
|
|
557
|
+
if (response.value)
|
|
558
|
+
response.value = endpoint.encodeReturning(response.value, { coerce: true });
|
|
557
559
|
}
|
|
558
560
|
catch (error) {
|
|
559
561
|
response.errors.push(error);
|
|
@@ -561,77 +563,96 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
561
563
|
}
|
|
562
564
|
async sendResponse(context) {
|
|
563
565
|
const { request, response } = context;
|
|
566
|
+
const { endpoint, resource } = request;
|
|
564
567
|
const outgoing = response.switchToHttp();
|
|
565
|
-
if (
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
568
|
+
if (response.errors?.length || (outgoing.statusCode >= 400 && outgoing.statusCode <= 599))
|
|
569
|
+
return this.sendErrorResponse(context, response.errors || []);
|
|
570
|
+
// if response redirected we do not send any response
|
|
571
|
+
if (outgoing.statusCode >= 300 && outgoing.statusCode < 400) {
|
|
572
|
+
outgoing.end();
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
let contentType = String(outgoing.getHeader('content-type') || '');
|
|
576
|
+
const returnType = endpoint.returnType;
|
|
577
|
+
if (endpoint.kind === 'action' && !contentType && endpoint.returnMime && response.value) {
|
|
578
|
+
contentType = endpoint.returnMime;
|
|
579
|
+
outgoing.setHeader('Content-Type', contentType);
|
|
580
|
+
}
|
|
581
|
+
// OperationResult response
|
|
582
|
+
if ((endpoint.kind === 'operation' &&
|
|
583
|
+
(resource instanceof common_1.Collection || resource instanceof common_1.Singleton ||
|
|
584
|
+
(resource instanceof common_1.Storage && endpoint.name !== 'get'))) ||
|
|
585
|
+
(endpoint.kind === 'action' &&
|
|
586
|
+
(!contentType || type_is_1.default.is(contentType, ['application/opra+json'])))) {
|
|
587
|
+
const incoming = context.switchToHttp().incoming;
|
|
588
|
+
const apiUrl = new common_1.OpraURL(incoming.baseUrl, incoming.protocol + '://' + incoming.get('host')).toString();
|
|
589
|
+
const body = new common_1.OperationResult({
|
|
590
|
+
context: endpoint.getFullPath(false),
|
|
591
|
+
contextUrl: apiUrl + '/#' + endpoint.getFullPath(true)
|
|
592
|
+
});
|
|
593
|
+
const operationName = endpoint.kind === 'operation' ? endpoint.name : '';
|
|
594
|
+
if (operationName === 'delete' || operationName === 'deleteMany' ||
|
|
595
|
+
operationName === 'updateMany' || operationName === 'post')
|
|
596
|
+
body.affected = response.value;
|
|
597
|
+
else {
|
|
598
|
+
outgoing.statusCode = outgoing.statusCode || common_1.HttpStatusCodes.OK;
|
|
599
|
+
if (operationName === 'create')
|
|
600
|
+
outgoing.statusCode = 201;
|
|
601
|
+
if (operationName === 'update' || operationName === 'create')
|
|
602
|
+
body.affected = response.value ? 1 : 0;
|
|
603
|
+
if (operationName === 'findMany') {
|
|
604
|
+
body.count = response.value.length;
|
|
605
|
+
body.totalMatches = response.totalMatches;
|
|
577
606
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
607
|
+
if (returnType) {
|
|
608
|
+
if (response.value == null)
|
|
609
|
+
throw new common_1.InternalServerError(`"${request.endpoint.name}" endpoint should return value`);
|
|
610
|
+
if (returnType.name) {
|
|
611
|
+
const ns = this.api.getDataTypeNs(returnType);
|
|
612
|
+
// const isOpraSpec = returnType.document.url?.startsWith('https://oprajs.com/spec/v1.0')
|
|
613
|
+
body.type = (ns ? ns + ':' : '') + returnType.name;
|
|
614
|
+
body.typeUrl =
|
|
615
|
+
(ns
|
|
616
|
+
? new common_1.OpraURL('/#/types/' + returnType.name, returnType.document.url || 'http://tempuri.org').toString()
|
|
617
|
+
: apiUrl + '/#/types/' + returnType.name);
|
|
618
|
+
}
|
|
619
|
+
else
|
|
620
|
+
body.typeUrl = body.contextUrl + '/type';
|
|
621
|
+
body.payload = this.i18n.deep(response.value);
|
|
581
622
|
}
|
|
582
623
|
}
|
|
624
|
+
outgoing.setHeader(common_1.HttpHeaderCodes.Content_Type, 'application/opra+json; charset=utf-8');
|
|
625
|
+
outgoing.send(JSON.stringify(body));
|
|
583
626
|
outgoing.end();
|
|
584
627
|
return;
|
|
585
628
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
if (response.value != null)
|
|
598
|
-
responseValue = responseObject.data = request.endpoint.encode(response.value, { coerce: true });
|
|
599
|
-
}
|
|
600
|
-
if (request.operation === 'action') {
|
|
601
|
-
if (responseValue != null)
|
|
602
|
-
responseObject.data = responseValue;
|
|
603
|
-
}
|
|
604
|
-
else if (request.resource instanceof common_1.Collection || request.resource instanceof common_1.Singleton) {
|
|
605
|
-
if (request.operation === 'delete' || request.operation === 'deleteMany' ||
|
|
606
|
-
request.operation === 'updateMany') {
|
|
607
|
-
responseObject.affected = responseValue || 0;
|
|
629
|
+
outgoing.statusCode = outgoing.statusCode || common_1.HttpStatusCodes.OK;
|
|
630
|
+
if (response.value != null) {
|
|
631
|
+
if (typeof response.value === 'string') {
|
|
632
|
+
if (!contentType)
|
|
633
|
+
outgoing.setHeader('content-type', 'text/plain');
|
|
634
|
+
outgoing.send(response.value);
|
|
635
|
+
}
|
|
636
|
+
else if (Buffer.isBuffer(response.value) || (0, common_1.isReadable)(response.value)) {
|
|
637
|
+
if (!contentType)
|
|
638
|
+
outgoing.setHeader('content-type', 'application/octet-stream');
|
|
639
|
+
outgoing.send(response.value);
|
|
608
640
|
}
|
|
609
641
|
else {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
if (request.operation === 'create')
|
|
613
|
-
outgoing.statusCode = 201;
|
|
614
|
-
if (request.operation === 'create' || request.operation === 'update')
|
|
615
|
-
responseObject.affected = 1;
|
|
616
|
-
else if (request.operation === 'get' || request.operation === 'update')
|
|
617
|
-
responseObject.key = request.key;
|
|
618
|
-
if (request.operation === 'findMany' && response.count != null && response.count >= 0)
|
|
619
|
-
responseObject.totalCount = response.count;
|
|
642
|
+
outgoing.setHeader('content-type', 'application/json; charset=utf-8');
|
|
643
|
+
outgoing.send(JSON.stringify(response.value));
|
|
620
644
|
}
|
|
621
|
-
outgoing.statusCode = outgoing.statusCode || common_1.HttpStatusCodes.OK;
|
|
622
645
|
}
|
|
623
|
-
const body = this.i18n.deep(responseObject);
|
|
624
|
-
outgoing.setHeader(common_1.HttpHeaderCodes.Content_Type, 'application/opra+json; charset=utf-8');
|
|
625
|
-
outgoing.send(JSON.stringify(body));
|
|
626
646
|
outgoing.end();
|
|
627
647
|
}
|
|
628
|
-
async
|
|
629
|
-
const { errors } = context;
|
|
648
|
+
async sendErrorResponse(context, errors) {
|
|
630
649
|
const { outgoing } = context.switchToHttp();
|
|
631
650
|
if (outgoing.headersSent) {
|
|
632
651
|
outgoing.end();
|
|
633
652
|
return;
|
|
634
653
|
}
|
|
654
|
+
if (!errors.length)
|
|
655
|
+
errors.push((0, common_1.wrapException)({ status: outgoing.statusCode || 500 }));
|
|
635
656
|
errors.forEach(x => {
|
|
636
657
|
if (x instanceof common_1.OpraException) {
|
|
637
658
|
switch (x.severity) {
|
|
@@ -663,9 +684,9 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
663
684
|
status = common_1.HttpStatusCodes.INTERNAL_SERVER_ERROR;
|
|
664
685
|
}
|
|
665
686
|
outgoing.statusCode = status;
|
|
666
|
-
const body = {
|
|
687
|
+
const body = new common_1.OperationResult({
|
|
667
688
|
errors: wrappedErrors.map(x => this._i18n.deep(x.toJSON()))
|
|
668
|
-
};
|
|
689
|
+
});
|
|
669
690
|
outgoing.setHeader(common_1.HttpHeaderCodes.Content_Type, 'application/opra+json; charset=utf-8');
|
|
670
691
|
outgoing.setHeader(common_1.HttpHeaderCodes.Cache_Control, 'no-cache');
|
|
671
692
|
outgoing.setHeader(common_1.HttpHeaderCodes.Pragma, 'no-cache');
|