@opra/core 1.0.0-alpha.3 → 1.0.0-alpha.30

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 (63) hide show
  1. package/cjs/augmentation/18n.augmentation.js +1 -1
  2. package/cjs/constants.js +1 -2
  3. package/cjs/execution-context.js +1 -1
  4. package/cjs/http/express-adapter.js +25 -34
  5. package/cjs/http/http-adapter.js +2 -5
  6. package/cjs/http/http-context.js +20 -32
  7. package/cjs/http/{impl/http-handler.js → http-handler.js} +249 -213
  8. package/cjs/http/impl/http-incoming.host.js +3 -3
  9. package/cjs/http/impl/http-outgoing.host.js +2 -2
  10. package/cjs/http/impl/multipart-reader.js +141 -50
  11. package/cjs/http/impl/node-incoming-message.host.js +5 -3
  12. package/cjs/http/interfaces/node-incoming-message.interface.js +3 -2
  13. package/cjs/http/utils/body-reader.js +6 -5
  14. package/cjs/http/utils/common.js +6 -5
  15. package/cjs/http/utils/concat-readable.js +1 -2
  16. package/cjs/http/utils/convert-to-headers.js +2 -3
  17. package/cjs/http/utils/convert-to-raw-headers.js +1 -2
  18. package/cjs/http/utils/match-known-fields.js +2 -2
  19. package/cjs/http/utils/wrap-exception.js +1 -2
  20. package/cjs/index.js +4 -4
  21. package/cjs/platform-adapter.js +1 -4
  22. package/cjs/type-guards.js +4 -5
  23. package/esm/augmentation/18n.augmentation.js +1 -1
  24. package/esm/constants.js +0 -1
  25. package/esm/execution-context.js +1 -1
  26. package/esm/http/express-adapter.js +25 -34
  27. package/esm/http/http-adapter.js +2 -5
  28. package/esm/http/http-context.js +21 -33
  29. package/esm/http/{impl/http-handler.js → http-handler.js} +243 -207
  30. package/esm/http/impl/http-incoming.host.js +3 -3
  31. package/esm/http/impl/http-outgoing.host.js +2 -2
  32. package/esm/http/impl/multipart-reader.js +142 -51
  33. package/esm/http/impl/node-incoming-message.host.js +5 -3
  34. package/esm/http/interfaces/node-incoming-message.interface.js +3 -2
  35. package/esm/http/utils/body-reader.js +6 -5
  36. package/esm/http/utils/common.js +2 -1
  37. package/esm/index.js +4 -4
  38. package/esm/platform-adapter.js +1 -4
  39. package/package.json +21 -14
  40. package/types/augmentation/18n.augmentation.d.ts +1 -1
  41. package/types/constants.d.ts +0 -1
  42. package/types/execution-context.d.ts +2 -3
  43. package/types/http/express-adapter.d.ts +1 -1
  44. package/types/http/http-adapter.d.ts +35 -8
  45. package/types/http/http-context.d.ts +4 -4
  46. package/types/http/{impl/http-handler.d.ts → http-handler.d.ts} +11 -9
  47. package/types/http/impl/http-incoming.host.d.ts +1 -2
  48. package/types/http/impl/http-outgoing.host.d.ts +1 -1
  49. package/types/http/impl/multipart-reader.d.ts +38 -20
  50. package/types/http/impl/node-incoming-message.host.d.ts +2 -6
  51. package/types/http/impl/node-outgoing-message.host.d.ts +4 -7
  52. package/types/http/interfaces/http-incoming.interface.d.ts +1 -2
  53. package/types/http/interfaces/http-outgoing.interface.d.ts +1 -1
  54. package/types/http/interfaces/node-incoming-message.interface.d.ts +0 -2
  55. package/types/http/interfaces/node-outgoing-message.interface.d.ts +0 -2
  56. package/types/http/utils/body-reader.d.ts +2 -5
  57. package/types/http/utils/concat-readable.d.ts +0 -1
  58. package/types/http/utils/convert-to-raw-headers.d.ts +0 -1
  59. package/types/index.d.ts +4 -4
  60. package/types/platform-adapter.d.ts +1 -5
  61. package/cjs/helpers/logger.js +0 -35
  62. package/esm/helpers/logger.js +0 -31
  63. package/types/helpers/logger.d.ts +0 -14
@@ -2,22 +2,22 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.HttpHandler = void 0;
4
4
  const tslib_1 = require("tslib");
5
+ const process = tslib_1.__importStar(require("node:process"));
6
+ const type_is_1 = tslib_1.__importDefault(require("@browsery/type-is"));
7
+ const common_1 = require("@opra/common");
5
8
  const content_type_1 = require("content-type");
6
9
  const fast_tokenizer_1 = require("fast-tokenizer");
7
- const process = tslib_1.__importStar(require("node:process"));
8
10
  const ts_gems_1 = require("ts-gems");
