@opra/core 1.0.0-alpha.8 → 1.0.0-beta.1
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/augmentation/18n.augmentation.js +13 -13
- package/cjs/constants.js +1 -2
- package/cjs/execution-context.js +1 -0
- package/cjs/http/express-adapter.js +22 -21
- package/cjs/http/http-adapter.js +2 -5
- package/cjs/http/http-context.js +16 -31
- package/cjs/http/{impl/http-handler.js → http-handler.js} +194 -169
- package/cjs/http/impl/http-outgoing.host.js +2 -2
- package/cjs/http/impl/multipart-reader.js +141 -44
- package/cjs/http/utils/body-reader.js +0 -1
- package/cjs/http/utils/common.js +4 -4
- package/cjs/http/utils/concat-readable.js +1 -2
- package/cjs/http/utils/convert-to-headers.js +2 -3
- package/cjs/http/utils/convert-to-raw-headers.js +1 -2
- package/cjs/http/utils/match-known-fields.js +2 -2
- package/cjs/http/utils/wrap-exception.js +1 -2
- package/cjs/index.js +1 -1
- package/cjs/platform-adapter.js +0 -3
- package/cjs/type-guards.js +4 -5
- package/esm/augmentation/18n.augmentation.js +2 -2
- package/esm/constants.js +0 -1
- package/esm/execution-context.js +1 -0
- package/esm/http/express-adapter.js +22 -21
- package/esm/http/http-adapter.js +2 -5
- package/esm/http/http-context.js +17 -32
- package/esm/http/{impl/http-handler.js → http-handler.js} +195 -170
- package/esm/http/impl/http-outgoing.host.js +1 -1
- package/esm/http/impl/multipart-reader.js +142 -45
- package/esm/http/utils/body-reader.js +0 -1
- package/esm/index.js +1 -1
- package/esm/package.json +3 -0
- package/esm/platform-adapter.js +0 -3
- package/package.json +35 -63
- package/types/augmentation/http-controller.augmentation.d.ts +1 -2
- package/types/constants.d.ts +0 -1
- package/types/execution-context.d.ts +2 -1
- package/types/helpers/service-base.d.ts +1 -1
- package/types/http/express-adapter.d.ts +1 -1
- package/types/http/http-adapter.d.ts +35 -8
- package/types/http/http-context.d.ts +4 -4
- package/types/http/{impl/http-handler.d.ts → http-handler.d.ts} +11 -9
- package/types/http/impl/http-incoming.host.d.ts +1 -2
- package/types/http/impl/http-outgoing.host.d.ts +1 -1
- package/types/http/impl/multipart-reader.d.ts +38 -20
- package/types/http/impl/node-incoming-message.host.d.ts +3 -7
- package/types/http/impl/node-outgoing-message.host.d.ts +5 -8
- package/types/http/interfaces/http-incoming.interface.d.ts +2 -3
- package/types/http/interfaces/http-outgoing.interface.d.ts +2 -2
- package/types/http/interfaces/node-incoming-message.interface.d.ts +0 -2
- package/types/http/interfaces/node-outgoing-message.interface.d.ts +1 -3
- package/types/http/utils/body-reader.d.ts +1 -4
- package/types/http/utils/concat-readable.d.ts +0 -1
- package/types/http/utils/convert-to-raw-headers.d.ts +1 -2
- package/types/index.d.cts +28 -0
- package/types/index.d.ts +1 -1
- package/types/platform-adapter.d.ts +0 -4
- package/cjs/helpers/logger.js +0 -35
- package/esm/helpers/logger.js +0 -31
- package/i18n/i18n/en/error.json +0 -21
- package/types/helpers/logger.d.ts +0 -14
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import * as process from 'node:process';
|
|
2
2
|
import typeIs from '@browsery/type-is';
|
|
3
|
-
import { BadRequestError, HttpHeaderCodes, HttpStatusCode, InternalServerError, isBlob, isReadableStream, IssueSeverity, MethodNotAllowedError, MimeTypes, OperationResult, OpraException, OpraSchema,
|
|
3
|
+
import { BadRequestError, HttpHeaderCodes, HttpStatusCode, InternalServerError, isBlob, isReadableStream, IssueSeverity, MethodNotAllowedError, MimeTypes, OperationResult, OpraException, OpraSchema, safeJsonStringify, } from '@opra/common';
|
|
4
4
|
import { parse as parseContentType } from 'content-type';
|
|
5
5
|
import { splitString } from 'fast-tokenizer';
|
|
6
|
+
import { md5 } from 'super-fast-md5';
|
|
6
7
|
import { asMutable } from 'ts-gems';
|
|
7
8
|
import { toArray, ValidationError, vg } from 'valgen';
|
|
8
|
-
import { kAssetCache } from '
|
|
9
|
-
import { wrapException } from '
|
|
9
|
+
import { kAssetCache } from '../constants.js';
|
|
10
|
+
import { wrapException } from './utils/wrap-exception.js';
|
|
10
11
|
/**
|
|
11
12
|
* @class HttpHandler
|
|
12
13
|
*/
|
|
@@ -38,7 +39,7 @@ export class HttpHandler {
|
|
|
38
39
|
throw e;
|
|
39
40
|
if (e instanceof ValidationError) {
|
|
40
41
|
throw new BadRequestError({
|
|
41
|
-
message:
|
|
42
|
+
message: 'Response validation failed',
|
|
42
43
|
code: 'RESPONSE_VALIDATION',
|
|
43
44
|
details: e.issues,
|
|
44
45
|
}, e);
|
|
@@ -48,11 +49,14 @@ export class HttpHandler {
|
|
|
48
49
|
await this.adapter.emitAsync('request', context);
|
|
49
50
|
// Call interceptors than execute request
|
|
50
51
|
if (this.adapter.interceptors) {
|
|
52
|
+
const interceptors = this.adapter.interceptors;
|
|
51
53
|
let i = 0;
|
|
52
54
|
const next = async () => {
|
|
53
|
-
const interceptor =
|
|
54
|
-
if (interceptor)
|
|
55
|
+
const interceptor = interceptors[i++];
|
|
56
|
+
if (typeof interceptor === 'function')
|
|
55
57
|
await interceptor(context, next);
|
|
58
|
+
else if (typeof interceptor?.intercept === 'function')
|
|
59
|
+
await interceptor.intercept(context, next);
|
|
56
60
|
await this._executeRequest(context);
|
|
57
61
|
};
|
|
58
62
|
await next();
|
|
@@ -64,19 +68,17 @@ export class HttpHandler {
|
|
|
64
68
|
let e = error;
|
|
65
69
|
if (e instanceof ValidationError) {
|
|
66
70
|
e = new InternalServerError({
|
|
67
|
-
message:
|
|
71
|
+
message: 'Response validation failed',
|
|
68
72
|
code: 'RESPONSE_VALIDATION',
|
|
69
73
|
details: e.issues,
|
|
70
74
|
}, e);
|
|
71
75
|
}
|
|
72
76
|
else
|
|
73
77
|
e = wrapException(e);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
response.end();
|
|
79
|
-
});
|
|
78
|
+
if (this.onError)
|
|
79
|
+
await this.onError(context, error);
|
|
80
|
+
context.errors.push(e);
|
|
81
|
+
await this.sendResponse(context);
|
|
80
82
|
}
|
|
81
83
|
finally {
|
|
82
84
|
await context.emitAsync('finish');
|
|
@@ -89,7 +91,7 @@ export class HttpHandler {
|
|
|
89
91
|
async parseRequest(context) {
|
|
90
92
|
await this._parseParameters(context);
|
|
91
93
|
await this._parseContentType(context);
|
|
92
|
-
if (context.operation
|
|
94
|
+
if (context.operation?.requestBody?.immediateFetch)
|
|
93
95
|
await context.getBody();
|
|
94
96
|
/** Set default status code as the first status code between 200 and 299 */
|
|
95
97
|
if (context.operation) {
|
|
@@ -109,6 +111,8 @@ export class HttpHandler {
|
|
|
109
111
|
*/
|
|
110
112
|
async _parseParameters(context) {
|
|
111
113
|
const { operation, request } = context;
|
|
114
|
+
if (!operation)
|
|
115
|
+
return;
|
|
112
116
|
let key = '';
|
|
113
117
|
try {
|
|
114
118
|
const onFail = (issue) => {
|
|
@@ -210,6 +214,7 @@ export class HttpHandler {
|
|
|
210
214
|
}
|
|
211
215
|
}
|
|
212
216
|
for (const prm of paramsLeft) {
|
|
217
|
+
key = String(prm.name);
|
|
213
218
|
// Throw error for required parameters
|
|
214
219
|
if (prm.required) {
|
|
215
220
|
const decode = getDecoder(prm);
|
|
@@ -235,6 +240,8 @@ export class HttpHandler {
|
|
|
235
240
|
*/
|
|
236
241
|
async _parseContentType(context) {
|
|
237
242
|
const { request, operation } = context;
|
|
243
|
+
if (!operation)
|
|
244
|
+
return;
|
|
238
245
|
if (operation.requestBody?.content.length) {
|
|
239
246
|
let mediaType;
|
|
240
247
|
let contentType = request.header('content-type');
|
|
@@ -261,7 +268,7 @@ export class HttpHandler {
|
|
|
261
268
|
const responseValue = await context.operationHandler.call(context.controllerInstance, context);
|
|
262
269
|
const { response } = context;
|
|
263
270
|
if (!response.writableEnded) {
|
|
264
|
-
await this.
|
|
271
|
+
await this.sendResponse(context, responseValue).finally(() => {
|
|
265
272
|
if (!response.writableEnded)
|
|
266
273
|
response.end();
|
|
267
274
|
});
|
|
@@ -273,94 +280,182 @@ export class HttpHandler {
|
|
|
273
280
|
* @param responseValue
|
|
274
281
|
* @protected
|
|
275
282
|
*/
|
|
276
|
-
async
|
|
283
|
+
async sendResponse(context, responseValue) {
|
|
284
|
+
if (context.errors.length)
|
|
285
|
+
return this._sendErrorResponse(context);
|
|
277
286
|
const { response } = context;
|
|
278
287
|
const { document } = this.adapter;
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
288
|
+
try {
|
|
289
|
+
const responseArgs = this._determineResponseArgs(context, responseValue);
|
|
290
|
+
const { operationResponse, statusCode } = responseArgs;
|
|
291
|
+
let { contentType, body } = responseArgs;
|
|
292
|
+
const operationResultType = document.node.getDataType(OperationResult);
|
|
293
|
+
let operationResultEncoder = this[kAssetCache].get(operationResultType, 'encode');
|
|
294
|
+
if (!operationResultEncoder) {
|
|
295
|
+
operationResultEncoder = operationResultType.generateCodec('encode', {
|
|
296
|
+
ignoreWriteonlyFields: true,
|
|
297
|
+
ignoreHiddenFields: true,
|
|
298
|
+
});
|
|
299
|
+
this[kAssetCache].set(operationResultType, 'encode', operationResultEncoder);
|
|
300
|
+
}
|
|
301
|
+
/** Validate response */
|
|
302
|
+
if (operationResponse?.type) {
|
|
303
|
+
if (!(body == null && statusCode === HttpStatusCode.NO_CONTENT)) {
|
|
304
|
+
/** Generate encoder */
|
|
305
|
+
const projection = responseArgs.projection || '*';
|
|
306
|
+
const assetKey = md5(String(projection));
|
|
307
|
+
let encode = this[kAssetCache].get(operationResponse, 'encode:' + assetKey);
|
|
308
|
+
if (!encode) {
|
|
309
|
+
encode = operationResponse.type.generateCodec('encode', {
|
|
310
|
+
partial: operationResponse.partial,
|
|
311
|
+
projection,
|
|
312
|
+
ignoreWriteonlyFields: true,
|
|
313
|
+
ignoreHiddenFields: true,
|
|
314
|
+
onFail: issue => `Response body validation failed: ` + issue.message,
|
|
315
|
+
});
|
|
316
|
+
if (operationResponse) {
|
|
317
|
+
if (operationResponse.isArray)
|
|
318
|
+
encode = vg.isArray(encode);
|
|
319
|
+
this[kAssetCache].set(operationResponse, 'encode:' + assetKey, encode);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/** Encode body */
|
|
323
|
+
if (operationResponse.type.extendsFrom(operationResultType)) {
|
|
324
|
+
if (body instanceof OperationResult)
|
|
325
|
+
body = encode(body);
|
|
326
|
+
else {
|
|
327
|
+
body.payload = encode(body.payload);
|
|
328
|
+
body = operationResultEncoder(body);
|
|
329
|
+
}
|
|
303
330
|
}
|
|
304
|
-
}
|
|
305
|
-
/** Encode body */
|
|
306
|
-
if (operationResponse.type.extendsFrom(operationResultType)) {
|
|
307
|
-
if (body instanceof OperationResult)
|
|
308
|
-
body = encode(body);
|
|
309
331
|
else {
|
|
310
|
-
body
|
|
311
|
-
|
|
332
|
+
if (body instanceof OperationResult &&
|
|
333
|
+
contentType &&
|
|
334
|
+
typeIs.is(contentType, [MimeTypes.opra_response_json])) {
|
|
335
|
+
body.payload = encode(body.payload);
|
|
336
|
+
body = operationResultEncoder(body);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
body = encode(body);
|
|
340
|
+
}
|
|
312
341
|
}
|
|
313
|
-
}
|
|
314
|
-
else {
|
|
315
342
|
if (body instanceof OperationResult &&
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
body.
|
|
319
|
-
body = operationResultEncoder(body);
|
|
343
|
+
operationResponse.type &&
|
|
344
|
+
operationResponse.type !== document.node.getDataType(OperationResult)) {
|
|
345
|
+
body.type = operationResponse.type.name ? operationResponse.type.name : '#embedded';
|
|
320
346
|
}
|
|
321
|
-
else
|
|
322
|
-
body = encode(body);
|
|
323
|
-
}
|
|
324
|
-
if (body instanceof OperationResult && operationResponse.type) {
|
|
325
|
-
body.type = operationResponse.type.name ? operationResponse.type.name : '#embedded';
|
|
326
347
|
}
|
|
327
348
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
349
|
+
else if (body != null) {
|
|
350
|
+
if (body instanceof OperationResult) {
|
|
351
|
+
body = operationResultEncoder(body);
|
|
352
|
+
contentType = MimeTypes.opra_response_json;
|
|
353
|
+
}
|
|
354
|
+
else if (Buffer.isBuffer(body))
|
|
355
|
+
contentType = MimeTypes.binary;
|
|
356
|
+
else if (typeof body === 'object') {
|
|
357
|
+
contentType = contentType || MimeTypes.json;
|
|
358
|
+
if (typeof body.toJSON === 'function')
|
|
359
|
+
body = body.toJSON();
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
contentType = contentType || MimeTypes.text;
|
|
363
|
+
body = String(body);
|
|
364
|
+
}
|
|
340
365
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
366
|
+
/** Set content-type header value if not set */
|
|
367
|
+
if (contentType && contentType !== responseArgs.contentType)
|
|
368
|
+
response.setHeader('content-type', contentType);
|
|
369
|
+
response.status(statusCode);
|
|
370
|
+
if (body == null) {
|
|
371
|
+
response.end();
|
|
372
|
+
return;
|
|
344
373
|
}
|
|
374
|
+
let x;
|
|
375
|
+
if (Buffer.isBuffer(body) || isReadableStream(body))
|
|
376
|
+
x = body;
|
|
377
|
+
else if (isBlob(body))
|
|
378
|
+
x = body.stream();
|
|
379
|
+
else if (typeof body === 'object')
|
|
380
|
+
x = JSON.stringify(body);
|
|
381
|
+
else
|
|
382
|
+
x = String(body);
|
|
383
|
+
response.end(x);
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
context.errors.push(error);
|
|
387
|
+
return this._sendErrorResponse(context);
|
|
345
388
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
389
|
+
}
|
|
390
|
+
async _sendErrorResponse(context) {
|
|
391
|
+
context.errors = this._wrapExceptions(context.errors);
|
|
392
|
+
try {
|
|
393
|
+
await this.adapter.emitAsync('error', context);
|
|
394
|
+
context.errors = this._wrapExceptions(context.errors);
|
|
395
|
+
}
|
|
396
|
+
catch (e) {
|
|
397
|
+
context.errors = this._wrapExceptions([e, ...context.errors]);
|
|
398
|
+
}
|
|
399
|
+
const { response, errors } = context;
|
|
400
|
+
if (response.headersSent) {
|
|
351
401
|
response.end();
|
|
352
402
|
return;
|
|
353
403
|
}
|
|
354
|
-
let
|
|
355
|
-
if (
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
404
|
+
let status = response.statusCode || 0;
|
|
405
|
+
if (!status || status < Number(HttpStatusCode.BAD_REQUEST)) {
|
|
406
|
+
status = errors[0].status;
|
|
407
|
+
if (status < Number(HttpStatusCode.BAD_REQUEST))
|
|
408
|
+
status = HttpStatusCode.INTERNAL_SERVER_ERROR;
|
|
409
|
+
}
|
|
410
|
+
response.statusCode = status;
|
|
411
|
+
const { document } = this.adapter;
|
|
412
|
+
const dt = document.node.getComplexType('OperationResult');
|
|
413
|
+
let encode = this[kAssetCache].get(dt, 'encode');
|
|
414
|
+
if (!encode) {
|
|
415
|
+
encode = dt.generateCodec('encode', { ignoreWriteonlyFields: true });
|
|
416
|
+
this[kAssetCache].set(dt, 'encode', encode);
|
|
417
|
+
}
|
|
418
|
+
// const { i18n } = this.adapter;
|
|
419
|
+
const bodyObject = new OperationResult({
|
|
420
|
+
errors: errors.map(x => {
|
|
421
|
+
const o = x.toJSON();
|
|
422
|
+
if (!(process.env.NODE_ENV === 'dev' || process.env.NODE_ENV === 'development'))
|
|
423
|
+
delete o.stack;
|
|
424
|
+
return o; // i18n.deep(o);
|
|
425
|
+
}),
|
|
426
|
+
});
|
|
427
|
+
const body = encode(bodyObject);
|
|
428
|
+
response.setHeader(HttpHeaderCodes.Content_Type, MimeTypes.opra_response_json + '; charset=utf-8');
|
|
429
|
+
response.setHeader(HttpHeaderCodes.Cache_Control, 'no-cache');
|
|
430
|
+
response.setHeader(HttpHeaderCodes.Pragma, 'no-cache');
|
|
431
|
+
response.setHeader(HttpHeaderCodes.Expires, '-1');
|
|
432
|
+
response.setHeader(HttpHeaderCodes.X_Opra_Version, OpraSchema.SpecVersion);
|
|
433
|
+
response.send(safeJsonStringify(body));
|
|
434
|
+
response.end();
|
|
435
|
+
}
|
|
436
|
+
async sendDocumentSchema(context) {
|
|
437
|
+
const { request, response } = context;
|
|
438
|
+
const { document } = this.adapter;
|
|
439
|
+
response.setHeader('content-type', MimeTypes.json);
|
|
440
|
+
const url = new URL(request.originalUrl || request.url || '/', 'http://tempuri.org');
|
|
441
|
+
const { searchParams } = url;
|
|
442
|
+
const documentId = searchParams.get('id');
|
|
443
|
+
const doc = documentId ? document.findDocument(documentId) : document;
|
|
444
|
+
if (!doc) {
|
|
445
|
+
context.errors.push(new BadRequestError({
|
|
446
|
+
message: `Document with given id [${documentId}] does not exists`,
|
|
447
|
+
}));
|
|
448
|
+
return this.sendResponse(context);
|
|
449
|
+
}
|
|
450
|
+
/** Check if response cache exists */
|
|
451
|
+
let responseBody = this[kAssetCache].get(doc, `$schema`);
|
|
452
|
+
/** Create response if response cache does not exists */
|
|
453
|
+
if (!responseBody) {
|
|
454
|
+
const schema = doc.export();
|
|
455
|
+
responseBody = JSON.stringify(schema);
|
|
456
|
+
this[kAssetCache].set(doc, `$schema`, responseBody);
|
|
457
|
+
}
|
|
458
|
+
response.end(responseBody);
|
|
364
459
|
}
|
|
365
460
|
/**
|
|
366
461
|
*
|
|
@@ -387,7 +482,7 @@ export class HttpHandler {
|
|
|
387
482
|
let responseArgs = this[kAssetCache].get(response, cacheKey);
|
|
388
483
|
if (!responseArgs) {
|
|
389
484
|
responseArgs = { statusCode, contentType };
|
|
390
|
-
if (operation
|
|
485
|
+
if (operation?.responses.length) {
|
|
391
486
|
/** Filter available HttpOperationResponse instances according to status code. */
|
|
392
487
|
const filteredResponses = operation.responses.filter(r => r.statusCode.find(sc => sc.start <= statusCode && sc.end >= statusCode));
|
|
393
488
|
/** Throw InternalServerError if controller returns non-configured status code */
|
|
@@ -419,6 +514,8 @@ export class HttpHandler {
|
|
|
419
514
|
: operationResponse.contentType);
|
|
420
515
|
if (typeof ct === 'string')
|
|
421
516
|
responseArgs.contentType = contentType = ct;
|
|
517
|
+
else if (operationResponse.type)
|
|
518
|
+
responseArgs.contentType = MimeTypes.opra_response_json;
|
|
422
519
|
}
|
|
423
520
|
}
|
|
424
521
|
}
|
|
@@ -430,6 +527,10 @@ export class HttpHandler {
|
|
|
430
527
|
}
|
|
431
528
|
if (!hasBody)
|
|
432
529
|
delete responseArgs.contentType;
|
|
530
|
+
if (operation?.composition?.startsWith('Entity.')) {
|
|
531
|
+
if (context.queryParams.projection)
|
|
532
|
+
responseArgs.projection = context.queryParams.projection;
|
|
533
|
+
}
|
|
433
534
|
this[kAssetCache].set(response, cacheKey, { ...responseArgs });
|
|
434
535
|
}
|
|
435
536
|
/** Fix response value according to composition */
|
|
@@ -487,56 +588,10 @@ export class HttpHandler {
|
|
|
487
588
|
responseArgs.body = body;
|
|
488
589
|
return responseArgs;
|
|
489
590
|
}
|
|
490
|
-
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
const url = new URL(request.originalUrl || request.url || '/', 'http://tempuri.org');
|
|
495
|
-
const { searchParams } = url;
|
|
496
|
-
const documentId = searchParams.get('id');
|
|
497
|
-
const doc = documentId ? document.findDocument(documentId) : document;
|
|
498
|
-
if (!doc) {
|
|
499
|
-
return this.sendErrorResponse(response, [
|
|
500
|
-
new BadRequestError({
|
|
501
|
-
message: `Document with given id [${documentId}] does not exists`,
|
|
502
|
-
}),
|
|
503
|
-
]);
|
|
504
|
-
}
|
|
505
|
-
/** Check if response cache exists */
|
|
506
|
-
let responseBody = this[kAssetCache].get(doc, `$schema`);
|
|
507
|
-
/** Create response if response cache does not exists */
|
|
508
|
-
if (!responseBody) {
|
|
509
|
-
const schema = doc.export();
|
|
510
|
-
responseBody = JSON.stringify(schema);
|
|
511
|
-
this[kAssetCache].set(doc, `$schema`, responseBody);
|
|
512
|
-
}
|
|
513
|
-
response.end(responseBody);
|
|
514
|
-
}
|
|
515
|
-
async sendErrorResponse(response, errors) {
|
|
516
|
-
if (response.headersSent) {
|
|
517
|
-
response.end();
|
|
518
|
-
return;
|
|
519
|
-
}
|
|
520
|
-
if (!errors.length)
|
|
521
|
-
errors.push(wrapException({ status: response.statusCode || 500 }));
|
|
522
|
-
const { logger } = this.adapter;
|
|
523
|
-
errors.forEach(x => {
|
|
524
|
-
if (x instanceof OpraException) {
|
|
525
|
-
switch (x.severity) {
|
|
526
|
-
case 'fatal':
|
|
527
|
-
logger.fatal(x);
|
|
528
|
-
break;
|
|
529
|
-
case 'warning':
|
|
530
|
-
logger.warn(x);
|
|
531
|
-
break;
|
|
532
|
-
default:
|
|
533
|
-
logger.error(x);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
else
|
|
537
|
-
logger.fatal(x);
|
|
538
|
-
});
|
|
539
|
-
const wrappedErrors = errors.map(wrapException);
|
|
591
|
+
_wrapExceptions(exceptions) {
|
|
592
|
+
const wrappedErrors = exceptions.map(wrapException);
|
|
593
|
+
if (!wrappedErrors.length)
|
|
594
|
+
wrappedErrors.push(new InternalServerError());
|
|
540
595
|
// Sort errors from fatal to info
|
|
541
596
|
wrappedErrors.sort((a, b) => {
|
|
542
597
|
const i = IssueSeverity.Keys.indexOf(a.severity) - IssueSeverity.Keys.indexOf(b.severity);
|
|
@@ -544,36 +599,6 @@ export class HttpHandler {
|
|
|
544
599
|
return b.status - a.status;
|
|
545
600
|
return i;
|
|
546
601
|
});
|
|
547
|
-
|
|
548
|
-
if (!status || status < Number(HttpStatusCode.BAD_REQUEST)) {
|
|
549
|
-
status = wrappedErrors[0].status;
|
|
550
|
-
if (status < Number(HttpStatusCode.BAD_REQUEST))
|
|
551
|
-
status = HttpStatusCode.INTERNAL_SERVER_ERROR;
|
|
552
|
-
}
|
|
553
|
-
response.statusCode = status;
|
|
554
|
-
const { document } = this.adapter;
|
|
555
|
-
const dt = document.node.getComplexType('OperationResult');
|
|
556
|
-
let encode = this[kAssetCache].get(dt, 'encode');
|
|
557
|
-
if (!encode) {
|
|
558
|
-
encode = dt.generateCodec('encode', { ignoreWriteonlyFields: true });
|
|
559
|
-
this[kAssetCache].set(dt, 'encode', encode);
|
|
560
|
-
}
|
|
561
|
-
const { i18n } = this.adapter;
|
|
562
|
-
const bodyObject = new OperationResult({
|
|
563
|
-
errors: wrappedErrors.map(x => {
|
|
564
|
-
const o = x.toJSON();
|
|
565
|
-
if (!(process.env.NODE_ENV === 'dev' || process.env.NODE_ENV === 'development'))
|
|
566
|
-
delete o.stack;
|
|
567
|
-
return i18n.deep(o);
|
|
568
|
-
}),
|
|
569
|
-
});
|
|
570
|
-
const body = encode(bodyObject);
|
|
571
|
-
response.setHeader(HttpHeaderCodes.Content_Type, MimeTypes.opra_response_json + '; charset=utf-8');
|
|
572
|
-
response.setHeader(HttpHeaderCodes.Cache_Control, 'no-cache');
|
|
573
|
-
response.setHeader(HttpHeaderCodes.Pragma, 'no-cache');
|
|
574
|
-
response.setHeader(HttpHeaderCodes.Expires, '-1');
|
|
575
|
-
response.setHeader(HttpHeaderCodes.X_Opra_Version, OpraSchema.SpecVersion);
|
|
576
|
-
response.send(JSON.stringify(body));
|
|
577
|
-
response.end();
|
|
602
|
+
return wrappedErrors;
|
|
578
603
|
}
|
|
579
604
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Some parts of this file contains codes from open source express library
|
|
3
3
|
https://github.com/expressjs
|
|
4
4
|
*/
|
|
5
|
+
import path from 'node:path';
|
|
5
6
|
import { HttpStatusCode } from '@opra/common';
|
|
6
7
|
import contentDisposition from 'content-disposition';
|
|
7
8
|
import contentType from 'content-type';
|
|
@@ -9,7 +10,6 @@ import cookie from 'cookie';
|
|
|
9
10
|
import cookieSignature from 'cookie-signature';
|
|
10
11
|
import encodeUrl from 'encodeurl';
|
|
11
12
|
import mime from 'mime-types';
|
|
12
|
-
import path from 'path';
|
|
13
13
|
import { toString } from 'putil-varhelpers';
|
|
14
14
|
import vary from 'vary';
|
|
15
15
|
const charsetRegExp = /;\s*charset\s*=/;
|