@opra/core 1.0.0-beta.2 → 1.0.0-beta.4

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.
Files changed (85) hide show
  1. package/cjs/{http/impl/asset-cache.js → asset-cache.js} +6 -0
  2. package/cjs/execution-context.js +3 -14
  3. package/cjs/index.js +3 -25
  4. package/cjs/platform-adapter.js +3 -3
  5. package/cjs/{helpers/service-base.js → service-base.js} +9 -4
  6. package/esm/{http/impl/asset-cache.js → asset-cache.js} +6 -0
  7. package/esm/execution-context.js +2 -13
  8. package/esm/index.js +3 -24
  9. package/esm/platform-adapter.js +2 -2
  10. package/esm/{helpers/service-base.js → service-base.js} +9 -4
  11. package/package.json +5 -39
  12. package/types/{http/impl/asset-cache.d.ts → asset-cache.d.ts} +1 -0
  13. package/types/execution-context.d.ts +4 -8
  14. package/types/index.d.cts +3 -23
  15. package/types/index.d.ts +3 -23
  16. package/types/interfaces/logger.interface.d.ts +1 -1
  17. package/types/platform-adapter.d.ts +3 -3
  18. package/types/service-base.d.ts +11 -0
  19. package/cjs/augmentation/http-controller.augmentation.js +0 -25
  20. package/cjs/http/express-adapter.js +0 -155
  21. package/cjs/http/http-adapter.js +0 -24
  22. package/cjs/http/http-context.js +0 -104
  23. package/cjs/http/http-handler.js +0 -609
  24. package/cjs/http/impl/http-incoming.host.js +0 -112
  25. package/cjs/http/impl/http-outgoing.host.js +0 -207
  26. package/cjs/http/impl/multipart-reader.js +0 -196
  27. package/cjs/http/impl/node-incoming-message.host.js +0 -109
  28. package/cjs/http/impl/node-outgoing-message.host.js +0 -195
  29. package/cjs/http/interfaces/http-incoming.interface.js +0 -25
  30. package/cjs/http/interfaces/http-outgoing.interface.js +0 -22
  31. package/cjs/http/interfaces/node-incoming-message.interface.js +0 -64
  32. package/cjs/http/interfaces/node-outgoing-message.interface.js +0 -15
  33. package/cjs/http/utils/body-reader.js +0 -216
  34. package/cjs/http/utils/common.js +0 -67
  35. package/cjs/http/utils/concat-readable.js +0 -19
  36. package/cjs/http/utils/convert-to-headers.js +0 -64
  37. package/cjs/http/utils/convert-to-raw-headers.js +0 -23
  38. package/cjs/http/utils/match-known-fields.js +0 -49
  39. package/cjs/http/utils/wrap-exception.js +0 -33
  40. package/cjs/type-guards.js +0 -22
  41. package/esm/augmentation/http-controller.augmentation.js +0 -23
  42. package/esm/http/express-adapter.js +0 -150
  43. package/esm/http/http-adapter.js +0 -20
  44. package/esm/http/http-context.js +0 -99
  45. package/esm/http/http-handler.js +0 -604
  46. package/esm/http/impl/http-incoming.host.js +0 -107
  47. package/esm/http/impl/http-outgoing.host.js +0 -202
  48. package/esm/http/impl/multipart-reader.js +0 -191
  49. package/esm/http/impl/node-incoming-message.host.js +0 -105
  50. package/esm/http/impl/node-outgoing-message.host.js +0 -191
  51. package/esm/http/interfaces/http-incoming.interface.js +0 -22
  52. package/esm/http/interfaces/http-outgoing.interface.js +0 -19
  53. package/esm/http/interfaces/node-incoming-message.interface.js +0 -61
  54. package/esm/http/interfaces/node-outgoing-message.interface.js +0 -12
  55. package/esm/http/utils/body-reader.js +0 -211
  56. package/esm/http/utils/common.js +0 -61
  57. package/esm/http/utils/concat-readable.js +0 -16
  58. package/esm/http/utils/convert-to-headers.js +0 -60
  59. package/esm/http/utils/convert-to-raw-headers.js +0 -20
  60. package/esm/http/utils/match-known-fields.js +0 -45
  61. package/esm/http/utils/wrap-exception.js +0 -30
  62. package/esm/type-guards.js +0 -16
  63. package/types/augmentation/http-controller.augmentation.d.ts +0 -20
  64. package/types/helpers/service-base.d.ts +0 -10
  65. package/types/http/express-adapter.d.ts +0 -13
  66. package/types/http/http-adapter.d.ts +0 -54
  67. package/types/http/http-context.d.ts +0 -44
  68. package/types/http/http-handler.d.ts +0 -75
  69. package/types/http/impl/http-incoming.host.d.ts +0 -22
  70. package/types/http/impl/http-outgoing.host.d.ts +0 -17
  71. package/types/http/impl/multipart-reader.d.ts +0 -46
  72. package/types/http/impl/node-incoming-message.host.d.ts +0 -45
  73. package/types/http/impl/node-outgoing-message.host.d.ts +0 -49
  74. package/types/http/interfaces/http-incoming.interface.d.ts +0 -192
  75. package/types/http/interfaces/http-outgoing.interface.d.ts +0 -144
  76. package/types/http/interfaces/node-incoming-message.interface.d.ts +0 -36
  77. package/types/http/interfaces/node-outgoing-message.interface.d.ts +0 -27
  78. package/types/http/utils/body-reader.d.ts +0 -38
  79. package/types/http/utils/common.d.ts +0 -17
  80. package/types/http/utils/concat-readable.d.ts +0 -2
  81. package/types/http/utils/convert-to-headers.d.ts +0 -2
  82. package/types/http/utils/convert-to-raw-headers.d.ts +0 -2
  83. package/types/http/utils/match-known-fields.d.ts +0 -6
  84. package/types/http/utils/wrap-exception.d.ts +0 -2
  85. package/types/type-guards.d.ts +0 -8
