@opra/core 1.0.0-alpha.25 → 1.0.0-alpha.26

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.
@@ -69,26 +69,7 @@ class HttpContext extends execution_context_js_1.ExecutionContext {
69
69
  /** Retrieve all fields */
70
70
  const parts = await reader.getAll();
71
71
  /** Filter fields according to configuration */
72
- this._body = [];
73
- const multipartFields = mediaType?.multipartFields;
74
- if (mediaType && multipartFields?.length) {
75
- const fieldsFound = new Map();
76
- for (const item of parts) {
77
- const field = mediaType.findMultipartField(item.field, item.kind);
78
- if (field) {
79
- fieldsFound.set(field, true);
80
- this._body.push(item);
81
- }
82
- }
83
- /** Check required fields */
84
- for (const field of multipartFields) {
85
- if (field.required && !fieldsFound.get(field)) {
86
- throw new common_1.BadRequestError({
87
- message: `Multipart field (${field.fieldName}) is required`,
88
- });
89
- }
90
- }
91
- }
72
+ this._body = [...parts];
92
73
  return this._body;
93
74
  }
94
75
  this._body = await this.request.readBody({ limit: operation.requestBody?.maxContentSize });
@@ -378,30 +378,26 @@ class HttpHandler {
378
378
  }
379
379
  }