9
11
  const valgen_1 = require("valgen");
10
- const type_is_1 = tslib_1.__importDefault(require("@browsery/type-is"));
11
- const common_1 = require("@opra/common");
12
- const constants_js_1 = require("../../constants.js");
13
- const wrap_exception_js_1 = require("../utils/wrap-exception.js");
12
+ const constants_1 = require("../constants");
13
+ const wrap_exception_1 = require("./utils/wrap-exception");
14
14
  /**
15
15
  * @class HttpHandler
16
16
  */
17
17
  class HttpHandler {
18
18
  constructor(adapter) {
19
19
  this.adapter = adapter;
20
- this[constants_js_1.kAssetCache] = adapter[constants_js_1.kAssetCache];
20
+ this[constants_1.kAssetCache] = adapter[constants_1.kAssetCache];
21
21
  }
22
22
  /**
23
23
  * Main http request handler
@@ -42,7 +42,7 @@ class HttpHandler {
42
42
  throw e;
43
43
  if (e instanceof valgen_1.ValidationError) {
44
44
  throw new common_1.BadRequestError({
45
- message: (0, common_1.translate)('error:RESPONSE_VALIDATION,', 'Response validation failed'),
45
+ message: 'Response validation failed',
46
46
  code: 'RESPONSE_VALIDATION',
47
47
  details: e.issues,
48
48
  }, e);
@@ -52,11 +52,14 @@ class HttpHandler {
52
52
  await this.adapter.emitAsync('request', context);
53
53
  // Call interceptors than execute request
54
54
  if (this.adapter.interceptors) {
55
+ const interceptors = this.adapter.interceptors;
55
56
  let i = 0;
56
57
  const next = async () => {
57
- const interceptor = this.adapter.interceptors[i++];
58
- if (interceptor)
58
+ const interceptor = interceptors[i++];
59
+ if (typeof interceptor === 'function')
59
60
  await interceptor(context, next);
61
+ else if (typeof interceptor?.intercept === 'function')
62
+ await interceptor.intercept(context, next);
60
63
  await this._executeRequest(context);
61
64
  };
62
65
  await next();
@@ -64,23 +67,21 @@ class HttpHandler {
64
67
  else
65
68
  await this._executeRequest(context);
66
69
  }
67
- catch (e) {
70
+ catch (error) {
71
+ let e = error;
68
72
  if (e instanceof valgen_1.ValidationError) {
69
73
  e = new common_1.InternalServerError({
70
- message: (0, common_1.translate)('error:RESPONSE_VALIDATION,', 'Response validation failed'),
74
+ message: 'Response validation failed',
71
75
  code: 'RESPONSE_VALIDATION',
72
76
  details: e.issues,
73
77
  }, e);
74
78
  }
75
79
  else
76
- e = (0, wrap_exception_js_1.wrapException)(e);
77
- response.status(e.statusCode || e.status || common_1.HttpStatusCode.INTERNAL_SERVER_ERROR);
78
- response.contentType(common_1.MimeTypes.opra_response_json);
79
- await this._sendResponse(context, new common_1.OperationResult({ errors: [e] })).finally(() => {
80
- if (!response.finished)
81
- response.end();
82
- });
83
- // if (!outgoing.writableEnded) await this._sendErrorResponse(context.response, [error]);
80
+ e = (0, wrap_exception_1.wrapException)(e);
81
+ if (this.onError)
82
+ await this.onError(context, error);
83
+ context.errors.push(e);
84
+ await this.sendResponse(context);
84
85
  }
85
86
  finally {
86
87
  await context.emitAsync('finish');
@@ -93,7 +94,7 @@ class HttpHandler {
93
94
  async parseRequest(context) {
94
95
  await this._parseParameters(context);
95
96
  await this._parseContentType(context);
96
- if (context.operation.requestBody?.immediateFetch)
97
+ if (context.operation?.requestBody?.immediateFetch)
97
98
  await context.getBody();
98
99
  /** Set default status code as the first status code between 200 and 299 */
99
100
  if (context.operation) {
@@ -113,27 +114,29 @@ class HttpHandler {
113
114
  */
114
115
  async _parseParameters(context) {
115
116
  const { operation, request } = context;
116
- let prmName = '';
117
+ if (!operation)
118
+ return;
119
+ let key = '';
117
120
  try {
118
121
  const onFail = (issue) => {
119
- issue.location = prmName;
122
+ issue.location = key;
120
123
  return issue;
121
124
  };
122
125
  /** prepare decoders */
123
126
  const getDecoder = (prm) => {
124
- let decode = this[constants_js_1.kAssetCache].get(prm, 'decode');
127
+ let decode = this[constants_1.kAssetCache].get(prm, 'decode');
125
128
  if (!decode) {
126
- decode = prm.type?.generateCodec('decode') || valgen_1.vg.isAny();
127
- this[constants_js_1.kAssetCache].set(prm, 'decode', decode);
129
+ decode = prm.type?.generateCodec('decode', { ignoreReadonlyFields: true }) || valgen_1.vg.isAny();
130
+ this[constants_1.kAssetCache].set(prm, 'decode', decode);
128
131
  }
129
132
  return decode;
130
133
  };
131
134
  const paramsLeft = new Set([...operation.parameters, ...operation.owner.parameters]);
132
135
  /** parse cookie parameters */
133
136
  if (request.cookies) {
134
- for (prmName of Object.keys(request.cookies)) {
135
- const oprPrm = operation.findParameter(prmName, 'cookie');
136
- const cntPrm = operation.owner.findParameter(prmName, 'cookie');
137
+ for (key of Object.keys(request.cookies)) {
138
+ const oprPrm = operation.findParameter(key, 'cookie');
139
+ const cntPrm = operation.owner.findParameter(key, 'cookie');
137
140
  const prm = oprPrm || cntPrm;
138
141
  if (!prm)
139
142
  continue;
@@ -142,16 +145,17 @@ class HttpHandler {
142
145
  if (cntPrm)
143
146
  paramsLeft.delete(cntPrm);
144
147
  const decode = getDecoder(prm);
145
- const v = decode(request.cookies[prmName], { coerce: true, label: prmName, onFail });
148
+ const v = decode(request.cookies[key], { coerce: true, label: key, onFail });
149
+ const prmName = typeof prm.name === 'string' ? prm.name : key;
146
150
  if (v !== undefined)
147
151
  context.cookies[prmName] = v;
148
152
  }
149
153
  }
150
154
  /** parse headers */
151
155
  if (request.headers) {
152
- for (prmName of Object.keys(request.headers)) {
153
- const oprPrm = operation.findParameter(prmName, 'header');
154
- const cntPrm = operation.owner.findParameter(prmName, 'header');
156
+ for (key of Object.keys(request.headers)) {
157
+ const oprPrm = operation.findParameter(key, 'header');
158
+ const cntPrm = operation.owner.findParameter(key, 'header');
155
159
  const prm = oprPrm || cntPrm;
156
160
  if (!prm)
157
161
  continue;
@@ -160,16 +164,17 @@ class HttpHandler {
160
164
  if (cntPrm)
161
165
  paramsLeft.delete(cntPrm);
162
166
  const decode = getDecoder(prm);
163
- const v = decode(request.headers[prmName], { coerce: true, label: prmName, onFail });
167
+ const v = decode(request.headers[key], { coerce: true, label: key, onFail });
168
+ const prmName = typeof prm.name === 'string' ? prm.name : key;
164
169
  if (v !== undefined)
165
170
  context.headers[prmName] = v;
166
171
  }
167
172
  }
168
173
  /** parse path parameters */
169
174
  if (request.params) {
170
- for (prmName of Object.keys(request.params)) {
171
- const oprPrm = operation.findParameter(prmName, 'path');
172
- const cntPrm = operation.owner.findParameter(prmName, 'path');
175
+ for (key of Object.keys(request.params)) {
176
+ const oprPrm = operation.findParameter(key, 'path');
177
+ const cntPrm = operation.owner.findParameter(key, 'path');
173
178
  const prm = oprPrm || cntPrm;
174
179
  if (!prm)
175
180
  continue;
@@ -178,17 +183,17 @@ class HttpHandler {
178
183
  if (cntPrm)
179
184
  paramsLeft.delete(cntPrm);
180
185
  const decode = getDecoder(prm);
181
- const v = decode(request.params[prmName], { coerce: true, label: prmName, onFail });
186
+ const v = decode(request.params[key], { coerce: true, label: key, onFail });
182
187
  if (v !== undefined)
183
- context.pathParams[prmName] = v;
188
+ context.pathParams[key] = v;
184
189
  }
185
190
  }
186
191
  /** parse query parameters */
187
192
  const url = new URL(request.originalUrl || request.url || '/', 'http://tempuri.org');
188
193
  const { searchParams } = url;
189
- for (prmName of searchParams.keys()) {
190
- const oprPrm = operation.findParameter(prmName, 'query');
191
- const cntPrm = operation.owner.findParameter(prmName, 'query');
194
+ for (key of searchParams.keys()) {
195
+ const oprPrm = operation.findParameter(key, 'query');
196
+ const cntPrm = operation.owner.findParameter(key, 'query');
192
197
  const prm = oprPrm || cntPrm;
193
198
  if (!prm)
194
199
  continue;
@@ -197,20 +202,22 @@ class HttpHandler {
197
202
  if (cntPrm)
198
203
  paramsLeft.delete(cntPrm);
199
204
  const decode = getDecoder(prm);
200
- let values = searchParams?.getAll(prmName);
205
+ let values = searchParams?.getAll(key);
206
+ const prmName = typeof prm.name === 'string' ? prm.name : key;
201
207
  if (values?.length && prm.isArray) {
202
208
  values = values.map(v => (0, fast_tokenizer_1.splitString)(v, { delimiters: prm.arraySeparator, quotes: true })).flat();
203
- values = values.map(v => decode(v, { coerce: true, label: prmName, onFail }));
209
+ values = values.map(v => decode(v, { coerce: true, label: key, onFail }));
204
210
  if (values.length)
205
211
  context.queryParams[prmName] = values;
206
212
  }
207
213
  else {
208
- const v = decode(values[0], { coerce: true, label: prmName, onFail });
214
+ const v = decode(values[0], { coerce: true, label: key, onFail });
209
215
  if (values.length)
210
216
  context.queryParams[prmName] = v;
211
217
  }
212
218
  }
213
219
  for (const prm of paramsLeft) {
220
+ key = String(prm.name);
214
221
  // Throw error for required parameters
215
222
  if (prm.required) {
216
223
  const decode = getDecoder(prm);
@@ -220,8 +227,8 @@ class HttpHandler {
220
227
  }
221
228
  catch (e) {
222
229
  if (e instanceof valgen_1.ValidationError) {
223
- e = new common_1.BadRequestError({
224
- message: `Invalid parameter (${prmName}) value. ` + e.message,
230
+ throw new common_1.BadRequestError({
231
+ message: `Invalid parameter (${key}) value. ` + e.message,
225
232
  code: 'REQUEST_VALIDATION',
226
233
  details: e.issues,
227
234
  }, e);
@@ -236,14 +243,15 @@ class HttpHandler {
236
243
  */
237
244
  async _parseContentType(context) {
238
245
  const { request, operation } = context;
246
+ if (!operation)
247
+ return;
239
248
  if (operation.requestBody?.content.length) {
240
249
  let mediaType;
241
250
  let contentType = request.header('content-type');
242
251
  if (contentType) {
243
252
  contentType = (0, content_type_1.parse)(contentType).type;
244
- mediaType = operation.requestBody.content.find(mc => {
245
- return (mc.contentType && type_is_1.default.is(contentType, Array.isArray(mc.contentType) ? mc.contentType : [mc.contentType]));
246
- });
253
+ mediaType = operation.requestBody.content.find(mc => mc.contentType &&
254
+ type_is_1.default.is(contentType, Array.isArray(mc.contentType) ? mc.contentType : [mc.contentType]));
247
255
  }
248
256
  if (!mediaType) {
249
257
  const contentTypes = operation.requestBody.content.map(mc => mc.contentType).flat();
@@ -262,11 +270,12 @@ class HttpHandler {
262
270
  throw new common_1.MethodNotAllowedError();
263
271
  const responseValue = await context.operationHandler.call(context.controllerInstance, context);
264
272
  const { response } = context;
265
- if (!response.writableEnded)
266
- await this._sendResponse(context, responseValue).finally(() => {
273
+ if (!response.writableEnded) {
274
+ await this.sendResponse(context, responseValue).finally(() => {
267
275
  if (!response.writableEnded)
268
276
  response.end();
269
277
  });
278
+ }
270
279
  }
271
280
  /**
272
281
  *
@@ -274,93 +283,180 @@ class HttpHandler {
274
283
  * @param responseValue
275
284
  * @protected
276
285
  */
277
- async _sendResponse(context, responseValue) {
286
+ async sendResponse(context, responseValue) {
287
+ if (context.errors.length)
288
+ return this._sendErrorResponse(context);
278
289
  const { response } = context;
279
290
  const { document } = this.adapter;
280
- const responseArgs = this._determineResponseArgs(context, responseValue);
281
- const { operationResponse, statusCode } = responseArgs;
282
- let { contentType, body } = responseArgs;
283
- const operationResultType = document.node.getDataType(common_1.OperationResult);
284
- let operationResultEncoder = this[constants_js_1.kAssetCache].get(operationResultType, 'encode');
285
- if (!operationResultEncoder) {
286
- operationResultEncoder = operationResultType.generateCodec('encode');
287
- this[constants_js_1.kAssetCache].set(operationResultType, 'encode', operationResultEncoder);
288
- }
289
- /** Validate response */
290
- if (operationResponse?.type) {
291
- if (!(body == null && statusCode === common_1.HttpStatusCode.NO_CONTENT)) {
292
- /** Generate encoder */
293
- let encode = this[constants_js_1.kAssetCache].get(operationResponse, 'encode');
294
- if (!encode) {
295
- encode = operationResponse.type.generateCodec('encode', {
296
- partial: operationResponse.partial,
297
- projection: '*',
298
- });
299
- if (operationResponse) {
300
- if (operationResponse.isArray)
301
- encode = valgen_1.vg.isArray(encode);
302
- this[constants_js_1.kAssetCache].set(operationResponse, 'encode', encode);
291
+ try {
292
+ const responseArgs = this._determineResponseArgs(context, responseValue);
293
+ const { operationResponse, statusCode } = responseArgs;
294
+ let { contentType, body } = responseArgs;
295
+ const operationResultType = document.node.getDataType(common_1.OperationResult);
296
+ let operationResultEncoder = this[constants_1.kAssetCache].get(operationResultType, 'encode');
297
+ if (!operationResultEncoder) {
298
+ operationResultEncoder = operationResultType.generateCodec('encode', {
299
+ ignoreWriteonlyFields: true,
300
+ ignoreHiddenFields: true,
301
+ });
302
+ this[constants_1.kAssetCache].set(operationResultType, 'encode', operationResultEncoder);
303
+ }
304
+ /** Validate response */
305
+ if (operationResponse?.type) {
306
+ if (!(body == null && statusCode === common_1.HttpStatusCode.NO_CONTENT)) {
307
+ /** Generate encoder */
308
+ let encode = this[constants_1.kAssetCache].get(operationResponse, 'encode');
309
+ if (!encode) {
310
+ encode = operationResponse.type.generateCodec('encode', {
311
+ partial: operationResponse.partial,
312
+ projection: responseArgs.projection || '*',
313
+ ignoreWriteonlyFields: true,
314
+ ignoreHiddenFields: true,
315
+ onFail: issue => `Response body validation failed: ` + issue.message,
316
+ });
317
+ if (operationResponse) {
318
+ if (operationResponse.isArray)
319
+ encode = valgen_1.vg.isArray(encode);
320
+ this[constants_1.kAssetCache].set(operationResponse, 'encode', encode);
321
+ }
322
+ }
323
+ /** Encode body */
324
+ if (operationResponse.type.extendsFrom(operationResultType)) {
325
+ if (body instanceof common_1.OperationResult)
326
+ body = encode(body);
327
+ else {
328
+ body.payload = encode(body.payload);
329
+ body = operationResultEncoder(body);
330
+ }
303
331
  }
304
- }
305
- /** Encode body */
306
- if (operationResponse.type.extendsFrom(operationResultType)) {
307
- if (body instanceof common_1.OperationResult)
308
- body = encode(body);
309
332
  else {
310
- body.payload = encode(body.payload);
311
- body = operationResultEncoder(body);
333
+ if (body instanceof common_1.OperationResult &&
334
+ contentType &&
335
+ type_is_1.default.is(contentType, [common_1.MimeTypes.opra_response_json])) {
336
+ body.payload = encode(body.payload);
337
+ body = operationResultEncoder(body);
338
+ }
339
+ else {
340
+ body = encode(body);
341
+ }
312
342
  }
313
- }
314
- else {
315
343
  if (body instanceof common_1.OperationResult &&
316
- contentType &&
317
- type_is_1.default.is(contentType, [common_1.MimeTypes.opra_response_json])) {
318
- body.payload = encode(body.payload);
319
- body = operationResultEncoder(body);
344
+ operationResponse.type &&
345
+ operationResponse.type !== document.node.getDataType(common_1.OperationResult)) {
346
+ body.type = operationResponse.type.name ? operationResponse.type.name : '#embedded';
320
347
  }
321
- else
322
- body = encode(body);
323
- }
324
- if (body instanceof common_1.OperationResult && operationResponse.type) {
325
- body.type = operationResponse.type.name ? operationResponse.type.name : '#embedded';
326
348
  }
327
349
  }
328
- }
329
- else if (body != null) {
330
- if (body instanceof common_1.OperationResult) {
331
- body = operationResultEncoder(body);
332
- contentType = common_1.MimeTypes.opra_response_json;
333
- }
334
- else if (Buffer.isBuffer(body))
335
- contentType = common_1.MimeTypes.binary;
336
- else if (typeof body === 'object') {
337
- contentType = contentType || common_1.MimeTypes.json;
338
- if (typeof body.toJSON === 'function')
339
- body = body.toJSON();
350
+ else if (body != null) {
351
+ if (body instanceof common_1.OperationResult) {
352
+ body = operationResultEncoder(body);
353
+ contentType = common_1.MimeTypes.opra_response_json;
354
+ }
355
+ else if (Buffer.isBuffer(body))
356
+ contentType = common_1.MimeTypes.binary;
357
+ else if (typeof body === 'object') {
358
+ contentType = contentType || common_1.MimeTypes.json;
359
+ if (typeof body.toJSON === 'function')
360
+ body = body.toJSON();
361
+ }
362
+ else {
363
+ contentType = contentType || common_1.MimeTypes.text;
364
+ body = String(body);
365
+ }
340
366
  }
341
- else {
342
- contentType = contentType || common_1.MimeTypes.text;
343
- body = String(body);
367
+ /** Set content-type header value if not set */
368
+ if (contentType && contentType !== responseArgs.contentType)
369
+ response.setHeader('content-type', contentType);
370
+ response.status(statusCode);
371
+ if (body == null) {
372
+ response.end();
373
+ return;
344
374
  }
375
+ let x;
376
+ if (Buffer.isBuffer(body) || (0, common_1.isReadableStream)(body))
377
+ x = body;
378
+ else if ((0, common_1.isBlob)(body))
379
+ x = body.stream();
380
+ else if (typeof body === 'object')
381
+ x = JSON.stringify(body);
382
+ else
383
+ x = String(body);
384
+ response.end(x);
385
+ }
386
+ catch (error) {
387
+ context.errors.push(error);
388
+ return this._sendErrorResponse(context);
389
+ }
390
+ }
391
+ async _sendErrorResponse(context) {
392
+ context.errors = this._wrapExceptions(context.errors);
393
+ try {
394
+ await this.adapter.emitAsync('error', context);
395
+ context.errors = this._wrapExceptions(context.errors);
396
+ }
397
+ catch (e) {
398
+ context.errors = this._wrapExceptions([e, ...context.errors]);
345
399
  }
346
- /** Set content-type header value if not set */
347
- if (contentType && contentType !== responseArgs.contentType)
348
- response.setHeader('content-type', contentType);
349
- response.status(statusCode);
350
- if (body == null) {
400
+ const { response, errors } = context;
401
+ if (response.headersSent) {
351
402
  response.end();
352
403
  return;
353
404
  }
354
- let x;
355
- if (Buffer.isBuffer(body) || (0, common_1.isReadableStream)(body))
356
- x = body;
357
- else if ((0, common_1.isBlob)(body))
358
- x = body.stream();
359
- else if (typeof body === 'object')
360
- x = JSON.stringify(body);
361
- else
362
- x = String(body);
363
- response.end(x);
405
+ let status = response.statusCode || 0;
406
+ if (!status || status < Number(common_1.HttpStatusCode.BAD_REQUEST)) {
407
+ status = errors[0].status;
408
+ if (status < Number(common_1.HttpStatusCode.BAD_REQUEST))
409
+ status = common_1.HttpStatusCode.INTERNAL_SERVER_ERROR;
410
+ }
411
+ response.statusCode = status;
412
+ const { document } = this.adapter;
413
+ const dt = document.node.getComplexType('OperationResult');
414
+ let encode = this[constants_1.kAssetCache].get(dt, 'encode');
415
+ if (!encode) {
416
+ encode = dt.generateCodec('encode', { ignoreWriteonlyFields: true });
417
+ this[constants_1.kAssetCache].set(dt, 'encode', encode);
418
+ }
419
+ const { i18n } = this.adapter;
420
+ const bodyObject = new common_1.OperationResult({
421
+ errors: errors.map(x => {
422
+ const o = x.toJSON();
423
+ if (!(process.env.NODE_ENV === 'dev' || process.env.NODE_ENV === 'development'))
424
+ delete o.stack;
425
+ return i18n.deep(o);
426
+ }),
427
+ });
428
+ const body = encode(bodyObject);
429
+ response.setHeader(common_1.HttpHeaderCodes.Content_Type, common_1.MimeTypes.opra_response_json + '; charset=utf-8');
430
+ response.setHeader(common_1.HttpHeaderCodes.Cache_Control, 'no-cache');
431
+ response.setHeader(common_1.HttpHeaderCodes.Pragma, 'no-cache');
432
+ response.setHeader(common_1.HttpHeaderCodes.Expires, '-1');
433
+ response.setHeader(common_1.HttpHeaderCodes.X_Opra_Version, common_1.OpraSchema.SpecVersion);
434
+ response.send(JSON.stringify(body));
435
+ response.end();
436
+ }
437
+ async sendDocumentSchema(context) {
438
+ const { request, response } = context;
439
+ const { document } = this.adapter;
440
+ response.setHeader('content-type', common_1.MimeTypes.json);
441
+ const url = new URL(request.originalUrl || request.url || '/', 'http://tempuri.org');
442
+ const { searchParams } = url;
443
+ const documentId = searchParams.get('id');
444
+ const doc = documentId ? document.findDocument(documentId) : document;
445
+ if (!doc) {
446
+ context.errors.push(new common_1.BadRequestError({
447
+ message: `Document with given id [${documentId}] does not exists`,
448
+ }));
449
+ return this.sendResponse(context);
450
+ }
451
+ /** Check if response cache exists */
452
+ let responseBody = this[constants_1.kAssetCache].get(doc, `$schema`);
453
+ /** Create response if response cache does not exists */
454
+ if (!responseBody) {
455
+ const schema = doc.export();
456
+ responseBody = JSON.stringify(schema);
457
+ this[constants_1.kAssetCache].set(doc, `$schema`, responseBody);
458
+ }
459
+ response.end(responseBody);
364
460
  }
365
461
  /**
366
462
  *
@@ -384,15 +480,16 @@ class HttpHandler {
384
480
  }
385
481
  let operationResponse;
386
482
  const cacheKey = `HttpOperationResponse:${statusCode}${contentType ? ':' + contentType : ''}`;
387
- let responseArgs = this[constants_js_1.kAssetCache].get(response, cacheKey);
483
+ let responseArgs = this[constants_1.kAssetCache].get(response, cacheKey);
388
484
  if (!responseArgs) {
389
485
  responseArgs = { statusCode, contentType };
390
- if (operation.responses.length) {
486
+ if (operation?.responses.length) {
391
487
  /** Filter available HttpOperationResponse instances according to status code. */
392
488
  const filteredResponses = operation.responses.filter(r => r.statusCode.find(sc => sc.start <= statusCode && sc.end >= statusCode));
393
489
  /** Throw InternalServerError if controller returns non-configured status code */
394
- if (!filteredResponses.length && statusCode < 400)
490
+ if (!filteredResponses.length && statusCode < 400) {
395
491
  throw new common_1.InternalServerError(`No responses defined for status code ${statusCode} in operation "${operation.name}"`);
492
+ }
396
493
  /** We search for content-type in filtered HttpOperationResponse array */
397
494
  if (filteredResponses.length) {
398
495
  /** If no response returned, and content-type has not been set (No response wants to be returned by operation) */
@@ -405,8 +502,9 @@ class HttpHandler {
405
502
  if (contentType) {
406
503
  // Find HttpEndpointResponse instance according to content-type header
407
504
  operationResponse = filteredResponses.find(r => type_is_1.default.is(contentType, (0, valgen_1.toArray)(r.contentType)));
408
- if (!operationResponse)
505
+ if (!operationResponse) {
409
506
  throw new common_1.InternalServerError(`Operation didn't configured to return "${contentType}" content`);
507
+ }
410
508
  }
411
509
  else {
412
510
  /** Select first HttpOperationResponse if content-type header has not been set */
@@ -417,6 +515,8 @@ class HttpHandler {
417
515
  : operationResponse.contentType);
418
516
  if (typeof ct === 'string')
419
517
  responseArgs.contentType = contentType = ct;
518
+ else if (operationResponse.type)
519
+ responseArgs.contentType = common_1.MimeTypes.opra_response_json;
420
520
  }
421
521
  }
422
522
  }
@@ -428,7 +528,11 @@ class HttpHandler {
428
528
  }
429
529
  if (!hasBody)
430
530
  delete responseArgs.contentType;
431
- this[constants_js_1.kAssetCache].set(response, cacheKey, { ...responseArgs });
531
+ if (operation?.composition?.startsWith('Entity.')) {
532
+ if (context.queryParams.projection)
533
+ responseArgs.projection = context.queryParams.projection;
534
+ }
535
+ this[constants_1.kAssetCache].set(response, cacheKey, { ...responseArgs });
432
536
  }
433
537
  /** Fix response value according to composition */
434
538
  const composition = operationResponse?.owner.composition;
@@ -438,23 +542,26 @@ class HttpHandler {
438
542
  case 'Entity.Get':
439
543
  case 'Entity.FindMany':
440
544
  case 'Entity.Update': {
441
- if (!(body instanceof common_1.OperationResult))
545
+ if (!(body instanceof common_1.OperationResult)) {
442
546
  body = new common_1.OperationResult({
443
547
  payload: body,
444
548
  });
549
+ }
445
550
  if ((composition === 'Entity.Create' || composition === 'Entity.Update') &&
446
551
  composition &&
447
- body.affected == null)
552
+ body.affected == null) {
448
553
  body.affected = 1;
554
+ }
449
555
  break;
450
556
  }
451
557
  case 'Entity.Delete':
452
558
  case 'Entity.DeleteMany':
453
559
  case 'Entity.UpdateMany': {
454
- if (!(body instanceof common_1.OperationResult))
560
+ if (!(body instanceof common_1.OperationResult)) {
455
561
  body = new common_1.OperationResult({
456
562
  affected: body,
457
563
  });
564
+ }
458
565
  body.affected =
459
566
  typeof body.affected === 'number'
460
567
  ? body.affected
@@ -465,68 +572,27 @@ class HttpHandler {
465
572
  : undefined;
466
573
  break;
467
574
  }
575
+ default:
576
+ break;
468
577
  }
469
578
  }
470
- if (responseArgs.contentType && responseArgs.contentType !== parsedContentType?.type)
579
+ if (responseArgs.contentType && responseArgs.contentType !== parsedContentType?.type) {
471
580
  response.setHeader('content-type', responseArgs.contentType);
581
+ }
472
582
  if (responseArgs.contentType &&
473
583
  body != null &&
474
584
  !(body instanceof common_1.OperationResult) &&
475
- type_is_1.default.is(responseArgs.contentType, [common_1.MimeTypes.opra_response_json]))
585
+ type_is_1.default.is(responseArgs.contentType, [common_1.MimeTypes.opra_response_json])) {
476
586
  body = new common_1.OperationResult({ payload: body });
587
+ }
477
588
  if (hasBody)
478
589
  responseArgs.body = body;
479
590
  return responseArgs;
480
591
  }
481
- async sendDocumentSchema(context) {
482
- const { request, response } = context;
483
- const { document } = this.adapter;
484
- response.setHeader('content-type', common_1.MimeTypes.json);
485
- const url = new URL(request.originalUrl || request.url || '/', 'http://tempuri.org');
486
- const { searchParams } = url;
487
- const documentId = searchParams.get('id');
488
- const doc = documentId ? document.findDocument(documentId) : document;
489
- if (!doc)
490
- return this.sendErrorResponse(response, [
491
- new common_1.BadRequestError({
492
- message: `Document with given id [${documentId}] does not exists`,
493
- }),
494
- ]);
495
- /** Check if response cache exists */
496
- let responseBody = this[constants_js_1.kAssetCache].get(doc, `$schema`);
497
- /** Create response if response cache does not exists */
498
- if (!responseBody) {
499
- const schema = doc.export();
500
- responseBody = JSON.stringify(schema);
501
- this[constants_js_1.kAssetCache].set(doc, `$schema`, responseBody);
502
- }
503
- response.end(responseBody);
504
- }
505
- async sendErrorResponse(response, errors) {
506
- if (response.headersSent) {
507
- response.end();
508
- return;
509
- }
510
- if (!errors.length)
511
- errors.push((0, wrap_exception_js_1.wrapException)({ status: response.statusCode || 500 }));
512
- const { logger } = this.adapter;
513
- errors.forEach(x => {
514
- if (x instanceof common_1.OpraException) {
515
- switch (x.severity) {
516
- case 'fatal':
517
- logger.fatal(x);
518
- break;
519
- case 'warning':
520
- logger.warn(x);
521
- break;
522
- default:
523
- logger.error(x);
524
- }
525
- }
526
- else
527
- logger.fatal(x);
528
- });
529
- const wrappedErrors = errors.map(wrap_exception_js_1.wrapException);
592
+ _wrapExceptions(exceptions) {
593
+ const wrappedErrors = exceptions.map(wrap_exception_1.wrapException);
594
+ if (!wrappedErrors.length)
595
+ wrappedErrors.push(new common_1.InternalServerError());
530
596
  // Sort errors from fatal to info
531
597
  wrappedErrors.sort((a, b) => {
532
598
  const i = common_1.IssueSeverity.Keys.indexOf(a.severity) - common_1.IssueSeverity.Keys.indexOf(b.severity);
@@ -534,37 +600,7 @@ class HttpHandler {
534
600
  return b.status - a.status;
535
601
  return i;
536
602
  });
537
- let status = response.statusCode || 0;
538
- if (!status || status < Number(common_1.HttpStatusCode.BAD_REQUEST)) {
539
- status = wrappedErrors[0].status;
540
- if (status < Number(common_1.HttpStatusCode.BAD_REQUEST))
541
- status = common_1.HttpStatusCode.INTERNAL_SERVER_ERROR;
542
- }
543
- response.statusCode = status;
544
- const { document } = this.adapter;
545
- const dt = document.node.getComplexType('OperationResult');
546
- let encode = this[constants_js_1.kAssetCache].get(dt, 'encode');
547
- if (!encode) {
548
- encode = dt.generateCodec('encode');
549
- this[constants_js_1.kAssetCache].set(dt, 'encode', encode);
550
- }
551
- const { i18n } = this.adapter;
552
- const bodyObject = new common_1.OperationResult({
553
- errors: wrappedErrors.map(x => {
554
- const o = x.toJSON();
555
- if (!(process.env.NODE_ENV === 'dev' || process.env.NODE_ENV === 'development'))
556
- delete o.stack;
557
- return i18n.deep(o);
558
- }),
559
- });
560
- const body = encode(bodyObject);
561
- response.setHeader(common_1.HttpHeaderCodes.Content_Type, common_1.MimeTypes.opra_response_json + '; charset=utf-8');
562
- response.setHeader(common_1.HttpHeaderCodes.Cache_Control, 'no-cache');
563
- response.setHeader(common_1.HttpHeaderCodes.Pragma, 'no-cache');
564
- response.setHeader(common_1.HttpHeaderCodes.Expires, '-1');
565
- response.setHeader(common_1.HttpHeaderCodes.X_Opra_Version, common_1.OpraSchema.SpecVersion);
566
- response.send(JSON.stringify(body));
567
- response.end();
603
+ return wrappedErrors;
568
604
  }
569
605
  }
570
606
  exports.HttpHandler = HttpHandler;