@opra/core 1.0.0-alpha.2 → 1.0.0-alpha.21
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 +1 -1
- package/cjs/execution-context.js +0 -1
- package/cjs/http/express-adapter.js +6 -17
- package/cjs/http/http-context.js +6 -3
- package/cjs/http/impl/http-handler.js +73 -62
- package/cjs/http/impl/http-incoming.host.js +3 -3
- package/cjs/http/impl/http-outgoing.host.js +2 -2
- package/cjs/http/impl/multipart-reader.js +4 -10
- package/cjs/http/impl/node-incoming-message.host.js +5 -3
- package/cjs/http/interfaces/node-incoming-message.interface.js +3 -2
- package/cjs/http/utils/body-reader.js +6 -5
- package/cjs/http/utils/common.js +6 -5
- 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 +3 -3
- package/cjs/platform-adapter.js +1 -1
- package/cjs/type-guards.js +4 -5
- package/esm/augmentation/18n.augmentation.js +1 -1
- package/esm/execution-context.js +0 -1
- package/esm/http/express-adapter.js +6 -17
- package/esm/http/http-context.js +6 -3
- package/esm/http/impl/http-handler.js +73 -62
- package/esm/http/impl/http-incoming.host.js +3 -3
- package/esm/http/impl/http-outgoing.host.js +2 -2
- package/esm/http/impl/multipart-reader.js +4 -10
- package/esm/http/impl/node-incoming-message.host.js +5 -3
- package/esm/http/interfaces/node-incoming-message.interface.js +3 -2
- package/esm/http/utils/body-reader.js +6 -5
- package/esm/http/utils/common.js +2 -1
- package/esm/index.js +3 -3
- package/esm/platform-adapter.js +1 -1
- package/i18n/i18n/en/error.json +21 -0
- package/package.json +13 -7
- package/types/augmentation/18n.augmentation.d.ts +1 -1
- package/types/execution-context.d.ts +1 -3
- package/types/http/express-adapter.d.ts +1 -1
- package/types/http/http-context.d.ts +2 -2
- 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 +0 -1
- package/types/http/impl/node-incoming-message.host.d.ts +2 -6
- package/types/http/impl/node-outgoing-message.host.d.ts +2 -5
- package/types/http/interfaces/http-incoming.interface.d.ts +1 -2
- package/types/http/interfaces/http-outgoing.interface.d.ts +1 -1
- package/types/http/interfaces/node-incoming-message.interface.d.ts +0 -2
- package/types/http/interfaces/node-outgoing-message.interface.d.ts +0 -2
- 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 +0 -1
- package/types/index.d.ts +3 -3
- package/types/platform-adapter.d.ts +2 -2
package/cjs/http/utils/common.js
CHANGED
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
https://github.com/nodejs/
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.
|
|
7
|
+
exports.validateHeaderValue = exports.validateHeaderName = void 0;
|
|
8
|
+
exports.checkIsHttpToken = checkIsHttpToken;
|
|
9
|
+
exports.hideStackFrames = hideStackFrames;
|
|
10
|
+
exports.validateString = validateString;
|
|
8
11
|
const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/;
|
|
9
12
|
const nodeInternalPrefix = '__node_internal_';
|
|
10
13
|
/**
|
|
@@ -17,7 +20,6 @@ const nodeInternalPrefix = '__node_internal_';
|
|
|
17
20
|
function checkIsHttpToken(val) {
|
|
18
21
|
return typeof val === 'string' && tokenRegExp.exec(val) !== null;
|
|
19
22
|
}
|
|
20
|
-
exports.checkIsHttpToken = checkIsHttpToken;
|
|
21
23
|
const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
|
|
22
24
|
/**
|
|
23
25
|
* True if val contains an invalid field-vchar
|
|
@@ -44,7 +46,6 @@ function hideStackFrames(fn) {
|
|
|
44
46
|
Object.defineProperty(fn, 'name', { __proto__: null, value: hidden });
|
|
45
47
|
return fn;
|
|
46
48
|
}
|
|
47
|
-
exports.hideStackFrames = hideStackFrames;
|
|
48
49
|
exports.validateHeaderName = hideStackFrames((name, label) => {
|
|
49
50
|
// noinspection SuspiciousTypeOfGuard
|
|
50
51
|
if (typeof name !== 'string' || !name || !checkIsHttpToken(name)) {
|
|
@@ -60,7 +61,7 @@ exports.validateHeaderValue = hideStackFrames((name, value) => {
|
|
|
60
61
|
}
|
|
61
62
|
});
|
|
62
63
|
function validateString(value, name) {
|
|
63
|
-
if (typeof value !== 'string')
|
|
64
|
+
if (typeof value !== 'string') {
|
|
64
65
|
throw new TypeError(`Invalid ${name ? name + ' ' : ''}argument. Value must be a string`);
|
|
66
|
+
}
|
|
65
67
|
}
|
|
66
|
-
exports.validateString = validateString;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.concatReadable =
|
|
3
|
+
exports.concatReadable = concatReadable;
|
|
4
4
|
const stream_1 = require("stream");
|
|
5
5
|
function concatReadable(...streams) {
|
|
6
6
|
const out = new stream_1.PassThrough();
|
|
@@ -17,4 +17,3 @@ function concatReadable(...streams) {
|
|
|
17
17
|
pipeNext();
|
|
18
18
|
return out;
|
|
19
19
|
}
|
|
20
|
-
exports.concatReadable = concatReadable;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.convertToHeaders = convertToHeaders;
|
|
4
|
+
exports.convertToHeadersDistinct = convertToHeadersDistinct;
|
|
4
5
|
const match_known_fields_js_1 = require("./match-known-fields.js");
|
|
5
6
|
function convertToHeaders(src, dst, joinDuplicateHeaders) {
|
|
6
7
|
for (let n = 0; n < src.length; n += 2) {
|
|
@@ -8,7 +9,6 @@ function convertToHeaders(src, dst, joinDuplicateHeaders) {
|
|
|
8
9
|
}
|
|
9
10
|
return dst;
|
|
10
11
|
}
|
|
11
|
-
exports.convertToHeaders = convertToHeaders;
|
|
12
12
|
function convertToHeadersDistinct(src, dst) {
|
|
13
13
|
const count = src.length % 2;
|
|
14
14
|
for (let n = 0; n < count; n += 2) {
|
|
@@ -16,7 +16,6 @@ function convertToHeadersDistinct(src, dst) {
|
|
|
16
16
|
}
|
|
17
17
|
return dst;
|
|
18
18
|
}
|
|
19
|
-
exports.convertToHeadersDistinct = convertToHeadersDistinct;
|
|
20
19
|
function addHeaderLine(field, value, dest, joinDuplicateHeaders) {
|
|
21
20
|
if (value == null)
|
|
22
21
|
return;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.convertToRawHeaders =
|
|
3
|
+
exports.convertToRawHeaders = convertToRawHeaders;
|
|
4
4
|
const match_known_fields_js_1 = require("./match-known-fields.js");
|
|
5
5
|
function convertToRawHeaders(src) {
|
|
6
6
|
return Object.entries(src).reduce((a, [field, v]) => {
|
|
@@ -21,4 +21,3 @@ function convertToRawHeaders(src) {
|
|
|
21
21
|
return a;
|
|
22
22
|
}, []);
|
|
23
23
|
}
|
|
24
|
-
exports.convertToRawHeaders = convertToRawHeaders;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.ARRAY_FIELD = exports.SEMICOLON_DELIMITED_FIELD = exports.COMMA_DELIMITED_FIELD = exports.NO_DUPLICATES_FIELD = void 0;
|
|
4
|
+
exports.matchKnownFields = matchKnownFields;
|
|
4
5
|
const common_1 = require("@opra/common");
|
|
5
6
|
exports.NO_DUPLICATES_FIELD = 0;
|
|
6
7
|
exports.COMMA_DELIMITED_FIELD = 1;
|
|
@@ -46,4 +47,3 @@ function matchKnownFields(field) {
|
|
|
46
47
|
const x = KNOWN_FIELDS[field.toLowerCase()];
|
|
47
48
|
return x ? x : [field, exports.COMMA_DELIMITED_FIELD];
|
|
48
49
|
}
|
|
49
|
-
exports.matchKnownFields = matchKnownFields;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.wrapException =
|
|
3
|
+
exports.wrapException = wrapException;
|
|
4
4
|
const common_1 = require("@opra/common");
|
|
5
5
|
function wrapException(error) {
|
|
6
6
|
if (error instanceof common_1.OpraHttpError)
|
|
@@ -31,4 +31,3 @@ function wrapException(error) {
|
|
|
31
31
|
return new common_1.InternalServerError(error);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
exports.wrapException = wrapException;
|
package/cjs/index.js
CHANGED
|
@@ -10,20 +10,20 @@ const HttpOutgoingHost_ = tslib_1.__importStar(require("./http/impl/http-outgoin
|
|
|
10
10
|
const NodeIncomingMessageHost_ = tslib_1.__importStar(require("./http/impl/node-incoming-message.host.js"));
|
|
11
11
|
const NodeOutgoingMessageHost_ = tslib_1.__importStar(require("./http/impl/node-outgoing-message.host.js"));
|
|
12
12
|
tslib_1.__exportStar(require("./execution-context.js"), exports);
|
|
13
|
-
tslib_1.__exportStar(require("./platform-adapter.js"), exports);
|
|
14
|
-
tslib_1.__exportStar(require("./type-guards.js"), exports);
|
|
15
13
|
tslib_1.__exportStar(require("./helpers/logger.js"), exports);
|
|
16
14
|
tslib_1.__exportStar(require("./helpers/service-base.js"), exports);
|
|
17
15
|
tslib_1.__exportStar(require("./http/express-adapter.js"), exports);
|
|
18
16
|
tslib_1.__exportStar(require("./http/http-adapter.js"), exports);
|
|
19
17
|
tslib_1.__exportStar(require("./http/http-context.js"), exports);
|
|
18
|
+
tslib_1.__exportStar(require("./http/impl/multipart-reader.js"), exports);
|
|
20
19
|
tslib_1.__exportStar(require("./http/interfaces/http-incoming.interface.js"), exports);
|
|
21
20
|
tslib_1.__exportStar(require("./http/interfaces/http-outgoing.interface.js"), exports);
|
|
22
21
|
tslib_1.__exportStar(require("./http/interfaces/node-incoming-message.interface.js"), exports);
|
|
23
22
|
tslib_1.__exportStar(require("./http/interfaces/node-outgoing-message.interface.js"), exports);
|
|
24
|
-
tslib_1.__exportStar(require("./http/impl/multipart-reader.js"), exports);
|
|
25
23
|
tslib_1.__exportStar(require("./http/utils/wrap-exception.js"), exports);
|
|
26
24
|
tslib_1.__exportStar(require("./interfaces/logger.interface.js"), exports);
|
|
25
|
+
tslib_1.__exportStar(require("./platform-adapter.js"), exports);
|
|
26
|
+
tslib_1.__exportStar(require("./type-guards.js"), exports);
|
|
27
27
|
var classes;
|
|
28
28
|
(function (classes) {
|
|
29
29
|
classes.HttpIncomingHost = HttpIncomingHost_.HttpIncomingHost;
|
package/cjs/platform-adapter.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PlatformAdapter = void 0;
|
|
4
4
|
require("./augmentation/18n.augmentation.js");
|
|
5
|
-
const strict_typed_events_1 = require("strict-typed-events");
|
|
6
5
|
const common_1 = require("@opra/common");
|
|
6
|
+
const strict_typed_events_1 = require("strict-typed-events");
|
|
7
7
|
const constants_js_1 = require("./constants.js");
|
|
8
8
|
const logger_js_1 = require("./helpers/logger.js");
|
|
9
9
|
const asset_cache_js_1 = require("./http/impl/asset-cache.js");
|
package/cjs/type-guards.js
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.isNodeIncomingMessage = isNodeIncomingMessage;
|
|
4
|
+
exports.isHttpIncoming = isHttpIncoming;
|
|
5
|
+
exports.isNodeOutgoingMessage = isNodeOutgoingMessage;
|
|
6
|
+
exports.isHttpOutgoing = isHttpOutgoing;
|
|
4
7
|
const common_1 = require("@opra/common");
|
|
5
8
|
function isNodeIncomingMessage(v) {
|
|
6
9
|
return v && typeof v.method === 'string' && Array.isArray(v.rawHeaders) && (0, common_1.isReadable)(v);
|
|
7
10
|
}
|
|
8
|
-
exports.isNodeIncomingMessage = isNodeIncomingMessage;
|
|
9
11
|
function isHttpIncoming(v) {
|
|
10
12
|
return (isNodeIncomingMessage(v) &&
|
|
11
13
|
typeof v.header === 'function' &&
|
|
12
14
|
typeof v.acceptsLanguages === 'function' &&
|
|
13
15
|
typeof v.readBody === 'function');
|
|
14
16
|
}
|
|
15
|
-
exports.isHttpIncoming = isHttpIncoming;
|
|
16
17
|
function isNodeOutgoingMessage(v) {
|
|
17
18
|
return v && typeof v.getHeaders === 'function' && (0, common_1.isStream)(v);
|
|
18
19
|
}
|
|
19
|
-
exports.isNodeOutgoingMessage = isNodeOutgoingMessage;
|
|
20
20
|
function isHttpOutgoing(v) {
|
|
21
21
|
return isNodeOutgoingMessage(v) && typeof v.clearCookie === 'function' && typeof v.cookie === 'function';
|
|
22
22
|
}
|
|
23
|
-
exports.isHttpOutgoing = isHttpOutgoing;
|
package/esm/execution-context.js
CHANGED
|
@@ -8,7 +8,6 @@ export class ExecutionContext extends AsyncEventEmitter {
|
|
|
8
8
|
this.document = init.document;
|
|
9
9
|
this.protocol = init.protocol;
|
|
10
10
|
this.platform = init.platform;
|
|
11
|
-
this.platformArgs = init.platformArgs;
|
|
12
11
|
}
|
|
13
12
|
addListener(event, listener) {
|
|
14
13
|
return super.addListener(event, listener);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { HttpApi, NotFoundError } from '@opra/common';
|
|
1
2
|
import { Router } from 'express';
|
|
2
3
|
import * as nodePath from 'path';
|
|
3
|
-
import { HttpApi, NotFoundError } from '@opra/common';
|
|
4
4
|
import { kHandler } from '../constants.js';
|
|
5
5
|
import { HttpAdapter } from './http-adapter.js';
|
|
6
6
|
import { HttpContext } from './http-context.js';
|
|
@@ -31,13 +31,14 @@ export class ExpressAdapter extends HttpAdapter {
|
|
|
31
31
|
}
|
|
32
32
|
if (resource.onShutdown) {
|
|
33
33
|
const instance = this._controllerInstances.get(resource) || resource.instance;
|
|
34
|
-
if (instance)
|
|
34
|
+
if (instance) {
|
|
35
35
|
try {
|
|
36
36
|
await resource.onShutdown.call(instance, resource);
|
|
37
37
|
}
|
|
38
38
|
catch (e) {
|
|
39
39
|
this.logger.error(e);
|
|
40
40
|
}
|
|
41
|
+
}
|
|
41
42
|
}
|
|
42
43
|
};
|
|
43
44
|
for (const c of this.api.controllers.values())
|
|
@@ -61,14 +62,9 @@ export class ExpressAdapter extends HttpAdapter {
|
|
|
61
62
|
const createContext = (_req, _res, args) => {
|
|
62
63
|
const request = HttpIncoming.from(_req);
|
|
63
64
|
const response = HttpOutgoing.from(_res);
|
|
64
|
-
const platformArgs = {
|
|
65
|
-
request: _req,
|
|
66
|
-
response: _res,
|
|
67
|
-
};
|
|
68
65
|
return new HttpContext({
|
|
69
66
|
adapter: this,
|
|
70
67
|
platform: this.platform,
|
|
71
|
-
platformArgs,
|
|
72
68
|
request,
|
|
73
69
|
response,
|
|
74
70
|
controller: args?.controller,
|
|
@@ -78,16 +74,9 @@ export class ExpressAdapter extends HttpAdapter {
|
|
|
78
74
|
});
|
|
79
75
|
};
|
|
80
76
|
/** Add an endpoint that returns document schema */
|
|
81
|
-
router.get('
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (url === '/$schema') {
|
|
85
|
-
const context = createContext(_req, _res);
|
|
86
|
-
this[kHandler].sendDocumentSchema(context).catch(next);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
next();
|
|
77
|
+
router.get('/\\$schema', (_req, _res, next) => {
|
|
78
|
+
const context = createContext(_req, _res);
|
|
79
|
+
this[kHandler].sendDocumentSchema(context).catch(next);
|
|
91
80
|
});
|
|
92
81
|
/** Add operation endpoints */
|
|
93
82
|
if (this.api.controllers.size) {
|
package/esm/http/http-context.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { vg } from 'valgen';
|
|
2
1
|
import typeIs from '@browsery/type-is';
|
|
3
2
|
import { BadRequestError, InternalServerError, NotAcceptableError, } from '@opra/common';
|
|
3
|
+
import { vg } from 'valgen';
|
|
4
4
|
import { kAssetCache } from '../constants.js';
|
|
5
5
|
import { ExecutionContext } from '../execution-context.js';
|
|
6
6
|
import { MultipartReader } from './impl/multipart-reader.js';
|
|
@@ -74,10 +74,11 @@ export class HttpContext extends ExecutionContext {
|
|
|
74
74
|
}
|
|
75
75
|
/** Check required fields */
|
|
76
76
|
for (const field of multipartFields) {
|
|
77
|
-
if (field.required && !fieldsFound.get(field))
|
|
77
|
+
if (field.required && !fieldsFound.get(field)) {
|
|
78
78
|
throw new BadRequestError({
|
|
79
79
|
message: `Multipart field (${field.fieldName}) is required`,
|
|
80
80
|
});
|
|
81
|
+
}
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
84
|
return this._body;
|
|
@@ -85,8 +86,9 @@ export class HttpContext extends ExecutionContext {
|
|
|
85
86
|
this._body = await this.request.readBody({ limit: operation.requestBody?.maxContentSize });
|
|
86
87
|
if (this._body != null) {
|
|
87
88
|
// Convert Buffer to string if media is text
|
|
88
|
-
if (Buffer.isBuffer(this._body) && request.is(['json', 'xml', 'txt', 'text']))
|
|
89
|
+
if (Buffer.isBuffer(this._body) && request.is(['json', 'xml', 'txt', 'text'])) {
|
|
89
90
|
this._body = this._body.toString('utf-8');
|
|
91
|
+
}
|
|
90
92
|
// Transform text to Object if media is JSON
|
|
91
93
|
if (typeof this._body === 'string' && request.is(['json']))
|
|
92
94
|
this._body = JSON.parse(this._body);
|
|
@@ -100,6 +102,7 @@ export class HttpContext extends ExecutionContext {
|
|
|
100
102
|
mediaType.type?.generateCodec('decode', {
|
|
101
103
|
partial: operation.requestBody?.partial,
|
|
102
104
|
projection: '*',
|
|
105
|
+
ignoreReadonlyFields: true,
|
|
103
106
|
}) || vg.isAny();
|
|
104
107
|
this.adapter[kAssetCache].set(mediaType, 'decode', decode);
|
|
105
108
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
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, translate, } from '@opra/common';
|
|
1
4
|
import { parse as parseContentType } from 'content-type';
|
|
2
5
|
import { splitString } from 'fast-tokenizer';
|
|
3
|
-
import * as process from 'node:process';
|
|
4
6
|
import { asMutable } from 'ts-gems';
|
|
5
7
|
import { toArray, ValidationError, vg } from 'valgen';
|
|
6
|
-
import typeIs from '@browsery/type-is';
|
|
7
|
-
import { BadRequestError, HttpHeaderCodes, HttpStatusCode, InternalServerError, isBlob, isReadableStream, IssueSeverity, MethodNotAllowedError, MimeTypes, OperationResult, OpraException, OpraSchema, translate, } from '@opra/common';
|
|
8
8
|
import { kAssetCache } from '../../constants.js';
|
|
9
9
|
import { wrapException } from '../utils/wrap-exception.js';
|
|
10
10
|
/**
|
|
@@ -60,7 +60,8 @@ export class HttpHandler {
|
|
|
60
60
|
else
|
|
61
61
|
await this._executeRequest(context);
|
|
62
62
|
}
|
|
63
|
-
catch (
|
|
63
|
+
catch (error) {
|
|
64
|
+
let e = error;
|
|
64
65
|
if (e instanceof ValidationError) {
|
|
65
66
|
e = new InternalServerError({
|
|
66
67
|
message: translate('error:RESPONSE_VALIDATION,', 'Response validation failed'),
|
|
@@ -72,11 +73,10 @@ export class HttpHandler {
|
|
|
72
73
|
e = wrapException(e);
|
|
73
74
|
response.status(e.statusCode || e.status || HttpStatusCode.INTERNAL_SERVER_ERROR);
|
|
74
75
|
response.contentType(MimeTypes.opra_response_json);
|
|
75
|
-
await this._sendResponse(context, new OperationResult({ errors: [e] })).finally(() => {
|
|
76
|
+
await this._sendResponse(context, new OperationResult({ errors: [e.toJSON()] })).finally(() => {
|
|
76
77
|
if (!response.finished)
|
|
77
78
|
response.end();
|
|
78
79
|
});
|
|
79
|
-
// if (!outgoing.writableEnded) await this._sendErrorResponse(context.response, [error]);
|
|
80
80
|
}
|
|
81
81
|
finally {
|
|
82
82
|
await context.emitAsync('finish');
|
|
@@ -109,17 +109,17 @@ export class HttpHandler {
|
|
|
109
109
|
*/
|
|
110
110
|
async _parseParameters(context) {
|
|
111
111
|
const { operation, request } = context;
|
|
112
|
-
let
|
|
112
|
+
let key = '';
|
|
113
113
|
try {
|
|
114
114
|
const onFail = (issue) => {
|
|
115
|
-
issue.location =
|
|
115
|
+
issue.location = key;
|
|
116
116
|
return issue;
|
|
117
117
|
};
|
|
118
118
|
/** prepare decoders */
|
|
119
119
|
const getDecoder = (prm) => {
|
|
120
120
|
let decode = this[kAssetCache].get(prm, 'decode');
|
|
121
121
|
if (!decode) {
|
|
122
|
-
decode = prm.type?.generateCodec('decode') || vg.isAny();
|
|
122
|
+
decode = prm.type?.generateCodec('decode', { ignoreReadonlyFields: true }) || vg.isAny();
|
|
123
123
|
this[kAssetCache].set(prm, 'decode', decode);
|
|
124
124
|
}
|
|
125
125
|
return decode;
|
|
@@ -127,9 +127,9 @@ export class HttpHandler {
|
|
|
127
127
|
const paramsLeft = new Set([...operation.parameters, ...operation.owner.parameters]);
|
|
128
128
|
/** parse cookie parameters */
|
|
129
129
|
if (request.cookies) {
|
|
130
|
-
for (
|
|
131
|
-
const oprPrm = operation.findParameter(
|
|
132
|
-
const cntPrm = operation.owner.findParameter(
|
|
130
|
+
for (key of Object.keys(request.cookies)) {
|
|
131
|
+
const oprPrm = operation.findParameter(key, 'cookie');
|
|
132
|
+
const cntPrm = operation.owner.findParameter(key, 'cookie');
|
|
133
133
|
const prm = oprPrm || cntPrm;
|
|
134
134
|
if (!prm)
|
|
135
135
|
continue;
|
|
@@ -138,16 +138,17 @@ export class HttpHandler {
|
|
|
138
138
|
if (cntPrm)
|
|
139
139
|
paramsLeft.delete(cntPrm);
|
|
140
140
|
const decode = getDecoder(prm);
|
|
141
|
-
const v = decode(request.cookies[
|
|
141
|
+
const v = decode(request.cookies[key], { coerce: true, label: key, onFail });
|
|
142
|
+
const prmName = typeof prm.name === 'string' ? prm.name : key;
|
|
142
143
|
if (v !== undefined)
|
|
143
144
|
context.cookies[prmName] = v;
|
|
144
145
|
}
|
|
145
146
|
}
|
|
146
147
|
/** parse headers */
|
|
147
148
|
if (request.headers) {
|
|
148
|
-
for (
|
|
149
|
-
const oprPrm = operation.findParameter(
|
|
150
|
-
const cntPrm = operation.owner.findParameter(
|
|
149
|
+
for (key of Object.keys(request.headers)) {
|
|
150
|
+
const oprPrm = operation.findParameter(key, 'header');
|
|
151
|
+
const cntPrm = operation.owner.findParameter(key, 'header');
|
|
151
152
|
const prm = oprPrm || cntPrm;
|
|
152
153
|
if (!prm)
|
|
153
154
|
continue;
|
|
@@ -156,16 +157,17 @@ export class HttpHandler {
|
|
|
156
157
|
if (cntPrm)
|
|
157
158
|
paramsLeft.delete(cntPrm);
|
|
158
159
|
const decode = getDecoder(prm);
|
|
159
|
-
const v = decode(request.headers[
|
|
160
|
+
const v = decode(request.headers[key], { coerce: true, label: key, onFail });
|
|
161
|
+
const prmName = typeof prm.name === 'string' ? prm.name : key;
|
|
160
162
|
if (v !== undefined)
|
|
161
163
|
context.headers[prmName] = v;
|
|
162
164
|
}
|
|
163
165
|
}
|
|
164
166
|
/** parse path parameters */
|
|
165
167
|
if (request.params) {
|
|
166
|
-
for (
|
|
167
|
-
const oprPrm = operation.findParameter(
|
|
168
|
-
const cntPrm = operation.owner.findParameter(
|
|
168
|
+
for (key of Object.keys(request.params)) {
|
|
169
|
+
const oprPrm = operation.findParameter(key, 'path');
|
|
170
|
+
const cntPrm = operation.owner.findParameter(key, 'path');
|
|
169
171
|
const prm = oprPrm || cntPrm;
|
|
170
172
|
if (!prm)
|
|
171
173
|
continue;
|
|
@@ -174,17 +176,17 @@ export class HttpHandler {
|
|
|
174
176
|
if (cntPrm)
|
|
175
177
|
paramsLeft.delete(cntPrm);
|
|
176
178
|
const decode = getDecoder(prm);
|
|
177
|
-
const v = decode(request.params[
|
|
179
|
+
const v = decode(request.params[key], { coerce: true, label: key, onFail });
|
|
178
180
|
if (v !== undefined)
|
|
179
|
-
context.pathParams[
|
|
181
|
+
context.pathParams[key] = v;
|
|
180
182
|
}
|
|
181
183
|
}
|
|
182
184
|
/** parse query parameters */
|
|
183
185
|
const url = new URL(request.originalUrl || request.url || '/', 'http://tempuri.org');
|
|
184
186
|
const { searchParams } = url;
|
|
185
|
-
for (
|
|
186
|
-
const oprPrm = operation.findParameter(
|
|
187
|
-
const cntPrm = operation.owner.findParameter(
|
|
187
|
+
for (key of searchParams.keys()) {
|
|
188
|
+
const oprPrm = operation.findParameter(key, 'query');
|
|
189
|
+
const cntPrm = operation.owner.findParameter(key, 'query');
|
|
188
190
|
const prm = oprPrm || cntPrm;
|
|
189
191
|
if (!prm)
|
|
190
192
|
continue;
|
|
@@ -193,15 +195,16 @@ export class HttpHandler {
|
|
|
193
195
|
if (cntPrm)
|
|
194
196
|
paramsLeft.delete(cntPrm);
|
|
195
197
|
const decode = getDecoder(prm);
|
|
196
|
-
let values = searchParams?.getAll(
|
|
198
|
+
let values = searchParams?.getAll(key);
|
|
199
|
+
const prmName = typeof prm.name === 'string' ? prm.name : key;
|
|
197
200
|
if (values?.length && prm.isArray) {
|
|
198
201
|
values = values.map(v => splitString(v, { delimiters: prm.arraySeparator, quotes: true })).flat();
|
|
199
|
-
values = values.map(v => decode(v, { coerce: true, label:
|
|
202
|
+
values = values.map(v => decode(v, { coerce: true, label: key, onFail }));
|
|
200
203
|
if (values.length)
|
|
201
204
|
context.queryParams[prmName] = values;
|
|
202
205
|
}
|
|
203
206
|
else {
|
|
204
|
-
const v = decode(values[0], { coerce: true, label:
|
|
207
|
+
const v = decode(values[0], { coerce: true, label: key, onFail });
|
|
205
208
|
if (values.length)
|
|
206
209
|
context.queryParams[prmName] = v;
|
|
207
210
|
}
|
|
@@ -216,8 +219,8 @@ export class HttpHandler {
|
|
|
216
219
|
}
|
|
217
220
|
catch (e) {
|
|
218
221
|
if (e instanceof ValidationError) {
|
|
219
|
-
|
|
220
|
-
message: `Invalid parameter (${
|
|
222
|
+
throw new BadRequestError({
|
|
223
|
+
message: `Invalid parameter (${key}) value. ` + e.message,
|
|
221
224
|
code: 'REQUEST_VALIDATION',
|
|
222
225
|
details: e.issues,
|
|
223
226
|
}, e);
|
|
@@ -237,9 +240,8 @@ export class HttpHandler {
|
|
|
237
240
|
let contentType = request.header('content-type');
|
|
238
241
|
if (contentType) {
|
|
239
242
|
contentType = parseContentType(contentType).type;
|
|
240
|
-
mediaType = operation.requestBody.content.find(mc =>
|
|
241
|
-
|
|
242
|
-
});
|
|
243
|
+
mediaType = operation.requestBody.content.find(mc => mc.contentType &&
|
|
244
|
+
typeIs.is(contentType, Array.isArray(mc.contentType) ? mc.contentType : [mc.contentType]));
|
|
243
245
|
}
|
|
244
246
|
if (!mediaType) {
|
|
245
247
|
const contentTypes = operation.requestBody.content.map(mc => mc.contentType).flat();
|
|
@@ -258,11 +260,12 @@ export class HttpHandler {
|
|
|
258
260
|
throw new MethodNotAllowedError();
|
|
259
261
|
const responseValue = await context.operationHandler.call(context.controllerInstance, context);
|
|
260
262
|
const { response } = context;
|
|
261
|
-
if (!response.writableEnded)
|
|
263
|
+
if (!response.writableEnded) {
|
|
262
264
|
await this._sendResponse(context, responseValue).finally(() => {
|
|
263
265
|
if (!response.writableEnded)
|
|
264
266
|
response.end();
|
|
265
267
|
});
|
|
268
|
+
}
|
|
266
269
|
}
|
|
267
270
|
/**
|
|
268
271
|
*
|
|
@@ -279,7 +282,7 @@ export class HttpHandler {
|
|
|
279
282
|
const operationResultType = document.node.getDataType(OperationResult);
|
|
280
283
|
let operationResultEncoder = this[kAssetCache].get(operationResultType, 'encode');
|
|
281
284
|
if (!operationResultEncoder) {
|
|
282
|
-
operationResultEncoder = operationResultType.generateCodec('encode');
|
|
285
|
+
operationResultEncoder = operationResultType.generateCodec('encode', { ignoreWriteonlyFields: true });
|
|
283
286
|
this[kAssetCache].set(operationResultType, 'encode', operationResultEncoder);
|
|
284
287
|
}
|
|
285
288
|
/** Validate response */
|
|
@@ -291,6 +294,7 @@ export class HttpHandler {
|
|
|
291
294
|
encode = operationResponse.type.generateCodec('encode', {
|
|
292
295
|
partial: operationResponse.partial,
|
|
293
296
|
projection: '*',
|
|
297
|
+
ignoreWriteonlyFields: true,
|
|
294
298
|
});
|
|
295
299
|
if (operationResponse) {
|
|
296
300
|
if (operationResponse.isArray)
|
|
@@ -317,7 +321,9 @@ export class HttpHandler {
|
|
|
317
321
|
else
|
|
318
322
|
body = encode(body);
|
|
319
323
|
}
|
|
320
|
-
if (body instanceof OperationResult &&
|
|
324
|
+
if (body instanceof OperationResult &&
|
|
325
|
+
operationResponse.type &&
|
|
326
|
+
operationResponse.type !== document.node.getDataType(OperationResult)) {
|
|
321
327
|
body.type = operationResponse.type.name ? operationResponse.type.name : '#embedded';
|
|
322
328
|
}
|
|
323
329
|
}
|
|
@@ -387,8 +393,9 @@ export class HttpHandler {
|
|
|
387
393
|
/** Filter available HttpOperationResponse instances according to status code. */
|
|
388
394
|
const filteredResponses = operation.responses.filter(r => r.statusCode.find(sc => sc.start <= statusCode && sc.end >= statusCode));
|
|
389
395
|
/** Throw InternalServerError if controller returns non-configured status code */
|
|
390
|
-
if (!filteredResponses.length && statusCode < 400)
|
|
396
|
+
if (!filteredResponses.length && statusCode < 400) {
|
|
391
397
|
throw new InternalServerError(`No responses defined for status code ${statusCode} in operation "${operation.name}"`);
|
|
398
|
+
}
|
|
392
399
|
/** We search for content-type in filtered HttpOperationResponse array */
|
|
393
400
|
if (filteredResponses.length) {
|
|
394
401
|
/** If no response returned, and content-type has not been set (No response wants to be returned by operation) */
|
|
@@ -401,8 +408,9 @@ export class HttpHandler {
|
|
|
401
408
|
if (contentType) {
|
|
402
409
|
// Find HttpEndpointResponse instance according to content-type header
|
|
403
410
|
operationResponse = filteredResponses.find(r => typeIs.is(contentType, toArray(r.contentType)));
|
|
404
|
-
if (!operationResponse)
|
|
411
|
+
if (!operationResponse) {
|
|
405
412
|
throw new InternalServerError(`Operation didn't configured to return "${contentType}" content`);
|
|
413
|
+
}
|
|
406
414
|
}
|
|
407
415
|
else {
|
|
408
416
|
/** Select first HttpOperationResponse if content-type header has not been set */
|
|
@@ -434,23 +442,26 @@ export class HttpHandler {
|
|
|
434
442
|
case 'Entity.Get':
|
|
435
443
|
case 'Entity.FindMany':
|
|
436
444
|
case 'Entity.Update': {
|
|
437
|
-
if (!(body instanceof OperationResult))
|
|
445
|
+
if (!(body instanceof OperationResult)) {
|
|
438
446
|
body = new OperationResult({
|
|
439
447
|
payload: body,
|
|
440
448
|
});
|
|
449
|
+
}
|
|
441
450
|
if ((composition === 'Entity.Create' || composition === 'Entity.Update') &&
|
|
442
451
|
composition &&
|
|
443
|
-
body.affected == null)
|
|
452
|
+
body.affected == null) {
|
|
444
453
|
body.affected = 1;
|
|
454
|
+
}
|
|
445
455
|
break;
|
|
446
456
|
}
|
|
447
457
|
case 'Entity.Delete':
|
|
448
458
|
case 'Entity.DeleteMany':
|
|
449
459
|
case 'Entity.UpdateMany': {
|
|
450
|
-
if (!(body instanceof OperationResult))
|
|
460
|
+
if (!(body instanceof OperationResult)) {
|
|
451
461
|
body = new OperationResult({
|
|
452
462
|
affected: body,
|
|
453
463
|
});
|
|
464
|
+
}
|
|
454
465
|
body.affected =
|
|
455
466
|
typeof body.affected === 'number'
|
|
456
467
|
? body.affected
|
|
@@ -461,15 +472,19 @@ export class HttpHandler {
|
|
|
461
472
|
: undefined;
|
|
462
473
|
break;
|
|
463
474
|
}
|
|
475
|
+
default:
|
|
476
|
+
break;
|
|
464
477
|
}
|
|
465
478
|
}
|
|
466
|
-
if (responseArgs.contentType && responseArgs.contentType !== parsedContentType?.type)
|
|
479
|
+
if (responseArgs.contentType && responseArgs.contentType !== parsedContentType?.type) {
|
|
467
480
|
response.setHeader('content-type', responseArgs.contentType);
|
|
481
|
+
}
|
|
468
482
|
if (responseArgs.contentType &&
|
|
469
483
|
body != null &&
|
|
470
484
|
!(body instanceof OperationResult) &&
|
|
471
|
-
typeIs.is(responseArgs.contentType, [MimeTypes.opra_response_json]))
|
|
485
|
+
typeIs.is(responseArgs.contentType, [MimeTypes.opra_response_json])) {
|
|
472
486
|
body = new OperationResult({ payload: body });
|
|
487
|
+
}
|
|
473
488
|
if (hasBody)
|
|
474
489
|
responseArgs.body = body;
|
|
475
490
|
return responseArgs;
|
|
@@ -478,28 +493,24 @@ export class HttpHandler {
|
|
|
478
493
|
const { request, response } = context;
|
|
479
494
|
const { document } = this.adapter;
|
|
480
495
|
response.setHeader('content-type', MimeTypes.json);
|
|
496
|
+
const url = new URL(request.originalUrl || request.url || '/', 'http://tempuri.org');
|
|
497
|
+
const { searchParams } = url;
|
|
498
|
+
const documentId = searchParams.get('id');
|
|
499
|
+
const doc = documentId ? document.findDocument(documentId) : document;
|
|
500
|
+
if (!doc) {
|
|
501
|
+
return this.sendErrorResponse(response, [
|
|
502
|
+
new BadRequestError({
|
|
503
|
+
message: `Document with given id [${documentId}] does not exists`,
|
|
504
|
+
}),
|
|
505
|
+
]);
|
|
506
|
+
}
|
|
481
507
|
/** Check if response cache exists */
|
|
482
|
-
let responseBody = this[kAssetCache].get(
|
|
508
|
+
let responseBody = this[kAssetCache].get(doc, `$schema`);
|
|
483
509
|
/** Create response if response cache does not exists */
|
|
484
510
|
if (!responseBody) {
|
|
485
|
-
const
|
|
486
|
-
const { searchParams } = url;
|
|
487
|
-
// const nsPath = searchParams.get('ns');
|
|
488
|
-
// if (nsPath) {
|
|
489
|
-
// const arr = nsPath.split('/');
|
|
490
|
-
// let doc = document;
|
|
491
|
-
// for (const a of arr) {
|
|
492
|
-
// }
|
|
493
|
-
// }
|
|
494
|
-
const schema = document.export({ references: searchParams.get('references') });
|
|
495
|
-
const dt = document.node.getComplexType('OperationResult');
|
|
496
|
-
let encode = this[kAssetCache].get(dt, 'encode');
|
|
497
|
-
if (!encode) {
|
|
498
|
-
encode = dt.generateCodec('encode');
|
|
499
|
-
this[kAssetCache].set(dt, 'encode', encode);
|
|
500
|
-
}
|
|
511
|
+
const schema = doc.export();
|
|
501
512
|
responseBody = JSON.stringify(schema);
|
|
502
|
-
this[kAssetCache].set(
|
|
513
|
+
this[kAssetCache].set(doc, `$schema`, responseBody);
|
|
503
514
|
}
|
|
504
515
|
response.end(responseBody);
|
|
505
516
|
}
|
|
@@ -546,7 +557,7 @@ export class HttpHandler {
|
|
|
546
557
|
const dt = document.node.getComplexType('OperationResult');
|
|
547
558
|
let encode = this[kAssetCache].get(dt, 'encode');
|
|
548
559
|
if (!encode) {
|
|
549
|
-
encode = dt.generateCodec('encode');
|
|
560
|
+
encode = dt.generateCodec('encode', { ignoreWriteonlyFields: true });
|
|
550
561
|
this[kAssetCache].set(dt, 'encode', encode);
|
|
551
562
|
}
|
|
552
563
|
const { i18n } = this.adapter;
|