diff --git a/dist/framework/ajv/formats.d.ts b/dist/framework/ajv/formats.d.ts new file mode 100644 index 00000000..bbe9434b --- /dev/null +++ b/dist/framework/ajv/formats.d.ts @@ -0,0 +1,21 @@ +export declare const formats: { + int32: { + validate: (i: any) => boolean; + type: string; + }; + int64: { + validate: (i: any) => boolean; + type: string; + }; + float: { + validate: (i: any) => boolean; + type: string; + }; + double: { + validate: (i: any) => boolean; + type: string; + }; + byte: (b: any) => boolean; + binary: () => boolean; + password: () => boolean; +}; diff --git a/dist/framework/ajv/formats.js b/dist/framework/ajv/formats.js new file mode 100644 index 00000000..da50515b --- /dev/null +++ b/dist/framework/ajv/formats.js @@ -0,0 +1,35 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.formats = void 0; +const maxInt32 = 2 ** 31 - 1; +const minInt32 = (-2) ** 31; +const maxInt64 = 2 ** 63 - 1; +const minInt64 = (-2) ** 63; +const maxFloat = (2 - 2 ** -23) * 2 ** 127; +const minPosFloat = 2 ** -126; +const minFloat = -1 * maxFloat; +const maxNegFloat = -1 * minPosFloat; +const alwaysTrue = () => true; +const base64regExp = /^[A-Za-z0-9+/]*(=|==)?$/; +exports.formats = { + int32: { + validate: i => Number.isInteger(i) && i <= maxInt32 && i >= minInt32, + type: 'number', + }, + int64: { + validate: i => Number.isInteger(i) && i <= maxInt64 && i >= minInt64, + type: 'number', + }, + float: { + validate: i => typeof i === 'number' && (i === 0 || (i <= maxFloat && i >= minPosFloat) || (i >= minFloat && i <= maxNegFloat)), + type: 'number', + }, + double: { + validate: i => typeof i === 'number', + type: 'number', + }, + byte: b => b.length % 4 === 0 && base64regExp.test(b), + binary: alwaysTrue, + password: alwaysTrue, +}; +//# sourceMappingURL=formats.js.map \ No newline at end of file diff --git a/dist/framework/ajv/formats.js.map b/dist/framework/ajv/formats.js.map new file mode 100644 index 00000000..d3c29603 --- /dev/null +++ b/dist/framework/ajv/formats.js.map @@ -0,0 +1 @@ +{"version":3,"file":"formats.js","sourceRoot":"","sources":["../../../src/framework/ajv/formats.ts"],"names":[],"mappings":";;;AAAA,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAC7B,MAAM,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAE5B,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAC7B,MAAM,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAE5B,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC;AAC3C,MAAM,WAAW,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC;AAC9B,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC;AAC/B,MAAM,WAAW,GAAG,CAAC,CAAC,GAAG,WAAW,CAAC;AAErC,MAAM,UAAU,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC;AAC9B,MAAM,YAAY,GAAG,yBAAyB,CAAC;AAElC,QAAA,OAAO,GAAG;IACrB,KAAK,EAAE;QACL,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,QAAQ,IAAI,CAAC,IAAI,QAAQ;QACpE,IAAI,EAAE,QAAQ;KACf;IACD,KAAK,EAAE;QACL,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,QAAQ,IAAI,CAAC,IAAI,QAAQ;QACpE,IAAI,EAAE,QAAQ;KACf;IACD,KAAK,EAAE;QACL,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,QAAQ,IAAI,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,IAAI,QAAQ,IAAI,CAAC,IAAI,WAAW,CAAC,CAAC;QAC/H,IAAI,EAAE,QAAQ;KACf;IACD,MAAM,EAAE;QACN,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ;QACpC,IAAI,EAAE,QAAQ;KACf;IACD,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;IACrD,MAAM,EAAE,UAAU;IAClB,QAAQ,EAAE,UAAU;CACrB,CAAC"} \ No newline at end of file diff --git a/dist/framework/ajv/index.d.ts b/dist/framework/ajv/index.d.ts new file mode 100644 index 00000000..098f3d13 --- /dev/null +++ b/dist/framework/ajv/index.d.ts @@ -0,0 +1,4 @@ +import * as Ajv from 'ajv'; +import { OpenAPIV3, Options } from '../types'; +export declare function createRequestAjv(openApiSpec: OpenAPIV3.Document, options?: Options): Ajv.Ajv; +export declare function createResponseAjv(openApiSpec: OpenAPIV3.Document, options?: Options): Ajv.Ajv; diff --git a/dist/framework/ajv/index.js b/dist/framework/ajv/index.js new file mode 100644 index 00000000..f3cd060c --- /dev/null +++ b/dist/framework/ajv/index.js @@ -0,0 +1,153 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.createResponseAjv = exports.createRequestAjv = void 0; +const Ajv = require("ajv"); +const draftSchema = require("ajv/lib/refs/json-schema-draft-04.json"); +const formats_1 = require("./formats"); +function createRequestAjv(openApiSpec, options = {}) { + return createAjv(openApiSpec, options); +} +exports.createRequestAjv = createRequestAjv; +function createResponseAjv(openApiSpec, options = {}) { + return createAjv(openApiSpec, options, false); +} +exports.createResponseAjv = createResponseAjv; +function createAjv(openApiSpec, options = {}, request = true) { + var _a; + const ajv = new Ajv(Object.assign(Object.assign({}, options), { schemaId: 'auto', allErrors: true, meta: draftSchema, formats: Object.assign(Object.assign({}, formats_1.formats), options.formats), unknownFormats: options.unknownFormats })); + ajv.removeKeyword('propertyNames'); + ajv.removeKeyword('contains'); + ajv.removeKeyword('const'); + if (request) { + if (options.serDesMap) { + ajv.addKeyword('x-eov-serdes', { + modifying: true, + compile: (sch) => { + if (sch) { + return function validate(data, path, obj, propName) { + if (!!sch.deserialize) { + if (typeof data !== 'string') { + validate.errors = [ + { + keyword: 'serdes', + schemaPath: data, + dataPath: path, + message: `must be a string`, + params: { 'x-eov-serdes': propName }, + }, + ]; + return false; + } + try { + obj[propName] = sch.deserialize(data); + } + catch (e) { + validate.errors = [ + { + keyword: 'serdes', + schemaPath: data, + dataPath: path, + message: `format is invalid`, + params: { 'x-eov-serdes': propName }, + }, + ]; + return false; + } + } + return true; + }; + } + return () => true; + }, + // errors: 'full', + }); + } + ajv.removeKeyword('readOnly'); + ajv.addKeyword('readOnly', { + modifying: true, + compile: (sch) => { + if (sch) { + return function validate(data, path, obj, propName) { + const isValid = !(sch === true && data != null); + delete obj[propName]; + validate.errors = [ + { + keyword: 'readOnly', + schemaPath: data, + dataPath: path, + message: `is read-only`, + params: { readOnly: propName }, + }, + ]; + return isValid; + }; + } + return () => true; + }, + }); + } + else { + // response + if (options.serDesMap) { + ajv.addKeyword('x-eov-serdes', { + modifying: true, + compile: (sch) => { + if (sch) { + return function validate(data, path, obj, propName) { + if (typeof data === 'string') + return true; + if (!!sch.serialize) { + try { + obj[propName] = sch.serialize(data); + } + catch (e) { + validate.errors = [ + { + keyword: 'serdes', + schemaPath: data, + dataPath: path, + message: `format is invalid`, + params: { 'x-eov-serdes': propName }, + }, + ]; + return false; + } + } + return true; + }; + } + return () => true; + }, + }); + } + ajv.removeKeyword('writeOnly'); + ajv.addKeyword('writeOnly', { + modifying: true, + compile: (sch) => { + if (sch) { + return function validate(data, path, obj, propName) { + const isValid = !(sch === true && data != null); + validate.errors = [ + { + keyword: 'writeOnly', + dataPath: path, + schemaPath: path, + message: `is write-only`, + params: { writeOnly: propName }, + }, + ]; + return isValid; + }; + } + return () => true; + }, + }); + } + if ((_a = openApiSpec.components) === null || _a === void 0 ? void 0 : _a.schemas) { + Object.entries(openApiSpec.components.schemas).forEach(([id, schema]) => { + ajv.addSchema(openApiSpec.components.schemas[id], `#/components/schemas/${id}`); + }); + } + return ajv; +} +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/framework/ajv/index.js.map b/dist/framework/ajv/index.js.map new file mode 100644 index 00000000..eacb2e0a --- /dev/null +++ b/dist/framework/ajv/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/framework/ajv/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/framework/ajv/options.d.ts b/dist/framework/ajv/options.d.ts new file mode 100644 index 00000000..60838260 --- /dev/null +++ b/dist/framework/ajv/options.d.ts @@ -0,0 +1,11 @@ +import ajv = require('ajv'); +import { OpenApiValidatorOpts, Options, RequestValidatorOptions } from '../types'; +export declare class AjvOptions { + private options; + constructor(options: OpenApiValidatorOpts); + get preprocessor(): ajv.Options; + get response(): ajv.Options; + get request(): RequestValidatorOptions; + get multipart(): Options; + private baseOptions; +} diff --git a/dist/framework/ajv/options.js b/dist/framework/ajv/options.js new file mode 100644 index 00000000..ea4fa097 --- /dev/null +++ b/dist/framework/ajv/options.js @@ -0,0 +1,61 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AjvOptions = void 0; +class AjvOptions { + constructor(options) { + this.options = options; + } + get preprocessor() { + return this.baseOptions(); + } + get response() { + const { coerceTypes, removeAdditional } = (this.options.validateResponses); + return Object.assign(Object.assign({}, this.baseOptions()), { useDefaults: false, coerceTypes, + removeAdditional }); + } + get request() { + const { allowUnknownQueryParameters, coerceTypes, removeAdditional } = this.options.validateRequests; + return Object.assign(Object.assign({}, this.baseOptions()), { allowUnknownQueryParameters, + coerceTypes, + removeAdditional }); + } + get multipart() { + return this.baseOptions(); + } + baseOptions() { + const { coerceTypes, unknownFormats, validateFormats, serDes, } = this.options; + const serDesMap = {}; + for (const serDesObject of serDes) { + if (!serDesMap[serDesObject.format]) { + serDesMap[serDesObject.format] = serDesObject; + } + else { + if (serDesObject.serialize) { + serDesMap[serDesObject.format].serialize = serDesObject.serialize; + } + if (serDesObject.deserialize) { + serDesMap[serDesObject.format].deserialize = serDesObject.deserialize; + } + } + } + return { + validateSchema: false, + nullable: true, + coerceTypes, + useDefaults: true, + removeAdditional: false, + unknownFormats, + format: validateFormats, + formats: this.options.formats.reduce((acc, f) => { + acc[f.name] = { + type: f.type, + validate: f.validate, + }; + return acc; + }, {}), + serDesMap: serDesMap, + }; + } +} +exports.AjvOptions = AjvOptions; +//# sourceMappingURL=options.js.map \ No newline at end of file diff --git a/dist/framework/ajv/options.js.map b/dist/framework/ajv/options.js.map new file mode 100644 index 00000000..b7903e7c --- /dev/null +++ b/dist/framework/ajv/options.js.map @@ -0,0 +1 @@ +{"version":3,"file":"options.js","sourceRoot":"","sources":["../../../src/framework/ajv/options.ts"],"names":[],"mappings":";;;AASA,MAAa,UAAU;IAErB,YAAY,OAA6B;QACvC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IACD,IAAI,YAAY;QACd,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;IAC5B,CAAC;IAED,IAAI,QAAQ;QACV,MAAM,EAAE,WAAW,EAAE,gBAAgB,EAAE,GAAyB,CAC9D,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAC/B,CAAC;QACF,uCACK,IAAI,CAAC,WAAW,EAAE,KACrB,WAAW,EAAE,KAAK,EAClB,WAAW;YACX,gBAAgB,IAChB;IACJ,CAAC;IAED,IAAI,OAAO;QACT,MAAM,EAAE,2BAA2B,EAAE,WAAW,EAAE,gBAAgB,EAAE,GAEnE,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC;QAC/B,uCACK,IAAI,CAAC,WAAW,EAAE,KACrB,2BAA2B;YAC3B,WAAW;YACX,gBAAgB,IAChB;IACJ,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;IAC5B,CAAC;IAEO,WAAW;QACjB,MAAM,EACJ,WAAW,EACX,cAAc,EACd,eAAe,EACf,MAAM,GACP,GAAG,IAAI,CAAC,OAAO,CAAC;QACjB,MAAM,SAAS,GAAG,EAAE,CAAC;QACrB,KAAK,MAAM,YAAY,IAAI,MAAM,EAAE;YACjC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE;gBACnC,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,YAAY,CAAC;aAC/C;iBAAM;gBACL,IAAI,YAAY,CAAC,SAAS,EAAE;oBAC1B,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,SAAS,GAAG,YAAY,CAAC,SAAS,CAAC;iBACnE;gBACD,IAAI,YAAY,CAAC,WAAW,EAAE;oBAC5B,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,WAAW,GAAG,YAAY,CAAC,WAAW,CAAC;iBACvE;aACF;SACF;QAED,OAAO;YACL,cAAc,EAAE,KAAK;YACrB,QAAQ,EAAE,IAAI;YACd,WAAW;YACX,WAAW,EAAE,IAAI;YACjB,gBAAgB,EAAE,KAAK;YACvB,cAAc;YACd,MAAM,EAAE,eAAe;YACvB,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;gBAC9C,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG;oBACZ,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,QAAQ,EAAE,CAAC,CAAC,QAAQ;iBACrB,CAAC;gBACF,OAAO,GAAG,CAAC;YACb,CAAC,EAAE,EAAE,CAAC;YACN,SAAS,EAAE,SAAS;SACrB,CAAC;IACJ,CAAC;CACF;AA5ED,gCA4EC"} \ No newline at end of file diff --git a/dist/framework/base.path.d.ts b/dist/framework/base.path.d.ts new file mode 100644 index 00000000..2d41d31a --- /dev/null +++ b/dist/framework/base.path.d.ts @@ -0,0 +1,19 @@ +import { OpenAPIV3 } from './types'; +interface ServerUrlVariables { + [key: string]: ServerUrlValues; +} +interface ServerUrlValues { + enum: string[]; + default?: string; +} +export declare class BasePath { + readonly variables: ServerUrlVariables; + readonly expressPath: string; + private allPaths; + constructor(server: OpenAPIV3.ServerObject); + static fromServers(servers: OpenAPIV3.ServerObject[]): BasePath[]; + hasVariables(): boolean; + all(): string[]; + private findUrlPath; +} +export {}; diff --git a/dist/framework/base.path.js b/dist/framework/base.path.js new file mode 100644 index 00000000..a4f1e5ca --- /dev/null +++ b/dist/framework/base.path.js @@ -0,0 +1,113 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.BasePath = void 0; +const path_to_regexp_1 = require("path-to-regexp"); +class BasePath { + constructor(server) { + var _a; + this.variables = {}; + this.expressPath = ''; + this.allPaths = null; + // break the url into parts + // baseUrl param added to make the parsing of relative paths go well + let urlPath = this.findUrlPath(server.url); + if (/:/.test(urlPath)) { + // escape colons as (any at this point) do not signify express route params. + // this is an openapi base path, thus route params are wrapped in braces {}, + // not prefixed by colon : (like express route params) + urlPath = urlPath.replace(':', '\\:'); + } + if (/{\w+}/.test(urlPath)) { + // has variable that we need to check out + urlPath = urlPath.replace(/{(\w+)}/g, (substring, p1) => `:${p1}(.*)`); + } + this.expressPath = urlPath; + for (const variable in server.variables) { + if (server.variables.hasOwnProperty(variable)) { + const v = server.variables[variable]; + const enums = (_a = v.enum) !== null && _a !== void 0 ? _a : []; + if (enums.length === 0 && v.default) + enums.push(v.default); + this.variables[variable] = { + enum: enums, + default: v.default, + }; + } + } + } + static fromServers(servers) { + if (!servers) { + return [new BasePath({ url: '' })]; + } + return servers.map(server => new BasePath(server)); + } + hasVariables() { + return Object.keys(this.variables).length > 0; + } + all() { + if (!this.hasVariables()) + return [this.expressPath]; + if (this.allPaths) + return this.allPaths; + // TODO performance optimization + // ignore variables that are not part of path params + const allParams = Object.entries(this.variables).reduce((acc, v) => { + const [key, value] = v; + const params = value.enum.map(e => ({ + [key]: e, + })); + acc.push(params); + return acc; + }, []); + const allParamCombos = cartesian(...allParams); + const toPath = path_to_regexp_1.compile(this.expressPath); + const paths = new Set(); + for (const combo of allParamCombos) { + paths.add(toPath(combo)); + } + this.allPaths = Array.from(paths); + return this.allPaths; + } + findUrlPath(u) { + const findColonSlashSlash = p => { + const r = /:\/\//.exec(p); + if (r) + return r.index; + return -1; + }; + const findFirstSlash = p => { + const r = /\//.exec(p); + if (r) + return r.index; + return -1; + }; + const fcssIdx = findColonSlashSlash(u); + const startSearchIdx = fcssIdx !== -1 ? fcssIdx + 3 : 0; + const startPathIdx = findFirstSlash(u.substring(startSearchIdx)); + if (startPathIdx === -1) + return '/'; + const pathIdx = startPathIdx + startSearchIdx; + const path = u.substring(pathIdx); + // ensure a trailing slash is always present + return path[path.length - 1] === '/' ? path : path + '/'; + } +} +exports.BasePath = BasePath; +function cartesian(...arg) { + const r = [], max = arg.length - 1; + function helper(obj, i) { + const values = arg[i]; + for (var j = 0, l = values.length; j < l; j++) { + const a = Object.assign({}, obj); + const key = Object.keys(values[j])[0]; + a[key] = values[j][key]; + if (i == max) + r.push(a); + else + helper(a, i + 1); + } + } + helper({}, 0); + return r; +} +//# sourceMappingURL=base.path.js.map \ No newline at end of file diff --git a/dist/framework/base.path.js.map b/dist/framework/base.path.js.map new file mode 100644 index 00000000..40eac171 --- /dev/null +++ b/dist/framework/base.path.js.map @@ -0,0 +1 @@ +{"version":3,"file":"base.path.js","sourceRoot":"","sources":["../../src/framework/base.path.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/framework/base.serdes.d.ts b/dist/framework/base.serdes.d.ts new file mode 100644 index 00000000..fc1b6be2 --- /dev/null +++ b/dist/framework/base.serdes.d.ts @@ -0,0 +1,4 @@ +import { SerDes, SerDesSingleton } from './types'; +export declare const dateTime: SerDesSingleton; +export declare const date: SerDesSingleton; +export declare const defaultSerDes: SerDes[]; diff --git a/dist/framework/base.serdes.js b/dist/framework/base.serdes.js new file mode 100644 index 00000000..9f4d1a8d --- /dev/null +++ b/dist/framework/base.serdes.js @@ -0,0 +1,27 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.defaultSerDes = exports.date = exports.dateTime = void 0; +const types_1 = require("./types"); +exports.dateTime = new types_1.SerDesSingleton({ + format: 'date-time', + serialize: (d) => { + return d && d.toISOString(); + }, + deserialize: (s) => { + return new Date(s); + } +}); +exports.date = new types_1.SerDesSingleton({ + format: 'date', + serialize: (d) => { + return d && d.toISOString().split('T')[0]; + }, + deserialize: (s) => { + return new Date(s); + } +}); +exports.defaultSerDes = [ + exports.date.serializer, + exports.dateTime.serializer +]; +//# sourceMappingURL=base.serdes.js.map \ No newline at end of file diff --git a/dist/framework/base.serdes.js.map b/dist/framework/base.serdes.js.map new file mode 100644 index 00000000..54ee37fc --- /dev/null +++ b/dist/framework/base.serdes.js.map @@ -0,0 +1 @@ +{"version":3,"file":"base.serdes.js","sourceRoot":"","sources":["../../src/framework/base.serdes.ts"],"names":[],"mappings":";;;AAAA,mCAAkD;AAErC,QAAA,QAAQ,GAAqB,IAAI,uBAAe,CAAC;IAC5D,MAAM,EAAG,WAAW;IACpB,SAAS,EAAE,CAAC,CAAO,EAAE,EAAE;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9B,CAAC;IACD,WAAW,EAAE,CAAC,CAAS,EAAE,EAAE;QACzB,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;IACrB,CAAC;CACF,CAAC,CAAC;AAEU,QAAA,IAAI,GAAqB,IAAI,uBAAe,CAAC;IACxD,MAAM,EAAG,MAAM;IACf,SAAS,EAAE,CAAC,CAAO,EAAE,EAAE;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC;IACD,WAAW,EAAE,CAAC,CAAS,EAAE,EAAE;QACzB,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;IACrB,CAAC;CACF,CAAC,CAAC;AAEU,QAAA,aAAa,GAAc;IACtC,YAAI,CAAC,UAAU;IACf,gBAAQ,CAAC,UAAU;CACpB,CAAC"} \ No newline at end of file diff --git a/dist/framework/index.d.ts b/dist/framework/index.d.ts new file mode 100644 index 00000000..15d7841e --- /dev/null +++ b/dist/framework/index.d.ts @@ -0,0 +1,10 @@ +import { OpenAPIFrameworkArgs, OpenAPIFrameworkInit, OpenAPIFrameworkVisitor } from './types'; +export declare class OpenAPIFramework { + private readonly args; + private readonly loggingPrefix; + constructor(args: OpenAPIFrameworkArgs); + initialize(visitor: OpenAPIFrameworkVisitor): Promise; + private loadSpec; + private sortApiDocTags; + private getBasePathsFromServers; +} diff --git a/dist/framework/index.js b/dist/framework/index.js new file mode 100644 index 00000000..c9d5a871 --- /dev/null +++ b/dist/framework/index.js @@ -0,0 +1,94 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.OpenAPIFramework = void 0; +const fs = require("fs"); +const path = require("path"); +const $RefParser = require("json-schema-ref-parser"); +const openapi_schema_validator_1 = require("./openapi.schema.validator"); +const base_path_1 = require("./base.path"); +class OpenAPIFramework { + constructor(args) { + this.loggingPrefix = 'openapi.validator: '; + this.args = args; + } + async initialize(visitor) { + const args = this.args; + const apiDoc = await this.loadSpec(args.apiDoc, args.$refParser); + const basePathObs = this.getBasePathsFromServers(apiDoc.servers); + const basePaths = Array.from(basePathObs.reduce((acc, bp) => { + bp.all().forEach((path) => acc.add(path)); + return acc; + }, new Set())); + const validateApiSpec = 'validateApiSpec' in args ? !!args.validateApiSpec : true; + const validator = new openapi_schema_validator_1.OpenAPISchemaValidator({ + version: apiDoc.openapi, + validateApiSpec, + // extensions: this.apiDoc[`x-${args.name}-schema-extension`], + }); + if (validateApiSpec) { + const apiDocValidation = validator.validate(apiDoc); + if (apiDocValidation.errors.length) { + console.error(`${this.loggingPrefix}Validating schema`); + console.error(`${this.loggingPrefix}validation errors`, JSON.stringify(apiDocValidation.errors, null, ' ')); + throw new Error(`${this.loggingPrefix}args.apiDoc was invalid. See the output.`); + } + } + const getApiDoc = () => { + return apiDoc; + }; + this.sortApiDocTags(apiDoc); + if (visitor.visitApi) { + // const basePaths = basePathObs; + visitor.visitApi({ + basePaths, + getApiDoc, + }); + } + return { + apiDoc, + basePaths, + }; + } + loadSpec(filePath, $refParser = { mode: 'bundle' }) { + // Because of this issue ( https://github.com/APIDevTools/json-schema-ref-parser/issues/101#issuecomment-421755168 ) + // We need this workaround ( use '$RefParser.dereference' instead of '$RefParser.bundle' ) if asked by user + if (typeof filePath === 'string') { + const origCwd = process.cwd(); + const absolutePath = path.resolve(origCwd, filePath); + if (fs.existsSync(absolutePath)) { + // Get document, or throw exception on error + const doc = $refParser.mode === 'dereference' + ? $RefParser.dereference(absolutePath) + : $RefParser.bundle(absolutePath); + return doc; + } + else { + throw new Error(`${this.loggingPrefix}spec could not be read at ${filePath}`); + } + } + const doc = $refParser.mode === 'dereference' + ? $RefParser.dereference(filePath) + : $RefParser.bundle(filePath); + return doc; + } + sortApiDocTags(apiDoc) { + if (apiDoc && Array.isArray(apiDoc.tags)) { + apiDoc.tags.sort((a, b) => { + return a.name < b.name ? -1 : 1; + }); + } + } + getBasePathsFromServers(servers) { + if (!servers || servers.length === 0) { + return [new base_path_1.BasePath({ url: '' })]; + } + const basePathsMap = {}; + for (const server of servers) { + const basePath = new base_path_1.BasePath(server); + basePathsMap[basePath.expressPath] = basePath; + } + return Object.keys(basePathsMap).map((key) => basePathsMap[key]); + } +} +exports.OpenAPIFramework = OpenAPIFramework; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/framework/index.js.map b/dist/framework/index.js.map new file mode 100644 index 00000000..c1e7515f --- /dev/null +++ b/dist/framework/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/framework/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/framework/json.ref.schema.d.ts b/dist/framework/json.ref.schema.d.ts new file mode 100644 index 00000000..e69de29b diff --git a/dist/framework/json.ref.schema.js b/dist/framework/json.ref.schema.js new file mode 100644 index 00000000..84641c87 --- /dev/null +++ b/dist/framework/json.ref.schema.js @@ -0,0 +1 @@ +//# sourceMappingURL=json.ref.schema.js.map \ No newline at end of file diff --git a/dist/framework/json.ref.schema.js.map b/dist/framework/json.ref.schema.js.map new file mode 100644 index 00000000..1d867db5 --- /dev/null +++ b/dist/framework/json.ref.schema.js.map @@ -0,0 +1 @@ +{"version":3,"file":"json.ref.schema.js","sourceRoot":"","sources":["../../src/framework/json.ref.schema.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/framework/modded.express.mung.d.ts b/dist/framework/modded.express.mung.d.ts new file mode 100644 index 00000000..9626179f --- /dev/null +++ b/dist/framework/modded.express.mung.d.ts @@ -0,0 +1,6 @@ +/** + * Modification of richardschneider;s mung + * https://github.com/richardschneider/express-mung * + */ +declare let mung: any; +export default mung; diff --git a/dist/framework/modded.express.mung.js b/dist/framework/modded.express.mung.js new file mode 100644 index 00000000..46a3d011 --- /dev/null +++ b/dist/framework/modded.express.mung.js @@ -0,0 +1,188 @@ +/* istanbul ignore file */ +/** + * Modification of richardschneider;s mung + * https://github.com/richardschneider/express-mung * + */ +'use strict'; +Object.defineProperty(exports, "__esModule", { value: true }); +let mung = {}; +let faux_fin = { end: () => null }; +function isScalar(v) { + return typeof v !== 'object' && !Array.isArray(v); +} +mung.onError = (err, req, res, next) => { + res + .status(500) + .set('content-language', 'en') + .json({ message: err.message }) + .end(); + return res; +}; +mung.json = function json(fn, options) { + return function (req, res, next) { + let original = res.json; + options = options || {}; + let mungError = options.mungError; + function json_hook(json) { + let originalJson = json; + res.json = original; + if (res.headersSent) + return res; + if (!mungError && res.statusCode >= 500) + return original.call(this, json); + // Run the munger + try { + json = fn(json, req, res); + } + catch (e) { + return mung.onError(e, req, res, next); + } + if (res.headersSent) + return res; + // If no returned value from fn, then assume json has been mucked with. + if (json === undefined) + json = originalJson; + // If null, then 204 No Content + if (json === null) + return res.status(204).end(); + // If munged scalar value, then text/plain + if (originalJson !== json && isScalar(json)) { + res.set('content-type', 'text/plain'); + return res.send(String(json)); + } + return original.call(this, json); + } + res.json = json_hook; + next && next(); + }; +}; +mung.jsonAsync = function json(fn, options) { + return function (req, res, next) { + let original = res.json; + options = options || {}; + let mungError = options.mungError; + function json_async_hook(json) { + let originalJson = json; + res.json = original; + if (res.headersSent) + return; + if (!mungError && res.statusCode >= 400) + return original.call(this, json); + try { + fn(json, req, res) + .then(json => { + if (res.headersSent) + return; + // If null, then 204 No Content + if (json === null) + return res.status(204).end(); + // If munged scalar value, then text/plain + if (json !== originalJson && isScalar(json)) { + res.set('content-type', 'text/plain'); + return res.send(String(json)); + } + return original.call(this, json); + }) + .catch(e => mung.onError(e, req, res, next)); + } + catch (e) { + mung.onError(e, req, res, next); + } + return faux_fin; + } + res.json = json_async_hook; + next && next(); + }; +}; +mung.headers = function headers(fn) { + return function (req, res, next) { + let original = res.end; + function headers_hook() { + res.end = original; + if (!res.headersSent) { + try { + fn(req, res); + } + catch (e) { + return mung.onError(e, req, res, next); + } + if (res.headersSent) { + console.error('sending response while in mung.headers is undefined behaviour'); + return; + } + } + return original.apply(this, arguments); + } + res.end = headers_hook; + next && next(); + }; +}; +mung.headersAsync = function headersAsync(fn) { + return function (req, res, next) { + let original = res.end; + let onError = e => { + res.end = original; + return mung.onError(e, req, res, next); + }; + function headers_async_hook() { + if (res.headersSent) + return original.apply(this, null); // (this, args) + let args = arguments; + res.end = () => null; + try { + fn(req, res) + .then(() => { + res.end = original; + if (res.headersSent) + return; + original.apply(this, args); + }) + .catch(e => onError(e)); + } + catch (e) { + onError(e); + } + } + res.end = headers_async_hook; + next && next(); + }; +}; +mung.write = function write(fn, options = {}) { + return function (req, res, next) { + const original = res.write; + const mungError = options.mungError; + function write_hook(chunk, encoding, callback) { + // If res.end has already been called, do nothing. + if (res.finished) { + return false; + } + // Do not mung on errors + if (!mungError && res.statusCode >= 400) { + return original.apply(res, arguments); + } + try { + let modifiedChunk = fn(chunk, + // Since `encoding` is an optional argument to `res.write`, + // make sure it is a string and not actually the callback. + typeof encoding === 'string' ? encoding : null, req, res); + // res.finished is set to `true` once res.end has been called. + // If it is called in the mung function, stop execution here. + if (res.finished) { + return false; + } + // If no returned value from fn, then set it back to the original value + if (modifiedChunk === undefined) { + modifiedChunk = chunk; + } + return original.call(res, modifiedChunk, encoding, callback); + } + catch (err) { + return mung.onError(err, req, res, next); + } + } + res.write = write_hook; + next && next(); + }; +}; +exports.default = mung; +//# sourceMappingURL=modded.express.mung.js.map \ No newline at end of file diff --git a/dist/framework/modded.express.mung.js.map b/dist/framework/modded.express.mung.js.map new file mode 100644 index 00000000..261722b6 --- /dev/null +++ b/dist/framework/modded.express.mung.js.map @@ -0,0 +1 @@ +{"version":3,"file":"modded.express.mung.js","sourceRoot":"","sources":["../../src/framework/modded.express.mung.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/framework/openapi.context.d.ts b/dist/framework/openapi.context.d.ts new file mode 100644 index 00000000..535f9111 --- /dev/null +++ b/dist/framework/openapi.context.d.ts @@ -0,0 +1,21 @@ +import { OpenAPIV3 } from './types'; +import { Spec, RouteMetadata } from './openapi.spec.loader'; +export interface RoutePair { + expressRoute: string; + openApiRoute: string; +} +export declare class OpenApiContext { + readonly apiDoc: OpenAPIV3.Document; + readonly expressRouteMap: {}; + readonly openApiRouteMap: {}; + readonly routes: RouteMetadata[]; + readonly ignoreUndocumented: boolean; + private readonly basePaths; + private readonly ignorePaths; + constructor(spec: Spec, ignorePaths: RegExp | Function, ignoreUndocumented?: boolean); + isManagedRoute(path: string): boolean; + shouldIgnoreRoute(path: string): any; + routePair(route: string): RoutePair; + private methods; + private buildRouteMaps; +} diff --git a/dist/framework/openapi.context.js b/dist/framework/openapi.context.js new file mode 100644 index 00000000..65faeca1 --- /dev/null +++ b/dist/framework/openapi.context.js @@ -0,0 +1,66 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.OpenApiContext = void 0; +class OpenApiContext { + constructor(spec, ignorePaths, ignoreUndocumented = false) { + this.expressRouteMap = {}; + this.openApiRouteMap = {}; + this.routes = []; + this.apiDoc = spec.apiDoc; + this.basePaths = spec.basePaths; + this.routes = spec.routes; + this.ignorePaths = ignorePaths; + this.ignoreUndocumented = ignoreUndocumented; + this.buildRouteMaps(spec.routes); + } + isManagedRoute(path) { + for (const bp of this.basePaths) { + if (path.startsWith(bp) && !this.shouldIgnoreRoute(path)) { + return true; + } + } + return false; + } + shouldIgnoreRoute(path) { + var _a; + return typeof this.ignorePaths === 'function' ? this.ignorePaths(path) : (_a = this.ignorePaths) === null || _a === void 0 ? void 0 : _a.test(path); + } + routePair(route) { + const methods = this.methods(route); + if (methods) { + return { + expressRoute: methods._expressRoute, + openApiRoute: methods._openApiRoute, + }; + } + return null; + } + methods(route) { + const expressRouteMethods = this.expressRouteMap[route]; + if (expressRouteMethods) + return expressRouteMethods; + const openApiRouteMethods = this.openApiRouteMap[route]; + return openApiRouteMethods; + } + // side-effecting builds express/openapi route maps + buildRouteMaps(routes) { + for (const route of routes) { + const { basePath, expressRoute, openApiRoute, method } = route; + const routeMethods = this.expressRouteMap[expressRoute]; + const pathKey = openApiRoute.substring(basePath.length); + const schema = this.apiDoc.paths[pathKey][method.toLowerCase()]; + if (routeMethods) { + routeMethods[route.method] = schema; + } + else { + const { basePath, openApiRoute, expressRoute } = route; + const routeMethod = { [route.method]: schema }; + const routeDetails = Object.assign({ basePath, _openApiRoute: openApiRoute, _expressRoute: expressRoute }, routeMethod); + this.expressRouteMap[route.expressRoute] = routeDetails; + this.openApiRouteMap[route.openApiRoute] = routeDetails; + } + } + } +} +exports.OpenApiContext = OpenApiContext; +//# sourceMappingURL=openapi.context.js.map \ No newline at end of file diff --git a/dist/framework/openapi.context.js.map b/dist/framework/openapi.context.js.map new file mode 100644 index 00000000..950da83f --- /dev/null +++ b/dist/framework/openapi.context.js.map @@ -0,0 +1 @@ +{"version":3,"file":"openapi.context.js","sourceRoot":"","sources":["../../src/framework/openapi.context.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/framework/openapi.schema.validator.d.ts b/dist/framework/openapi.schema.validator.d.ts new file mode 100644 index 00000000..ecd79eef --- /dev/null +++ b/dist/framework/openapi.schema.validator.d.ts @@ -0,0 +1,14 @@ +import * as Ajv from 'ajv'; +import { OpenAPIV3 } from './types.js'; +export interface OpenAPISchemaValidatorOpts { + version: string; + validateApiSpec: boolean; + extensions?: object; +} +export declare class OpenAPISchemaValidator { + private validator; + constructor(opts: OpenAPISchemaValidatorOpts); + validate(openapiDoc: OpenAPIV3.Document): { + errors: Array | null; + }; +} diff --git a/dist/framework/openapi.schema.validator.js b/dist/framework/openapi.schema.validator.js new file mode 100644 index 00000000..f821b300 --- /dev/null +++ b/dist/framework/openapi.schema.validator.js @@ -0,0 +1,38 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.OpenAPISchemaValidator = void 0; +const Ajv = require("ajv"); +const draftSchema = require("ajv/lib/refs/json-schema-draft-04.json"); +// https://github.com/OAI/OpenAPI-Specification/blob/master/schemas/v3.0/schema.json +const openapi3Schema = require("./openapi.v3.schema.json"); +class OpenAPISchemaValidator { + constructor(opts) { + const options = { + schemaId: 'auto', + allErrors: true, + }; + if (!opts.validateApiSpec) { + options.validateSchema = false; + } + const v = new Ajv(options); + v.addMetaSchema(draftSchema); + const ver = opts.version && parseInt(String(opts.version), 10); + if (!ver) + throw Error('version missing from OpenAPI specification'); + if (ver != 3) + throw Error('OpenAPI v3 specification version is required'); + v.addSchema(openapi3Schema); + this.validator = v.compile(openapi3Schema); + } + validate(openapiDoc) { + const valid = this.validator(openapiDoc); + if (!valid) { + return { errors: this.validator.errors }; + } + else { + return { errors: [] }; + } + } +} +exports.OpenAPISchemaValidator = OpenAPISchemaValidator; +//# sourceMappingURL=openapi.schema.validator.js.map \ No newline at end of file diff --git a/dist/framework/openapi.schema.validator.js.map b/dist/framework/openapi.schema.validator.js.map new file mode 100644 index 00000000..5d9a1b15 --- /dev/null +++ b/dist/framework/openapi.schema.validator.js.map @@ -0,0 +1 @@ +{"version":3,"file":"openapi.schema.validator.js","sourceRoot":"","sources":["../../src/framework/openapi.schema.validator.ts"],"names":[],"mappings":";;;AAAA,2BAA2B;AAC3B,sEAAsE;AACtE,oFAAoF;AACpF,2DAA2D;AAQ3D,MAAa,sBAAsB;IAEjC,YAAY,IAAgC;QAC1C,MAAM,OAAO,GAAQ;YACnB,QAAQ,EAAE,MAAM;YAChB,SAAS,EAAE,IAAI;SAChB,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE;YACzB,OAAO,CAAC,cAAc,GAAG,KAAK,CAAC;SAChC;QAED,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;QAC3B,CAAC,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;QAE7B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,IAAI,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/D,IAAI,CAAC,GAAG;YAAE,MAAM,KAAK,CAAC,4CAA4C,CAAC,CAAC;QACpE,IAAI,GAAG,IAAI,CAAC;YAAE,MAAM,KAAK,CAAC,8CAA8C,CAAC,CAAC;QAE1E,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QAC5B,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7C,CAAC;IAEM,QAAQ,CACb,UAA8B;QAE9B,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QACzC,IAAI,CAAC,KAAK,EAAE;YACV,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;SAC1C;aAAM;YACL,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;SACvB;IACH,CAAC;CACF;AAjCD,wDAiCC"} \ No newline at end of file diff --git a/dist/framework/openapi.spec.loader.d.ts b/dist/framework/openapi.spec.loader.d.ts new file mode 100644 index 00000000..e1a44f15 --- /dev/null +++ b/dist/framework/openapi.spec.loader.d.ts @@ -0,0 +1,21 @@ +import { OpenAPIV3, OpenAPIFrameworkArgs } from './types'; +export interface Spec { + apiDoc: OpenAPIV3.Document; + basePaths: string[]; + routes: RouteMetadata[]; +} +export interface RouteMetadata { + basePath: string; + expressRoute: string; + openApiRoute: string; + method: string; + pathParams: string[]; +} +export declare const sortRoutes: (r1: any, r2: any) => 1 | -1; +export declare class OpenApiSpecLoader { + private readonly framework; + constructor(opts: OpenAPIFrameworkArgs); + load(): Promise; + private discoverRoutes; + private toExpressParams; +} diff --git a/dist/framework/openapi.spec.loader.js b/dist/framework/openapi.spec.loader.js new file mode 100644 index 00000000..b2c1279e --- /dev/null +++ b/dist/framework/openapi.spec.loader.js @@ -0,0 +1,84 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.OpenApiSpecLoader = exports.sortRoutes = void 0; +const index_1 = require("./index"); +// Sort routes by most specific to least specific i.e. static routes before dynamic +// e.g. /users/my_route before /users/{id} +// Exported for tests +const sortRoutes = (r1, r2) => { + const e1 = r1.expressRoute.replace(/\/:/g, '/~'); + const e2 = r2.expressRoute.replace(/\/:/g, '/~'); + return e1 > e2 ? 1 : -1; +}; +exports.sortRoutes = sortRoutes; +class OpenApiSpecLoader { + constructor(opts) { + this.framework = new index_1.OpenAPIFramework(opts); + } + async load() { + return this.discoverRoutes(); + } + async discoverRoutes() { + const routes = []; + const toExpressParams = this.toExpressParams; + // const basePaths = this.framework.basePaths; + // let apiDoc: OpenAPIV3.Document = null; + // let basePaths: string[] = null; + const { apiDoc, basePaths } = await this.framework.initialize({ + visitApi(ctx) { + var _a; + const apiDoc = ctx.getApiDoc(); + const basePaths = ctx.basePaths; + for (const bpa of basePaths) { + const bp = bpa.replace(/\/$/, ''); + for (const [path, methods] of Object.entries(apiDoc.paths)) { + for (const [method, schema] of Object.entries(methods)) { + if (method.startsWith('x-') || + ['parameters', 'summary', 'description'].includes(method)) { + continue; + } + const pathParams = new Set(); + for (const param of (_a = schema.parameters) !== null && _a !== void 0 ? _a : []) { + if (param.in === 'path') { + pathParams.add(param.name); + } + } + const openApiRoute = `${bp}${path}`; + const expressRoute = `${openApiRoute}` + .split(':') + .map(toExpressParams) + .join('\\:'); + routes.push({ + basePath: bp, + expressRoute, + openApiRoute, + method: method.toUpperCase(), + pathParams: Array.from(pathParams), + }); + } + } + } + }, + }); + routes.sort(exports.sortRoutes); + return { + apiDoc, + basePaths, + routes, + }; + } + toExpressParams(part) { + // substitute wildcard path with express equivalent + // {/path} => /path(*) <--- RFC 6570 format (not supported by openapi) + // const pass1 = part.replace(/\{(\/)([^\*]+)(\*)}/g, '$1:$2$3'); + // instead create our own syntax that is compatible with express' pathToRegex + // /{path}* => /:path*) + // /{path}(*) => /:path*) + const pass1 = part.replace(/\/{([^\*]+)}\({0,1}(\*)\){0,1}/g, '/:$1$2'); + // substitute params with express equivalent + // /path/{id} => /path/:id + return pass1.replace(/\{([^}]+)}/g, ':$1'); + } +} +exports.OpenApiSpecLoader = OpenApiSpecLoader; +//# sourceMappingURL=openapi.spec.loader.js.map \ No newline at end of file diff --git a/dist/framework/openapi.spec.loader.js.map b/dist/framework/openapi.spec.loader.js.map new file mode 100644 index 00000000..b1ea41b4 --- /dev/null +++ b/dist/framework/openapi.spec.loader.js.map @@ -0,0 +1 @@ +{"version":3,"file":"openapi.spec.loader.js","sourceRoot":"","sources":["../../src/framework/openapi.spec.loader.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/framework/openapi.v3.schema.json b/dist/framework/openapi.v3.schema.json new file mode 100644 index 00000000..9bcb3bf7 --- /dev/null +++ b/dist/framework/openapi.v3.schema.json @@ -0,0 +1,1480 @@ +{ + "id": "https://spec.openapis.org/oas/3.0/schema/2019-04-02", + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Validation schema for OpenAPI Specification 3.0.X.", + "type": "object", + "required": ["openapi", "info", "paths"], + "properties": { + "openapi": { + "type": "string", + "pattern": "^3\\.0\\.\\d(-.+)?$" + }, + "info": { + "$ref": "#/definitions/Info" + }, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/definitions/Server" + } + }, + "security": { + "type": "array", + "items": { + "$ref": "#/definitions/SecurityRequirement" + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/definitions/Tag" + }, + "uniqueItems": true + }, + "paths": { + "$ref": "#/definitions/Paths" + }, + "components": { + "$ref": "#/definitions/Components" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false, + "definitions": { + "Reference": { + "type": "object", + "required": ["$ref"], + "patternProperties": { + "^\\$ref$": { + "type": "string", + "format": "uri-reference" + } + } + }, + "Info": { + "type": "object", + "required": ["title", "version"], + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "termsOfService": { + "type": "string", + "format": "uri-reference" + }, + "contact": { + "$ref": "#/definitions/Contact" + }, + "license": { + "$ref": "#/definitions/License" + }, + "version": { + "type": "string" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Contact": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri-reference" + }, + "email": { + "type": "string", + "format": "email" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "License": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri-reference" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Server": { + "type": "object", + "required": ["url"], + "properties": { + "url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "variables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ServerVariable" + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "ServerVariable": { + "type": "object", + "required": ["default"], + "properties": { + "enum": { + "type": "array", + "items": { + "type": "string" + } + }, + "default": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Components": { + "type": "object", + "properties": { + "schemas": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "responses": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Response" + } + ] + } + } + }, + "parameters": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Parameter" + } + ] + } + } + }, + "examples": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Example" + } + ] + } + } + }, + "requestBodies": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/RequestBody" + } + ] + } + } + }, + "headers": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Header" + } + ] + } + } + }, + "securitySchemes": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/SecurityScheme" + } + ] + } + } + }, + "links": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Link" + } + ] + } + } + }, + "callbacks": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Callback" + } + ] + } + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "multipleOf": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "minLength": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "pattern": { + "type": "string", + "format": "regex" + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxProperties": { + "type": "integer", + "minimum": 0 + }, + "minProperties": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "required": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + }, + "enum": { + "type": "array", + "items": {}, + "minItems": 1, + "uniqueItems": false + }, + "type": { + "type": "string", + "enum": ["array", "boolean", "integer", "number", "object", "string"] + }, + "not": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "allOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "oneOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "anyOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "properties": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + }, + { + "type": "boolean" + } + ], + "default": true + }, + "description": { + "type": "string" + }, + "format": { + "type": "string" + }, + "default": {}, + "nullable": { + "type": "boolean", + "default": false + }, + "discriminator": { + "$ref": "#/definitions/Discriminator" + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "example": {}, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "xml": { + "$ref": "#/definitions/XML" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Discriminator": { + "type": "object", + "required": ["propertyName"], + "properties": { + "propertyName": { + "type": "string" + }, + "mapping": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "XML": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string", + "format": "uri" + }, + "prefix": { + "type": "string" + }, + "attribute": { + "type": "boolean", + "default": false + }, + "wrapped": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Response": { + "type": "object", + "required": ["description"], + "properties": { + "description": { + "type": "string" + }, + "headers": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Header" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + } + }, + "links": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Link" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "MediaType": { + "type": "object", + "properties": { + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "example": {}, + "examples": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Example" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "encoding": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Encoding" + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false, + "allOf": [ + { + "$ref": "#/definitions/ExampleXORExamples" + } + ] + }, + "Example": { + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "value": {}, + "externalValue": { + "type": "string", + "format": "uri-reference" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Header": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "style": { + "type": "string", + "enum": ["simple"], + "default": "simple" + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + }, + "minProperties": 1, + "maxProperties": 1 + }, + "example": {}, + "examples": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Example" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false, + "allOf": [ + { + "$ref": "#/definitions/ExampleXORExamples" + }, + { + "$ref": "#/definitions/SchemaXORContent" + } + ] + }, + "Paths": { + "type": "object", + "patternProperties": { + "^\\/": { + "$ref": "#/definitions/PathItem" + }, + "^x-": {} + }, + "additionalProperties": false + }, + "PathItem": { + "type": "object", + "properties": { + "$ref": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/definitions/Server" + } + }, + "parameters": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Parameter" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "uniqueItems": true + } + }, + "patternProperties": { + "^(get|put|post|delete|options|head|patch|trace)$": { + "$ref": "#/definitions/Operation" + }, + "^x-": {} + }, + "additionalProperties": false + }, + "Operation": { + "type": "object", + "required": ["responses"], + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + }, + "operationId": { + "type": "string" + }, + "parameters": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Parameter" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "uniqueItems": true + }, + "requestBody": { + "oneOf": [ + { + "$ref": "#/definitions/RequestBody" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "responses": { + "$ref": "#/definitions/Responses" + }, + "callbacks": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Callback" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "security": { + "type": "array", + "items": { + "$ref": "#/definitions/SecurityRequirement" + } + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/definitions/Server" + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Responses": { + "type": "object", + "properties": { + "default": { + "oneOf": [ + { + "$ref": "#/definitions/Response" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "patternProperties": { + "^[1-5](?:\\d{2}|XX)$": { + "oneOf": [ + { + "$ref": "#/definitions/Response" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "^x-": {} + }, + "minProperties": 1, + "additionalProperties": false + }, + "SecurityRequirement": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "Tag": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "ExternalDocumentation": { + "type": "object", + "required": ["url"], + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri-reference" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "ExampleXORExamples": { + "description": "Example and examples are mutually exclusive", + "not": { + "required": ["example", "examples"] + } + }, + "SchemaXORContent": { + "description": "Schema and content are mutually exclusive, at least one is required", + "not": { + "required": ["schema", "content"] + }, + "oneOf": [ + { + "required": ["schema"] + }, + { + "required": ["content"], + "description": "Some properties are not allowed if content is present", + "allOf": [ + { + "not": { + "required": ["style"] + } + }, + { + "not": { + "required": ["explode"] + } + }, + { + "not": { + "required": ["allowReserved"] + } + }, + { + "not": { + "required": ["example"] + } + }, + { + "not": { + "required": ["examples"] + } + } + ] + } + ] + }, + "Parameter": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "in": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "style": { + "type": "string" + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + }, + "minProperties": 1, + "maxProperties": 1 + }, + "example": {}, + "examples": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Example" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false, + "required": ["name", "in"], + "allOf": [ + { + "$ref": "#/definitions/ExampleXORExamples" + }, + { + "$ref": "#/definitions/SchemaXORContent" + }, + { + "$ref": "#/definitions/ParameterLocation" + } + ] + }, + "ParameterLocation": { + "description": "Parameter location", + "oneOf": [ + { + "description": "Parameter in path", + "required": ["required"], + "properties": { + "in": { + "enum": ["path"] + }, + "style": { + "enum": ["matrix", "label", "simple"], + "default": "simple" + }, + "required": { + "enum": [true] + } + } + }, + { + "description": "Parameter in query", + "properties": { + "in": { + "enum": ["query"] + }, + "style": { + "enum": ["form", "spaceDelimited", "pipeDelimited", "deepObject"], + "default": "form" + } + } + }, + { + "description": "Parameter in header", + "properties": { + "in": { + "enum": ["header"] + }, + "style": { + "enum": ["simple"], + "default": "simple" + } + } + }, + { + "description": "Parameter in cookie", + "properties": { + "in": { + "enum": ["cookie"] + }, + "style": { + "enum": ["form"], + "default": "form" + } + } + } + ] + }, + "RequestBody": { + "type": "object", + "required": ["content"], + "properties": { + "description": { + "type": "string" + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + } + }, + "required": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "SecurityScheme": { + "oneOf": [ + { + "$ref": "#/definitions/APIKeySecurityScheme" + }, + { + "$ref": "#/definitions/HTTPSecurityScheme" + }, + { + "$ref": "#/definitions/OAuth2SecurityScheme" + }, + { + "$ref": "#/definitions/OpenIdConnectSecurityScheme" + } + ] + }, + "APIKeySecurityScheme": { + "type": "object", + "required": ["type", "name", "in"], + "properties": { + "type": { + "type": "string", + "enum": ["apiKey"] + }, + "name": { + "type": "string" + }, + "in": { + "type": "string", + "enum": ["header", "query", "cookie"] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "HTTPSecurityScheme": { + "type": "object", + "required": ["scheme", "type"], + "properties": { + "scheme": { + "type": "string" + }, + "bearerFormat": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["http"] + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false, + "oneOf": [ + { + "description": "Bearer", + "properties": { + "scheme": { + "enum": ["bearer"] + } + } + }, + { + "description": "Non Bearer", + "not": { + "required": ["bearerFormat"] + }, + "properties": { + "scheme": { + "not": { + "enum": ["bearer"] + } + } + } + } + ] + }, + "OAuth2SecurityScheme": { + "type": "object", + "required": ["type", "flows"], + "properties": { + "type": { + "type": "string", + "enum": ["oauth2"] + }, + "flows": { + "$ref": "#/definitions/OAuthFlows" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "OpenIdConnectSecurityScheme": { + "type": "object", + "required": ["type", "openIdConnectUrl"], + "properties": { + "type": { + "type": "string", + "enum": ["openIdConnect"] + }, + "openIdConnectUrl": { + "type": "string", + "format": "uri-reference" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "OAuthFlows": { + "type": "object", + "properties": { + "implicit": { + "$ref": "#/definitions/ImplicitOAuthFlow" + }, + "password": { + "$ref": "#/definitions/PasswordOAuthFlow" + }, + "clientCredentials": { + "$ref": "#/definitions/ClientCredentialsFlow" + }, + "authorizationCode": { + "$ref": "#/definitions/AuthorizationCodeOAuthFlow" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "ImplicitOAuthFlow": { + "type": "object", + "required": ["authorizationUrl", "scopes"], + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri-reference" + }, + "refreshUrl": { + "type": "string", + "format": "uri-reference" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "PasswordOAuthFlow": { + "type": "object", + "required": ["tokenUrl"], + "properties": { + "tokenUrl": { + "type": "string", + "format": "uri-reference" + }, + "refreshUrl": { + "type": "string", + "format": "uri-reference" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "ClientCredentialsFlow": { + "type": "object", + "required": ["tokenUrl"], + "properties": { + "tokenUrl": { + "type": "string", + "format": "uri-reference" + }, + "refreshUrl": { + "type": "string", + "format": "uri-reference" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "AuthorizationCodeOAuthFlow": { + "type": "object", + "required": ["authorizationUrl", "tokenUrl"], + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri-reference" + }, + "tokenUrl": { + "type": "string", + "format": "uri-reference" + }, + "refreshUrl": { + "type": "string", + "format": "uri-reference" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Link": { + "type": "object", + "properties": { + "operationId": { + "type": "string" + }, + "operationRef": { + "type": "string", + "format": "uri-reference" + }, + "parameters": { + "type": "object", + "additionalProperties": {} + }, + "requestBody": {}, + "description": { + "type": "string" + }, + "server": { + "$ref": "#/definitions/Server" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false, + "not": { + "description": "Operation Id and Operation Ref are mutually exclusive", + "required": ["operationId", "operationRef"] + } + }, + "Callback": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/PathItem" + }, + "patternProperties": { + "^x-": {} + } + }, + "Encoding": { + "type": "object", + "properties": { + "contentType": { + "type": "string" + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Header" + } + }, + "style": { + "type": "string", + "enum": ["form", "spaceDelimited", "pipeDelimited", "deepObject"] + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + } + } +} diff --git a/dist/framework/types.d.ts b/dist/framework/types.d.ts new file mode 100644 index 00000000..84e46816 --- /dev/null +++ b/dist/framework/types.d.ts @@ -0,0 +1,584 @@ +import * as ajv from 'ajv'; +import * as multer from 'multer'; +import { Request, Response, NextFunction } from 'express'; +export { OpenAPIFrameworkArgs }; +export declare type BodySchema = OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject | {}; +export interface ParametersSchema { + query: object; + headers: object; + params: object; + cookies: object; +} +export interface ValidationSchema extends ParametersSchema { + body: BodySchema; +} +export interface OpenAPIFrameworkInit { + apiDoc: OpenAPIV3.Document; + basePaths: string[]; +} +export declare type SecurityHandlers = { + [key: string]: (req: Request, scopes: string[], schema: OpenAPIV3.SecuritySchemeObject) => boolean | Promise; +}; +export interface MultipartOpts { + multerOpts: boolean | multer.Options; + ajvOpts: Options; +} +export interface Options extends ajv.Options { + serDesMap?: SerDesMap; +} +export interface RequestValidatorOptions extends Options, ValidateRequestOpts { +} +export declare type ValidateRequestOpts = { + allowUnknownQueryParameters?: boolean; + coerceTypes?: boolean | 'array'; + removeAdditional?: boolean | 'all' | 'failing'; +}; +export declare type ValidateResponseOpts = { + removeAdditional?: boolean | 'all' | 'failing'; + coerceTypes?: boolean | 'array'; + onError?: (err: InternalServerError, json: any, req: Request) => void; +}; +export declare type ValidateSecurityOpts = { + handlers?: SecurityHandlers; +}; +export declare type OperationHandlerOptions = { + basePath: string; + resolver: Function; +}; +export declare type Format = { + name: string; + type?: 'number' | 'string'; + validate: (v: any) => boolean; +}; +export declare type SerDes = { + format: string; + jsonType?: string; + serialize?: (o: unknown) => string; + deserialize?: (s: string) => unknown; +}; +export declare class SerDesSingleton implements SerDes { + serializer: SerDes; + deserializer: SerDes; + format: string; + jsonType: string; + serialize?: (o: unknown) => string; + deserialize?: (s: string) => unknown; + constructor(param: { + format: string; + jsonType?: string; + serialize: (o: unknown) => string; + deserialize: (s: string) => unknown; + }); +} +export declare type SerDesMap = { + [format: string]: SerDes; +}; +export interface OpenApiValidatorOpts { + apiSpec: OpenAPIV3.Document | string; + validateApiSpec?: boolean; + validateResponses?: boolean | ValidateResponseOpts; + validateRequests?: boolean | ValidateRequestOpts; + validateSecurity?: boolean | ValidateSecurityOpts; + ignorePaths?: RegExp | Function; + ignoreUndocumented?: boolean; + securityHandlers?: SecurityHandlers; + coerceTypes?: boolean | 'array'; + unknownFormats?: true | string[] | 'ignore'; + serDes?: SerDes[]; + formats?: Format[]; + fileUploader?: boolean | multer.Options; + multerOpts?: multer.Options; + $refParser?: { + mode: 'bundle' | 'dereference'; + }; + operationHandlers?: false | string | OperationHandlerOptions; + validateFormats?: false | 'fast' | 'full'; +} +export declare namespace OpenAPIV3 { + export interface Document { + openapi: string; + info: InfoObject; + servers?: ServerObject[]; + paths: PathsObject; + components?: ComponentsObject; + security?: SecurityRequirementObject[]; + tags?: TagObject[]; + externalDocs?: ExternalDocumentationObject; + } + export interface InfoObject { + title: string; + description?: string; + termsOfService?: string; + contact?: ContactObject; + license?: LicenseObject; + version: string; + } + export interface ContactObject { + name?: string; + url?: string; + email?: string; + } + export interface LicenseObject { + name: string; + url?: string; + } + export interface ServerObject { + url: string; + description?: string; + variables?: { + [variable: string]: ServerVariableObject; + }; + } + export interface ServerVariableObject { + enum?: string[]; + default: string; + description?: string; + } + export interface PathsObject { + [pattern: string]: PathItemObject; + } + export interface PathItemObject { + $ref?: string; + summary?: string; + description?: string; + get?: OperationObject; + put?: OperationObject; + post?: OperationObject; + delete?: OperationObject; + options?: OperationObject; + head?: OperationObject; + patch?: OperationObject; + trace?: OperationObject; + servers?: ServerObject[]; + parameters?: Array; + } + export interface OperationObject { + tags?: string[]; + summary?: string; + description?: string; + externalDocs?: ExternalDocumentationObject; + operationId?: string; + parameters?: Array; + requestBody?: ReferenceObject | RequestBodyObject; + responses?: ResponsesObject; + callbacks?: { + [callback: string]: ReferenceObject | CallbackObject; + }; + deprecated?: boolean; + security?: SecurityRequirementObject[]; + servers?: ServerObject[]; + } + export interface ExternalDocumentationObject { + description?: string; + url: string; + } + export interface ParameterObject extends ParameterBaseObject { + name: string; + in: string; + } + export interface HeaderObject extends ParameterBaseObject { + } + interface ParameterBaseObject { + description?: string; + required?: boolean; + deprecated?: boolean; + allowEmptyValue?: boolean; + style?: string; + explode?: boolean; + allowReserved?: boolean; + schema?: ReferenceObject | SchemaObject; + example?: any; + examples?: { + [media: string]: ReferenceObject | ExampleObject; + }; + content?: { + [media: string]: MediaTypeObject; + }; + } + export type NonArraySchemaObjectType = 'null' | 'boolean' | 'object' | 'number' | 'string' | 'integer'; + export type ArraySchemaObjectType = 'array'; + export type SchemaObject = ArraySchemaObject | NonArraySchemaObject; + export interface ArraySchemaObject extends BaseSchemaObject { + type: ArraySchemaObjectType; + items: ReferenceObject | SchemaObject; + } + export interface NonArraySchemaObject extends BaseSchemaObject { + type: NonArraySchemaObjectType; + } + interface BaseSchemaObject { + title?: string; + description?: string; + format?: string; + default?: any; + multipleOf?: number; + maximum?: number; + exclusiveMaximum?: boolean; + minimum?: number; + exclusiveMinimum?: boolean; + maxLength?: number; + minLength?: number; + pattern?: string; + additionalProperties?: boolean | ReferenceObject | SchemaObject; + maxItems?: number; + minItems?: number; + uniqueItems?: boolean; + maxProperties?: number; + minProperties?: number; + required?: string[]; + enum?: any[]; + properties?: { + [name: string]: ReferenceObject | SchemaObject; + }; + allOf?: Array; + oneOf?: Array; + anyOf?: Array; + not?: ReferenceObject | SchemaObject; + nullable?: boolean; + discriminator?: DiscriminatorObject; + readOnly?: boolean; + writeOnly?: boolean; + xml?: XMLObject; + externalDocs?: ExternalDocumentationObject; + example?: any; + deprecated?: boolean; + componentId?: string; + } + export interface DiscriminatorObject { + propertyName: string; + mapping?: { + [value: string]: string; + }; + } + export interface XMLObject { + name?: string; + namespace?: string; + prefix?: string; + attribute?: boolean; + wrapped?: boolean; + } + export interface ReferenceObject { + $ref: string; + } + export interface ExampleObject { + summary?: string; + description?: string; + value?: any; + externalValue?: string; + } + export interface MediaTypeObject { + schema?: ReferenceObject | SchemaObject; + example?: any; + examples?: { + [media: string]: ReferenceObject | ExampleObject; + }; + encoding?: { + [media: string]: EncodingObject; + }; + } + export interface EncodingObject { + contentType?: string; + headers?: { + [header: string]: ReferenceObject | HeaderObject; + }; + style?: string; + explode?: boolean; + allowReserved?: boolean; + } + export interface RequestBodyObject { + description?: string; + content: { + [media: string]: MediaTypeObject; + }; + required?: boolean; + } + export interface ResponsesObject { + [code: string]: ReferenceObject | ResponseObject; + } + export interface ResponseObject { + description: string; + headers?: { + [header: string]: ReferenceObject | HeaderObject; + }; + content?: { + [media: string]: MediaTypeObject; + }; + links?: { + [link: string]: ReferenceObject | LinkObject; + }; + } + export interface LinkObject { + operationRef?: string; + operationId?: string; + parameters?: { + [parameter: string]: any; + }; + requestBody?: any; + description?: string; + server?: ServerObject; + } + export interface CallbackObject { + [url: string]: PathItemObject; + } + export interface SecurityRequirementObject { + [name: string]: string[]; + } + export interface ComponentsObject { + schemas?: { + [key: string]: ReferenceObject | SchemaObject; + }; + responses?: { + [key: string]: ReferenceObject | ResponseObject; + }; + parameters?: { + [key: string]: ReferenceObject | ParameterObject; + }; + examples?: { + [key: string]: ReferenceObject | ExampleObject; + }; + requestBodies?: { + [key: string]: ReferenceObject | RequestBodyObject; + }; + headers?: { + [key: string]: ReferenceObject | HeaderObject; + }; + securitySchemes?: { + [key: string]: ReferenceObject | SecuritySchemeObject; + }; + links?: { + [key: string]: ReferenceObject | LinkObject; + }; + callbacks?: { + [key: string]: ReferenceObject | CallbackObject; + }; + } + export type SecuritySchemeObject = HttpSecurityScheme | ApiKeySecurityScheme | OAuth2SecurityScheme | OpenIdSecurityScheme; + export interface HttpSecurityScheme { + type: 'http'; + description?: string; + scheme: string; + bearerFormat?: string; + } + export interface ApiKeySecurityScheme { + type: 'apiKey'; + description?: string; + name: string; + in: string; + } + export interface OAuth2SecurityScheme { + type: 'oauth2'; + flows: { + implicit?: { + authorizationUrl: string; + refreshUrl?: string; + scopes: { + [scope: string]: string; + }; + }; + password?: { + tokenUrl: string; + refreshUrl?: string; + scopes: { + [scope: string]: string; + }; + }; + clientCredentials?: { + tokenUrl: string; + refreshUrl?: string; + scopes: { + [scope: string]: string; + }; + }; + authorizationCode?: { + authorizationUrl: string; + tokenUrl: string; + refreshUrl?: string; + scopes: { + [scope: string]: string; + }; + }; + }; + } + export interface OpenIdSecurityScheme { + type: 'openIdConnect'; + description?: string; + openIdConnectUrl: string; + } + export interface TagObject { + name: string; + description?: string; + externalDocs?: ExternalDocumentationObject; + } + export {}; +} +export interface OpenAPIFrameworkPathObject { + path?: string; + module?: any; +} +interface OpenAPIFrameworkArgs { + apiDoc: OpenAPIV3.Document | string; + validateApiSpec?: boolean; + $refParser?: { + mode: 'bundle' | 'dereference'; + }; +} +export interface OpenAPIFrameworkAPIContext { + basePaths: string[]; + getApiDoc(): OpenAPIV3.Document; +} +export interface OpenAPIFrameworkVisitor { + visitApi?(context: OpenAPIFrameworkAPIContext): void; +} +export interface OpenApiRequestMetadata { + expressRoute: string; + openApiRoute: string; + pathParams: { + [index: string]: string; + }; + schema: OpenAPIV3.OperationObject; +} +export interface OpenApiRequest extends Request { + openapi?: OpenApiRequestMetadata; +} +export declare type OpenApiRequestHandler = (req: OpenApiRequest, res: Response, next: NextFunction) => any; +export interface IJsonSchema { + id?: string; + $schema?: string; + title?: string; + description?: string; + multipleOf?: number; + maximum?: number; + exclusiveMaximum?: boolean; + minimum?: number; + exclusiveMinimum?: boolean; + maxLength?: number; + minLength?: number; + pattern?: string; + additionalItems?: boolean | IJsonSchema; + items?: IJsonSchema | IJsonSchema[]; + maxItems?: number; + minItems?: number; + uniqueItems?: boolean; + maxProperties?: number; + minProperties?: number; + required?: string[]; + additionalProperties?: boolean | IJsonSchema; + definitions?: { + [name: string]: IJsonSchema; + }; + properties?: { + [name: string]: IJsonSchema; + }; + patternProperties?: { + [name: string]: IJsonSchema; + }; + dependencies?: { + [name: string]: IJsonSchema | string[]; + }; + enum?: any[]; + type?: string | string[]; + allOf?: IJsonSchema[]; + anyOf?: IJsonSchema[]; + oneOf?: IJsonSchema[]; + not?: IJsonSchema; +} +export interface ValidationError { + message?: string; + status: number; + errors: ValidationErrorItem[]; +} +export interface ValidationErrorItem { + path: string; + message: string; + error_code?: string; +} +interface ErrorHeaders { + Allow?: string; +} +export declare class HttpError extends Error implements ValidationError { + status: number; + path?: string; + name: string; + message: string; + headers?: ErrorHeaders; + errors: ValidationErrorItem[]; + constructor(err: { + status: number; + path: string; + name: string; + message?: string; + headers?: ErrorHeaders; + errors?: ValidationErrorItem[]; + }); + static create(err: { + status: number; + path: string; + message?: string; + errors?: ValidationErrorItem[]; + }): InternalServerError | UnsupportedMediaType | RequestEntityTooLarge | BadRequest | MethodNotAllowed | NotAcceptable | NotFound | Unauthorized | Forbidden; +} +export declare class NotFound extends HttpError { + constructor(err: { + path: string; + message?: string; + overrideStatus?: number; + }); +} +export declare class NotAcceptable extends HttpError { + constructor(err: { + path: string; + message?: string; + overrideStatus?: number; + }); +} +export declare class MethodNotAllowed extends HttpError { + constructor(err: { + path: string; + message?: string; + headers?: ErrorHeaders; + overrideStatus?: number; + }); +} +export declare class BadRequest extends HttpError { + constructor(err: { + path: string; + message?: string; + overrideStatus?: number; + errors?: ValidationErrorItem[]; + }); +} +export declare class RequestEntityTooLarge extends HttpError { + constructor(err: { + path: string; + message?: string; + overrideStatus?: number; + }); +} +export declare class InternalServerError extends HttpError { + constructor(err: { + path?: string; + message?: string; + overrideStatus?: number; + errors?: ValidationErrorItem[]; + }); +} +export declare class UnsupportedMediaType extends HttpError { + constructor(err: { + path: string; + message?: string; + overrideStatus?: number; + }); +} +export declare class Unauthorized extends HttpError { + constructor(err: { + path: string; + message?: string; + overrideStatus?: number; + }); +} +export declare class Forbidden extends HttpError { + constructor(err: { + path: string; + message?: string; + overrideStatus?: number; + }); +} diff --git a/dist/framework/types.js b/dist/framework/types.js new file mode 100644 index 00000000..581775fe --- /dev/null +++ b/dist/framework/types.js @@ -0,0 +1,166 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Forbidden = exports.Unauthorized = exports.UnsupportedMediaType = exports.InternalServerError = exports.RequestEntityTooLarge = exports.BadRequest = exports.MethodNotAllowed = exports.NotAcceptable = exports.NotFound = exports.HttpError = exports.SerDesSingleton = void 0; +class SerDesSingleton { + constructor(param) { + this.format = param.format; + this.jsonType = param.jsonType || 'object'; + this.serialize = param.serialize; + this.deserialize = param.deserialize; + this.deserializer = { + format: param.format, + jsonType: param.jsonType || 'object', + deserialize: param.deserialize + }; + this.serializer = { + format: param.format, + jsonType: param.jsonType || 'object', + serialize: param.serialize + }; + } +} +exports.SerDesSingleton = SerDesSingleton; +; +class HttpError extends Error { + constructor(err) { + var _a; + super(err.name); + this.name = err.name; + this.status = err.status; + this.path = err.path; + this.message = err.message; + this.headers = err.headers; + this.errors = (_a = err.errors) !== null && _a !== void 0 ? _a : [ + { + path: err.path, + message: err.message, + }, + ]; + } + static create(err) { + switch (err.status) { + case 400: + return new BadRequest(err); + case 401: + return new Unauthorized(err); + case 403: + return new Forbidden(err); + case 404: + return new NotFound(err); + case 405: + return new MethodNotAllowed(err); + case 406: + return new NotAcceptable(err); + case 413: + return new RequestEntityTooLarge(err); + case 415: + return new UnsupportedMediaType(err); + default: + return new InternalServerError(err); + } + } +} +exports.HttpError = HttpError; +class NotFound extends HttpError { + constructor(err) { + super({ + status: err.overrideStatus || 404, + path: err.path, + message: err.message, + name: 'Not Found', + }); + } +} +exports.NotFound = NotFound; +class NotAcceptable extends HttpError { + constructor(err) { + super({ + status: err.overrideStatus || 406, + path: err.path, + name: 'Not Acceptable', + message: err.message, + }); + } +} +exports.NotAcceptable = NotAcceptable; +class MethodNotAllowed extends HttpError { + constructor(err) { + super({ + status: err.overrideStatus || 405, + path: err.path, + name: 'Method Not Allowed', + message: err.message, + headers: err.headers, + }); + } +} +exports.MethodNotAllowed = MethodNotAllowed; +class BadRequest extends HttpError { + constructor(err) { + super({ + status: err.overrideStatus || 400, + path: err.path, + name: 'Bad Request', + message: err.message, + errors: err.errors, + }); + } +} +exports.BadRequest = BadRequest; +class RequestEntityTooLarge extends HttpError { + constructor(err) { + super({ + status: err.overrideStatus || 413, + path: err.path, + name: 'Request Entity Too Large', + message: err.message, + }); + } +} +exports.RequestEntityTooLarge = RequestEntityTooLarge; +class InternalServerError extends HttpError { + constructor(err) { + super({ + status: err.overrideStatus || 500, + path: err.path, + name: 'Internal Server Error', + message: err.message, + errors: err.errors, + }); + } +} +exports.InternalServerError = InternalServerError; +class UnsupportedMediaType extends HttpError { + constructor(err) { + super({ + status: err.overrideStatus || 415, + path: err.path, + name: 'Unsupported Media Type', + message: err.message, + }); + } +} +exports.UnsupportedMediaType = UnsupportedMediaType; +class Unauthorized extends HttpError { + constructor(err) { + super({ + status: err.overrideStatus || 401, + path: err.path, + name: 'Unauthorized', + message: err.message, + }); + } +} +exports.Unauthorized = Unauthorized; +class Forbidden extends HttpError { + constructor(err) { + super({ + status: err.overrideStatus || 403, + path: err.path, + name: 'Forbidden', + message: err.message, + }); + } +} +exports.Forbidden = Forbidden; +//# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/dist/framework/types.js.map b/dist/framework/types.js.map new file mode 100644 index 00000000..c5ad1c22 --- /dev/null +++ b/dist/framework/types.js.map @@ -0,0 +1 @@ +{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/framework/types.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 00000000..3a0da0b4 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,18 @@ +import * as res from './resolvers'; +import { OpenApiValidatorOpts } from './openapi.validator'; +import { InternalServerError, UnsupportedMediaType, RequestEntityTooLarge, BadRequest, MethodNotAllowed, NotAcceptable, NotFound, Unauthorized, Forbidden } from './framework/types'; +export declare const resolvers: typeof res; +export declare const middleware: typeof openapiValidator; +export declare const error: { + InternalServerError: typeof InternalServerError; + UnsupportedMediaType: typeof UnsupportedMediaType; + RequestEntityTooLarge: typeof RequestEntityTooLarge; + BadRequest: typeof BadRequest; + MethodNotAllowed: typeof MethodNotAllowed; + NotAcceptable: typeof NotAcceptable; + NotFound: typeof NotFound; + Unauthorized: typeof Unauthorized; + Forbidden: typeof Forbidden; +}; +export * as serdes from './framework/base.serdes'; +declare function openapiValidator(options: OpenApiValidatorOpts): import("./framework/types").OpenApiRequestHandler[]; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 00000000..db78b215 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,33 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.serdes = exports.error = exports.middleware = exports.resolvers = void 0; +const cloneDeep = require("lodash.clonedeep"); +const res = require("./resolvers"); +const openapi_validator_1 = require("./openapi.validator"); +const openapi_spec_loader_1 = require("./framework/openapi.spec.loader"); +const types_1 = require("./framework/types"); +// export default openapiValidator; +exports.resolvers = res; +exports.middleware = openapiValidator; +exports.error = { + InternalServerError: types_1.InternalServerError, + UnsupportedMediaType: types_1.UnsupportedMediaType, + RequestEntityTooLarge: types_1.RequestEntityTooLarge, + BadRequest: types_1.BadRequest, + MethodNotAllowed: types_1.MethodNotAllowed, + NotAcceptable: types_1.NotAcceptable, + NotFound: types_1.NotFound, + Unauthorized: types_1.Unauthorized, + Forbidden: types_1.Forbidden, +}; +exports.serdes = require("./framework/base.serdes"); +function openapiValidator(options) { + const oav = new openapi_validator_1.OpenApiValidator(options); + exports.middleware._oav = oav; + return oav.installMiddleware(new openapi_spec_loader_1.OpenApiSpecLoader({ + apiDoc: cloneDeep(options.apiSpec), + validateApiSpec: options.validateApiSpec, + $refParser: options.$refParser, + }).load()); +} +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map new file mode 100644 index 00000000..e5ef85d7 --- /dev/null +++ b/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,8CAA8C;AAC9C,mCAAmC;AACnC,2DAA6E;AAC7E,yEAAoE;AACpE,6CAU2B;AAE3B,mCAAmC;AACtB,QAAA,SAAS,GAAG,GAAG,CAAC;AAChB,QAAA,UAAU,GAAG,gBAAgB,CAAC;AAC9B,QAAA,KAAK,GAAG;IACnB,mBAAmB,EAAnB,2BAAmB;IACnB,oBAAoB,EAApB,4BAAoB;IACpB,qBAAqB,EAArB,6BAAqB;IACrB,UAAU,EAAV,kBAAU;IACV,gBAAgB,EAAhB,wBAAgB;IAChB,aAAa,EAAb,qBAAa;IACb,QAAQ,EAAR,gBAAQ;IACR,YAAY,EAAZ,oBAAY;IACZ,SAAS,EAAT,iBAAS;CACV,CAAC;AAEF,oDAAkD;AAElD,SAAS,gBAAgB,CAAC,OAA6B;IACrD,MAAM,GAAG,GAAG,IAAI,oCAAgB,CAAC,OAAO,CAAC,CAAC;IAC1C,OAAO,CAAC,UAAU,CAAC,IAAI,GAAG,GAAG,CAAC;IAE9B,OAAO,GAAG,CAAC,iBAAiB,CAC1B,IAAI,uCAAiB,CAAC;QACpB,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC;QAClC,eAAe,EAAE,OAAO,CAAC,eAAe;QACxC,UAAU,EAAE,OAAO,CAAC,UAAU;KAC/B,CAAC,CAAC,IAAI,EAAE,CACV,CAAC;AACJ,CAAC"} \ No newline at end of file diff --git a/dist/middlewares/index.d.ts b/dist/middlewares/index.d.ts new file mode 100644 index 00000000..9600979b --- /dev/null +++ b/dist/middlewares/index.d.ts @@ -0,0 +1,5 @@ +export { applyOpenApiMetadata } from './openapi.metadata'; +export { RequestValidator } from './openapi.request.validator'; +export { ResponseValidator } from './openapi.response.validator'; +export { multipart } from './openapi.multipart'; +export { security } from './openapi.security'; diff --git a/dist/middlewares/index.js b/dist/middlewares/index.js new file mode 100644 index 00000000..a22ee790 --- /dev/null +++ b/dist/middlewares/index.js @@ -0,0 +1,14 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.security = exports.multipart = exports.ResponseValidator = exports.RequestValidator = exports.applyOpenApiMetadata = void 0; +var openapi_metadata_1 = require("./openapi.metadata"); +Object.defineProperty(exports, "applyOpenApiMetadata", { enumerable: true, get: function () { return openapi_metadata_1.applyOpenApiMetadata; } }); +var openapi_request_validator_1 = require("./openapi.request.validator"); +Object.defineProperty(exports, "RequestValidator", { enumerable: true, get: function () { return openapi_request_validator_1.RequestValidator; } }); +var openapi_response_validator_1 = require("./openapi.response.validator"); +Object.defineProperty(exports, "ResponseValidator", { enumerable: true, get: function () { return openapi_response_validator_1.ResponseValidator; } }); +var openapi_multipart_1 = require("./openapi.multipart"); +Object.defineProperty(exports, "multipart", { enumerable: true, get: function () { return openapi_multipart_1.multipart; } }); +var openapi_security_1 = require("./openapi.security"); +Object.defineProperty(exports, "security", { enumerable: true, get: function () { return openapi_security_1.security; } }); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/middlewares/index.js.map b/dist/middlewares/index.js.map new file mode 100644 index 00000000..50509b85 --- /dev/null +++ b/dist/middlewares/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middlewares/index.ts"],"names":[],"mappings":";;;AAAA,uDAA0D;AAAjD,wHAAA,oBAAoB,OAAA;AAC7B,yEAA+D;AAAtD,6HAAA,gBAAgB,OAAA;AACzB,2EAAiE;AAAxD,+HAAA,iBAAiB,OAAA;AAC1B,yDAAgD;AAAvC,8GAAA,SAAS,OAAA;AAClB,uDAA8C;AAArC,4GAAA,QAAQ,OAAA"} \ No newline at end of file diff --git a/dist/middlewares/openapi.metadata.d.ts b/dist/middlewares/openapi.metadata.d.ts new file mode 100644 index 00000000..4ce78b52 --- /dev/null +++ b/dist/middlewares/openapi.metadata.d.ts @@ -0,0 +1,3 @@ +import { OpenApiContext } from '../framework/openapi.context'; +import { OpenApiRequestHandler, OpenAPIV3 } from '../framework/types'; +export declare function applyOpenApiMetadata(openApiContext: OpenApiContext, responseApiDoc: OpenAPIV3.Document): OpenApiRequestHandler; diff --git a/dist/middlewares/openapi.metadata.js b/dist/middlewares/openapi.metadata.js new file mode 100644 index 00000000..37c99077 --- /dev/null +++ b/dist/middlewares/openapi.metadata.js @@ -0,0 +1,100 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.applyOpenApiMetadata = void 0; +const _zipObject = require("lodash.zipobject"); +const path_to_regexp_1 = require("path-to-regexp"); +const types_1 = require("../framework/types"); +const schema_preprocessor_1 = require("./parsers/schema.preprocessor"); +function applyOpenApiMetadata(openApiContext, responseApiDoc) { + return (req, res, next) => { + // note base path is empty when path is fully qualified i.e. req.path.startsWith('') + const path = req.path.startsWith(req.baseUrl) + ? req.path + : `${req.baseUrl}/${req.path}`; + if (openApiContext.shouldIgnoreRoute(path)) { + return next(); + } + const matched = lookupRoute(req); + if (matched) { + const { expressRoute, openApiRoute, pathParams, schema } = matched; + if (!schema) { + // Prevents validation for routes which match on path but mismatch on method + if (openApiContext.ignoreUndocumented) { + return next(); + } + throw new types_1.MethodNotAllowed({ + path: req.path, + message: `${req.method} method not allowed`, + headers: { + Allow: Object.keys(openApiContext.openApiRouteMap[openApiRoute]) + .filter((key) => schema_preprocessor_1.httpMethods.has(key.toLowerCase())) + .join(', '), + }, + }); + } + req.openapi = { + expressRoute: expressRoute, + openApiRoute: openApiRoute, + pathParams: pathParams, + schema: schema, + }; + req.params = pathParams; + if (responseApiDoc) { + // add the response schema if validating responses + req.openapi._responseSchema = matched._responseSchema; + } + } + else if (openApiContext.isManagedRoute(path) && !openApiContext.ignoreUndocumented) { + throw new types_1.NotFound({ + path: req.path, + message: 'not found', + }); + } + next(); + }; + function lookupRoute(req) { + const path = req.originalUrl.split('?')[0]; + const method = req.method; + const routeEntries = Object.entries(openApiContext.expressRouteMap); + for (const [expressRoute, methods] of routeEntries) { + const routePair = openApiContext.routePair(expressRoute); + const openApiRoute = routePair.openApiRoute; + const pathKey = openApiRoute.substring(methods.basePath.length); + const schema = openApiContext.apiDoc.paths[pathKey][method.toLowerCase()]; + const _schema = responseApiDoc === null || responseApiDoc === void 0 ? void 0 : responseApiDoc.paths[pathKey][method.toLowerCase()]; + const keys = []; + const strict = !!req.app.enabled('strict routing'); + const sensitive = !!req.app.enabled('case sensitive routing'); + const pathOpts = { + sensitive, + strict, + }; + const regexp = path_to_regexp_1.pathToRegexp(expressRoute, keys, pathOpts); + const matchedRoute = regexp.exec(path); + if (matchedRoute) { + const paramKeys = keys.map((k) => k.name); + try { + const paramsVals = matchedRoute.slice(1).map(decodeURIComponent); + const pathParams = _zipObject(paramKeys, paramsVals); + const r = { + schema, + expressRoute, + openApiRoute, + pathParams, + }; + r._responseSchema = _schema; + return r; + } + catch (error) { + throw new types_1.BadRequest({ + path: req.path, + message: `malformed uri'`, + }); + } + } + } + return null; + } +} +exports.applyOpenApiMetadata = applyOpenApiMetadata; +//# sourceMappingURL=openapi.metadata.js.map \ No newline at end of file diff --git a/dist/middlewares/openapi.metadata.js.map b/dist/middlewares/openapi.metadata.js.map new file mode 100644 index 00000000..8a42ef6e --- /dev/null +++ b/dist/middlewares/openapi.metadata.js.map @@ -0,0 +1 @@ +{"version":3,"file":"openapi.metadata.js","sourceRoot":"","sources":["../../src/middlewares/openapi.metadata.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/middlewares/openapi.multipart.d.ts b/dist/middlewares/openapi.multipart.d.ts new file mode 100644 index 00000000..d11f9508 --- /dev/null +++ b/dist/middlewares/openapi.multipart.d.ts @@ -0,0 +1,2 @@ +import { OpenAPIV3, OpenApiRequestHandler, MultipartOpts } from '../framework/types'; +export declare function multipart(apiDoc: OpenAPIV3.Document, options: MultipartOpts): OpenApiRequestHandler; diff --git a/dist/middlewares/openapi.multipart.js b/dist/middlewares/openapi.multipart.js new file mode 100644 index 00000000..31e4d44e --- /dev/null +++ b/dist/middlewares/openapi.multipart.js @@ -0,0 +1,128 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.multipart = void 0; +const ajv_1 = require("../framework/ajv"); +const types_1 = require("../framework/types"); +const multer = require('multer'); +function multipart(apiDoc, options) { + const mult = multer(options.multerOpts); + const Ajv = ajv_1.createRequestAjv(apiDoc, Object.assign({}, options.ajvOpts)); + return (req, res, next) => { + // TODO check that format: binary (for upload) else do not use multer.any() + // use multer.none() if no binary parameters exist + if (shouldHandle(Ajv, req)) { + mult.any()(req, res, (err) => { + if (err) { + next(error(req, err)); + } + else { + // TODO: + // If a form parameter 'file' is defined to take file value, but the user provides a string value instead + // req.files will be empty and req.body.file will be populated with a string + // This will incorrectly PASS validation. + // Instead, we should return a 400 with an invalid type e.g. file expects a file, but found string. + // + // In order to support this, we likely need to inspect the schema directly to find the type. + // For example, if param with type: 'string', format: 'binary' is defined, we expect to see it in + // req.files. If it's not present we should throw a 400 + // + // This is a bit complex because the schema may be defined inline (easy) or via a $ref (complex) in which + // case we must follow the $ref to check the type. + if (req.files) { + // to handle single and multiple file upload at the same time, let us this initialize this count variable + // for example { "files": 5 } + const count_by_fieldname = req.files + .map((file) => file.fieldname) + .reduce((acc, curr) => { + acc[curr] = (acc[curr] || 0) + 1; + return acc; + }, {}); + // add file(s) to body + Object.entries(count_by_fieldname).forEach(([fieldname, count]) => { + // TODO maybe also check in the api doc if it is a single upload or multiple + const is_multiple = count > 1; + req.body[fieldname] = is_multiple + ? new Array(count).fill('') + : ''; + }); + } + next(); + } + }); + } + else { + next(); + } + }; +} +exports.multipart = multipart; +function shouldHandle(Ajv, req) { + var _a, _b, _c, _d; + const reqContentType = req.headers['content-type']; + if (isMultipart(req) && (reqContentType === null || reqContentType === void 0 ? void 0 : reqContentType.includes('multipart/form-data'))) { + return true; + } + const bodyRef = (_b = (_a = req === null || req === void 0 ? void 0 : req.openapi) === null || _a === void 0 ? void 0 : _a.schema) === null || _b === void 0 ? void 0 : _b.$ref; + const requestBody = bodyRef + ? Ajv.getSchema(bodyRef) + : (_d = (_c = req === null || req === void 0 ? void 0 : req.openapi) === null || _c === void 0 ? void 0 : _c.schema) === null || _d === void 0 ? void 0 : _d.requestBody; + const bodyContent = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content; + if (!bodyContent) + return false; + const content = bodyContent; + const contentTypes = Object.entries(content); + for (const [contentType, mediaType] of contentTypes) { + if (!contentType.includes(reqContentType)) + continue; + const mediaTypeSchema = mediaType === null || mediaType === void 0 ? void 0 : mediaType.schema; + const schema = (mediaTypeSchema === null || mediaTypeSchema === void 0 ? void 0 : mediaTypeSchema.$ref) + ? Ajv.getSchema(mediaTypeSchema.$ref) + : mediaTypeSchema; + const format = schema === null || schema === void 0 ? void 0 : schema.format; + if (format === 'binary') { + return true; + } + } +} +function isMultipart(req) { + var _a, _b, _c, _d; + return (_d = (_c = (_b = (_a = req === null || req === void 0 ? void 0 : req.openapi) === null || _a === void 0 ? void 0 : _a.schema) === null || _b === void 0 ? void 0 : _b.requestBody) === null || _c === void 0 ? void 0 : _c.content) === null || _d === void 0 ? void 0 : _d['multipart/form-data']; +} +function error(req, err) { + var _a; + if (err instanceof multer.MulterError) { + // distinguish common errors : + // - 413 ( Request Entity Too Large ) : Too many parts / File too large / Too many files + // - 400 ( Bad Request ) : Field * too long / Too many fields + // - 500 ( Internal Server Error ) : Unexpected field + const multerError = err; + const payload_too_big = /LIMIT_(FILE|PART)_(SIZE|COUNT)/.test(multerError.code); + const unexpected = /LIMIT_UNEXPECTED_FILE/.test(multerError.code); + const status = payload_too_big ? 413 : !unexpected ? 400 : 500; + return types_1.HttpError.create({ + status: status, + path: req.path, + message: err.message, + }); + /*return payload_too_big + ? new RequestEntityTooLarge({ path: req.path, message: err.message }) + : !unexpected + ? new BadRequest({ path: req.path, message: err.message }) + : new InternalServerError({ path: req.path, message: err.message });*/ + } + else { + // HACK + // TODO improve multer error handling + const missingField = /Multipart: Boundary not found/i.test((_a = err.message) !== null && _a !== void 0 ? _a : ''); + if (missingField) { + return new types_1.BadRequest({ + path: req.path, + message: 'multipart file(s) required', + }); + } + else { + return new types_1.InternalServerError({ path: req.path, message: err.message }); + } + } +} +//# sourceMappingURL=openapi.multipart.js.map \ No newline at end of file diff --git a/dist/middlewares/openapi.multipart.js.map b/dist/middlewares/openapi.multipart.js.map new file mode 100644 index 00000000..8a267f14 --- /dev/null +++ b/dist/middlewares/openapi.multipart.js.map @@ -0,0 +1 @@ +{"version":3,"file":"openapi.multipart.js","sourceRoot":"","sources":["../../src/middlewares/openapi.multipart.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/middlewares/openapi.request.validator.d.ts b/dist/middlewares/openapi.request.validator.d.ts new file mode 100644 index 00000000..fb676656 --- /dev/null +++ b/dist/middlewares/openapi.request.validator.d.ts @@ -0,0 +1,14 @@ +import { NextFunction, Response } from 'express'; +import { OpenAPIV3, OpenApiRequest, RequestValidatorOptions } from '../framework/types'; +export declare class RequestValidator { + private middlewareCache; + private apiDoc; + private ajv; + private ajvBody; + private requestOpts; + constructor(apiDoc: OpenAPIV3.Document, options?: RequestValidatorOptions); + validate(req: OpenApiRequest, res: Response, next: NextFunction): void; + private buildMiddleware; + private discriminatorValidator; + private processQueryParam; +} diff --git a/dist/middlewares/openapi.request.validator.js b/dist/middlewares/openapi.request.validator.js new file mode 100644 index 00000000..85938b55 --- /dev/null +++ b/dist/middlewares/openapi.request.validator.js @@ -0,0 +1,233 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RequestValidator = void 0; +const ajv_1 = require("../framework/ajv"); +const util_1 = require("./util"); +const types_1 = require("../framework/types"); +const body_parse_1 = require("./parsers/body.parse"); +const schema_parse_1 = require("./parsers/schema.parse"); +const req_parameter_mutator_1 = require("./parsers/req.parameter.mutator"); +class RequestValidator { + constructor(apiDoc, options = {}) { + this.middlewareCache = {}; + this.requestOpts = {}; + this.middlewareCache = {}; + this.apiDoc = apiDoc; + this.requestOpts.allowUnknownQueryParameters = + options.allowUnknownQueryParameters; + this.ajv = ajv_1.createRequestAjv(apiDoc, Object.assign(Object.assign({}, options), { coerceTypes: true })); + this.ajvBody = ajv_1.createRequestAjv(apiDoc, options); + } + validate(req, res, next) { + var _a; + if (!req.openapi) { + // this path was not found in open api and + // this path is not defined under an openapi base path + // skip it + return next(); + } + const openapi = req.openapi; + const path = openapi.expressRoute; + const reqSchema = openapi.schema; + // cache middleware by combining method, path, and contentType + const contentType = util_1.ContentType.from(req); + const contentTypeKey = (_a = contentType.equivalents()[0]) !== null && _a !== void 0 ? _a : 'not_provided'; + // use openapi.expressRoute as path portion of key + const key = `${req.method}-${path}-${contentTypeKey}`; + if (!this.middlewareCache[key]) { + const middleware = this.buildMiddleware(path, reqSchema, contentType); + this.middlewareCache[key] = middleware; + } + return this.middlewareCache[key](req, res, next); + } + buildMiddleware(path, reqSchema, contentType) { + var _a; + const apiDoc = this.apiDoc; + const schemaParser = new schema_parse_1.ParametersSchemaParser(this.ajv, apiDoc); + const parameters = schemaParser.parse(path, reqSchema.parameters); + const securityQueryParam = Security.queryParam(apiDoc, reqSchema); + const body = new body_parse_1.BodySchemaParser().parse(path, reqSchema, contentType); + const validator = new Validator(this.apiDoc, parameters, body, { + general: this.ajv, + body: this.ajvBody, + }); + const allowUnknownQueryParameters = !!((_a = reqSchema['x-allow-unknown-query-parameters']) !== null && _a !== void 0 ? _a : this.requestOpts.allowUnknownQueryParameters); + return (req, res, next) => { + var _a, _b, _c, _d, _e, _f; + const openapi = req.openapi; + const pathParams = Object.keys(openapi.pathParams); + const hasPathParams = pathParams.length > 0; + if (hasPathParams) { + // handle wildcard path param syntax + if (openapi.expressRoute.endsWith('*')) { + // if we have an express route /data/:p*, we require a path param, p + // if the p param is empty, the user called /p which is not found + // if it was found, it would match a different route + if (pathParams.filter((p) => openapi.pathParams[p]).length === 0) { + throw new types_1.NotFound({ + path: req.path, + message: 'not found', + }); + } + } + req.params = (_a = openapi.pathParams) !== null && _a !== void 0 ? _a : req.params; + } + const schemaProperties = validator.allSchemaProperties; + const mutator = new req_parameter_mutator_1.RequestParameterMutator(this.ajv, apiDoc, path, schemaProperties); + mutator.modifyRequest(req); + if (!allowUnknownQueryParameters) { + this.processQueryParam(req.query, schemaProperties.query, securityQueryParam); + } + const cookies = req.cookies + ? Object.assign(Object.assign({}, req.cookies), req.signedCookies) : undefined; + const data = { + query: (_b = req.query) !== null && _b !== void 0 ? _b : {}, + headers: req.headers, + params: req.params, + cookies, + body: req.body, + }; + const schemaBody = validator === null || validator === void 0 ? void 0 : validator.schemaBody; + const discriminator = (_d = (_c = schemaBody === null || schemaBody === void 0 ? void 0 : schemaBody.properties) === null || _c === void 0 ? void 0 : _c.body) === null || _d === void 0 ? void 0 : _d._discriminator; + const discriminatorValidator = this.discriminatorValidator(req, discriminator); + const validatorBody = discriminatorValidator !== null && discriminatorValidator !== void 0 ? discriminatorValidator : validator.validatorBody; + const valid = validator.validatorGeneral(data); + const validBody = validatorBody(discriminatorValidator ? data.body : data); + if (valid && validBody) { + next(); + } + else { + const errors = util_1.augmentAjvErrors([] + .concat((_e = validator.validatorGeneral.errors) !== null && _e !== void 0 ? _e : []) + .concat((_f = validatorBody.errors) !== null && _f !== void 0 ? _f : [])); + const err = util_1.ajvErrorsToValidatorError(400, errors); + const message = this.ajv.errorsText(errors, { dataVar: 'request' }); + const error = new types_1.BadRequest({ + path: req.path, + message: message, + }); + error.errors = err.errors; + throw error; + } + }; + } + discriminatorValidator(req, discriminator) { + if (discriminator) { + const { options, property, validators } = discriminator; + const discriminatorValue = req.body[property]; // TODO may not always be in this position + if (options.find((o) => o.option === discriminatorValue)) { + return validators[discriminatorValue]; + } + else { + throw new types_1.BadRequest({ + path: req.path, + message: `'${property}' should be equal to one of the allowed values: ${options + .map((o) => o.option) + .join(', ')}.`, + }); + } + } + return null; + } + processQueryParam(query, schema, whiteList = []) { + var _a; + const entries = Object.entries((_a = schema.properties) !== null && _a !== void 0 ? _a : {}); + let keys = []; + for (const [key, prop] of entries) { + if (prop['type'] === 'object' && prop['additionalProperties']) { + // we have an object that allows additional properties + return; + } + keys.push(key); + } + const knownQueryParams = new Set(keys); + whiteList.forEach((item) => knownQueryParams.add(item)); + const queryParams = Object.keys(query); + const allowedEmpty = schema.allowEmptyValue; + for (const q of queryParams) { + if (!knownQueryParams.has(q)) { + throw new types_1.BadRequest({ + path: `.query.${q}`, + message: `Unknown query parameter '${q}'`, + }); + } + else if (!(allowedEmpty === null || allowedEmpty === void 0 ? void 0 : allowedEmpty.has(q)) && (query[q] === '' || null)) { + throw new types_1.BadRequest({ + path: `.query.${q}`, + message: `Empty value found for query parameter '${q}'`, + }); + } + } + } +} +exports.RequestValidator = RequestValidator; +class Validator { + constructor(apiDoc, parametersSchema, bodySchema, ajv) { + this.apiDoc = apiDoc; + this.schemaGeneral = this._schemaGeneral(parametersSchema); + this.schemaBody = this._schemaBody(bodySchema); + this.allSchemaProperties = Object.assign(Object.assign({}, this.schemaGeneral.properties), { body: this.schemaBody.properties.body }); + this.validatorGeneral = ajv.general.compile(this.schemaGeneral); + this.validatorBody = ajv.body.compile(this.schemaBody); + } + _schemaGeneral(parameters) { + // $schema: "http://json-schema.org/draft-04/schema#", + return { + paths: this.apiDoc.paths, + components: this.apiDoc.components, + required: ['query', 'headers', 'params'], + properties: Object.assign(Object.assign({}, parameters), { body: {} }), + }; + } + _schemaBody(body) { + // $schema: "http://json-schema.org/draft-04/schema#" + const isBodyBinary = (body === null || body === void 0 ? void 0 : body['format']) === 'binary'; + const bodyProps = isBodyBinary ? {} : body; + const bodySchema = { + paths: this.apiDoc.paths, + components: this.apiDoc.components, + properties: { + query: {}, + headers: {}, + params: {}, + cookies: {}, + body: bodyProps, + }, + }; + const requireBody = body.required && !isBodyBinary; + if (requireBody) { + bodySchema.required = ['body']; + } + return bodySchema; + } +} +class Security { + static queryParam(apiDocs, schema) { + var _a, _b, _c, _d, _e; + const hasPathSecurity = (_b = ((_a = schema.security) === null || _a === void 0 ? void 0 : _a.length) > 0) !== null && _b !== void 0 ? _b : false; + const hasRootSecurity = (_d = ((_c = apiDocs.security) === null || _c === void 0 ? void 0 : _c.length) > 0) !== null && _d !== void 0 ? _d : false; + let usedSecuritySchema = []; + if (hasPathSecurity) { + usedSecuritySchema = schema.security; + } + else if (hasRootSecurity) { + // if no security schema for the path, use top-level security schema + usedSecuritySchema = apiDocs.security; + } + const securityQueryParameter = this.getSecurityQueryParams(usedSecuritySchema, (_e = apiDocs.components) === null || _e === void 0 ? void 0 : _e.securitySchemes); + return securityQueryParameter; + } + static getSecurityQueryParams(usedSecuritySchema, securitySchema) { + return usedSecuritySchema && securitySchema + ? usedSecuritySchema + .filter((obj) => Object.entries(obj).length !== 0) + .map((sec) => { + const securityKey = Object.keys(sec)[0]; + return securitySchema[securityKey]; + }) + .filter((sec) => (sec === null || sec === void 0 ? void 0 : sec.type) === 'apiKey' && (sec === null || sec === void 0 ? void 0 : sec.in) == 'query') + .map((sec) => sec.name) + : []; + } +} +//# sourceMappingURL=openapi.request.validator.js.map \ No newline at end of file diff --git a/dist/middlewares/openapi.request.validator.js.map b/dist/middlewares/openapi.request.validator.js.map new file mode 100644 index 00000000..276539ad --- /dev/null +++ b/dist/middlewares/openapi.request.validator.js.map @@ -0,0 +1 @@ +{"version":3,"file":"openapi.request.validator.js","sourceRoot":"","sources":["../../src/middlewares/openapi.request.validator.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/middlewares/openapi.response.validator.d.ts b/dist/middlewares/openapi.response.validator.d.ts new file mode 100644 index 00000000..45eeff5c --- /dev/null +++ b/dist/middlewares/openapi.response.validator.d.ts @@ -0,0 +1,39 @@ +import { RequestHandler } from 'express'; +import * as ajv from 'ajv'; +import { OpenAPIV3, OpenApiRequest, ValidateResponseOpts } from '../framework/types'; +interface ValidateResult { + validators: { + [key: string]: ajv.ValidateFunction; + }; + body: object; + statusCode: number; + path: string; + accepts: string[]; +} +export declare class ResponseValidator { + private ajvBody; + private spec; + private validatorsCache; + private eovOptions; + constructor(openApiSpec: OpenAPIV3.Document, options?: ajv.Options, eovOptions?: ValidateResponseOpts); + validate(): RequestHandler; + _getOrBuildValidator(req: OpenApiRequest, responses: OpenAPIV3.ResponsesObject): { + [key: string]: ajv.ValidateFunction; + }; + _validate({ validators, body, statusCode, path, accepts, }: ValidateResult): void; + /** + * Build a map of response name to response validator, for the set of responses + * defined on the current endpoint + * @param responses + * @returns a map of validators + */ + private buildValidators; + /** + * Checks if specific Content-Type is validatable + * @param contentType + * @returns boolean + * @throws error on invalid content type format + */ + private canValidateContentType; +} +export {}; diff --git a/dist/middlewares/openapi.response.validator.js b/dist/middlewares/openapi.response.validator.js new file mode 100644 index 00000000..bc3a89d1 --- /dev/null +++ b/dist/middlewares/openapi.response.validator.js @@ -0,0 +1,257 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ResponseValidator = void 0; +const modded_express_mung_1 = require("../framework/modded.express.mung"); +const ajv_1 = require("../framework/ajv"); +const util_1 = require("./util"); +const types_1 = require("../framework/types"); +const mediaTypeParser = require("media-typer"); +const contentTypeParser = require("content-type"); +class ResponseValidator { + constructor(openApiSpec, options = {}, eovOptions = {}) { + this.validatorsCache = {}; + this.spec = openApiSpec; + this.ajvBody = ajv_1.createResponseAjv(openApiSpec, options); + this.eovOptions = eovOptions; + // This is a pseudo-middleware function. It doesn't get registered with + // express via `use` + modded_express_mung_1.default.onError = (err, req, res, next) => { + return next(err); + }; + } + validate() { + return modded_express_mung_1.default.json((body, req, res) => { + var _a; + if (req.openapi) { + const openapi = req.openapi; + // instead of openapi.schema, use openapi._responseSchema to get the response copy + const responses = (_a = openapi + ._responseSchema) === null || _a === void 0 ? void 0 : _a.responses; + const validators = this._getOrBuildValidator(req, responses); + const path = req.originalUrl; + const statusCode = res.statusCode; + const contentType = res.getHeaders()['content-type']; + const accept = req.headers['accept']; + // ir response has a content type use it, else use accept headers + const accepts = contentType + ? [contentType] + : accept + ? accept.split(',').map((h) => h.trim()) + : []; + try { + return this._validate({ + validators, + body, + statusCode, + path, + accepts, // return 406 if not acceptable + }); + } + catch (err) { + // If a custom error handler was provided, we call that + if (err instanceof types_1.InternalServerError && this.eovOptions.onError) { + this.eovOptions.onError(err, body, req); + } + else { + // No custom error handler, or something unexpected happen. + throw err; + } + } + } + return body; + }); + } + // TODO public for test only - fix me + // Build validators for each url/method/contenttype tuple + _getOrBuildValidator(req, responses) { + var _a, _b; + // get the request content type - used only to build the cache key + const contentTypeMeta = util_1.ContentType.from(req); + const contentType = (_b = (((_a = contentTypeMeta.contentType) === null || _a === void 0 ? void 0 : _a.indexOf('multipart')) > -1 + ? contentTypeMeta.equivalents()[0] + : contentTypeMeta.contentType)) !== null && _b !== void 0 ? _b : 'not_provided'; + const openapi = req.openapi; + const key = `${req.method}-${openapi.expressRoute}-${contentType}`; + let validators = this.validatorsCache[key]; + if (!validators) { + validators = this.buildValidators(responses); + this.validatorsCache[key] = validators; + } + return validators; + } + // TODO public for test only - fix me + _validate({ validators, body, statusCode, path, accepts, // optional + }) { + const status = statusCode !== null && statusCode !== void 0 ? statusCode : 'default'; + const statusXX = status.toString()[0] + 'XX'; + let svalidator; + if (status in validators) { + svalidator = validators[status]; + } + else if (statusXX in validators) { + svalidator = validators[statusXX]; + } + else if (validators.default) { + svalidator = validators.default; + } + else { + throw new types_1.InternalServerError({ + path: path, + message: `no schema defined for status code '${status}' in the openapi spec`, + }); + } + const validatorContentTypes = Object.keys(svalidator); + const contentType = util_1.findResponseContent(accepts, validatorContentTypes) || + validatorContentTypes[0]; // take first contentType, if none found + if (validatorContentTypes.length === 0) { + // spec specifies no content for this response + if (body !== undefined) { + // response contains content/body + throw new types_1.InternalServerError({ + path: '.response', + message: 'response should NOT have a body', + }); + } + // response contains no content/body so OK + return; + } + if (!contentType) { + // not contentType inferred, assume valid + console.warn('no contentType found'); + return; + } + const validator = svalidator[contentType]; + if (!validator) { + // no validator found, assume valid + console.warn('no validator found'); + return; + } + if (body === undefined || body === null) { + throw new types_1.InternalServerError({ + path: '.response', + message: 'response body required.', + }); + } + // CHECK If Content-Type is validatable + try { + if (!this.canValidateContentType(contentType)) { + console.warn('Cannot validate content type', contentType); + // assume valid + return; + } + } + catch (e) { + // Do nothing. Move on and validate response + } + const valid = validator({ + response: body, + }); + if (!valid) { + const errors = util_1.augmentAjvErrors(validator.errors); + const message = this.ajvBody.errorsText(errors, { + dataVar: '', // responses + }); + throw new types_1.InternalServerError({ + path: path, + errors: util_1.ajvErrorsToValidatorError(500, errors).errors, + message: message, + }); + } + } + /** + * Build a map of response name to response validator, for the set of responses + * defined on the current endpoint + * @param responses + * @returns a map of validators + */ + buildValidators(responses) { + var _a, _b, _c; + const validationTypes = (response) => { + if (!response.content) { + return ['no_content']; + } + if (typeof response.content !== 'object') { + return []; + } + const types = []; + for (let contentType of Object.keys(response.content)) { + try { + if (this.canValidateContentType(contentType)) { + if (response.content[contentType] && + response.content[contentType].schema) { + types.push(contentType); + } + } + } + catch (e) { + // Handle wildcards + if (response.content[contentType].schema && + (contentType === '*/*' || + new RegExp(/^[a-z]+\/\*$/).test(contentType))) { + types.push(contentType); + } + } + } + return types; + }; + const responseSchemas = {}; + for (const [name, resp] of Object.entries(responses)) { + let tmpResponse = resp; + if (tmpResponse.$ref) { + // resolve top level response $ref + const id = tmpResponse.$ref.replace(/^.+\//i, ''); + tmpResponse = (_b = (_a = this.spec.components) === null || _a === void 0 ? void 0 : _a.responses) === null || _b === void 0 ? void 0 : _b[id]; + } + const response = tmpResponse; + const types = validationTypes(response); + for (const mediaTypeToValidate of types) { + if (!mediaTypeToValidate) { + // TODO support content other than JSON + // don't validate + // assume is valid + continue; + } + else if (mediaTypeToValidate === 'no_content') { + responseSchemas[name] = {}; + continue; + } + const schema = response.content[mediaTypeToValidate].schema; + responseSchemas[name] = Object.assign(Object.assign({}, responseSchemas[name]), { [mediaTypeToValidate]: { + // $schema: 'http://json-schema.org/schema#', + // $schema: "http://json-schema.org/draft-04/schema#", + type: 'object', + properties: { + response: schema, + }, + components: (_c = this.spec.components) !== null && _c !== void 0 ? _c : {}, + } }); + } + } + const validators = {}; + for (const [code, contentTypeSchemas] of Object.entries(responseSchemas)) { + if (Object.keys(contentTypeSchemas).length === 0) { + validators[code] = {}; + } + for (const contentType of Object.keys(contentTypeSchemas)) { + const schema = contentTypeSchemas[contentType]; + schema.paths = this.spec.paths; // add paths for resolution with multi-file + schema.components = this.spec.components; // add components for resolution w/ multi-file + validators[code] = Object.assign(Object.assign({}, validators[code]), { [contentType]: this.ajvBody.compile(schema) }); + } + } + return validators; + } + /** + * Checks if specific Content-Type is validatable + * @param contentType + * @returns boolean + * @throws error on invalid content type format + */ + canValidateContentType(contentType) { + const contentTypeParsed = contentTypeParser.parse(contentType); + const mediaTypeParsed = mediaTypeParser.parse(contentTypeParsed.type); + return (mediaTypeParsed.subtype === 'json' || mediaTypeParsed.suffix === 'json'); + } +} +exports.ResponseValidator = ResponseValidator; +//# sourceMappingURL=openapi.response.validator.js.map \ No newline at end of file diff --git a/dist/middlewares/openapi.response.validator.js.map b/dist/middlewares/openapi.response.validator.js.map new file mode 100644 index 00000000..b815c39a --- /dev/null +++ b/dist/middlewares/openapi.response.validator.js.map @@ -0,0 +1 @@ +{"version":3,"file":"openapi.response.validator.js","sourceRoot":"","sources":["../../src/middlewares/openapi.response.validator.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/middlewares/openapi.security.d.ts b/dist/middlewares/openapi.security.d.ts new file mode 100644 index 00000000..9b8c87f6 --- /dev/null +++ b/dist/middlewares/openapi.security.d.ts @@ -0,0 +1,2 @@ +import { OpenAPIV3, SecurityHandlers, OpenApiRequestHandler } from '../framework/types'; +export declare function security(apiDoc: OpenAPIV3.Document, securityHandlers: SecurityHandlers): OpenApiRequestHandler; diff --git a/dist/middlewares/openapi.security.js b/dist/middlewares/openapi.security.js new file mode 100644 index 00000000..f9f7dbce --- /dev/null +++ b/dist/middlewares/openapi.security.js @@ -0,0 +1,237 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.security = void 0; +const types_1 = require("../framework/types"); +const defaultSecurityHandler = (req, scopes, schema) => true; +function security(apiDoc, securityHandlers) { + return async (req, res, next) => { + var _d, _e, _f; + // TODO move the following 3 check conditions to a dedicated upstream middleware + if (!req.openapi) { + // this path was not found in open api and + // this path is not defined under an openapi base path + // skip it + return next(); + } + const openapi = req.openapi; + // use the local security object or fallback to api doc's security or undefined + const securities = (_d = openapi.schema.security) !== null && _d !== void 0 ? _d : apiDoc.security; + const path = openapi.openApiRoute; + if (!path || !Array.isArray(securities) || securities.length === 0) { + return next(); + } + const securitySchemes = (_e = apiDoc.components) === null || _e === void 0 ? void 0 : _e.securitySchemes; + if (!securitySchemes) { + const message = `security referenced at path ${path}, but not defined in 'components.securitySchemes'`; + return next(new types_1.InternalServerError({ path: path, message: message })); + } + try { + const results = await new SecuritySchemes(securitySchemes, securityHandlers, securities).executeHandlers(req); + // TODO handle AND'd and OR'd security + // This assumes OR only! i.e. at least one security passed authentication + let firstError = null; + let success = false; + function checkErrorWithOr(res) { + return res.forEach((r) => { + if (r.success) { + success = true; + } + else if (!firstError) { + firstError = r; + } + }); + } + function checkErrorsWithAnd(res) { + let allSuccess = false; + res.forEach((r) => { + if (!r.success) { + allSuccess = false; + if (!firstError) { + firstError = r; + } + } + else if (!firstError) { + allSuccess = true; + } + }); + if (allSuccess) { + success = true; + } + } + results.forEach((result) => { + if (Array.isArray(result) && result.length > 1) { + checkErrorsWithAnd(result); + } + else { + checkErrorWithOr(result); + } + }); + if (success) { + next(); + } + else { + throw firstError; + } + } + catch (e) { + const message = ((_f = e === null || e === void 0 ? void 0 : e.error) === null || _f === void 0 ? void 0 : _f.message) || 'unauthorized'; + const err = types_1.HttpError.create({ + status: e.status, + path: path, + message: message, + }); + /*const err = + e.status == 500 + ? new InternalServerError({ path: path, message: message }) + : e.status == 403 + ? new Forbidden({ path: path, message: message }) + : new Unauthorized({ path: path, message: message });*/ + next(err); + } + }; +} +exports.security = security; +class SecuritySchemes { + constructor(securitySchemes, securityHandlers, securities) { + this.securitySchemes = securitySchemes; + this.securityHandlers = securityHandlers; + this.securities = securities; + } + async executeHandlers(req) { + // use a fallback handler if security handlers is not specified + // This means if security handlers is specified, the user must define + // all security handlers + const fallbackHandler = !this.securityHandlers + ? defaultSecurityHandler + : null; + const promises = this.securities.map(async (s) => { + if (Util.isEmptyObject(s)) { + // anonymous security + return [{ success: true }]; + } + return Promise.all(Object.keys(s).map(async (securityKey) => { + var _a, _b, _c; + try { + const scheme = this.securitySchemes[securityKey]; + const handler = (_b = + (_a = this.securityHandlers) === null || _a === void 0 + ? void 0 + : _a[securityKey]) !== null && _b !== void 0 + ? _b + : fallbackHandler; + const scopesTmp = s[securityKey]; + const scopes = Array.isArray(scopesTmp) ? scopesTmp : []; + if (!scheme) { + const message = `components.securitySchemes.${securityKey} does not exist`; + throw new types_1.InternalServerError({ message }); + } + if (!scheme.hasOwnProperty('type')) { + const message = `components.securitySchemes.${securityKey} must have property 'type'`; + throw new types_1.InternalServerError({ message }); + } + if (!handler) { + const message = `a security handler for '${securityKey}' does not exist`; + throw new types_1.InternalServerError({ message }); + } + new AuthValidator(req, scheme, scopes).validate(); + // expected handler results are: + // - throw exception, + // - return true, + // - return Promise, + // - return false, + // - return Promise + // everything else should be treated as false + const securityScheme = scheme; + const success = await handler(req, scopes, securityScheme); + if (success === true) { + return { success }; + } + else { + throw Error(); + } + } + catch (e) { + return { + success: false, + status: (_c = e.status) !== null && _c !== void 0 ? _c : 401, + error: e, + }; + } + })); + }); + return Promise.all(promises); + } +} +class AuthValidator { + constructor(req, scheme, scopes = []) { + const openapi = req.openapi; + this.req = req; + this.scheme = scheme; + this.path = openapi.openApiRoute; + this.scopes = scopes; + } + validate() { + this.validateApiKey(); + this.validateHttp(); + this.validateOauth2(); + this.validateOpenID(); + } + validateOauth2() { + const { req, scheme, path } = this; + if (['oauth2'].includes(scheme.type.toLowerCase())) { + // TODO oauth2 validation + } + } + validateOpenID() { + const { req, scheme, path } = this; + if (['openIdConnect'].includes(scheme.type.toLowerCase())) { + // TODO openidconnect validation + } + } + validateHttp() { + const { req, scheme, path } = this; + if (['http'].includes(scheme.type.toLowerCase())) { + const authHeader = req.headers['authorization'] && + req.headers['authorization'].toLowerCase(); + if (!authHeader) { + throw Error(`Authorization header required`); + } + const type = scheme.scheme && scheme.scheme.toLowerCase(); + if (type === 'bearer' && !authHeader.includes('bearer')) { + throw Error(`Authorization header with scheme 'Bearer' required`); + } + if (type === 'basic' && !authHeader.includes('basic')) { + throw Error(`Authorization header with scheme 'Basic' required`); + } + } + } + validateApiKey() { + var _d; + const { req, scheme, path } = this; + if (scheme.type === 'apiKey') { + if (scheme.in === 'header') { + if (!req.headers[scheme.name.toLowerCase()]) { + throw Error(`'${scheme.name}' header required`); + } + } + else if (scheme.in === 'query') { + if (!req.query[scheme.name]) { + throw Error(`query parameter '${scheme.name}' required`); + } + } + else if (scheme.in === 'cookie') { + if (!req.cookies[scheme.name] && !((_d = req.signedCookies) === null || _d === void 0 ? void 0 : _d[scheme.name])) { + throw Error(`cookie '${scheme.name}' required`); + } + } + } + } +} +class Util { + static isEmptyObject(o) { + return (typeof o === 'object' && + Object.entries(o).length === 0 && + o.constructor === Object); + } +} +//# sourceMappingURL=openapi.security.js.map \ No newline at end of file diff --git a/dist/middlewares/openapi.security.js.map b/dist/middlewares/openapi.security.js.map new file mode 100644 index 00000000..4028aba9 --- /dev/null +++ b/dist/middlewares/openapi.security.js.map @@ -0,0 +1 @@ +{"version":3,"file":"openapi.security.js","sourceRoot":"","sources":["../../src/middlewares/openapi.security.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/middlewares/parsers/body.parse.d.ts b/dist/middlewares/parsers/body.parse.d.ts new file mode 100644 index 00000000..ab767c36 --- /dev/null +++ b/dist/middlewares/parsers/body.parse.d.ts @@ -0,0 +1,7 @@ +import { ContentType } from '../util'; +import { OpenAPIV3, BodySchema } from '../../framework/types'; +export declare class BodySchemaParser { + constructor(); + parse(path: string, pathSchema: OpenAPIV3.OperationObject, contentType: ContentType): BodySchema; + private toSchema; +} diff --git a/dist/middlewares/parsers/body.parse.js b/dist/middlewares/parsers/body.parse.js new file mode 100644 index 00000000..f5297f3f --- /dev/null +++ b/dist/middlewares/parsers/body.parse.js @@ -0,0 +1,59 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.BodySchemaParser = void 0; +const types_1 = require("../../framework/types"); +class BodySchemaParser { + constructor() { + } + parse(path, pathSchema, contentType) { + // The schema.preprocessor will have dereferenced the RequestBodyObject + // thus we can assume a RequestBodyObject, not a ReferenceObject + const requestBody = pathSchema.requestBody; + if (requestBody === null || requestBody === void 0 ? void 0 : requestBody.hasOwnProperty('content')) { + return this.toSchema(path, contentType, requestBody); + } + return {}; + } + toSchema(path, contentType, requestBody) { + var _a; + if (!(requestBody === null || requestBody === void 0 ? void 0 : requestBody.content)) + return {}; + let content = null; + for (const type of contentType.equivalents()) { + content = requestBody.content[type]; + if (content) + break; + } + if (!content) { + for (const requestContentType of Object.keys(requestBody.content) + .sort() + .reverse()) { + if (requestContentType === '*/*') { + content = requestBody.content[requestContentType]; + break; + } + if (!new RegExp(/^[a-z]+\/\*$/).test(requestContentType)) + continue; // not a wildcard of type application/* + const [type] = requestContentType.split('/', 1); + if (new RegExp(`^${type}\/.+$`).test(contentType.contentType)) { + content = requestBody.content[requestContentType]; + break; + } + } + } + if (!content) { + // check if required is false, if so allow request when no content type is supplied + const contentNotProvided = contentType.contentType === 'not_provided'; + if ((contentType.contentType === undefined || contentNotProvided) && requestBody.required === false) { + return {}; + } + const msg = contentNotProvided + ? 'media type not specified' + : `unsupported media type ${contentType.contentType}`; + throw new types_1.UnsupportedMediaType({ path: path, message: msg }); + } + return (_a = content.schema) !== null && _a !== void 0 ? _a : {}; + } +} +exports.BodySchemaParser = BodySchemaParser; +//# sourceMappingURL=body.parse.js.map \ No newline at end of file diff --git a/dist/middlewares/parsers/body.parse.js.map b/dist/middlewares/parsers/body.parse.js.map new file mode 100644 index 00000000..bde0bf0a --- /dev/null +++ b/dist/middlewares/parsers/body.parse.js.map @@ -0,0 +1 @@ +{"version":3,"file":"body.parse.js","sourceRoot":"","sources":["../../../src/middlewares/parsers/body.parse.ts"],"names":[],"mappings":";;;AAEA,iDAI+B;AAE/B,MAAa,gBAAgB;IAC3B;IACA,CAAC;IACM,KAAK,CACV,IAAY,EACZ,UAAqC,EACrC,WAAwB;QAExB,uEAAuE;QACvE,gEAAgE;QAChE,MAAM,WAAW,GAAgC,UAAU,CAAC,WAAW,CAAC;QACxE,IAAI,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,cAAc,CAAC,SAAS,CAAC,EAAE;YAC1C,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC;SACtD;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;IAEO,QAAQ,CACd,IAAY,EACZ,WAAwB,EACxB,WAAwC;;QAExC,IAAI,CAAC,CAAA,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,OAAO,CAAA;YAAE,OAAO,EAAE,CAAC;QAErC,IAAI,OAAO,GAAG,IAAI,CAAC;QACnB,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,WAAW,EAAE,EAAE;YAC5C,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACpC,IAAI,OAAO;gBAAE,MAAM;SACpB;QAED,IAAI,CAAC,OAAO,EAAE;YACZ,KAAK,MAAM,kBAAkB,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC;iBAC9D,IAAI,EAAE;iBACN,OAAO,EAAE,EAAE;gBACZ,IAAI,kBAAkB,KAAK,KAAK,EAAE;oBAChC,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;oBAClD,MAAM;iBACP;gBAED,IAAI,CAAC,IAAI,MAAM,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC;oBAAE,SAAS,CAAC,uCAAuC;gBAE3G,MAAM,CAAC,IAAI,CAAC,GAAG,kBAAkB,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;gBAEhD,IAAI,IAAI,MAAM,CAAC,IAAI,IAAI,OAAO,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,EAAE;oBAC7D,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;oBAClD,MAAM;iBACP;aACF;SACF;QAED,IAAI,CAAC,OAAO,EAAE;YACZ,mFAAmF;YACnF,MAAM,kBAAkB,GAAG,WAAW,CAAC,WAAW,KAAK,cAAc,CAAC;YACtE,IAAI,CAAC,WAAW,CAAC,WAAW,KAAK,SAAS,IAAI,kBAAkB,CAAC,IAAI,WAAW,CAAC,QAAQ,KAAK,KAAK,EAAE;gBACnG,OAAO,EAAE,CAAC;aACX;YACD,MAAM,GAAG,GACP,kBAAkB;gBAChB,CAAC,CAAC,0BAA0B;gBAC5B,CAAC,CAAC,0BAA0B,WAAW,CAAC,WAAW,EAAE,CAAC;YAC1D,MAAM,IAAI,4BAAoB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;SAC9D;QACD,OAAO,MAAA,OAAO,CAAC,MAAM,mCAAI,EAAE,CAAC;IAC9B,CAAC;CACF;AAhED,4CAgEC"} \ No newline at end of file diff --git a/dist/middlewares/parsers/req.parameter.mutator.d.ts b/dist/middlewares/parsers/req.parameter.mutator.d.ts new file mode 100644 index 00000000..e52073ef --- /dev/null +++ b/dist/middlewares/parsers/req.parameter.mutator.d.ts @@ -0,0 +1,30 @@ +import { Ajv } from 'ajv'; +import { OpenAPIV3, OpenApiRequest, ValidationSchema } from '../../framework/types'; +/** + * A class top parse and mutate the incoming request parameters according to the openapi spec. + * the request is mutated to accomodate various styles and types e.g. form, explode, deepObject, etc + */ +export declare class RequestParameterMutator { + private _apiDocs; + private path; + private ajv; + private parsedSchema; + constructor(ajv: Ajv, apiDocs: OpenAPIV3.Document, path: string, parsedSchema: ValidationSchema); + /** + * Modifies an incoing request object by applying the openapi schema + * req values may be parsed/mutated as a JSON object, JSON Exploded Object, JSON Array, or JSON Exploded Array + * @param req + */ + modifyRequest(req: OpenApiRequest): void; + private handleDeepObject; + private handleContent; + private handleFormExplode; + private parseJsonAndMutateRequest; + private parseJsonArrayAndMutateRequest; + private explodedJsonObjectAndMutateRequest; + private explodeJsonArrayAndMutateRequest; + private isObjectOrXOf; + private validateArrayDelimiter; + private validateReservedCharacters; + private parseQueryStringUndecoded; +} diff --git a/dist/middlewares/parsers/req.parameter.mutator.js b/dist/middlewares/parsers/req.parameter.mutator.js new file mode 100644 index 00000000..f1c7126a --- /dev/null +++ b/dist/middlewares/parsers/req.parameter.mutator.js @@ -0,0 +1,286 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RequestParameterMutator = void 0; +const types_1 = require("../../framework/types"); +const url = require("url"); +const util_1 = require("./util"); +const mediaTypeParser = require("media-typer"); +const contentTypeParser = require("content-type"); +const RESERVED_CHARS = /[\:\/\?#\[\]@!\$&\'()\*\+,;=]/; +const ARRAY_DELIMITER = { + simple: ',', + form: ',', + spaceDelimited: ' ', + pipeDelimited: '|', +}; +const REQUEST_FIELDS = { + query: 'query', + header: 'headers', + path: 'params', + cookie: 'cookies', +}; +/** + * A class top parse and mutate the incoming request parameters according to the openapi spec. + * the request is mutated to accomodate various styles and types e.g. form, explode, deepObject, etc + */ +class RequestParameterMutator { + constructor(ajv, apiDocs, path, parsedSchema) { + this.ajv = ajv; + this._apiDocs = apiDocs; + this.path = path; + this.parsedSchema = parsedSchema; + } + /** + * Modifies an incoing request object by applying the openapi schema + * req values may be parsed/mutated as a JSON object, JSON Exploded Object, JSON Array, or JSON Exploded Array + * @param req + */ + modifyRequest(req) { + const { parameters } = req.openapi.schema; + const rawQuery = this.parseQueryStringUndecoded(url.parse(req.originalUrl).query); + (parameters || []).forEach((p) => { + const parameter = util_1.dereferenceParameter(this._apiDocs, p); + const { name, schema } = util_1.normalizeParameter(this.ajv, parameter); + const { type } = schema; + const { style, explode } = parameter; + const i = req.originalUrl.indexOf('?'); + const queryString = req.originalUrl.substr(i + 1); + if (parameter.in === 'query' && !parameter.allowReserved) { + this.validateReservedCharacters(name, rawQuery); + } + if (parameter.content) { + this.handleContent(req, name, parameter); + } + else if (parameter.in === 'query' && this.isObjectOrXOf(schema)) { + if (style === 'form' && explode) { + this.parseJsonAndMutateRequest(req, parameter.in, name); + this.handleFormExplode(req, name, schema, parameter); + } + else if (style === 'deepObject') { + this.handleDeepObject(req, queryString, name, schema); + } + else { + this.parseJsonAndMutateRequest(req, parameter.in, name); + } + } + else if (type === 'array' && !explode) { + const delimiter = ARRAY_DELIMITER[parameter.style]; + this.validateArrayDelimiter(delimiter, parameter); + this.parseJsonArrayAndMutateRequest(req, parameter.in, name, delimiter); + } + else if (type === 'array' && explode) { + this.explodeJsonArrayAndMutateRequest(req, parameter.in, name); + } + else if (style === 'form' && explode) { + this.handleFormExplode(req, name, schema, parameter); + } + }); + } + handleDeepObject(req, qs, name, schema) { + var _a; + const getDefaultSchemaValue = () => { + let defaultValue; + if (schema.default !== undefined) { + defaultValue = schema.default; + } + else if (schema.properties) { + Object.entries(schema.properties).forEach(([k, v]) => { + // Handle recursive objects + defaultValue !== null && defaultValue !== void 0 ? defaultValue : (defaultValue = {}); + if (v['default']) { + defaultValue[k] = v['default']; + } + }); + } + else { + ['allOf', 'oneOf', 'anyOf'].forEach((key) => { + if (schema[key]) { + schema[key].forEach((s) => { + if (s.$ref) { + const compiledSchema = this.ajv.getSchema(s.$ref); + // as any -> https://stackoverflow.com/a/23553128 + defaultValue = + defaultValue === undefined + ? compiledSchema.schema.default + : defaultValue; + } + else { + defaultValue = + defaultValue === undefined ? s.default : defaultValue; + } + }); + } + }); + } + return defaultValue; + }; + if (!((_a = req.query) === null || _a === void 0 ? void 0 : _a[name])) { + req.query[name] = getDefaultSchemaValue(); + } + this.parseJsonAndMutateRequest(req, 'query', name); + // TODO handle url encoded? + } + handleContent(req, name, parameter) { + /** + * Per the OpenAPI3 spec: + * A map containing the representations for the parameter. The key is the media type + * and the value describes it. The map MUST only contain one entry. + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#parameterContent + */ + const contentType = Object.keys(parameter.content)[0]; + const parsedContentType = contentTypeParser.parse(contentType); + const parsedMediaType = mediaTypeParser.parse(parsedContentType.type); + const { subtype, suffix } = parsedMediaType; + const isMediaTypeJson = [subtype, suffix].includes('json'); + if (isMediaTypeJson) { + const reqField = REQUEST_FIELDS[parameter.in]; + this.parseJsonAndMutateRequest(req, reqField, name); + } + } + handleFormExplode(req, name, schema, parameter) { + var _a; + // fetch the keys used for this kind of explode + const type = schema.type; + const hasXOf = schema.allOf || schema.oneOf || schema.anyOf; + const properties = hasXOf + ? xOfProperties(schema) + : type === 'object' + ? Object.keys((_a = schema.properties) !== null && _a !== void 0 ? _a : {}) + : []; + this.explodedJsonObjectAndMutateRequest(req, parameter.in, name, properties, schema); + function xOfProperties(schema) { + return ['allOf', 'oneOf', 'anyOf'].reduce((acc, key) => { + if (!schema.hasOwnProperty(key)) { + return acc; + } + else { + const foundProperties = schema[key].reduce((acc2, obj) => { + return obj.type === 'object' + ? acc2.concat(...Object.keys(obj.properties)) + : acc2; + }, []); + return foundProperties.length > 0 + ? acc.concat(...foundProperties) + : acc; + } + }, []); + } + } + parseJsonAndMutateRequest(req, $in, name) { + var _a; + /** + * support json in request params, query, headers and cookies + * like this filter={"type":"t-shirt","color":"blue"} + * + * https://swagger.io/docs/specification/describing-parameters/#schema-vs-content + */ + const field = REQUEST_FIELDS[$in]; + if ((_a = req[field]) === null || _a === void 0 ? void 0 : _a[name]) { + try { + const value = req[field][name]; + const json = JSON.parse(value); + req[field][name] = json; + } + catch (e) { + // NOOP If parsing failed but _should_ contain JSON, validator will catch it. + // May contain falsely flagged parameter (e.g. input was object OR string) + } + } + } + parseJsonArrayAndMutateRequest(req, $in, name, delimiter) { + var _a; + /** + * array deserialization + * filter=foo,bar,baz + * filter=foo|bar|baz + * filter=foo%20bar%20baz + */ + const field = REQUEST_FIELDS[$in]; + if ((_a = req[field]) === null || _a === void 0 ? void 0 : _a[name]) { + if (Array.isArray(req[field][name])) + return; + const value = req[field][name].split(delimiter); + req[field][name] = value; + } + } + explodedJsonObjectAndMutateRequest(req, $in, name, properties, schema) { + // forcing convert to object if scheme describes param as object + explode + // for easy validation, keep the schema but update whereabouts of its sub components + const field = REQUEST_FIELDS[$in]; + if (req[field]) { + // check if there is at least one of the nested properties before creating the root property + const atLeastOne = properties.some((p) => req[field].hasOwnProperty(p)); + if (atLeastOne) { + req[field][name] = {}; + properties.forEach((property) => { + var _a, _b; + if (req[field][property]) { + const schema = this.parsedSchema[field]; + const type = (_b = (_a = schema.properties[name].properties) === null || _a === void 0 ? void 0 : _a[property]) === null || _b === void 0 ? void 0 : _b.type; + const value = req[field][property]; + const coercedValue = type === 'array' && !Array.isArray(value) ? [value] : value; + req[field][name][property] = coercedValue; + delete req[field][property]; + } + }); + } + } + } + explodeJsonArrayAndMutateRequest(req, $in, name) { + var _a; + /** + * forcing convert to array if scheme describes param as array + explode + */ + const field = REQUEST_FIELDS[$in]; + if (((_a = req[field]) === null || _a === void 0 ? void 0 : _a[name]) && !(req[field][name] instanceof Array)) { + const value = [req[field][name]]; + req[field][name] = value; + } + } + isObjectOrXOf(schema) { + const schemaHasObject = (schema) => { + if (!schema) + return false; + if (schema.$ref) + return true; + const { type, allOf, oneOf, anyOf } = schema; + return (type === 'object' || + [].concat(allOf, oneOf, anyOf).some(schemaHasObject)); + }; + return schemaHasObject(schema); + } + validateArrayDelimiter(delimiter, parameter) { + if (!delimiter) { + const message = `Parameter 'style' has incorrect value '${parameter.style}' for [${parameter.name}]`; + throw new types_1.BadRequest({ + path: `.query.${parameter.name}`, + message: message, + }); + } + } + validateReservedCharacters(name, pairs) { + const vs = pairs.get(name); + if (!vs) + return; + for (const v of vs) { + if (v === null || v === void 0 ? void 0 : v.match(RESERVED_CHARS)) { + const message = `Parameter '${name}' must be url encoded. Its value may not contain reserved characters.`; + throw new types_1.BadRequest({ path: `.query.${name}`, message: message }); + } + } + } + parseQueryStringUndecoded(qs) { + if (!qs) + return new Map(); + const q = qs.replace('?', ''); + return q.split('&').reduce((m, p) => { + var _a; + const [k, v] = p.split('='); + m.set(k, (_a = m.get(k)) !== null && _a !== void 0 ? _a : []); + m.get(k).push(v); + return m; + }, new Map()); + } +} +exports.RequestParameterMutator = RequestParameterMutator; +//# sourceMappingURL=req.parameter.mutator.js.map \ No newline at end of file diff --git a/dist/middlewares/parsers/req.parameter.mutator.js.map b/dist/middlewares/parsers/req.parameter.mutator.js.map new file mode 100644 index 00000000..7bbe33c5 --- /dev/null +++ b/dist/middlewares/parsers/req.parameter.mutator.js.map @@ -0,0 +1 @@ +{"version":3,"file":"req.parameter.mutator.js","sourceRoot":"","sources":["../../../src/middlewares/parsers/req.parameter.mutator.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/middlewares/parsers/schema.parse.d.ts b/dist/middlewares/parsers/schema.parse.d.ts new file mode 100644 index 00000000..2bac5555 --- /dev/null +++ b/dist/middlewares/parsers/schema.parse.d.ts @@ -0,0 +1,21 @@ +import { OpenAPIV3, ParametersSchema } from '../../framework/types'; +import { Ajv } from 'ajv'; +declare type Parameter = OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject; +/** + * A class top arse incoing parameters and populate a list of request fields e.g. id and field types e.g. query + * whose value must later be parsed as a JSON object, JSON Exploded Object, JSON Array, or JSON Exploded Array + */ +export declare class ParametersSchemaParser { + private _ajv; + private _apiDocs; + constructor(ajv: Ajv, apiDocs: OpenAPIV3.Document); + /** + * Parse incoing parameters and populate a list of request fields e.g. id and field types e.g. query + * whose value must later be parsed as a JSON object, JSON Exploded Object, JSON Array, or JSON Exploded Array + * @param path + * @param parameters + */ + parse(path: string, parameters?: Parameter[]): ParametersSchema; + private validateParameterType; +} +export {}; diff --git a/dist/middlewares/parsers/schema.parse.js b/dist/middlewares/parsers/schema.parse.js new file mode 100644 index 00000000..6a7529ed --- /dev/null +++ b/dist/middlewares/parsers/schema.parse.js @@ -0,0 +1,74 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ParametersSchemaParser = void 0; +const types_1 = require("../../framework/types"); +const util_1 = require("./util"); +const PARAM_TYPE = { + query: 'query', + header: 'headers', + path: 'params', + cookie: 'cookies', +}; +/** + * A class top arse incoing parameters and populate a list of request fields e.g. id and field types e.g. query + * whose value must later be parsed as a JSON object, JSON Exploded Object, JSON Array, or JSON Exploded Array + */ +class ParametersSchemaParser { + constructor(ajv, apiDocs) { + this._ajv = ajv; + this._apiDocs = apiDocs; + } + /** + * Parse incoing parameters and populate a list of request fields e.g. id and field types e.g. query + * whose value must later be parsed as a JSON object, JSON Exploded Object, JSON Array, or JSON Exploded Array + * @param path + * @param parameters + */ + parse(path, parameters = []) { + const schemas = { query: {}, headers: {}, params: {}, cookies: {} }; + parameters.forEach((p) => { + const parameter = util_1.dereferenceParameter(this._apiDocs, p); + this.validateParameterType(path, parameter); + const reqField = PARAM_TYPE[parameter.in]; + const { name, schema } = util_1.normalizeParameter(this._ajv, parameter); + if (!schemas[reqField].properties) { + schemas[reqField] = { + type: 'object', + properties: {}, + }; + } + schemas[reqField].properties[name] = schema; + if (reqField === 'query' && parameter.allowEmptyValue) { + if (!schemas[reqField].allowEmptyValue) { + schemas[reqField].allowEmptyValue = new Set(); + } + schemas[reqField].allowEmptyValue.add(name); + } + if (parameter.required) { + if (!schemas[reqField].required) { + schemas[reqField].required = []; + } + schemas[reqField].required.push(name); + } + }); + return schemas; + } + validateParameterType(path, parameter) { + const isKnownType = PARAM_TYPE[parameter.in]; + if (!isKnownType) { + const message = `Parameter 'in' has incorrect value '${parameter.in}' for [${parameter.name}]`; + throw new types_1.BadRequest({ path: path, message: message }); + } + const hasSchema = () => { + var _a, _b; + const contentType = parameter.content && Object.keys(parameter.content)[0]; + return !parameter.schema || !((_b = (_a = parameter.content) === null || _a === void 0 ? void 0 : _a[contentType]) === null || _b === void 0 ? void 0 : _b.schema); + }; + if (!hasSchema()) { + const message = `No available parameter in 'schema' or 'content' for [${parameter.name}]`; + throw new types_1.BadRequest({ path: path, message: message }); + } + } +} +exports.ParametersSchemaParser = ParametersSchemaParser; +//# sourceMappingURL=schema.parse.js.map \ No newline at end of file diff --git a/dist/middlewares/parsers/schema.parse.js.map b/dist/middlewares/parsers/schema.parse.js.map new file mode 100644 index 00000000..97f578c2 --- /dev/null +++ b/dist/middlewares/parsers/schema.parse.js.map @@ -0,0 +1 @@ +{"version":3,"file":"schema.parse.js","sourceRoot":"","sources":["../../../src/middlewares/parsers/schema.parse.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/middlewares/parsers/schema.preprocessor.d.ts b/dist/middlewares/parsers/schema.preprocessor.d.ts new file mode 100644 index 00000000..2dbca173 --- /dev/null +++ b/dist/middlewares/parsers/schema.preprocessor.d.ts @@ -0,0 +1,41 @@ +import { OpenAPIV3, Options, ValidateResponseOpts } from '../../framework/types'; +export declare const httpMethods: Set; +export declare class SchemaPreprocessor { + private ajv; + private apiDoc; + private apiDocRes; + private serDesMap; + private responseOpts; + constructor(apiDoc: OpenAPIV3.Document, ajvOptions: Options, validateResponsesOpts: ValidateResponseOpts); + preProcess(): { + apiDoc: OpenAPIV3.Document; + apiDocRes: OpenAPIV3.Document; + }; + private gatherComponentSchemaNodes; + private gatherSchemaNodesFromPaths; + /** + * Traverse the schema starting at each node in nodes + * @param nodes the nodes to traverse + * @param visit a function to invoke per node + */ + private traverseSchemas; + private schemaVisitor; + private processDiscriminator; + private handleSerDes; + private handleReadonly; + /** + * extract all requestBodies' schemas from an operation + * @param op + */ + private extractRequestBodySchemaNodes; + private extractResponseSchemaNodes; + private resolveSchema; + /** + * add path level parameters to the schema's parameters list + * @param pathItemKey + * @param pathItem + */ + private preprocessPathLevelParameters; + private findKeys; + getKeyFromRef(ref: any): any; +} diff --git a/dist/middlewares/parsers/schema.preprocessor.js b/dist/middlewares/parsers/schema.preprocessor.js new file mode 100644 index 00000000..79d26121 --- /dev/null +++ b/dist/middlewares/parsers/schema.preprocessor.js @@ -0,0 +1,388 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SchemaPreprocessor = exports.httpMethods = void 0; +const cloneDeep = require("lodash.clonedeep"); +const _get = require("lodash.get"); +const ajv_1 = require("../../framework/ajv"); +class Node { + constructor(parent, schema, path) { + this.path = path; + this.parent = parent; + this.schema = schema; + } +} +class Root extends Node { + constructor(schema, path) { + super(null, schema, path); + } +} +if (!Array.prototype['flatMap']) { + // polyfill flatMap + // TODO remove me when dropping node 10 support + Array.prototype['flatMap'] = function (lambda) { + return Array.prototype.concat.apply([], this.map(lambda)); + }; + Object.defineProperty(Array.prototype, 'flatMap', { enumerable: false }); +} +exports.httpMethods = new Set([ + 'get', + 'put', + 'post', + 'delete', + 'options', + 'head', + 'patch', + 'trace', +]); +class SchemaPreprocessor { + constructor(apiDoc, ajvOptions, validateResponsesOpts) { + this.ajv = ajv_1.createRequestAjv(apiDoc, ajvOptions); + this.apiDoc = apiDoc; + this.serDesMap = ajvOptions.serDesMap; + this.responseOpts = validateResponsesOpts; + } + preProcess() { + const componentSchemas = this.gatherComponentSchemaNodes(); + const r = this.gatherSchemaNodesFromPaths(); + // Now that we've processed paths, clone a response spec if we are validating responses + this.apiDocRes = !!this.responseOpts ? cloneDeep(this.apiDoc) : null; + const schemaNodes = { + schemas: componentSchemas, + requestBodies: r.requestBodies, + responses: r.responses, + }; + // Traverse the schemas + this.traverseSchemas(schemaNodes, (parent, schema, opts) => this.schemaVisitor(parent, schema, opts)); + return { + apiDoc: this.apiDoc, + apiDocRes: this.apiDocRes, + }; + } + gatherComponentSchemaNodes() { + var _a, _b, _c; + const nodes = []; + const componentSchemaMap = (_c = (_b = (_a = this.apiDoc) === null || _a === void 0 ? void 0 : _a.components) === null || _b === void 0 ? void 0 : _b.schemas) !== null && _c !== void 0 ? _c : []; + for (const [id, s] of Object.entries(componentSchemaMap)) { + const schema = this.resolveSchema(s); + this.apiDoc.components.schemas[id] = schema; + const path = ['components', 'schemas', id]; + const node = new Root(schema, path); + nodes.push(node); + } + return nodes; + } + gatherSchemaNodesFromPaths() { + const requestBodySchemas = []; + const responseSchemas = []; + for (const [p, pi] of Object.entries(this.apiDoc.paths)) { + const pathItem = this.resolveSchema(pi); + for (const method of Object.keys(pathItem)) { + if (exports.httpMethods.has(method)) { + const operation = pathItem[method]; + // Adds path declared parameters to the schema's parameters list + this.preprocessPathLevelParameters(method, pathItem); + const path = ['paths', p, method]; + const node = new Root(operation, path); + const requestBodies = this.extractRequestBodySchemaNodes(node); + const responseBodies = this.extractResponseSchemaNodes(node); + requestBodySchemas.push(...requestBodies); + responseSchemas.push(...responseBodies); + } + } + } + return { + requestBodies: requestBodySchemas, + responses: responseSchemas, + }; + } + /** + * Traverse the schema starting at each node in nodes + * @param nodes the nodes to traverse + * @param visit a function to invoke per node + */ + traverseSchemas(nodes, visit) { + const seen = new Set(); + const recurse = (parent, node, opts) => { + const schema = node.schema; + if (!schema || seen.has(schema)) + return; + seen.add(schema); + if (schema.$ref) { + const resolvedSchema = this.resolveSchema(schema); + const path = schema.$ref.split('/').slice(1); + opts.req.originalSchema = schema; + opts.res.originalSchema = schema; + visit(parent, node, opts); + recurse(node, new Node(schema, resolvedSchema, path), opts); + return; + } + // Save the original schema so we can check if it was a $ref + opts.req.originalSchema = schema; + opts.res.originalSchema = schema; + visit(parent, node, opts); + if (schema.allOf) { + schema.allOf.forEach((s, i) => { + const child = new Node(node, s, [...node.path, 'allOf', i + '']); + recurse(node, child, opts); + }); + } + else if (schema.oneOf) { + schema.oneOf.forEach((s, i) => { + const child = new Node(node, s, [...node.path, 'oneOf', i + '']); + recurse(node, child, opts); + }); + } + else if (schema.anyOf) { + schema.anyOf.forEach((s, i) => { + const child = new Node(node, s, [...node.path, 'anyOf', i + '']); + recurse(node, child, opts); + }); + } + else if ( /*schema.type == 'array' && */schema.items) { + const child = new Node(node, schema.items, [...node.path, 'items']); + recurse(node, child, opts); + } + else if (schema.properties) { + Object.entries(schema.properties).forEach(([id, cschema]) => { + const path = [...node.path, 'properties', id]; + const child = new Node(node, cschema, path); + recurse(node, child, opts); + }); + } + }; + const initOpts = () => ({ + req: { discriminator: {}, kind: 'req', path: [] }, + res: { discriminator: {}, kind: 'res', path: [] }, + }); + for (const node of nodes.schemas) { + recurse(null, node, initOpts()); + } + for (const node of nodes.requestBodies) { + recurse(null, node, initOpts()); + } + for (const node of nodes.responses) { + recurse(null, node, initOpts()); + } + } + schemaVisitor(parent, node, opts) { + const pschemas = [parent === null || parent === void 0 ? void 0 : parent.schema]; + const nschemas = [node.schema]; + if (this.apiDocRes) { + const p = _get(this.apiDocRes, parent === null || parent === void 0 ? void 0 : parent.path); + const n = _get(this.apiDocRes, node === null || node === void 0 ? void 0 : node.path); + pschemas.push(p); + nschemas.push(n); + } + // visit the node in both the request and response schema + for (let i = 0; i < nschemas.length; i++) { + const kind = i === 0 ? 'req' : 'res'; + const pschema = pschemas[i]; + const nschema = nschemas[i]; + const options = opts[kind]; + options.path = node.path; + if (nschema) { + // This null check should no longer be necessary + this.handleSerDes(pschema, nschema, options); + this.handleReadonly(pschema, nschema, options); + this.processDiscriminator(pschema, nschema, options); + } + } + } + processDiscriminator(parent, schema, opts = {}) { + var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p; + const o = opts.discriminator; + const schemaObj = schema; + const xOf = schemaObj.oneOf ? 'oneOf' : schemaObj.anyOf ? 'anyOf' : null; + if (xOf && ((_a = schemaObj === null || schemaObj === void 0 ? void 0 : schemaObj.discriminator) === null || _a === void 0 ? void 0 : _a.propertyName) && !o.discriminator) { + const options = schemaObj[xOf].flatMap((refObject) => { + if (refObject['$ref'] === undefined) { + return []; + } + const keys = this.findKeys(schemaObj.discriminator.mapping, (value) => value === refObject['$ref']); + const ref = this.getKeyFromRef(refObject['$ref']); + return keys.length > 0 + ? keys.map((option) => ({ option, ref })) + : [{ option: ref, ref }]; + }); + o.options = options; + o.discriminator = (_b = schemaObj.discriminator) === null || _b === void 0 ? void 0 : _b.propertyName; + o.properties = Object.assign(Object.assign({}, ((_c = o.properties) !== null && _c !== void 0 ? _c : {})), ((_d = schemaObj.properties) !== null && _d !== void 0 ? _d : {})); + o.required = Array.from(new Set(((_e = o.required) !== null && _e !== void 0 ? _e : []).concat((_f = schemaObj.required) !== null && _f !== void 0 ? _f : []))); + } + if (xOf) + return; + if (o.discriminator) { + o.properties = Object.assign(Object.assign({}, ((_g = o.properties) !== null && _g !== void 0 ? _g : {})), ((_h = schemaObj.properties) !== null && _h !== void 0 ? _h : {})); + o.required = Array.from(new Set(((_j = o.required) !== null && _j !== void 0 ? _j : []).concat((_k = schemaObj.required) !== null && _k !== void 0 ? _k : []))); + const ancestor = parent; + const ref = opts.originalSchema.$ref; + if (!ref) + return; + const options = this.findKeys((_l = ancestor.discriminator) === null || _l === void 0 ? void 0 : _l.mapping, (value) => value === ref); + const refName = this.getKeyFromRef(ref); + if (options.length === 0 && ref) { + options.push(refName); + } + if (options.length > 0) { + const newSchema = JSON.parse(JSON.stringify(schemaObj)); + const newProperties = Object.assign(Object.assign({}, ((_m = o.properties) !== null && _m !== void 0 ? _m : {})), ((_o = newSchema.properties) !== null && _o !== void 0 ? _o : {})); + if (Object.keys(newProperties).length > 0) { + newSchema.properties = newProperties; + } + newSchema.required = o.required; + if (newSchema.required.length === 0) { + delete newSchema.required; + } + (_p = ancestor._discriminator) !== null && _p !== void 0 ? _p : (ancestor._discriminator = { + validators: {}, + options: o.options, + property: o.discriminator, + }); + for (const option of options) { + ancestor._discriminator.validators[option] = + this.ajv.compile(newSchema); + } + } + //reset data + o.properties = {}; + delete o.required; + } + } + handleSerDes(parent, schema, state) { + if (schema.type === 'string' && + !!schema.format && + this.serDesMap[schema.format]) { + schema.type = [this.serDesMap[schema.format].jsonType || 'object', 'string']; + schema['x-eov-serdes'] = this.serDesMap[schema.format]; + } + } + handleReadonly(parent, schema, opts) { + var _a, _b, _c; + if (opts.kind === 'res') + return; + const required = (_a = parent === null || parent === void 0 ? void 0 : parent.required) !== null && _a !== void 0 ? _a : []; + const prop = (_b = opts === null || opts === void 0 ? void 0 : opts.path) === null || _b === void 0 ? void 0 : _b[((_c = opts === null || opts === void 0 ? void 0 : opts.path) === null || _c === void 0 ? void 0 : _c.length) - 1]; + const index = required.indexOf(prop); + if (schema.readOnly && index > -1) { + // remove required if readOnly + parent.required = required + .slice(0, index) + .concat(required.slice(index + 1)); + if (parent.required.length === 0) { + delete parent.required; + } + } + } + /** + * extract all requestBodies' schemas from an operation + * @param op + */ + extractRequestBodySchemaNodes(node) { + const op = node.schema; + const bodySchema = this.resolveSchema(op.requestBody); + op.requestBody = bodySchema; + if (!(bodySchema === null || bodySchema === void 0 ? void 0 : bodySchema.content)) + return []; + const result = []; + const contentEntries = Object.entries(bodySchema.content); + for (const [type, mediaTypeObject] of contentEntries) { + const mediaTypeSchema = this.resolveSchema(mediaTypeObject.schema); + op.requestBody.content[type].schema = mediaTypeSchema; + const path = [...node.path, 'requestBody', 'content', type, 'schema']; + result.push(new Root(mediaTypeSchema, path)); + } + return result; + } + extractResponseSchemaNodes(node) { + const op = node.schema; + const responses = op.responses; + if (!responses) + return; + const schemas = []; + for (const [statusCode, response] of Object.entries(responses)) { + const rschema = this.resolveSchema(response); + if (!rschema) { + // issue #553 + // TODO the schema failed to resolve. + // This can occur with multi-file specs + // improve resolution, so that rschema resolves (use json ref parser?) + continue; + } + responses[statusCode] = rschema; + if (rschema.content) { + for (const [type, mediaType] of Object.entries(rschema.content)) { + const schema = this.resolveSchema(mediaType === null || mediaType === void 0 ? void 0 : mediaType.schema); + if (schema) { + rschema.content[type].schema = schema; + const path = [ + ...node.path, + 'responses', + statusCode, + 'content', + type, + 'schema', + ]; + schemas.push(new Root(schema, path)); + } + } + } + } + return schemas; + } + resolveSchema(schema) { + var _a; + if (!schema) + return null; + const ref = schema === null || schema === void 0 ? void 0 : schema['$ref']; + let res = (ref ? (_a = this.ajv.getSchema(ref)) === null || _a === void 0 ? void 0 : _a.schema : schema); + if (ref && !res) { + const path = ref.split('/').join('.'); + const p = path.substring(path.indexOf('.') + 1); + res = _get(this.apiDoc, p); + } + return res; + } + /** + * add path level parameters to the schema's parameters list + * @param pathItemKey + * @param pathItem + */ + preprocessPathLevelParameters(pathItemKey, pathItem) { + var _a; + const parameters = (_a = pathItem.parameters) !== null && _a !== void 0 ? _a : []; + if (parameters.length === 0) + return; + const v = this.resolveSchema(pathItem[pathItemKey]); + if (v === parameters) + return; + v.parameters = v.parameters || []; + const match = (pathParam, opParam) => + // if name or ref exists and are equal + (opParam['name'] && opParam['name'] === pathParam['name']) || + (opParam['$ref'] && opParam['$ref'] === pathParam['$ref']); + // Add Path level query param to list ONLY if there is not already an operation-level query param by the same name. + for (const param of parameters) { + if (!v.parameters.some((vparam) => match(param, vparam))) { + v.parameters.push(param); + } + } + } + findKeys(object, searchFunc) { + const matches = []; + if (!object) { + return matches; + } + const keys = Object.keys(object); + for (let i = 0; i < keys.length; i++) { + if (searchFunc(object[keys[i]])) { + matches.push(keys[i]); + } + } + return matches; + } + getKeyFromRef(ref) { + return ref.split('/components/schemas/')[1]; + } +} +exports.SchemaPreprocessor = SchemaPreprocessor; +//# sourceMappingURL=schema.preprocessor.js.map \ No newline at end of file diff --git a/dist/middlewares/parsers/schema.preprocessor.js.map b/dist/middlewares/parsers/schema.preprocessor.js.map new file mode 100644 index 00000000..c470382f --- /dev/null +++ b/dist/middlewares/parsers/schema.preprocessor.js.map @@ -0,0 +1 @@ +{"version":3,"file":"schema.preprocessor.js","sourceRoot":"","sources":["../../../src/middlewares/parsers/schema.preprocessor.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/middlewares/parsers/util.d.ts b/dist/middlewares/parsers/util.d.ts new file mode 100644 index 00000000..3c261d1c --- /dev/null +++ b/dist/middlewares/parsers/util.d.ts @@ -0,0 +1,8 @@ +import { Ajv } from 'ajv'; +import { OpenAPIV3 } from '../../framework/types'; +export declare function dereferenceParameter(apiDocs: OpenAPIV3.Document, parameter: OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject): OpenAPIV3.ParameterObject; +export declare function normalizeParameter(ajv: Ajv, parameter: OpenAPIV3.ParameterObject): { + name: string; + schema: OpenAPIV3.SchemaObject; +}; +export declare function dereferenceSchema(ajv: Ajv, ref: string): any; diff --git a/dist/middlewares/parsers/util.js b/dist/middlewares/parsers/util.js new file mode 100644 index 00000000..40f9451f --- /dev/null +++ b/dist/middlewares/parsers/util.js @@ -0,0 +1,86 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.dereferenceSchema = exports.normalizeParameter = exports.dereferenceParameter = void 0; +function dereferenceParameter(apiDocs, parameter) { + // TODO this should recurse or use ajv.getSchema - if implemented as such, may want to cache the result + // as it is called by query.paraer and req.parameter mutator + if (is$Ref(parameter)) { + const p = parameter; + const id = p.$ref.replace(/^.+\//i, ''); + return apiDocs.components.parameters[id]; + } + else { + return parameter; + } +} +exports.dereferenceParameter = dereferenceParameter; +function normalizeParameter(ajv, parameter) { + var _a, _b, _c; + let schema; + if (is$Ref(parameter)) { + schema = dereferenceSchema(ajv, parameter['$ref']); + } + else if ((_a = parameter === null || parameter === void 0 ? void 0 : parameter.schema) === null || _a === void 0 ? void 0 : _a['$ref']) { + schema = dereferenceSchema(ajv, parameter.schema['$ref']); + } + else { + schema = parameter.schema; + } + if (!schema && parameter.content) { + const contentType = Object.keys(parameter.content)[0]; + schema = (_c = (_b = parameter.content) === null || _b === void 0 ? void 0 : _b[contentType]) === null || _c === void 0 ? void 0 : _c.schema; + } + if (!schema) { + schema = parameter; + } + applyParameterStyle(parameter); + applyParameterExplode(parameter); + const name = parameter.in === 'header' ? parameter.name.toLowerCase() : parameter.name; + return { name, schema }; +} +exports.normalizeParameter = normalizeParameter; +function applyParameterStyle(param) { + if (!param.style) { + if (param.in === 'path') { + param.style = 'simple'; + } + else if (param.in === 'query') { + param.style = 'form'; + } + else if (param.style === 'header') { + param.style = 'simple'; + } + else if (param.style === 'cookie') { + param.style = 'form'; + } + } +} +function applyParameterExplode(param) { + if (param.explode == null) { + if (param.in === 'path') { + param.explode = false; + } + else if (param.in === 'query') { + param.explode = true; + } + else if (param.style === 'header') { + param.explode = false; + } + else if (param.style === 'cookie') { + param.explode = true; + } + } +} +function dereferenceSchema(ajv, ref) { + // TODO cache schemas - so that we don't recurse every time + const derefSchema = ajv.getSchema(ref); + if (derefSchema === null || derefSchema === void 0 ? void 0 : derefSchema['$ref']) { + return dereferenceSchema(ajv, ''); + } + return derefSchema === null || derefSchema === void 0 ? void 0 : derefSchema.schema; +} +exports.dereferenceSchema = dereferenceSchema; +function is$Ref(parameter) { + return parameter.hasOwnProperty('$ref'); +} +//# sourceMappingURL=util.js.map \ No newline at end of file diff --git a/dist/middlewares/parsers/util.js.map b/dist/middlewares/parsers/util.js.map new file mode 100644 index 00000000..faab9ee0 --- /dev/null +++ b/dist/middlewares/parsers/util.js.map @@ -0,0 +1 @@ +{"version":3,"file":"util.js","sourceRoot":"","sources":["../../../src/middlewares/parsers/util.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/middlewares/util.d.ts b/dist/middlewares/util.d.ts new file mode 100644 index 00000000..58146138 --- /dev/null +++ b/dist/middlewares/util.d.ts @@ -0,0 +1,32 @@ +import * as Ajv from 'ajv'; +import { Request } from 'express'; +import { ValidationError } from '../framework/types'; +export declare class ContentType { + readonly contentType: string; + readonly mediaType: string; + readonly charSet: string; + readonly withoutBoundary: string; + readonly isWildCard: boolean; + private constructor(); + static from(req: Request): ContentType; + static fromString(type: string): ContentType; + equivalents(): string[]; +} +/** + * (side-effecting) modifies the errors object + * TODO - do this some other way + * @param errors + */ +export declare function augmentAjvErrors(errors?: Ajv.ErrorObject[]): Ajv.ErrorObject[]; +export declare function ajvErrorsToValidatorError(status: number, errors: Ajv.ErrorObject[]): ValidationError; +export declare const deprecationWarning: { + (...data: any[]): void; + (message?: any, ...optionalParams: any[]): void; +}; +/** + * + * @param accepts the list of accepted media types + * @param expectedTypes - expected media types defined in the response schema + * @returns the content-type + */ +export declare const findResponseContent: (accepts: string[], expectedTypes: string[]) => string; diff --git a/dist/middlewares/util.js b/dist/middlewares/util.js new file mode 100644 index 00000000..296c5764 --- /dev/null +++ b/dist/middlewares/util.js @@ -0,0 +1,117 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.findResponseContent = exports.deprecationWarning = exports.ajvErrorsToValidatorError = exports.augmentAjvErrors = exports.ContentType = void 0; +class ContentType { + constructor(contentType) { + var _a; + this.contentType = null; + this.mediaType = null; + this.charSet = null; + this.withoutBoundary = null; + this.contentType = contentType; + if (contentType) { + this.withoutBoundary = contentType.replace(/;\s{0,}boundary.*/, '').toLowerCase(); + this.mediaType = this.withoutBoundary.split(';')[0].toLowerCase().trim(); + this.charSet = (_a = this.withoutBoundary.split(';')[1]) === null || _a === void 0 ? void 0 : _a.toLowerCase(); + this.isWildCard = RegExp(/^[a-z]+\/\*$/).test(this.contentType); + if (this.charSet) { + this.charSet = this.charSet.toLowerCase().trim(); + } + } + } + static from(req) { + return new ContentType(req.headers['content-type']); + } + static fromString(type) { + return new ContentType(type); + } + equivalents() { + if (!this.withoutBoundary) + return []; + if (this.charSet) { + return [this.mediaType, `${this.mediaType}; ${this.charSet}`]; + } + return [this.withoutBoundary, `${this.mediaType}; charset=utf-8`]; + } +} +exports.ContentType = ContentType; +/** + * (side-effecting) modifies the errors object + * TODO - do this some other way + * @param errors + */ +function augmentAjvErrors(errors = []) { + errors.forEach((e) => { + if (e.keyword === 'enum') { + const params = e.params; + const allowedEnumValues = params === null || params === void 0 ? void 0 : params.allowedValues; + e.message = !!allowedEnumValues + ? `${e.message}: ${allowedEnumValues.join(', ')}` + : e.message; + } + }); + return errors; +} +exports.augmentAjvErrors = augmentAjvErrors; +function ajvErrorsToValidatorError(status, errors) { + return { + status, + errors: errors.map((e) => { + var _a, _b; + const params = e.params; + const required = (params === null || params === void 0 ? void 0 : params.missingProperty) && e.dataPath + '.' + params.missingProperty; + const additionalProperty = (params === null || params === void 0 ? void 0 : params.additionalProperty) && + e.dataPath + '.' + params.additionalProperty; + const path = (_b = (_a = required !== null && required !== void 0 ? required : additionalProperty) !== null && _a !== void 0 ? _a : e.dataPath) !== null && _b !== void 0 ? _b : e.schemaPath; + return { + path, + message: e.message, + errorCode: `${e.keyword}.openapi.validation`, + }; + }), + }; +} +exports.ajvErrorsToValidatorError = ajvErrorsToValidatorError; +exports.deprecationWarning = process.env.NODE_ENV !== 'production' ? console.warn : () => { }; +/** + * + * @param accepts the list of accepted media types + * @param expectedTypes - expected media types defined in the response schema + * @returns the content-type + */ +const findResponseContent = function (accepts, expectedTypes) { + const expectedTypesSet = new Set(expectedTypes); + // if accepts are supplied, try to find a match, and use its validator + for (const accept of accepts) { + const act = ContentType.fromString(accept); + if (act.contentType === '*/*') { + return expectedTypes[0]; + } + else if (expectedTypesSet.has(act.contentType)) { + return act.contentType; + } + else if (expectedTypesSet.has(act.mediaType)) { + return act.mediaType; + } + else if (act.isWildCard) { + // wildcard of type application/* + const [type] = act.contentType.split('/', 1); + for (const expectedType of expectedTypesSet) { + if (new RegExp(`^${type}\/.+$`).test(expectedType)) { + return expectedType; + } + } + } + else { + for (const expectedType of expectedTypes) { + const ect = ContentType.fromString(expectedType); + if (ect.mediaType === act.mediaType) { + return expectedType; + } + } + } + } + return null; +}; +exports.findResponseContent = findResponseContent; +//# sourceMappingURL=util.js.map \ No newline at end of file diff --git a/dist/middlewares/util.js.map b/dist/middlewares/util.js.map new file mode 100644 index 00000000..e1d4050e --- /dev/null +++ b/dist/middlewares/util.js.map @@ -0,0 +1 @@ +{"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/middlewares/util.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/openapi.validator.d.ts b/dist/openapi.validator.d.ts new file mode 100644 index 00000000..5cb631f5 --- /dev/null +++ b/dist/openapi.validator.d.ts @@ -0,0 +1,22 @@ +import { Application, Router } from 'express'; +import { OpenApiContext } from './framework/openapi.context'; +import { Spec } from './framework/openapi.spec.loader'; +import { OpenApiValidatorOpts, OpenApiRequestHandler } from './framework/types'; +import { AjvOptions } from './framework/ajv/options'; +export { OpenApiValidatorOpts, InternalServerError, UnsupportedMediaType, RequestEntityTooLarge, BadRequest, MethodNotAllowed, NotAcceptable, NotFound, Unauthorized, Forbidden, } from './framework/types'; +export declare class OpenApiValidator { + readonly options: OpenApiValidatorOpts; + readonly ajvOpts: AjvOptions; + constructor(options: OpenApiValidatorOpts); + installMiddleware(spec: Promise): OpenApiRequestHandler[]; + installPathParams(app: Application | Router, context: OpenApiContext): void; + private metadataMiddleware; + private multipartMiddleware; + private securityMiddleware; + private requestValidationMiddleware; + private responseValidationMiddleware; + installOperationHandlers(baseUrl: string, context: OpenApiContext): Router; + private validateOptions; + private normalizeOptions; + private isOperationHandlerOptions; +} diff --git a/dist/openapi.validator.js b/dist/openapi.validator.js new file mode 100644 index 00000000..f530e636 --- /dev/null +++ b/dist/openapi.validator.js @@ -0,0 +1,321 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.OpenApiValidator = exports.Forbidden = exports.Unauthorized = exports.NotFound = exports.NotAcceptable = exports.MethodNotAllowed = exports.BadRequest = exports.RequestEntityTooLarge = exports.UnsupportedMediaType = exports.InternalServerError = void 0; +const ono_1 = require("ono"); +const express = require("express"); +const _uniq = require("lodash.uniq"); +const middlewares = require("./middlewares"); +const openapi_context_1 = require("./framework/openapi.context"); +const resolvers_1 = require("./resolvers"); +const base_serdes_1 = require("./framework/base.serdes"); +const schema_preprocessor_1 = require("./middlewares/parsers/schema.preprocessor"); +const options_1 = require("./framework/ajv/options"); +var types_1 = require("./framework/types"); +Object.defineProperty(exports, "InternalServerError", { enumerable: true, get: function () { return types_1.InternalServerError; } }); +Object.defineProperty(exports, "UnsupportedMediaType", { enumerable: true, get: function () { return types_1.UnsupportedMediaType; } }); +Object.defineProperty(exports, "RequestEntityTooLarge", { enumerable: true, get: function () { return types_1.RequestEntityTooLarge; } }); +Object.defineProperty(exports, "BadRequest", { enumerable: true, get: function () { return types_1.BadRequest; } }); +Object.defineProperty(exports, "MethodNotAllowed", { enumerable: true, get: function () { return types_1.MethodNotAllowed; } }); +Object.defineProperty(exports, "NotAcceptable", { enumerable: true, get: function () { return types_1.NotAcceptable; } }); +Object.defineProperty(exports, "NotFound", { enumerable: true, get: function () { return types_1.NotFound; } }); +Object.defineProperty(exports, "Unauthorized", { enumerable: true, get: function () { return types_1.Unauthorized; } }); +Object.defineProperty(exports, "Forbidden", { enumerable: true, get: function () { return types_1.Forbidden; } }); +class OpenApiValidator { + constructor(options) { + this.validateOptions(options); + this.normalizeOptions(options); + if (options.validateApiSpec == null) + options.validateApiSpec = true; + if (options.validateRequests == null) + options.validateRequests = true; + if (options.validateResponses == null) + options.validateResponses = false; + if (options.validateSecurity == null) + options.validateSecurity = true; + if (options.fileUploader == null) + options.fileUploader = {}; + if (options.$refParser == null) + options.$refParser = { mode: 'bundle' }; + if (options.unknownFormats == null) + options.unknownFormats === true; + if (options.validateFormats == null) + options.validateFormats = 'fast'; + if (options.formats == null) + options.formats = []; + if (typeof options.operationHandlers === 'string') { + /** + * Internally, we want to convert this to a value typed OperationHandlerOptions. + * In this way, we can treat the value as such when we go to install (rather than + * re-interpreting it over and over). + */ + options.operationHandlers = { + basePath: options.operationHandlers, + resolver: resolvers_1.defaultResolver, + }; + } + else if (typeof options.operationHandlers !== 'object') { + // This covers cases where operationHandlers is null, undefined or false. + options.operationHandlers = false; + } + if (options.validateResponses === true) { + options.validateResponses = { + removeAdditional: false, + coerceTypes: false, + onError: null, + }; + } + if (options.validateRequests === true) { + options.validateRequests = { + allowUnknownQueryParameters: false, + coerceTypes: false, + }; + } + if (options.validateSecurity === true) { + options.validateSecurity = {}; + } + this.options = options; + this.ajvOpts = new options_1.AjvOptions(options); + } + installMiddleware(spec) { + const middlewares = []; + const pContext = spec + .then((spec) => { + const apiDoc = spec.apiDoc; + const ajvOpts = this.ajvOpts.preprocessor; + const resOpts = this.options.validateResponses; + const sp = new schema_preprocessor_1.SchemaPreprocessor(apiDoc, ajvOpts, resOpts).preProcess(); + return { + context: new openapi_context_1.OpenApiContext(spec, this.options.ignorePaths, this.options.ignoreUndocumented), + responseApiDoc: sp.apiDocRes, + error: null, + }; + }) + .catch((e) => { + return { + context: null, + responseApiDoc: null, + error: e, + }; + }); + const self = this; // using named functions instead of anonymous functions to allow traces to be more useful + let inited = false; + // install path params + middlewares.push(function pathParamsMiddleware(req, res, next) { + return pContext + .then(({ context, error }) => { + // Throw if any error occurred during spec load. + if (error) + throw error; + if (!inited) { + // Would be nice to pass the current Router object here if the route + // is attach to a Router and not the app. + // Doing so would enable path params to be type coerced when provided to + // the final middleware. + // Unfortunately, it is not possible to get the current Router from a handler function + self.installPathParams(req.app, context); + inited = true; + } + next(); + }) + .catch(next); + }); + // metadata middleware + let metamw; + middlewares.push(function metadataMiddleware(req, res, next) { + return pContext + .then(({ context, responseApiDoc }) => { + metamw = metamw || self.metadataMiddleware(context, responseApiDoc); + return metamw(req, res, next); + }) + .catch(next); + }); + if (this.options.fileUploader) { + // multipart middleware + let fumw; + middlewares.push(function multipartMiddleware(req, res, next) { + return pContext + .then(({ context: { apiDoc } }) => { + fumw = fumw || self.multipartMiddleware(apiDoc); + return fumw(req, res, next); + }) + .catch(next); + }); + } + // security middlware + let scmw; + middlewares.push(function securityMiddleware(req, res, next) { + return pContext + .then(({ context: { apiDoc } }) => { + const components = apiDoc.components; + if (self.options.validateSecurity && (components === null || components === void 0 ? void 0 : components.securitySchemes)) { + scmw = scmw || self.securityMiddleware(apiDoc); + return scmw(req, res, next); + } + else { + next(); + } + }) + .catch(next); + }); + // request middlweare + if (this.options.validateRequests) { + let reqmw; + middlewares.push(function requestMiddleware(req, res, next) { + return pContext + .then(({ context: { apiDoc } }) => { + reqmw = reqmw || self.requestValidationMiddleware(apiDoc); + return reqmw(req, res, next); + }) + .catch(next); + }); + } + // response middleware + if (this.options.validateResponses) { + let resmw; + middlewares.push(function responseMiddleware(req, res, next) { + return pContext + .then(({ responseApiDoc }) => { + resmw = resmw || self.responseValidationMiddleware(responseApiDoc); + return resmw(req, res, next); + }) + .catch(next); + }); + } + // op handler middleware + if (this.options.operationHandlers) { + let router = null; + middlewares.push(function operationHandlersMiddleware(req, res, next) { + if (router) + return router(req, res, next); + return pContext + .then(({ context }) => (router = self.installOperationHandlers(req.baseUrl, context))) + .then((router) => router(req, res, next)) + .catch(next); + }); + } + return middlewares; + } + installPathParams(app, context) { + const pathParams = []; + for (const route of context.routes) { + if (route.pathParams.length > 0) { + pathParams.push(...route.pathParams); + } + } + // install param on routes with paths + for (const p of _uniq(pathParams)) { + app.param(p, (req, res, next, value, name) => { + const openapi = req.openapi; + if (openapi === null || openapi === void 0 ? void 0 : openapi.pathParams) { + const { pathParams } = openapi; + // override path params + req.params[name] = pathParams[name] || req.params[name]; + } + next(); + }); + } + } + metadataMiddleware(context, responseApiDoc) { + return middlewares.applyOpenApiMetadata(context, responseApiDoc); + } + multipartMiddleware(apiDoc) { + return middlewares.multipart(apiDoc, { + multerOpts: this.options.fileUploader, + ajvOpts: this.ajvOpts.multipart, + }); + } + securityMiddleware(apiDoc) { + var _a; + const securityHandlers = (_a = (this.options.validateSecurity)) === null || _a === void 0 ? void 0 : _a.handlers; + return middlewares.security(apiDoc, securityHandlers); + } + requestValidationMiddleware(apiDoc) { + const requestValidator = new middlewares.RequestValidator(apiDoc, this.ajvOpts.request); + return (req, res, next) => requestValidator.validate(req, res, next); + } + responseValidationMiddleware(apiDoc) { + return new middlewares.ResponseValidator(apiDoc, this.ajvOpts.response, + // This has already been converted from boolean if required + this.options.validateResponses).validate(); + } + installOperationHandlers(baseUrl, context) { + const router = express.Router({ mergeParams: true }); + this.installPathParams(router, context); + for (const route of context.routes) { + const { method, expressRoute } = route; + /** + * This if-statement is here to "narrow" the type of options.operationHandlers + * to OperationHandlerOptions (down from string | false | OperationHandlerOptions) + * At this point of execution it _should_ be impossible for this to NOT be the correct + * type as we re-assign during construction to verify this. + */ + if (this.isOperationHandlerOptions(this.options.operationHandlers)) { + const { basePath, resolver } = this.options.operationHandlers; + const path = expressRoute.indexOf(baseUrl) === 0 + ? expressRoute.substring(baseUrl.length) + : expressRoute; + router[method.toLowerCase()](path, resolver(basePath, route, context.apiDoc)); + } + } + return router; + } + validateOptions(options) { + if (!options.apiSpec) + throw ono_1.default('apiSpec required.'); + const securityHandlers = options.securityHandlers; + if (securityHandlers != null) { + throw ono_1.default('securityHandlers is not supported. Use validateSecurities.handlers instead.'); + } + if (options.coerceTypes) { + console.warn('coerceTypes is deprecated.'); + } + const multerOpts = options.multerOpts; + if (multerOpts != null) { + throw ono_1.default('multerOpts is not supported. Use fileUploader instead.'); + } + const unknownFormats = options.unknownFormats; + if (typeof unknownFormats === 'boolean') { + if (!unknownFormats) { + throw ono_1.default("unknownFormats must contain an array of unknownFormats, 'ignore' or true"); + } + } + else if (typeof unknownFormats === 'string' && + unknownFormats !== 'ignore' && + !Array.isArray(unknownFormats)) + throw ono_1.default("unknownFormats must contain an array of unknownFormats, 'ignore' or true"); + } + normalizeOptions(options) { + if (!options.serDes) { + options.serDes = base_serdes_1.defaultSerDes; + } + else { + if (!Array.isArray(options.unknownFormats)) { + options.unknownFormats = Array(); + } + options.serDes.forEach((currentSerDes) => { + if (options.unknownFormats.indexOf(currentSerDes.format) === + -1) { + options.unknownFormats.push(currentSerDes.format); + } + }); + base_serdes_1.defaultSerDes.forEach((currentDefaultSerDes) => { + let defaultSerDesOverride = options.serDes.find((currentOptionSerDes) => { + return currentDefaultSerDes.format === currentOptionSerDes.format; + }); + if (!defaultSerDesOverride) { + options.serDes.push(currentDefaultSerDes); + } + }); + } + } + isOperationHandlerOptions(value) { + if (value.resolver) { + return true; + } + else { + return false; + } + } +} +exports.OpenApiValidator = OpenApiValidator; +//# sourceMappingURL=openapi.validator.js.map \ No newline at end of file diff --git a/dist/openapi.validator.js.map b/dist/openapi.validator.js.map new file mode 100644 index 00000000..1aeac024 --- /dev/null +++ b/dist/openapi.validator.js.map @@ -0,0 +1 @@ +{"version":3,"file":"openapi.validator.js","sourceRoot":"","sources":["../src/openapi.validator.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/resolvers.d.ts b/dist/resolvers.d.ts new file mode 100644 index 00000000..2f4dc6f0 --- /dev/null +++ b/dist/resolvers.d.ts @@ -0,0 +1,5 @@ +import { RequestHandler } from 'express'; +import { RouteMetadata } from './framework/openapi.spec.loader'; +import { OpenAPIV3 } from './framework/types'; +export declare function defaultResolver(handlersPath: string, route: RouteMetadata, apiDoc: OpenAPIV3.Document): RequestHandler; +export declare function modulePathResolver(handlersPath: string, route: RouteMetadata, apiDoc: OpenAPIV3.Document): RequestHandler; diff --git a/dist/resolvers.js b/dist/resolvers.js new file mode 100644 index 00000000..832cd76a --- /dev/null +++ b/dist/resolvers.js @@ -0,0 +1,48 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.modulePathResolver = exports.defaultResolver = void 0; +const path = require("path"); +const cache = {}; +function defaultResolver(handlersPath, route, apiDoc) { + const tmpModules = {}; + const { basePath, expressRoute, openApiRoute, method } = route; + const pathKey = openApiRoute.substring(basePath.length); + const schema = apiDoc.paths[pathKey][method.toLowerCase()]; + const oId = schema['x-eov-operation-id'] || schema['operationId']; + const baseName = schema['x-eov-operation-handler']; + const cacheKey = `${expressRoute}-${method}-${oId}-${baseName}`; + if (cache[cacheKey]) + return cache[cacheKey]; + if (oId && !baseName) { + throw Error(`found x-eov-operation-id for route ${method} - ${expressRoute}]. x-eov-operation-handler required.`); + } + if (!oId && baseName) { + throw Error(`found x-eov-operation-handler for route [${method} - ${expressRoute}]. operationId or x-eov-operation-id required.`); + } + if (oId && baseName && typeof handlersPath === 'string') { + const modulePath = path.join(handlersPath, baseName); + if (!tmpModules[modulePath]) { + tmpModules[modulePath] = require(modulePath); + } + const handler = tmpModules[modulePath][oId] || tmpModules[modulePath].default; + if (!handler) { + throw Error(`Could not find 'x-eov-operation-handler' with id ${oId} in module '${modulePath}'. Make sure operation '${oId}' defined in your API spec exists as a handler function (or module has a default export) in '${modulePath}'.`); + } + cache[cacheKey] = handler; + return handler; + } +} +exports.defaultResolver = defaultResolver; +function modulePathResolver(handlersPath, route, apiDoc) { + const pathKey = route.openApiRoute.substring(route.basePath.length); + const schema = apiDoc.paths[pathKey][route.method.toLowerCase()]; + const [controller, method] = schema['operationId'].split('.'); + const modulePath = path.join(handlersPath, controller); + const handler = require(modulePath); + if (handler[method] === undefined) { + throw new Error(`Could not find a [${method}] function in ${modulePath} when trying to route [${route.method} ${route.expressRoute}].`); + } + return handler[method]; +} +exports.modulePathResolver = modulePathResolver; +//# sourceMappingURL=resolvers.js.map \ No newline at end of file diff --git a/dist/resolvers.js.map b/dist/resolvers.js.map new file mode 100644 index 00000000..afd790bc --- /dev/null +++ b/dist/resolvers.js.map @@ -0,0 +1 @@ +{"version":3,"file":"resolvers.js","sourceRoot":"","sources":["../src/resolvers.ts"],"names":[],"mappings":";;;AAAA,6BAA6B;AAK7B,MAAM,KAAK,GAAG,EAAE,CAAC;AACjB,SAAgB,eAAe,CAC7B,YAAoB,EACpB,KAAoB,EACpB,MAA0B;IAE1B,MAAM,UAAU,GAAG,EAAE,CAAC;IACtB,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IAC/D,MAAM,OAAO,GAAG,YAAY,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;IAC3D,MAAM,GAAG,GAAG,MAAM,CAAC,oBAAoB,CAAC,IAAI,MAAM,CAAC,aAAa,CAAC,CAAC;IAClE,MAAM,QAAQ,GAAG,MAAM,CAAC,yBAAyB,CAAC,CAAC;IAEnD,MAAM,QAAQ,GAAG,GAAG,YAAY,IAAI,MAAM,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAC;IAChE,IAAI,KAAK,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC,QAAQ,CAAC,CAAC;IAE5C,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE;QACpB,MAAM,KAAK,CACT,sCAAsC,MAAM,MAAM,YAAY,sCAAsC,CACrG,CAAC;KACH;IACD,IAAI,CAAC,GAAG,IAAI,QAAQ,EAAE;QACpB,MAAM,KAAK,CACT,4CAA4C,MAAM,MAAM,YAAY,gDAAgD,CACrH,CAAC;KACH;IACD,IAAI,GAAG,IAAI,QAAQ,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE;QACvD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;QACrD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE;YAC3B,UAAU,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;SAC9C;QAED,MAAM,OAAO,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC;QAE9E,IAAI,CAAC,OAAO,EAAE;YACZ,MAAM,KAAK,CACT,oDAAoD,GAAG,eAAe,UAAU,2BAA2B,GAAG,gGAAgG,UAAU,IAAI,CAC7N,CAAC;SACH;QAED,KAAK,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC;QAC1B,OAAO,OAAO,CAAC;KAChB;AACH,CAAC;AA1CD,0CA0CC;AAED,SAAgB,kBAAkB,CAChC,YAAoB,EACpB,KAAoB,EACpB,MAA0B;IAE1B,MAAM,OAAO,GAAG,KAAK,CAAC,YAAY,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACpE,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;IACjE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAE9D,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAEpC,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,SAAS,EAAE;QACjC,MAAM,IAAI,KAAK,CACb,qBAAqB,MAAM,iBAAiB,UAAU,0BAA0B,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,YAAY,IAAI,CACvH,CAAC;KACH;IAED,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC;AACzB,CAAC;AAnBD,gDAmBC"} \ No newline at end of file