380
380
  async _sendErrorResponse(context) {
381
+ context.errors = this._wrapExceptions(context.errors);
382
+ try {
383
+ await this.adapter.emitAsync('error', context.errors, context);
384
+ context.errors = this._wrapExceptions(context.errors);
385
+ }
386
+ catch (e) {
387
+ context.errors = this._wrapExceptions([e, ...context.errors]);
388
+ }
381
389
  const { response, errors } = context;
382
390
  if (response.headersSent) {
383
391
  response.end();
384
392
  return;
385
393
  }
386
- const wrappedErrors = errors.map(wrap_exception_1.wrapException);
387
- if (!wrappedErrors.length)
388
- wrappedErrors.push(new common_1.InternalServerError());
389
- // Sort errors from fatal to info
390
- wrappedErrors.sort((a, b) => {
391
- const i = common_1.IssueSeverity.Keys.indexOf(a.severity) - common_1.IssueSeverity.Keys.indexOf(b.severity);
392
- if (i === 0)
393
- return b.status - a.status;
394
- return i;
395
- });
396
- context.errors = wrappedErrors;
397
394
  let status = response.statusCode || 0;
398
395
  if (!status || status < Number(common_1.HttpStatusCode.BAD_REQUEST)) {
399
- status = wrappedErrors[0].status;
396
+ status = errors[0].status;
400
397
  if (status < Number(common_1.HttpStatusCode.BAD_REQUEST))
401
398
  status = common_1.HttpStatusCode.INTERNAL_SERVER_ERROR;
402
399
  }
403
400
  response.statusCode = status;
404
- this.adapter.emitAsync('error', wrappedErrors[0], context).catch(() => undefined);
405
401
  const { document } = this.adapter;
406
402
  const dt = document.node.getComplexType('OperationResult');
407
403
  let encode = this[constants_1.kAssetCache].get(dt, 'encode');
@@ -411,7 +407,7 @@ class HttpHandler {
411
407
  }
412
408
  const { i18n } = this.adapter;
413
409
  const bodyObject = new common_1.OperationResult({
414
- errors: wrappedErrors.map(x => {
410
+ errors: errors.map(x => {
415
411
  const o = x.toJSON();
416
412
  if (!(process.env.NODE_ENV === 'dev' || process.env.NODE_ENV === 'development'))
417
413
  delete o.stack;
@@ -427,6 +423,30 @@ class HttpHandler {
427
423
  response.send(JSON.stringify(body));
428
424
  response.end();
429
425
  }
426
+ async sendDocumentSchema(context) {
427
+ const { request, response } = context;
428
+ const { document } = this.adapter;
429
+ response.setHeader('content-type', common_1.MimeTypes.json);
430
+ const url = new URL(request.originalUrl || request.url || '/', 'http://tempuri.org');
431
+ const { searchParams } = url;
432
+ const documentId = searchParams.get('id');
433
+ const doc = documentId ? document.findDocument(documentId) : document;
434
+ if (!doc) {
435
+ context.errors.push(new common_1.BadRequestError({
436
+ message: `Document with given id [${documentId}] does not exists`,
437
+ }));
438
+ return this.sendResponse(context);
439
+ }
440
+ /** Check if response cache exists */
441
+ let responseBody = this[constants_1.kAssetCache].get(doc, `$schema`);
442
+ /** Create response if response cache does not exists */
443
+ if (!responseBody) {
444
+ const schema = doc.export();
445
+ responseBody = JSON.stringify(schema);
446
+ this[constants_1.kAssetCache].set(doc, `$schema`, responseBody);
447
+ }
448
+ response.end(responseBody);
449
+ }
430
450
  /**
431
451
  *
432
452
  * @param context
@@ -554,29 +574,18 @@ class HttpHandler {
554
574
  responseArgs.body = body;
555
575
  return responseArgs;
556
576
  }
557
- async sendDocumentSchema(context) {
558
- const { request, response } = context;
559
- const { document } = this.adapter;
560
- response.setHeader('content-type', common_1.MimeTypes.json);
561
- const url = new URL(request.originalUrl || request.url || '/', 'http://tempuri.org');
562
- const { searchParams } = url;
563
- const documentId = searchParams.get('id');
564
- const doc = documentId ? document.findDocument(documentId) : document;
565
- if (!doc) {
566
- context.errors.push(new common_1.BadRequestError({
567
- message: `Document with given id [${documentId}] does not exists`,
568
- }));
569
- return this.sendResponse(context);
570
- }
571
- /** Check if response cache exists */
572
- let responseBody = this[constants_1.kAssetCache].get(doc, `$schema`);
573
- /** Create response if response cache does not exists */
574
- if (!responseBody) {
575
- const schema = doc.export();
576
- responseBody = JSON.stringify(schema);
577
- this[constants_1.kAssetCache].set(doc, `$schema`, responseBody);
578
- }
579
- response.end(responseBody);
577
+ _wrapExceptions(exceptions) {
578
+ const wrappedErrors = exceptions.map(wrap_exception_1.wrapException);
579
+ if (!wrappedErrors.length)
580
+ wrappedErrors.push(new common_1.InternalServerError());
581
+ // Sort errors from fatal to info
582
+ wrappedErrors.sort((a, b) => {
583
+ const i = common_1.IssueSeverity.Keys.indexOf(a.severity) - common_1.IssueSeverity.Keys.indexOf(b.severity);
584
+ if (i === 0)
585
+ return b.status - a.status;
586
+ return i;
587
+ });
588
+ return wrappedErrors;
580
589
  }
581
590
  }
582
591
  exports.HttpHandler = HttpHandler;
@@ -76,15 +76,22 @@ class MultipartReader extends events_1.EventEmitter {
76
76
  if (!item && !this._finished) {
77
77
  this.resume();
78
78
  item = await new Promise((resolve, reject) => {
79
+ let resolved = false;
79
80
  if (this._stack.length)
80
81
  return resolve(this._stack.shift());
81
82
  if (this._form.ended)
82
83
  return resolve(undefined);
83
84
  this._form.once('close', () => {
85
+ if (resolved)
86
+ return;
87
+ resolved = true;
84
88
  resolve(this._stack.shift());
85
89
  });
86
90
  this.once('item', () => {
87
91
  this.pause();
92
+ if (resolved)
93
+ return;
94
+ resolved = true;
88
95
  resolve(this._stack.shift());
89
96
  });
90
97
  this.once('error', e => reject(e));
@@ -119,6 +126,8 @@ class MultipartReader extends events_1.EventEmitter {
119
126
  }
120
127
  let issues;
121
128
  for (const field of fieldsLeft) {
129
+ if (!field.required)
130
+ continue;
122
131
  try {
123
132
  (0, valgen_1.isNotNullish)(null, { onFail: () => `Multi part field "${String(field.fieldName)}" is required` });
124
133
  }
@@ -137,7 +146,7 @@ class MultipartReader extends events_1.EventEmitter {
137
146
  return item;
138
147
  }
139
148
  async getAll() {
140
- const items = [];
149
+ const items = [...this._items];
141
150
  let item;
142
151
  while (!this._cancelled && (item = await this.getNext())) {
143
152
  items.push(item);
@@ -1,5 +1,5 @@
1
1
  import typeIs from '@browsery/type-is';
2
- import { BadRequestError, InternalServerError, NotAcceptableError, } from '@opra/common';
2
+ import { InternalServerError, NotAcceptableError, } from '@opra/common';
3
3
  import { vg } from 'valgen';
4
4
  import { kAssetCache } from '../constants.js';
5
5
  import { ExecutionContext } from '../execution-context.js';
@@ -65,26 +65,7 @@ export class HttpContext extends ExecutionContext {
65
65
  /** Retrieve all fields */
66
66
  const parts = await reader.getAll();
67
67
  /** Filter fields according to configuration */
68
- this._body = [];
69
- const multipartFields = mediaType?.multipartFields;
70
- if (mediaType && multipartFields?.length) {
71
- const fieldsFound = new Map();
72
- for (const item of parts) {
73
- const field = mediaType.findMultipartField(item.field, item.kind);
74
- if (field) {
75
- fieldsFound.set(field, true);
76
- this._body.push(item);
77
- }
78
- }
79
- /** Check required fields */
80
- for (const field of multipartFields) {
81
- if (field.required && !fieldsFound.get(field)) {
82
- throw new BadRequestError({
83
- message: `Multipart field (${field.fieldName}) is required`,
84
- });
85
- }
86
- }
87
- }
68
+ this._body = [...parts];
88
69
  return this._body;
89
70
  }
90
71
  this._body = await this.request.readBody({ limit: operation.requestBody?.maxContentSize });
@@ -374,30 +374,26 @@ export class HttpHandler {
374
374
  }
375
375
  }
376
376
  async _sendErrorResponse(context) {
377
+ context.errors = this._wrapExceptions(context.errors);
378
+ try {
379
+ await this.adapter.emitAsync('error', context.errors, context);
380
+ context.errors = this._wrapExceptions(context.errors);
381
+ }
382
+ catch (e) {
383
+ context.errors = this._wrapExceptions([e, ...context.errors]);
384
+ }
377
385
  const { response, errors } = context;
378
386
  if (response.headersSent) {
379
387
  response.end();
380
388
  return;
381
389
  }
382
- const wrappedErrors = errors.map(wrapException);
383
- if (!wrappedErrors.length)
384
- wrappedErrors.push(new InternalServerError());
385
- // Sort errors from fatal to info
386
- wrappedErrors.sort((a, b) => {
387
- const i = IssueSeverity.Keys.indexOf(a.severity) - IssueSeverity.Keys.indexOf(b.severity);
388
- if (i === 0)
389
- return b.status - a.status;
390
- return i;
391
- });
392
- context.errors = wrappedErrors;
393
390
  let status = response.statusCode || 0;
394
391
  if (!status || status < Number(HttpStatusCode.BAD_REQUEST)) {
395
- status = wrappedErrors[0].status;
392
+ status = errors[0].status;
396
393
  if (status < Number(HttpStatusCode.BAD_REQUEST))
397
394
  status = HttpStatusCode.INTERNAL_SERVER_ERROR;
398
395
  }
399
396
  response.statusCode = status;
400
- this.adapter.emitAsync('error', wrappedErrors[0], context).catch(() => undefined);
401
397
  const { document } = this.adapter;
402
398
  const dt = document.node.getComplexType('OperationResult');
403
399
  let encode = this[kAssetCache].get(dt, 'encode');
@@ -407,7 +403,7 @@ export class HttpHandler {
407
403
  }
408
404
  const { i18n } = this.adapter;
409
405
  const bodyObject = new OperationResult({
410
- errors: wrappedErrors.map(x => {
406
+ errors: errors.map(x => {
411
407
  const o = x.toJSON();
412
408
  if (!(process.env.NODE_ENV === 'dev' || process.env.NODE_ENV === 'development'))
413
409
  delete o.stack;
@@ -423,6 +419,30 @@ export class HttpHandler {
423
419
  response.send(JSON.stringify(body));
424
420
  response.end();
425
421
  }
422
+ async sendDocumentSchema(context) {
423
+ const { request, response } = context;
424
+ const { document } = this.adapter;
425
+ response.setHeader('content-type', MimeTypes.json);
426
+ const url = new URL(request.originalUrl || request.url || '/', 'http://tempuri.org');
427
+ const { searchParams } = url;
428
+ const documentId = searchParams.get('id');
429
+ const doc = documentId ? document.findDocument(documentId) : document;
430
+ if (!doc) {
431
+ context.errors.push(new BadRequestError({
432
+ message: `Document with given id [${documentId}] does not exists`,
433
+ }));
434
+ return this.sendResponse(context);
435
+ }
436
+ /** Check if response cache exists */
437
+ let responseBody = this[kAssetCache].get(doc, `$schema`);
438
+ /** Create response if response cache does not exists */
439
+ if (!responseBody) {
440
+ const schema = doc.export();
441
+ responseBody = JSON.stringify(schema);
442
+ this[kAssetCache].set(doc, `$schema`, responseBody);
443
+ }
444
+ response.end(responseBody);
445
+ }
426
446
  /**
427
447
  *
428
448
  * @param context
@@ -550,28 +570,17 @@ export class HttpHandler {
550
570
  responseArgs.body = body;
551
571
  return responseArgs;
552
572
  }
553
- async sendDocumentSchema(context) {
554
- const { request, response } = context;
555
- const { document } = this.adapter;
556
- response.setHeader('content-type', MimeTypes.json);
557
- const url = new URL(request.originalUrl || request.url || '/', 'http://tempuri.org');
558
- const { searchParams } = url;
559
- const documentId = searchParams.get('id');
560
- const doc = documentId ? document.findDocument(documentId) : document;
561
- if (!doc) {
562
- context.errors.push(new BadRequestError({
563
- message: `Document with given id [${documentId}] does not exists`,
564
- }));
565
- return this.sendResponse(context);
566
- }
567
- /** Check if response cache exists */
568
- let responseBody = this[kAssetCache].get(doc, `$schema`);
569
- /** Create response if response cache does not exists */
570
- if (!responseBody) {
571
- const schema = doc.export();
572
- responseBody = JSON.stringify(schema);
573
- this[kAssetCache].set(doc, `$schema`, responseBody);
574
- }
575
- response.end(responseBody);
573
+ _wrapExceptions(exceptions) {
574
+ const wrappedErrors = exceptions.map(wrapException);
575
+ if (!wrappedErrors.length)
576
+ wrappedErrors.push(new InternalServerError());
577
+ // Sort errors from fatal to info
578
+ wrappedErrors.sort((a, b) => {
579
+ const i = IssueSeverity.Keys.indexOf(a.severity) - IssueSeverity.Keys.indexOf(b.severity);
580
+ if (i === 0)
581
+ return b.status - a.status;
582
+ return i;
583
+ });
584
+ return wrappedErrors;
576
585
  }
577
586
  }
@@ -72,15 +72,22 @@ export class MultipartReader extends EventEmitter {
72
72
  if (!item && !this._finished) {
73
73
  this.resume();
74
74
  item = await new Promise((resolve, reject) => {
75
+ let resolved = false;
75
76
  if (this._stack.length)
76
77
  return resolve(this._stack.shift());
77
78
  if (this._form.ended)
78
79
  return resolve(undefined);
79
80
  this._form.once('close', () => {
81
+ if (resolved)
82
+ return;
83
+ resolved = true;
80
84
  resolve(this._stack.shift());
81
85
  });
82
86
  this.once('item', () => {
83
87
  this.pause();
88
+ if (resolved)
89
+ return;
90
+ resolved = true;
84
91
  resolve(this._stack.shift());
85
92
  });
86
93
  this.once('error', e => reject(e));
@@ -115,6 +122,8 @@ export class MultipartReader extends EventEmitter {
115
122
  }
116
123
  let issues;
117
124
  for (const field of fieldsLeft) {
125
+ if (!field.required)
126
+ continue;
118
127
  try {
119
128
  isNotNullish(null, { onFail: () => `Multi part field "${String(field.fieldName)}" is required` });
120
129
  }
@@ -133,7 +142,7 @@ export class MultipartReader extends EventEmitter {
133
142
  return item;
134
143
  }
135
144
  async getAll() {
136
- const items = [];
145
+ const items = [...this._items];
137
146
  let item;
138
147
  while (!this._cancelled && (item = await this.getNext())) {
139
148
  items.push(item);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opra/core",
3
- "version": "1.0.0-alpha.25",
3
+ "version": "1.0.0-alpha.26",
4
4
  "description": "Opra schema package",
5
5
  "author": "Panates",
6
6
  "license": "MIT",
@@ -33,7 +33,7 @@
33
33
  "dependencies": {
34
34
  "@browsery/http-parser": "^0.5.8",
35
35
  "@browsery/type-is": "^1.6.18-r2",
36
- "@opra/common": "^1.0.0-alpha.25",
36
+ "@opra/common": "^1.0.0-alpha.26",
37
37
  "accepts": "^1.3.8",
38
38
  "base64-stream": "^1.0.0",
39
39
  "busboy": "^1.6.0",
@@ -1,4 +1,4 @@
1
- import { ApiDocument, OpraException, OpraSchema } from '@opra/common';
1
+ import { ApiDocument, OpraHttpError, OpraSchema } from '@opra/common';
2
2
  import { AsyncEventEmitter } from 'strict-typed-events';
3
3
  /**
4
4
  * @namespace ExecutionContext
@@ -18,7 +18,7 @@ export declare abstract class ExecutionContext extends AsyncEventEmitter {
18
18
  readonly document: ApiDocument;
19
19
  readonly protocol: OpraSchema.Protocol;
20
20
  readonly platform: string;
21
- errors: OpraException[];
21
+ errors: OpraHttpError[];
22
22
  protected constructor(init: ExecutionContext.Initiator);
23
23
  addListener(event: 'finish', listener: ExecutionContext.OnFinishListener): this;
24
24
  removeListener(event: 'finish', listener: ExecutionContext.OnFinishListener): this;
@@ -1,4 +1,4 @@
1
- import { HttpOperationResponse, OpraException } from '@opra/common';
1
+ import { HttpOperationResponse, OpraException, OpraHttpError } from '@opra/common';
2
2
  import { kAssetCache } from '../constants';
3
3
  import type { HttpAdapter } from './http-adapter';
4
4
  import { HttpContext } from './http-context';
@@ -62,6 +62,7 @@ export declare class HttpHandler {
62
62
  */
63
63
  sendResponse(context: HttpContext, responseValue?: any): Promise<void>;
64
64
  protected _sendErrorResponse(context: HttpContext): Promise<void>;
65
+ sendDocumentSchema(context: HttpContext): Promise<void>;
65
66
  /**
66
67
  *
67
68
  * @param context
@@ -69,5 +70,5 @@ export declare class HttpHandler {
69
70
  * @protected
70
71
  */
71
72
  protected _determineResponseArgs(context: HttpContext, body: any): HttpHandler.ResponseArgs;
72
- sendDocumentSchema(context: HttpContext): Promise<void>;
73
+ protected _wrapExceptions(exceptions: any[]): OpraHttpError[];
73
74
  }