@@ -1,604 +0,0 @@
1
- import * as process from 'node:process';
2
- import typeIs from '@browsery/type-is';
3
- import { BadRequestError, HttpHeaderCodes, HttpStatusCode, InternalServerError, isBlob, isReadableStream, IssueSeverity, MethodNotAllowedError, MimeTypes, OperationResult, OpraException, OpraSchema, safeJsonStringify, } from '@opra/common';
4
- import { parse as parseContentType } from 'content-type';
5
- import { splitString } from 'fast-tokenizer';
6
- import { md5 } from 'super-fast-md5';
7
- import { asMutable } from 'ts-gems';
8
- import { toArray, ValidationError, vg } from 'valgen';
9
- import { kAssetCache } from '../constants.js';
10
- import { wrapException } from './utils/wrap-exception.js';
11
- /**
12
- * @class HttpHandler
13
- */
14
- export class HttpHandler {
15
- constructor(adapter) {
16
- this.adapter = adapter;
17
- this[kAssetCache] = adapter[kAssetCache];
18
- }
19
- /**
20
- * Main http request handler
21
- * @param context
22
- * @protected
23
- */
24
- async handleRequest(context) {
25
- const { response } = context;
26
- try {
27
- response.setHeader(HttpHeaderCodes.X_Opra_Version, OpraSchema.SpecVersion);
28
- // Expose headers if cors enabled
29
- if (response.getHeader(HttpHeaderCodes.Access_Control_Allow_Origin)) {
30
- // Expose X-Opra-* headers
31
- response.appendHeader(HttpHeaderCodes.Access_Control_Expose_Headers, Object.values(HttpHeaderCodes).filter(k => k.toLowerCase().startsWith('x-opra-')));
32
- }
33
- // Parse request
34
- try {
35
- await this.parseRequest(context);
36
- }
37
- catch (e) {
38
- if (e instanceof OpraException)
39
- throw e;
40
- if (e instanceof ValidationError) {
41
- throw new BadRequestError({
42
- message: 'Response validation failed',
43
- code: 'RESPONSE_VALIDATION',
44
- details: e.issues,
45
- }, e);
46
- }
47
- throw new BadRequestError(e);
48
- }
49
- await this.adapter.emitAsync('request', context);
50
- // Call interceptors than execute request
51
- if (this.adapter.interceptors) {
52
- const interceptors = this.adapter.interceptors;
53
- let i = 0;
54
- const next = async () => {
55
- const interceptor = interceptors[i++];
56
- if (typeof interceptor === 'function')
57
- await interceptor(context, next);
58
- else if (typeof interceptor?.intercept === 'function')
59
- await interceptor.intercept(context, next);
60
- await this._executeRequest(context);
61
- };
62
- await next();
63
- }
64
- else
65
- await this._executeRequest(context);
66
- }
67
- catch (error) {
68
- let e = error;
69
- if (e instanceof ValidationError) {
70
- e = new InternalServerError({
71
- message: 'Response validation failed',
72
- code: 'RESPONSE_VALIDATION',
73
- details: e.issues,
74
- }, e);
75
- }
76
- else
77
- e = wrapException(e);
78
- if (this.onError)
79
- await this.onError(context, error);
80
- context.errors.push(e);
81
- await this.sendResponse(context);
82
- }
83
- finally {
84
- await context.emitAsync('finish');
85
- }
86
- }
87
- /**
88
- *
89
- * @param context
90
- */
91
- async parseRequest(context) {
92
- await this._parseParameters(context);
93
- await this._parseContentType(context);
94
- if (context.operation?.requestBody?.immediateFetch)
95
- await context.getBody();
96
- /** Set default status code as the first status code between 200 and 299 */
97
- if (context.operation) {
98
- for (const r of context.operation.responses) {
99
- const st = r.statusCode.find(sc => sc.start <= 299 && sc.end >= 200);
100
- if (st) {
101
- context.response.status(st.start);
102
- break;
103
- }
104
- }
105
- }
106
- }
107
- /**
108
- *
109
- * @param context
110
- * @protected
111
- */
112
- async _parseParameters(context) {
113
- const { operation, request } = context;
114
- if (!operation)
115
- return;
116
- let key = '';
117
- try {
118
- const onFail = (issue) => {
119
- issue.location = key;
120
- return issue;
121
- };
122
- /** prepare decoders */
123
- const getDecoder = (prm) => {
124
- let decode = this[kAssetCache].get(prm, 'decode');
125
- if (!decode) {
126
- decode = prm.type?.generateCodec('decode', { ignoreReadonlyFields: true }) || vg.isAny();
127
- this[kAssetCache].set(prm, 'decode', decode);
128
- }
129
- return decode;
130
- };
131
- const paramsLeft = new Set([...operation.parameters, ...operation.owner.parameters]);
132
- /** parse cookie parameters */
133
- if (request.cookies) {
134
- for (key of Object.keys(request.cookies)) {
135
- const oprPrm = operation.findParameter(key, 'cookie');
136
- const cntPrm = operation.owner.findParameter(key, 'cookie');
137
- const prm = oprPrm || cntPrm;
138
- if (!prm)
139
- continue;
140
- if (oprPrm)
141
- paramsLeft.delete(oprPrm);
142
- if (cntPrm)
143
- paramsLeft.delete(cntPrm);
144
- const decode = getDecoder(prm);
145
- const v = decode(request.cookies[key], { coerce: true, label: key, onFail });
146
- const prmName = typeof prm.name === 'string' ? prm.name : key;
147
- if (v !== undefined)
148
- context.cookies[prmName] = v;
149
- }
150
- }
151
- /** parse headers */
152
- if (request.headers) {
153
- for (key of Object.keys(request.headers)) {
154
- const oprPrm = operation.findParameter(key, 'header');
155
- const cntPrm = operation.owner.findParameter(key, 'header');
156
- const prm = oprPrm || cntPrm;
157
- if (!prm)
158
- continue;
159
- if (oprPrm)
160
- paramsLeft.delete(oprPrm);
161
- if (cntPrm)
162
- paramsLeft.delete(cntPrm);
163
- const decode = getDecoder(prm);
164
- const v = decode(request.headers[key], { coerce: true, label: key, onFail });
165
- const prmName = typeof prm.name === 'string' ? prm.name : key;
166
- if (v !== undefined)
167
- context.headers[prmName] = v;
168
- }
169
- }
170
- /** parse path parameters */
171
- if (request.params) {
172
- for (key of Object.keys(request.params)) {
173
- const oprPrm = operation.findParameter(key, 'path');
174
- const cntPrm = operation.owner.findParameter(key, 'path');
175
- const prm = oprPrm || cntPrm;
176
- if (!prm)
177
- continue;
178
- if (oprPrm)
179
- paramsLeft.delete(oprPrm);
180
- if (cntPrm)
181
- paramsLeft.delete(cntPrm);
182
- const decode = getDecoder(prm);
183
- const v = decode(request.params[key], { coerce: true, label: key, onFail });
184
- if (v !== undefined)
185
- context.pathParams[key] = v;
186
- }
187
- }
188
- /** parse query parameters */
189
- const url = new URL(request.originalUrl || request.url || '/', 'http://tempuri.org');
190
- const { searchParams } = url;
191
- for (key of searchParams.keys()) {
192
- const oprPrm = operation.findParameter(key, 'query');
193
- const cntPrm = operation.owner.findParameter(key, 'query');
194
- const prm = oprPrm || cntPrm;
195
- if (!prm)
196
- continue;
197
- if (oprPrm)
198
- paramsLeft.delete(oprPrm);
199
- if (cntPrm)
200
- paramsLeft.delete(cntPrm);
201
- const decode = getDecoder(prm);
202
- let values = searchParams?.getAll(key);
203
- const prmName = typeof prm.name === 'string' ? prm.name : key;
204
- if (values?.length && prm.isArray) {
205
- values = values.map(v => splitString(v, { delimiters: prm.arraySeparator, quotes: true })).flat();
206
- values = values.map(v => decode(v, { coerce: true, label: key, onFail }));
207
- if (values.length)
208
- context.queryParams[prmName] = values;
209
- }
210
- else {
211
- const v = decode(values[0], { coerce: true, label: key, onFail });
212
- if (values.length)
213
- context.queryParams[prmName] = v;
214
- }
215
- }
216
- for (const prm of paramsLeft) {
217
- key = String(prm.name);
218
- // Throw error for required parameters
219
- if (prm.required) {
220
- const decode = getDecoder(prm);
221
- decode(undefined, { coerce: true, label: String(prm.name), onFail });
222
- }
223
- }
224
- }
225
- catch (e) {
226
- if (e instanceof ValidationError) {
227
- throw new BadRequestError({
228
- message: `Invalid parameter (${key}) value. ` + e.message,
229
- code: 'REQUEST_VALIDATION',
230
- details: e.issues,
231
- }, e);
232
- }
233
- throw e;
234
- }
235
- }
236
- /**
237
- *
238
- * @param context
239
- * @protected
240
- */
241
- async _parseContentType(context) {
242
- const { request, operation } = context;
243
- if (!operation)
244
- return;
245
- if (operation.requestBody?.content.length) {
246
- let mediaType;
247
- let contentType = request.header('content-type');
248
- if (contentType) {
249
- contentType = parseContentType(contentType).type;
250
- mediaType = operation.requestBody.content.find(mc => mc.contentType &&
251
- typeIs.is(contentType, Array.isArray(mc.contentType) ? mc.contentType : [mc.contentType]));
252
- }
253
- if (!mediaType) {
254
- const contentTypes = operation.requestBody.content.map(mc => mc.contentType).flat();
255
- throw new BadRequestError(`Request body should be one of required content types (${contentTypes.join(', ')})`);
256
- }
257
- asMutable(context).mediaType = mediaType;
258
- }
259
- }
260
- /**
261
- *
262
- * @param context
263
- * @protected
264
- */
265
- async _executeRequest(context) {
266
- if (!context.operationHandler)
267
- throw new MethodNotAllowedError();
268
- const responseValue = await context.operationHandler.call(context.controllerInstance, context);
269
- const { response } = context;
270
- if (!response.writableEnded) {
271
- await this.sendResponse(context, responseValue).finally(() => {
272
- if (!response.writableEnded)
273
- response.end();
274
- });
275
- }
276
- }
277
- /**
278
- *
279
- * @param context
280
- * @param responseValue
281
- * @protected
282
- */
283
- async sendResponse(context, responseValue) {
284
- if (context.errors.length)
285
- return this._sendErrorResponse(context);
286
- const { response } = context;
287
- const { document } = this.adapter;
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
- }
330
- }
331
- else {
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
- }
341
- }
342
- if (body instanceof OperationResult &&
343
- operationResponse.type &&
344
- operationResponse.type !== document.node.getDataType(OperationResult)) {
345
- body.type = operationResponse.type.name ? operationResponse.type.name : '#embedded';
346
- }
347
- }
348
- }
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
- }
365
- }
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;
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);
388
- }
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) {
401
- response.end();
402
- return;
403
- }
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);
459
- }
460
- /**
461
- *
462
- * @param context
463
- * @param body
464
- * @protected
465
- */
466
- _determineResponseArgs(context, body) {
467
- const { response, operation } = context;
468
- const hasBody = body != null;
469
- const statusCode = !hasBody && response.statusCode === HttpStatusCode.OK ? HttpStatusCode.NO_CONTENT : response.statusCode;
470
- /** Parse content-type header */
471
- const parsedContentType = hasBody && response.hasHeader('content-type') ? parseContentType(response) : undefined;
472
- let contentType = parsedContentType?.type;
473
- /** Estimate content type if not defined */
474
- if (hasBody && !contentType) {
475
- if (body instanceof OperationResult)
476
- contentType = MimeTypes.opra_response_json;
477
- else if (Buffer.isBuffer(body))
478
- contentType = MimeTypes.binary;
479
- }
480
- let operationResponse;
481
- const cacheKey = `HttpOperationResponse:${statusCode}${contentType ? ':' + contentType : ''}`;
482
- let responseArgs = this[kAssetCache].get(response, cacheKey);
483
- if (!responseArgs) {
484
- responseArgs = { statusCode, contentType };
485
- if (operation?.responses.length) {
486
- /** Filter available HttpOperationResponse instances according to status code. */
487
- const filteredResponses = operation.responses.filter(r => r.statusCode.find(sc => sc.start <= statusCode && sc.end >= statusCode));
488
- /** Throw InternalServerError if controller returns non-configured status code */
489
- if (!filteredResponses.length && statusCode < 400) {
490
- throw new InternalServerError(`No responses defined for status code ${statusCode} in operation "${operation.name}"`);
491
- }
492
- /** We search for content-type in filtered HttpOperationResponse array */
493
- if (filteredResponses.length) {
494
- /** If no response returned, and content-type has not been set (No response wants to be returned by operation) */
495
- if (!hasBody) {
496
- /** Find HttpOperationResponse with no content-type */
497
- operationResponse = filteredResponses.find(r => !r.contentType);
498
- }
499
- if (!operationResponse) {
500
- /** Find HttpOperationResponse according to content-type */
501
- if (contentType) {
502
- // Find HttpEndpointResponse instance according to content-type header
503
- operationResponse = filteredResponses.find(r => typeIs.is(contentType, toArray(r.contentType)));
504
- if (!operationResponse) {
505
- throw new InternalServerError(`Operation didn't configured to return "${contentType}" content`);
506
- }
507
- }
508
- else {
509
- /** Select first HttpOperationResponse if content-type header has not been set */
510
- operationResponse = filteredResponses[0];
511
- if (operationResponse.contentType) {
512
- const ct = typeIs.normalize(Array.isArray(operationResponse.contentType)
513
- ? operationResponse.contentType[0]
514
- : operationResponse.contentType);
515
- if (typeof ct === 'string')
516
- responseArgs.contentType = contentType = ct;
517
- else if (operationResponse.type)
518
- responseArgs.contentType = MimeTypes.opra_response_json;
519
- }
520
- }
521
- }
522
- responseArgs.operationResponse = operationResponse;
523
- if (!operationResponse.statusCode.find(sc => sc.start <= statusCode && sc.end >= statusCode)) {
524
- responseArgs.statusCode = operationResponse.statusCode[0].start;
525
- }
526
- }
527
- }
528
- if (!hasBody)
529
- delete responseArgs.contentType;
530
- if (operation?.composition?.startsWith('Entity.')) {
531
- if (context.queryParams.projection)
532
- responseArgs.projection = context.queryParams.projection;
533
- }
534
- this[kAssetCache].set(response, cacheKey, { ...responseArgs });
535
- }
536
- /** Fix response value according to composition */
537
- const composition = operationResponse?.owner.composition;
538
- if (composition && body != null) {
539
- switch (composition) {
540
- case 'Entity.Create':
541
- case 'Entity.Get':
542
- case 'Entity.FindMany':
543
- case 'Entity.Update': {
544
- if (!(body instanceof OperationResult)) {
545
- body = new OperationResult({
546
- payload: body,
547
- });
548
- }
549
- if ((composition === 'Entity.Create' || composition === 'Entity.Update') &&
550
- composition &&
551
- body.affected == null) {
552
- body.affected = 1;
553
- }
554
- break;
555
- }
556
- case 'Entity.Delete':
557
- case 'Entity.DeleteMany':
558
- case 'Entity.UpdateMany': {
559
- if (!(body instanceof OperationResult)) {
560
- body = new OperationResult({
561
- affected: body,
562
- });
563
- }
564
- body.affected =
565
- typeof body.affected === 'number'
566
- ? body.affected
567
- : typeof body.affected === 'boolean'
568
- ? body.affected
569
- ? 1
570
- : 0
571
- : undefined;
572
- break;
573
- }
574
- default:
575
- break;
576
- }
577
- }
578
- if (responseArgs.contentType && responseArgs.contentType !== parsedContentType?.type) {
579
- response.setHeader('content-type', responseArgs.contentType);
580
- }
581
- if (responseArgs.contentType &&
582
- body != null &&
583
- !(body instanceof OperationResult) &&
584
- typeIs.is(responseArgs.contentType, [MimeTypes.opra_response_json])) {
585
- body = new OperationResult({ payload: body });
586
- }
587
- if (hasBody)
588
- responseArgs.body = body;
589
- return responseArgs;
590
- }
591
- _wrapExceptions(exceptions) {
592
- const wrappedErrors = exceptions.map(wrapException);
593
- if (!wrappedErrors.length)
594
- wrappedErrors.push(new InternalServerError());
595
- // Sort errors from fatal to info
596
- wrappedErrors.sort((a, b) => {
597
- const i = IssueSeverity.Keys.indexOf(a.severity) - IssueSeverity.Keys.indexOf(b.severity);
598
- if (i === 0)
599
- return b.status - a.status;
600
- return i;
601
- });
602
- return wrappedErrors;
603
- }
604
- }