express-openapi-validator
Advanced tools
Comparing version 0.3.36 to 0.9.1
{ | ||
"singleQuote": true, | ||
"trailingComma": "es5" | ||
"trailingComma": "all" | ||
} |
"use strict"; | ||
exports.__esModule = true; | ||
exports.validationError = function (status, path, message) { return ({ | ||
status: status, | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.validationError = (status, path, message) => ({ | ||
status, | ||
errors: [ | ||
{ | ||
path: path, | ||
message: message | ||
path, | ||
message, | ||
}, | ||
] | ||
}); }; | ||
], | ||
}); | ||
//# sourceMappingURL=index.js.map |
import { OpenAPIV3 } from 'openapi-types'; | ||
interface ServerUrlVariables { | ||
[key: string]: ServerUrlValues; | ||
} | ||
interface ServerUrlValues { | ||
enum: string[]; | ||
default?: string; | ||
} | ||
export default class BasePath { | ||
readonly variables: { | ||
[key: string]: { | ||
enum: string[]; | ||
}; | ||
}; | ||
readonly variables: ServerUrlVariables; | ||
readonly path: string; | ||
private allPaths; | ||
constructor(server: OpenAPIV3.ServerObject); | ||
hasVariables(): boolean; | ||
all(): string[]; | ||
static fromServers(servers: OpenAPIV3.ServerObject[]): BasePath[]; | ||
private findUrlPath; | ||
} | ||
export {}; |
"use strict"; | ||
exports.__esModule = true; | ||
var url_1 = require("url"); | ||
var BasePath = /** @class */ (function () { | ||
function BasePath(server) { | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const pathToRegexp = require("path-to-regexp"); | ||
class BasePath { | ||
constructor(server) { | ||
this.variables = {}; | ||
this.path = ''; | ||
this.allPaths = null; | ||
// break the url into parts | ||
// baseUrl param added to make the parsing of relative paths go well | ||
var serverUrl = new url_1.URL(server.url, 'http://localhost'); | ||
var urlPath = decodeURI(serverUrl.pathname).replace(/\/$/, ''); | ||
let urlPath = this.findUrlPath(server.url); | ||
if (/{\w+}/.test(urlPath)) { | ||
// has variable that we need to check out | ||
urlPath = urlPath.replace(/{(\w+)}/g, function (substring, p1) { return ":" + p1; }); | ||
urlPath = urlPath.replace(/{(\w+)}/g, (substring, p1) => `:${p1}`); | ||
} | ||
this.path = urlPath; | ||
for (var variable in server.variables) { | ||
for (const variable in server.variables) { | ||
if (server.variables.hasOwnProperty(variable)) { | ||
this.variables[variable] = { "enum": server.variables[variable]["enum"] }; | ||
const v = server.variables[variable]; | ||
const enums = v.enum || []; | ||
if (enums.length === 0 && v.default) | ||
enums.push(v.default); | ||
this.variables[variable] = { | ||
enum: enums, | ||
default: v.default, | ||
}; | ||
} | ||
} | ||
} | ||
BasePath.prototype.hasVariables = function () { | ||
hasVariables() { | ||
return Object.keys(this.variables).length > 0; | ||
}; | ||
BasePath.fromServers = function (servers) { | ||
} | ||
all() { | ||
if (!this.hasVariables()) | ||
return [this.path]; | ||
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 = pathToRegexp.compile(this.path); | ||
const paths = new Set(); | ||
for (const combo of allParamCombos) { | ||
paths.add(toPath(combo)); | ||
} | ||
this.allPaths = Array.from(paths); | ||
return this.allPaths; | ||
} | ||
static fromServers(servers) { | ||
if (!servers) { | ||
return [new BasePath({ url: '' })]; | ||
} | ||
return servers.map(function (server) { return new BasePath(server); }); | ||
}; | ||
return BasePath; | ||
}()); | ||
exports["default"] = BasePath; | ||
return servers.map(server => new BasePath(server)); | ||
} | ||
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; | ||
return u.substring(pathIdx); | ||
} | ||
} | ||
exports.default = 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 |
@@ -11,14 +11,4 @@ import BasePath from './base.path'; | ||
readonly name: any; | ||
private customFormats; | ||
private dependencies; | ||
private enableObjectCoercion; | ||
private errorTransformer; | ||
private externalSchemas; | ||
private originalApiDoc; | ||
private operations; | ||
private paths; | ||
private pathsIgnore; | ||
private pathSecurity; | ||
private routesGlob; | ||
private routesIndexFileRegExp; | ||
private securityHandlers; | ||
@@ -25,0 +15,0 @@ private validateApiDoc; |
"use strict"; | ||
exports.__esModule = true; | ||
// import fsRoutes from 'fs-routes'; | ||
// import OpenAPIDefaultSetter from 'openapi-default-setter'; | ||
// import OpenAPIRequestCoercer from 'openapi-request-coercer'; | ||
// import OpenAPIRequestValidator from 'openapi-request-validator'; | ||
// import OpenAPIResponseValidator from 'openapi-response-validator'; | ||
var openapi_schema_validator_1 = require("openapi-schema-validator"); | ||
var openapi_security_handler_1 = require("openapi-security-handler"); | ||
var base_path_1 = require("./base.path"); | ||
exports.BasePath = base_path_1["default"]; | ||
var types_1 = require("./types"); | ||
var util_1 = require("./util"); | ||
var OpenAPIFramework = /** @class */ (function () { | ||
function OpenAPIFramework(args) { | ||
if (args === void 0) { args = {}; } | ||
var _this = this; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const openapi_schema_validator_1 = require("openapi-schema-validator"); | ||
const openapi_security_handler_1 = require("openapi-security-handler"); | ||
const base_path_1 = require("./base.path"); | ||
exports.BasePath = base_path_1.default; | ||
const types_1 = require("./types"); | ||
const util_1 = require("./util"); | ||
class OpenAPIFramework { | ||
constructor(args = {}) { | ||
this.args = args; | ||
this.name = args.name; | ||
this.featureType = args.featureType; | ||
this.loggingPrefix = args.name ? this.name + ": " : ''; | ||
this.loggingPrefix = args.name ? `${this.name}: ` : ''; | ||
this.logger = args.logger ? args.logger : new types_1.ConsoleDebugAdapterLogger(); | ||
@@ -33,21 +26,21 @@ // monkey patch for node v6: | ||
{ name: 'name', required: true }, | ||
{ name: 'pathSecurity', "class": Array, className: 'Array' }, | ||
{ name: 'pathSecurity', class: Array, className: 'Array' }, | ||
{ name: 'securityHandlers', type: 'object' }, | ||
].forEach(function (arg) { | ||
].forEach(arg => { | ||
if (arg.required && !(arg.name in args)) { | ||
throw new Error(_this.loggingPrefix + "args." + arg.name + " is required"); | ||
throw new Error(`${this.loggingPrefix}args.${arg.name} is required`); | ||
} | ||
if (arg.type && arg.name in args && typeof args[arg.name] !== arg.type) { | ||
throw new Error(_this.loggingPrefix + "args." + arg.name + " must be a " + arg.type + " when given"); | ||
throw new Error(`${this.loggingPrefix}args.${arg.name} must be a ${arg.type} when given`); | ||
} | ||
if (arg["class"] && | ||
if (arg.class && | ||
arg.name in args && | ||
!(args[arg.name] instanceof arg["class"])) { | ||
throw new Error(_this.loggingPrefix + "args." + arg.name + " must be an instance of " + arg.className + " when given"); | ||
!(args[arg.name] instanceof arg.class)) { | ||
throw new Error(`${this.loggingPrefix}args.${arg.name} must be an instance of ${arg.className} when given`); | ||
} | ||
}); | ||
this.enableObjectCoercion = !!args.enableObjectCoercion; | ||
// this.enableObjectCoercion = !!args.enableObjectCoercion; | ||
this.originalApiDoc = util_1.handleYaml(util_1.loadSpecFile(args.apiDoc)); | ||
if (!this.originalApiDoc) { | ||
throw new Error("spec could not be read at " + args.apiDoc); | ||
throw new Error(`spec could not be read at ${args.apiDoc}`); | ||
} | ||
@@ -58,4 +51,4 @@ this.apiDoc = util_1.copy(this.originalApiDoc); | ||
: [ | ||
new base_path_1["default"]({ | ||
url: (this.apiDoc.basePath || '').replace(/\/$/, '') | ||
new base_path_1.default({ | ||
url: (this.apiDoc.basePath || '').replace(/\/$/, ''), | ||
}), | ||
@@ -65,52 +58,51 @@ ]; | ||
'validateApiDoc' in args ? !!args.validateApiDoc : true; | ||
this.validator = new openapi_schema_validator_1["default"]({ | ||
this.validator = new openapi_schema_validator_1.default({ | ||
version: this.apiDoc.openapi || | ||
this.apiDoc.swagger, | ||
extensions: this.apiDoc["x-" + this.name + "-schema-extension"] | ||
extensions: this.apiDoc[`x-${this.name}-schema-extension`], | ||
}); | ||
this.customFormats = args.customFormats; | ||
this.dependencies = args.dependencies; | ||
this.errorTransformer = args.errorTransformer; | ||
this.externalSchemas = args.externalSchemas; | ||
this.operations = args.operations; | ||
this.pathsIgnore = args.pathsIgnore; | ||
// this.customFormats = args.customFormats; | ||
// this.dependencies = args.dependencies; | ||
// this.errorTransformer = args.errorTransformer; | ||
// this.externalSchemas = args.externalSchemas; | ||
// this.operations = args.operations; | ||
// this.pathsIgnore = args.pathsIgnore; | ||
this.pathSecurity = Array.isArray(args.pathSecurity) | ||
? args.pathSecurity | ||
: []; | ||
this.routesGlob = args.routesGlob; | ||
this.routesIndexFileRegExp = args.routesIndexFileRegExp; | ||
// this.routesGlob = args.routesGlob; | ||
// this.routesIndexFileRegExp = args.routesIndexFileRegExp; | ||
this.securityHandlers = args.securityHandlers; | ||
this.pathSecurity.forEach(util_1.assertRegExpAndSecurity.bind(null, this)); | ||
if (this.validateApiDoc) { | ||
var apiDocValidation = this.validator.validate(this.apiDoc); | ||
const apiDocValidation = this.validator.validate(this.apiDoc); | ||
if (apiDocValidation.errors.length) { | ||
this.logger.error(this.loggingPrefix + "Validating schema before populating paths"); | ||
this.logger.error(this.loggingPrefix + "validation errors", JSON.stringify(apiDocValidation.errors, null, ' ')); | ||
throw new Error(this.loggingPrefix + "args.apiDoc was invalid. See the output."); | ||
this.logger.error(`${this.loggingPrefix}Validating schema before populating paths`); | ||
this.logger.error(`${this.loggingPrefix}validation errors`, JSON.stringify(apiDocValidation.errors, null, ' ')); | ||
throw new Error(`${this.loggingPrefix}args.apiDoc was invalid. See the output.`); | ||
} | ||
} | ||
} | ||
OpenAPIFramework.prototype.initialize = function (visitor) { | ||
var _this = this; | ||
var securitySchemes = this.apiDoc.openapi | ||
initialize(visitor) { | ||
const securitySchemes = this.apiDoc.openapi | ||
? (this.apiDoc.components || {}).securitySchemes | ||
: this.apiDoc.securityDefinitions; | ||
var apiSecurityMiddleware = this.securityHandlers && this.apiDoc.security && securitySchemes | ||
? new openapi_security_handler_1["default"]({ | ||
const apiSecurityMiddleware = this.securityHandlers && this.apiDoc.security && securitySchemes | ||
? new openapi_security_handler_1.default({ | ||
securityDefinitions: securitySchemes, | ||
securityHandlers: this.securityHandlers, | ||
operationSecurity: this.apiDoc.security, | ||
loggingKey: this.name + "-security" | ||
loggingKey: `${this.name}-security`, | ||
}) | ||
: null; | ||
var getApiDoc = function () { | ||
return util_1.copy(_this.apiDoc); | ||
const getApiDoc = () => { | ||
return util_1.copy(this.apiDoc); | ||
}; | ||
util_1.sortApiDocTags(this.apiDoc); | ||
if (this.validateApiDoc) { | ||
var apiDocValidation = this.validator.validate(this.apiDoc); | ||
const apiDocValidation = this.validator.validate(this.apiDoc); | ||
if (apiDocValidation.errors.length) { | ||
this.logger.error(this.loggingPrefix + "Validating schema after populating paths"); | ||
this.logger.error(this.loggingPrefix + "validation errors", JSON.stringify(apiDocValidation.errors, null, ' ')); | ||
throw new Error(this.loggingPrefix + "args.apiDoc was invalid after populating paths. See the output."); | ||
this.logger.error(`${this.loggingPrefix}Validating schema after populating paths`); | ||
this.logger.error(`${this.loggingPrefix}validation errors`, JSON.stringify(apiDocValidation.errors, null, ' ')); | ||
throw new Error(`${this.loggingPrefix}args.apiDoc was invalid after populating paths. See the output.`); | ||
} | ||
@@ -121,9 +113,8 @@ } | ||
basePaths: this.basePaths, | ||
getApiDoc: getApiDoc | ||
getApiDoc, | ||
}); | ||
} | ||
}; | ||
return OpenAPIFramework; | ||
}()); | ||
exports["default"] = OpenAPIFramework; | ||
} | ||
} | ||
exports.default = OpenAPIFramework; | ||
//# sourceMappingURL=index.js.map |
@@ -0,1 +1,2 @@ | ||
import { Request } from 'express'; | ||
import { SecurityHandlers } from 'openapi-security-handler'; | ||
@@ -6,13 +7,2 @@ import { IJsonSchema, OpenAPIV2, OpenAPIV3 } from 'openapi-types'; | ||
export { OpenAPIFrameworkArgs, OpenAPIFrameworkConstructorArgs, OpenAPIErrorTransformer, }; | ||
export declare class ConsoleDebugAdapterLogger implements Logger { | ||
/** | ||
* `console.debug` is just an alias for `.log()`, and we want debug logging to be optional. | ||
* This class delegates to `console` and overrides `.debug()` to be a no-op. | ||
*/ | ||
debug(message?: any, ...optionalParams: any[]): void; | ||
error(message?: any, ...optionalParams: any[]): void; | ||
info(message?: any, ...optionalParams: any[]): void; | ||
trace(message?: any, ...optionalParams: any[]): void; | ||
warn(message?: any, ...optionalParams: any[]): void; | ||
} | ||
declare type OpenAPIErrorTransformer = ({}: {}, {}: {}) => object; | ||
@@ -75,1 +65,15 @@ declare type PathSecurityTuple = [RegExp, SecurityRequirement[]]; | ||
} | ||
export interface OpenApiRequest extends Request { | ||
openapi: any; | ||
} | ||
export declare class ConsoleDebugAdapterLogger implements Logger { | ||
/** | ||
* `console.debug` is just an alias for `.log()`, and we want debug logging to be optional. | ||
* This class delegates to `console` and overrides `.debug()` to be a no-op. | ||
*/ | ||
debug(message?: any, ...optionalParams: any[]): void; | ||
error(message?: any, ...optionalParams: any[]): void; | ||
info(message?: any, ...optionalParams: any[]): void; | ||
trace(message?: any, ...optionalParams: any[]): void; | ||
warn(message?: any, ...optionalParams: any[]): void; | ||
} |
"use strict"; | ||
exports.__esModule = true; | ||
var ConsoleDebugAdapterLogger = /** @class */ (function () { | ||
function ConsoleDebugAdapterLogger() { | ||
} | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/* istanbul ignore next */ | ||
class ConsoleDebugAdapterLogger { | ||
/** | ||
@@ -10,40 +9,19 @@ * `console.debug` is just an alias for `.log()`, and we want debug logging to be optional. | ||
*/ | ||
ConsoleDebugAdapterLogger.prototype.debug = function (message) { | ||
var optionalParams = []; | ||
for (var _i = 1; _i < arguments.length; _i++) { | ||
optionalParams[_i - 1] = arguments[_i]; | ||
} | ||
debug(message, ...optionalParams) { | ||
// no-op | ||
}; | ||
ConsoleDebugAdapterLogger.prototype.error = function (message) { | ||
var optionalParams = []; | ||
for (var _i = 1; _i < arguments.length; _i++) { | ||
optionalParams[_i - 1] = arguments[_i]; | ||
} | ||
console.error.apply(console, [message].concat(optionalParams)); | ||
}; | ||
ConsoleDebugAdapterLogger.prototype.info = function (message) { | ||
var optionalParams = []; | ||
for (var _i = 1; _i < arguments.length; _i++) { | ||
optionalParams[_i - 1] = arguments[_i]; | ||
} | ||
console.info.apply(console, [message].concat(optionalParams)); | ||
}; | ||
ConsoleDebugAdapterLogger.prototype.trace = function (message) { | ||
var optionalParams = []; | ||
for (var _i = 1; _i < arguments.length; _i++) { | ||
optionalParams[_i - 1] = arguments[_i]; | ||
} | ||
console.trace.apply(console, [message].concat(optionalParams)); | ||
}; | ||
ConsoleDebugAdapterLogger.prototype.warn = function (message) { | ||
var optionalParams = []; | ||
for (var _i = 1; _i < arguments.length; _i++) { | ||
optionalParams[_i - 1] = arguments[_i]; | ||
} | ||
console.warn.apply(console, [message].concat(optionalParams)); | ||
}; | ||
return ConsoleDebugAdapterLogger; | ||
}()); | ||
} | ||
error(message, ...optionalParams) { | ||
console.error(message, ...optionalParams); | ||
} | ||
info(message, ...optionalParams) { | ||
console.info(message, ...optionalParams); | ||
} | ||
trace(message, ...optionalParams) { | ||
console.trace(message, ...optionalParams); | ||
} | ||
warn(message, ...optionalParams) { | ||
console.warn(message, ...optionalParams); | ||
} | ||
} | ||
exports.ConsoleDebugAdapterLogger = ConsoleDebugAdapterLogger; | ||
//# sourceMappingURL=types.js.map |
"use strict"; | ||
exports.__esModule = true; | ||
var base_path_1 = require("./base.path"); | ||
var fs = require("fs"); | ||
var jsYaml = require("js-yaml"); | ||
var path = require("path"); | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const base_path_1 = require("./base.path"); | ||
const fs = require("fs"); | ||
const jsYaml = require("js-yaml"); | ||
const path = require("path"); | ||
function assertRegExpAndSecurity(framework, tuple) { | ||
if (!Array.isArray(tuple)) { | ||
throw new Error(framework.name + "args.pathSecurity expects an array of tuples."); | ||
throw new Error(`${framework.name}args.pathSecurity expects an array of tuples.`); | ||
} | ||
else if (!(tuple[0] instanceof RegExp)) { | ||
throw new Error(framework.name + "args.pathSecurity tuples expect the first argument to be a RegExp."); | ||
throw new Error(`${framework.name}args.pathSecurity tuples expect the first argument to be a RegExp.`); | ||
} | ||
else if (!Array.isArray(tuple[1])) { | ||
throw new Error(framework.name + "args.pathSecurity tuples expect the second argument to be a security Array."); | ||
throw new Error(`${framework.name}args.pathSecurity tuples expect the second argument to be a security Array.`); | ||
} | ||
@@ -25,3 +25,3 @@ } | ||
if (typeof filePath === 'string') { | ||
var absolutePath = path.resolve(process.cwd(), filePath); | ||
const absolutePath = path.resolve(process.cwd(), filePath); | ||
if (fs.existsSync(absolutePath)) { | ||
@@ -48,3 +48,3 @@ try { | ||
if (apiDoc && Array.isArray(apiDoc.tags)) { | ||
apiDoc.tags.sort(function (a, b) { | ||
apiDoc.tags.sort((a, b) => { | ||
return a.name > b.name; | ||
@@ -57,13 +57,12 @@ }); | ||
if (!servers) { | ||
return [new base_path_1["default"]({ url: '' })]; | ||
return [new base_path_1.default({ url: '' })]; | ||
} | ||
var basePathsMap = {}; | ||
for (var _i = 0, servers_1 = servers; _i < servers_1.length; _i++) { | ||
var server = servers_1[_i]; | ||
var basePath = new base_path_1["default"](server); | ||
const basePathsMap = {}; | ||
for (const server of servers) { | ||
const basePath = new base_path_1.default(server); | ||
basePathsMap[basePath.path] = basePath; | ||
} | ||
return Object.keys(basePathsMap).map(function (key) { return basePathsMap[key]; }); | ||
return Object.keys(basePathsMap).map(key => basePathsMap[key]); | ||
} | ||
exports.getBasePathsFromServers = getBasePathsFromServers; | ||
//# sourceMappingURL=util.js.map |
"use strict"; | ||
exports.__esModule = true; | ||
var _ = require("lodash"); | ||
var openapi_context_1 = require("./openapi.context"); | ||
var middlewares = require("./middlewares"); | ||
var ono_1 = require("ono"); | ||
var loggingKey = 'express-openapi-validator'; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const _ = require("lodash"); | ||
const openapi_context_1 = require("./openapi.context"); | ||
const middlewares = require("./middlewares"); | ||
const ono_1 = require("ono"); | ||
const loggingKey = 'express-openapi-validator'; | ||
function OpenApiValidator(options) { | ||
if (!options.apiSpecPath) | ||
throw ono_1["default"]('apiSpecPath required'); | ||
var openApiContext = new openapi_context_1.OpenApiContext({ apiDoc: options.apiSpecPath }); | ||
var opts = { | ||
throw ono_1.default('apiSpecPath required'); | ||
const openApiContext = new openapi_context_1.OpenApiContext({ apiDoc: options.apiSpecPath }); | ||
const opts = { | ||
enableObjectCoercion: true, | ||
apiDoc: openApiContext.apiDoc | ||
apiDoc: openApiContext.apiDoc, | ||
}; | ||
@@ -21,13 +21,11 @@ this.opts = opts; | ||
OpenApiValidator.prototype.install = function (app) { | ||
var pathParams = []; | ||
for (var _i = 0, _a = this.context.routes; _i < _a.length; _i++) { | ||
var route = _a[_i]; | ||
const pathParams = []; | ||
for (const route of this.context.routes) { | ||
if (route.pathParams.length > 0) { | ||
pathParams.push.apply(pathParams, route.pathParams); | ||
pathParams.push(...route.pathParams); | ||
} | ||
} | ||
// install param on routes with paths | ||
for (var _b = 0, _c = _.uniq(pathParams); _b < _c.length; _b++) { | ||
var p = _c[_b]; | ||
app.param(p, function (req, res, next, value, name) { | ||
for (const p of _.uniq(pathParams)) { | ||
app.param(p, (req, res, next, value, name) => { | ||
if (req.openapi.pathParams) { | ||
@@ -42,6 +40,6 @@ // override path params | ||
apiDoc: this.context.apiDoc, | ||
loggingKey: loggingKey, | ||
enableObjectCoercion: this.opts.enableObjectCoercion | ||
loggingKey, | ||
enableObjectCoercion: this.opts.enableObjectCoercion, | ||
})); | ||
}; | ||
//# sourceMappingURL=index.js.map |
"use strict"; | ||
exports.__esModule = true; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var openapi_metadata_1 = require("./openapi.metadata"); | ||
@@ -4,0 +4,0 @@ exports.applyOpenApiMetadata = openapi_metadata_1.applyOpenApiMetadata; |
"use strict"; | ||
exports.__esModule = true; | ||
var pathToRegexp = require("path-to-regexp"); | ||
var _ = require("lodash"); | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const pathToRegexp = require("path-to-regexp"); | ||
const _ = require("lodash"); | ||
function applyOpenApiMetadata(openApiContext) { | ||
return function (req, res, next) { | ||
var matched = lookupRoute(req); | ||
return (req, res, next) => { | ||
const matched = lookupRoute(req); | ||
if (matched) { | ||
req.openapi = {}; | ||
var expressRoute = matched.expressRoute, openApiRoute = matched.openApiRoute, pathParams = matched.pathParams, schema = matched.schema; | ||
const { expressRoute, openApiRoute, pathParams, schema } = matched; | ||
req.openapi.expressRoute = expressRoute; | ||
@@ -17,12 +17,4 @@ req.openapi.openApiRoute = openApiRoute; | ||
} | ||
else { | ||
// add openapi object if the route was not matched | ||
// but is contained beneath a base path | ||
for (var _i = 0, _a = openApiContext.basePaths; _i < _a.length; _i++) { | ||
var bp = _a[_i]; | ||
if (req.path.startsWith(bp.path + '/')) { | ||
req.openapi = {}; | ||
break; | ||
} | ||
} | ||
else if (openApiContext.isManagedRoute(req.path)) { | ||
req.openapi = {}; | ||
} | ||
@@ -32,24 +24,23 @@ next(); | ||
function lookupRoute(req) { | ||
var path = req.path; | ||
var method = req.method; | ||
var routeEntries = Object.entries(openApiContext.expressRouteMap); | ||
for (var _i = 0, routeEntries_1 = routeEntries; _i < routeEntries_1.length; _i++) { | ||
var _a = routeEntries_1[_i], expressRoute = _a[0], methods = _a[1]; | ||
var schema = methods[method]; | ||
var routePair = openApiContext.routePair(expressRoute); | ||
var openApiRoute = routePair.openApiRoute; | ||
var keys = []; | ||
var regexp = pathToRegexp(expressRoute, keys); | ||
var matchedRoute = regexp.exec(path); | ||
const path = req.path; | ||
const method = req.method; | ||
const routeEntries = Object.entries(openApiContext.expressRouteMap); | ||
for (const [expressRoute, methods] of routeEntries) { | ||
const schema = methods[method]; | ||
const routePair = openApiContext.routePair(expressRoute); | ||
const openApiRoute = routePair.openApiRoute; | ||
const keys = []; | ||
const regexp = pathToRegexp(expressRoute, keys); | ||
const matchedRoute = regexp.exec(path); | ||
if (matchedRoute) { | ||
var paramKeys = keys.map(function (k) { return k.name; }); | ||
var paramsVals = matchedRoute.slice(1); | ||
var pathParams = _.zipObject(paramKeys, paramsVals); | ||
const paramKeys = keys.map(k => k.name); | ||
const paramsVals = matchedRoute.slice(1); | ||
const pathParams = _.zipObject(paramKeys, paramsVals); | ||
return { | ||
schema: schema, | ||
schema, | ||
// schema may or may not contain express and openApi routes, | ||
// thus we include them here | ||
expressRoute: expressRoute, | ||
openApiRoute: openApiRoute, | ||
pathParams: pathParams | ||
expressRoute, | ||
openApiRoute, | ||
pathParams, | ||
}; | ||
@@ -56,0 +47,0 @@ } |
"use strict"; | ||
exports.__esModule = true; | ||
var openapi_request_validator_1 = require("openapi-request-validator"); | ||
var openapi_request_coercer_1 = require("openapi-request-coercer"); | ||
var errors_1 = require("../errors"); | ||
var ono_1 = require("ono"); | ||
function validateRequest(_a) { | ||
var apiDoc = _a.apiDoc, loggingKey = _a.loggingKey, enableObjectCoercion = _a.enableObjectCoercion; | ||
return function (req, res, next) { | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const openapi_request_validator_1 = require("openapi-request-validator"); | ||
const openapi_request_coercer_1 = require("openapi-request-coercer"); | ||
const errors_1 = require("../errors"); | ||
const ono_1 = require("ono"); | ||
function validateRequest({ apiDoc, loggingKey, enableObjectCoercion }) { | ||
return (req, res, next) => { | ||
if (!req.openapi) { | ||
@@ -16,15 +15,15 @@ // this path was not found in open api and | ||
} | ||
var path = req.openapi.expressRoute; | ||
const path = req.openapi.expressRoute; | ||
if (!path) { | ||
var message = 'not found'; | ||
var err = errors_1.validationError(404, req.path, message); | ||
throw ono_1["default"](err, message); | ||
const message = 'not found'; | ||
const err = errors_1.validationError(404, req.path, message); | ||
throw ono_1.default(err, message); | ||
} | ||
var schema = req.openapi.schema; | ||
const schema = req.openapi.schema; | ||
if (!schema) { | ||
// add openapi metadata to make this case more clear | ||
// its not obvious that missig schema means methodNotAllowed | ||
var message = req.method + " method not allowed"; | ||
var err = errors_1.validationError(405, req.path, message); | ||
throw ono_1["default"](err, message); | ||
const message = `${req.method} method not allowed`; | ||
const err = errors_1.validationError(405, req.path, message); | ||
throw ono_1.default(err, message); | ||
} | ||
@@ -34,3 +33,3 @@ if (!schema.parameters) { | ||
} | ||
var shouldUpdatePathParams = Object.keys(req.openapi.pathParams).length > 0; | ||
const shouldUpdatePathParams = Object.keys(req.openapi.pathParams).length > 0; | ||
if (shouldUpdatePathParams) { | ||
@@ -42,9 +41,9 @@ req.params = req.openapi.pathParams || req.params; | ||
// this modifies the request object with coerced types | ||
new openapi_request_coercer_1["default"]({ | ||
loggingKey: loggingKey, | ||
enableObjectCoercion: enableObjectCoercion, | ||
parameters: schema.parameters | ||
new openapi_request_coercer_1.default({ | ||
loggingKey, | ||
enableObjectCoercion, | ||
parameters: schema.parameters, | ||
}).coerce(req); | ||
} | ||
var validationResult = new openapi_request_validator_1["default"]({ | ||
const validationResult = new openapi_request_validator_1.default({ | ||
// TODO create custom error transformere here as there are a lot of props we can utilize | ||
@@ -57,7 +56,7 @@ // errorTransformer, | ||
? apiDoc.components.schemas | ||
: undefined | ||
: undefined, | ||
}).validate(req); | ||
if (validationResult && validationResult.errors.length > 0) { | ||
var message = validationResult.errors[0].message; | ||
throw ono_1["default"](validationResult, message); | ||
const message = validationResult.errors[0].message; | ||
throw ono_1.default(validationResult, message); | ||
} | ||
@@ -64,0 +63,0 @@ next(); |
@@ -1,2 +0,2 @@ | ||
import { OpenAPIFrameworkArgs, BasePath } from './framework'; | ||
import { OpenAPIFrameworkArgs } from './framework'; | ||
export declare class OpenApiContext { | ||
@@ -7,5 +7,6 @@ expressRouteMap: {}; | ||
apiDoc: any; | ||
basePaths: BasePath[]; | ||
private basePaths; | ||
constructor(opts: OpenAPIFrameworkArgs); | ||
private initializeRoutes; | ||
isManagedRoute(path: any): boolean; | ||
routePair(route: any): { | ||
@@ -12,0 +13,0 @@ expressRoute: any; |
"use strict"; | ||
var __assign = (this && this.__assign) || function () { | ||
__assign = Object.assign || function(t) { | ||
for (var s, i = 1, n = arguments.length; i < n; i++) { | ||
s = arguments[i]; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) | ||
t[p] = s[p]; | ||
} | ||
return t; | ||
}; | ||
return __assign.apply(this, arguments); | ||
}; | ||
exports.__esModule = true; | ||
var openapi_spec_loader_1 = require("./openapi.spec.loader"); | ||
var OpenApiContext = /** @class */ (function () { | ||
function OpenApiContext(opts) { | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const openapi_spec_loader_1 = require("./openapi.spec.loader"); | ||
class OpenApiContext { | ||
constructor(opts) { | ||
// TODO cleanup structure (group related functionality) | ||
@@ -21,4 +10,4 @@ this.expressRouteMap = {}; | ||
this.routes = []; | ||
var openApiRouteDiscovery = new openapi_spec_loader_1.OpenApiSpecLoader(opts); | ||
var _a = openApiRouteDiscovery.load(), apiDoc = _a.apiDoc, basePaths = _a.basePaths, routes = _a.routes; | ||
const openApiRouteDiscovery = new openapi_spec_loader_1.OpenApiSpecLoader(opts); | ||
const { apiDoc, basePaths, routes } = openApiRouteDiscovery.load(); | ||
this.apiDoc = apiDoc; | ||
@@ -28,7 +17,5 @@ this.basePaths = basePaths; | ||
} | ||
OpenApiContext.prototype.initializeRoutes = function (routes) { | ||
var _a; | ||
for (var _i = 0, routes_1 = routes; _i < routes_1.length; _i++) { | ||
var route = routes_1[_i]; | ||
var routeMethods = this.expressRouteMap[route.expressRoute]; | ||
initializeRoutes(routes) { | ||
for (const route of routes) { | ||
const routeMethods = this.expressRouteMap[route.expressRoute]; | ||
if (routeMethods) { | ||
@@ -38,5 +25,5 @@ routeMethods[route.method] = route.schema; | ||
else { | ||
var schema = route.schema, openApiRoute = route.openApiRoute, expressRoute = route.expressRoute; | ||
var routeMethod = (_a = {}, _a[route.method] = schema, _a); | ||
var routeDetails = __assign({ _openApiRoute: openApiRoute, _expressRoute: expressRoute }, routeMethod); | ||
const { schema, openApiRoute, expressRoute } = route; | ||
const routeMethod = { [route.method]: schema }; | ||
const routeDetails = Object.assign({ _openApiRoute: openApiRoute, _expressRoute: expressRoute }, routeMethod); | ||
this.expressRouteMap[route.expressRoute] = routeDetails; | ||
@@ -47,31 +34,37 @@ this.openApiRouteMap[route.openApiRoute] = routeDetails; | ||
return routes; | ||
}; | ||
OpenApiContext.prototype.routePair = function (route) { | ||
var methods = this.methods(route); | ||
} | ||
isManagedRoute(path) { | ||
for (const bp of this.basePaths) { | ||
if (path.startsWith(bp)) | ||
return true; | ||
} | ||
return false; | ||
} | ||
routePair(route) { | ||
const methods = this.methods(route); | ||
if (methods) { | ||
return { | ||
expressRoute: methods._expressRoute, | ||
openApiRoute: methods._openApiRoute | ||
openApiRoute: methods._openApiRoute, | ||
}; | ||
} | ||
return null; | ||
}; | ||
OpenApiContext.prototype.methods = function (route) { | ||
var expressRouteMethods = this.expressRouteMap[route]; | ||
} | ||
methods(route) { | ||
const expressRouteMethods = this.expressRouteMap[route]; | ||
if (expressRouteMethods) | ||
return expressRouteMethods; | ||
var openApiRouteMethods = this.openApiRouteMap[route]; | ||
const openApiRouteMethods = this.openApiRouteMap[route]; | ||
return openApiRouteMethods; | ||
}; | ||
OpenApiContext.prototype.schema = function (route, method) { | ||
var methods = this.methods(route); | ||
} | ||
schema(route, method) { | ||
const methods = this.methods(route); | ||
if (methods) { | ||
var schema = methods[method]; | ||
const schema = methods[method]; | ||
return schema; | ||
} | ||
return null; | ||
}; | ||
return OpenApiContext; | ||
}()); | ||
} | ||
} | ||
exports.OpenApiContext = OpenApiContext; | ||
//# sourceMappingURL=openapi.context.js.map |
@@ -7,3 +7,3 @@ import { OpenAPIFrameworkArgs } from './framework'; | ||
apiDoc: any; | ||
basePaths: import("./framework").BasePath[]; | ||
basePaths: Set<any>; | ||
routes: any[]; | ||
@@ -10,0 +10,0 @@ }; |
"use strict"; | ||
var __assign = (this && this.__assign) || function () { | ||
__assign = Object.assign || function(t) { | ||
for (var s, i = 1, n = arguments.length; i < n; i++) { | ||
s = arguments[i]; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) | ||
t[p] = s[p]; | ||
} | ||
return t; | ||
}; | ||
return __assign.apply(this, arguments); | ||
}; | ||
exports.__esModule = true; | ||
var framework_1 = require("./framework"); | ||
var OpenApiSpecLoader = /** @class */ (function () { | ||
function OpenApiSpecLoader(opts) { | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const framework_1 = require("./framework"); | ||
class OpenApiSpecLoader { | ||
constructor(opts) { | ||
this.opts = opts; | ||
} | ||
OpenApiSpecLoader.prototype.load = function () { | ||
var framework = this.createFramework(this.opts); | ||
var apiDoc = framework.apiDoc || {}; | ||
var basePaths = framework.basePaths || []; | ||
var routes = this.discoverRoutes(framework); | ||
load() { | ||
const framework = this.createFramework(this.opts); | ||
const apiDoc = framework.apiDoc || {}; | ||
const bps = framework.basePaths || []; | ||
const basePaths = bps.reduce((acc, bp) => { | ||
const all = bp.all().forEach(path => acc.add(path)); | ||
return acc; | ||
}, new Set()); | ||
const routes = this.discoverRoutes(framework, basePaths); | ||
return { | ||
apiDoc: apiDoc, | ||
basePaths: basePaths, | ||
routes: routes | ||
apiDoc, | ||
basePaths, | ||
routes, | ||
}; | ||
}; | ||
OpenApiSpecLoader.prototype.createFramework = function (args) { | ||
var frameworkArgs = __assign({ featureType: 'middleware', name: 'express-openapi-validator' }, args); | ||
var framework = new framework_1["default"](frameworkArgs); | ||
} | ||
createFramework(args) { | ||
const frameworkArgs = Object.assign({ featureType: 'middleware', name: 'express-openapi-validator' }, args); | ||
const framework = new framework_1.default(frameworkArgs); | ||
return framework; | ||
}; | ||
OpenApiSpecLoader.prototype.discoverRoutes = function (framework) { | ||
var routes = []; | ||
var toExpressParams = this.toExpressParams; | ||
} | ||
discoverRoutes(framework, basePaths) { | ||
const routes = []; | ||
const toExpressParams = this.toExpressParams; | ||
framework.initialize({ | ||
visitApi: function (ctx) { | ||
var apiDoc = ctx.getApiDoc(); | ||
for (var _i = 0, _a = ctx.basePaths; _i < _a.length; _i++) { | ||
var bp = _a[_i]; | ||
for (var _b = 0, _c = Object.entries(apiDoc.paths); _b < _c.length; _b++) { | ||
var _d = _c[_b], path = _d[0], methods = _d[1]; | ||
for (var _e = 0, _f = Object.entries(methods); _e < _f.length; _e++) { | ||
var _g = _f[_e], method = _g[0], schema = _g[1]; | ||
var pathParams = new Set(); | ||
for (var _h = 0, _j = schema.parameters || []; _h < _j.length; _h++) { | ||
var param = _j[_h]; | ||
if (param["in"] === 'path') { | ||
visitApi(ctx) { | ||
const apiDoc = ctx.getApiDoc(); | ||
for (const bp of basePaths) { | ||
for (const [path, methods] of Object.entries(apiDoc.paths)) { | ||
for (const [method, schema] of Object.entries(methods)) { | ||
const pathParams = new Set(); | ||
for (const param of schema.parameters || []) { | ||
if (param.in === 'path') { | ||
pathParams.add(param.name); | ||
} | ||
} | ||
var openApiRoute = "" + bp.path + path; | ||
var expressRoute = ("" + openApiRoute) | ||
const openApiRoute = `${bp}${path}`; | ||
const expressRoute = `${openApiRoute}` | ||
.split('/') | ||
@@ -60,7 +49,7 @@ .map(toExpressParams) | ||
routes.push({ | ||
expressRoute: expressRoute, | ||
openApiRoute: openApiRoute, | ||
expressRoute, | ||
openApiRoute, | ||
method: method.toUpperCase(), | ||
pathParams: Array.from(pathParams), | ||
schema: schema | ||
schema, | ||
}); | ||
@@ -70,12 +59,11 @@ } | ||
} | ||
} | ||
}, | ||
}); | ||
return routes; | ||
}; | ||
OpenApiSpecLoader.prototype.toExpressParams = function (part) { | ||
} | ||
toExpressParams(part) { | ||
return part.replace(/\{([^}]+)}/g, ':$1'); | ||
}; | ||
return OpenApiSpecLoader; | ||
}()); | ||
} | ||
} | ||
exports.OpenApiSpecLoader = OpenApiSpecLoader; | ||
//# sourceMappingURL=openapi.spec.loader.js.map |
{ | ||
"name": "express-openapi-validator", | ||
"version": "0.3.36", | ||
"version": "0.9.1", | ||
"description": "", | ||
@@ -8,3 +8,4 @@ "main": "dist/index.js", | ||
"compile": "rm -rf dist/ && tsc", | ||
"test": "nyc mocha -r source-map-support/register -r ts-node/register --recursive test/**/*.spec.ts", | ||
"test": "mocha -r source-map-support/register -r ts-node/register --recursive test/**/*.spec.ts", | ||
"test:coverage": "nyc mocha -r source-map-support/register -r ts-node/register --recursive test/**/*.spec.ts", | ||
"coveralls": "cat coverage/lcov.info | coveralls -v", | ||
@@ -24,3 +25,3 @@ "codacy": "cat coverage/lcov.info | codacy-coverage" | ||
"openapi-security-handler": "^2.0.4", | ||
"openapi-types": "1.3.4", | ||
"openapi-types": "^1.3.4", | ||
"path-to-regexp": "^3.0.0", | ||
@@ -30,3 +31,6 @@ "ts-log": "^2.1.4" | ||
"devDependencies": { | ||
"@types/cookie-parser": "^1.4.1", | ||
"@types/express": "^4.16.1", | ||
"@types/mocha": "^5.2.6", | ||
"@types/morgan": "^1.7.35", | ||
"@types/node": "^11.11.3", | ||
@@ -36,2 +40,3 @@ "@types/supertest": "^2.0.7", | ||
"chai": "^4.2.0", | ||
"codacy-coverage": "^3.4.0", | ||
"cookie-parser": "^1.4.4", | ||
@@ -38,0 +43,0 @@ "coveralls": "^3.0.3", |
# express-openapi-validator | ||
![](https://travis-ci.com/cdimascio/express-openapi-validator.svg?branch=master) ![](https://img.shields.io/npm/v/express-openapi-validator.svg) [![Coverage Status](https://coveralls.io/repos/github/cdimascio/express-middleware-openapi/badge.svg?branch=master)](https://coveralls.io/github/cdimascio/express-middleware-openapi?branch=master) ![](https://img.shields.io/badge/license-MIT-blue.svg) | ||
![](https://travis-ci.com/cdimascio/express-openapi-validator.svg?branch=master) ![](https://img.shields.io/npm/v/express-openapi-validator.svg) [![Coverage Status](https://coveralls.io/repos/github/cdimascio/express-openapi-validator/badge.svg)](https://coveralls.io/github/cdimascio/express-openapi-validator) ![](https://img.shields.io/badge/license-MIT-blue.svg) | ||
@@ -11,3 +11,3 @@ An OpenApi validator for ExpressJS that automatically validates API requests using an OpenAPI 3.0 specification. | ||
[express-openapi-validator](https://github.com/cdimascio/express-openapi-validator) is unopinionated and does not impose any coding convention or project structure. Simply, install the validator onto your express app, then define and implement routes the way you prefer. See an [example](#example-express-api-server). | ||
[express-openapi-validator](https://github.com/cdimascio/express-openapi-validator) is unopinionated and does not impose any coding convention or project structure. Simply, install the validator onto your express app, point it to your OpenApi 3 specification, then define and implement routes the way you prefer. See an [example](#example-express-api-server). | ||
@@ -52,5 +52,7 @@ ## Install | ||
var http = require('http'); | ||
var OpenApiValidator = require('express-openapi-validator').OpenApiValidator; | ||
var app = express(); | ||
// 1. Import the express-openapi-validator library | ||
var OpenApiValidator = require('express-openapi-validator').OpenApiValidator; | ||
app.use(bodyParser.json()); | ||
@@ -63,2 +65,7 @@ app.use(logger('dev')); | ||
// 2. (optionally) Serve the OpenAPI spec | ||
const spec = path.join(__dirname, 'openapi.yaml'); | ||
app.use('/spec', express.static(spec)); | ||
// 3. Install the OpenApiValidator onto your express app | ||
new OpenApiValidator({ | ||
@@ -68,2 +75,3 @@ apiSpecPath: './openapi.yaml', | ||
// 4. Define routes using Express | ||
app.get('/v1/pets', function(req, res, next) { | ||
@@ -81,5 +89,5 @@ res.json([{ id: 1, name: 'max' }, { id: 2, name: 'mini' }]); | ||
// Register error handler | ||
// 5. Create an Express error handler | ||
app.use((err, req, res, next) => { | ||
// format error | ||
// 6. Customize errors | ||
res.status(err.status).json({ | ||
@@ -89,8 +97,2 @@ errors: err.errors, | ||
}); | ||
var server = http.createServer(app); | ||
server.listen(3000); | ||
console.log('Listening on port 3000'); | ||
module.exports = app; | ||
``` | ||
@@ -124,3 +126,3 @@ | ||
`/pets?limit=?` should be of type integer with a value greater than 5, express-openapi-validator returns: | ||
`/pets?limit=?` should be of type integer with a value greater than 5. It should also require an additional query paramter, `test`, express-openapi-validator returns: | ||
@@ -127,0 +129,0 @@ ```shell |
export const validationError = ( | ||
status: number, | ||
path: string, | ||
message: string | ||
message: string, | ||
) => ({ | ||
@@ -6,0 +6,0 @@ status, |
@@ -0,7 +1,16 @@ | ||
import * as pathToRegexp from 'path-to-regexp'; | ||
import { OpenAPIV3 } from 'openapi-types'; | ||
import { URL } from 'url'; | ||
interface ServerUrlVariables { | ||
[key: string]: ServerUrlValues; | ||
} | ||
interface ServerUrlValues { | ||
enum: string[]; | ||
default?: string; | ||
} | ||
export default class BasePath { | ||
public readonly variables: { [key: string]: { enum: string[] } } = {}; | ||
public readonly variables: ServerUrlVariables = {}; | ||
public readonly path: string = ''; | ||
private allPaths: string[] = null; | ||
@@ -11,4 +20,3 @@ constructor(server: OpenAPIV3.ServerObject) { | ||
// baseUrl param added to make the parsing of relative paths go well | ||
const serverUrl = new URL(server.url, 'http://localhost'); | ||
let urlPath = decodeURI(serverUrl.pathname).replace(/\/$/, ''); | ||
let urlPath = this.findUrlPath(server.url); | ||
if (/{\w+}/.test(urlPath)) { | ||
@@ -21,3 +29,10 @@ // has variable that we need to check out | ||
if (server.variables.hasOwnProperty(variable)) { | ||
this.variables[variable] = { enum: server.variables[variable].enum }; | ||
const v = server.variables[variable]; | ||
const enums = v.enum || []; | ||
if (enums.length === 0 && v.default) enums.push(v.default); | ||
this.variables[variable] = { | ||
enum: enums, | ||
default: v.default, | ||
}; | ||
} | ||
@@ -27,6 +42,30 @@ } | ||
public hasVariables() { | ||
public hasVariables(): boolean { | ||
return Object.keys(this.variables).length > 0; | ||
} | ||
public all(): string[] { | ||
if (!this.hasVariables()) return [this.path]; | ||
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 = pathToRegexp.compile(this.path); | ||
const paths = new Set(); | ||
for (const combo of allParamCombos) { | ||
paths.add(toPath(combo)); | ||
} | ||
this.allPaths = Array.from(paths); | ||
return this.allPaths; | ||
} | ||
public static fromServers(servers: OpenAPIV3.ServerObject[]) { | ||
@@ -38,2 +77,40 @@ if (!servers) { | ||
} | ||
private 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; | ||
return u.substring(pathIdx); | ||
} | ||
} | ||
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 = { ...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; | ||
} |
@@ -1,9 +0,4 @@ | ||
// import fsRoutes from 'fs-routes'; | ||
// import OpenAPIDefaultSetter from 'openapi-default-setter'; | ||
// import OpenAPIRequestCoercer from 'openapi-request-coercer'; | ||
// import OpenAPIRequestValidator from 'openapi-request-validator'; | ||
// import OpenAPIResponseValidator from 'openapi-response-validator'; | ||
import OpenAPISchemaValidator from 'openapi-schema-validator'; | ||
import OpenAPISecurityHandler from 'openapi-security-handler'; | ||
import { OpenAPI, OpenAPIV2, OpenAPIV3 } from 'openapi-types'; | ||
import { OpenAPIV2, OpenAPIV3 } from 'openapi-types'; | ||
import BasePath from './base.path'; | ||
@@ -16,3 +11,2 @@ import { | ||
OpenAPIFrameworkConstructorArgs, | ||
// OpenAPIFrameworkOperationContext, | ||
OpenAPIFrameworkPathContext, | ||
@@ -23,29 +17,8 @@ OpenAPIFrameworkPathObject, | ||
import { | ||
// addOperationTagToApiDoc, | ||
// allowsCoercionFeature, | ||
// allowsDefaultsFeature, | ||
// allowsFeatures, | ||
// allowsResponseValidationFeature, | ||
// allowsValidationFeature, | ||
assertRegExpAndSecurity, | ||
// byDefault, | ||
// byDirectory, | ||
// byMethods, | ||
// byRoute, | ||
// byString, | ||
copy, | ||
// getAdditionalFeatures, | ||
getBasePathsFromServers, | ||
// getMethodDoc, | ||
// getSecurityDefinitionByPath, | ||
loadSpecFile, | ||
handleYaml, | ||
// injectDependencies, | ||
// METHOD_ALIASES, | ||
// resolveParameterRefs, | ||
// resolveResponseRefs, | ||
sortApiDocTags, | ||
// sortOperationDocTags, | ||
// toAbsolutePath, | ||
// withNoDuplicates, | ||
} from './util'; | ||
@@ -68,14 +41,14 @@ | ||
public readonly name; | ||
private customFormats; | ||
private dependencies; | ||
private enableObjectCoercion; | ||
private errorTransformer; | ||
private externalSchemas; | ||
// private customFormats; | ||
// private dependencies; | ||
// private enableObjectCoercion; | ||
// private errorTransformer; | ||
// private externalSchemas; | ||
private originalApiDoc; | ||
private operations; | ||
private paths; | ||
private pathsIgnore; | ||
// private operations; | ||
// private paths; | ||
// private pathsIgnore; | ||
private pathSecurity; | ||
private routesGlob; | ||
private routesIndexFileRegExp; | ||
// private routesGlob; | ||
// private routesIndexFileRegExp; | ||
private securityHandlers; | ||
@@ -113,3 +86,3 @@ private validateApiDoc; | ||
arg.type | ||
} when given` | ||
} when given`, | ||
); | ||
@@ -126,3 +99,3 @@ } | ||
arg.className | ||
} when given` | ||
} when given`, | ||
); | ||
@@ -132,3 +105,3 @@ } | ||
this.enableObjectCoercion = !!args.enableObjectCoercion; | ||
// this.enableObjectCoercion = !!args.enableObjectCoercion; | ||
this.originalApiDoc = handleYaml(loadSpecFile(args.apiDoc)); | ||
@@ -154,13 +127,13 @@ if (!this.originalApiDoc) { | ||
}); | ||
this.customFormats = args.customFormats; | ||
this.dependencies = args.dependencies; | ||
this.errorTransformer = args.errorTransformer; | ||
this.externalSchemas = args.externalSchemas; | ||
this.operations = args.operations; | ||
this.pathsIgnore = args.pathsIgnore; | ||
// this.customFormats = args.customFormats; | ||
// this.dependencies = args.dependencies; | ||
// this.errorTransformer = args.errorTransformer; | ||
// this.externalSchemas = args.externalSchemas; | ||
// this.operations = args.operations; | ||
// this.pathsIgnore = args.pathsIgnore; | ||
this.pathSecurity = Array.isArray(args.pathSecurity) | ||
? args.pathSecurity | ||
: []; | ||
this.routesGlob = args.routesGlob; | ||
this.routesIndexFileRegExp = args.routesIndexFileRegExp; | ||
// this.routesGlob = args.routesGlob; | ||
// this.routesIndexFileRegExp = args.routesIndexFileRegExp; | ||
this.securityHandlers = args.securityHandlers; | ||
@@ -174,10 +147,10 @@ this.pathSecurity.forEach(assertRegExpAndSecurity.bind(null, this)); | ||
this.logger.error( | ||
`${this.loggingPrefix}Validating schema before populating paths` | ||
`${this.loggingPrefix}Validating schema before populating paths`, | ||
); | ||
this.logger.error( | ||
`${this.loggingPrefix}validation errors`, | ||
JSON.stringify(apiDocValidation.errors, null, ' ') | ||
JSON.stringify(apiDocValidation.errors, null, ' '), | ||
); | ||
throw new Error( | ||
`${this.loggingPrefix}args.apiDoc was invalid. See the output.` | ||
`${this.loggingPrefix}args.apiDoc was invalid. See the output.`, | ||
); | ||
@@ -214,7 +187,7 @@ } | ||
this.logger.error( | ||
`${this.loggingPrefix}Validating schema after populating paths` | ||
`${this.loggingPrefix}Validating schema after populating paths`, | ||
); | ||
this.logger.error( | ||
`${this.loggingPrefix}validation errors`, | ||
JSON.stringify(apiDocValidation.errors, null, ' ') | ||
JSON.stringify(apiDocValidation.errors, null, ' '), | ||
); | ||
@@ -224,3 +197,3 @@ throw new Error( | ||
this.loggingPrefix | ||
}args.apiDoc was invalid after populating paths. See the output.` | ||
}args.apiDoc was invalid after populating paths. See the output.`, | ||
); | ||
@@ -227,0 +200,0 @@ } |
@@ -1,9 +0,3 @@ | ||
// import { IOpenAPIDefaultSetter } from 'openapi-default-setter'; | ||
// import { IOpenAPIRequestCoercer } from 'openapi-request-coercer'; | ||
// import { IOpenAPIRequestValidator } from 'openapi-request-validator'; | ||
// import { IOpenAPIResponseValidator } from 'openapi-response-validator'; | ||
import { | ||
// IOpenAPISecurityHandler, | ||
SecurityHandlers, | ||
} from 'openapi-security-handler'; | ||
import { Request } from 'express'; | ||
import { SecurityHandlers } from 'openapi-security-handler'; | ||
import { IJsonSchema, OpenAPIV2, OpenAPIV3 } from 'openapi-types'; | ||
@@ -18,28 +12,2 @@ import { Logger } from 'ts-log'; | ||
export class ConsoleDebugAdapterLogger implements Logger { | ||
/** | ||
* `console.debug` is just an alias for `.log()`, and we want debug logging to be optional. | ||
* This class delegates to `console` and overrides `.debug()` to be a no-op. | ||
*/ | ||
public debug(message?: any, ...optionalParams: any[]): void { | ||
// no-op | ||
} | ||
public error(message?: any, ...optionalParams: any[]): void { | ||
console.error(message, ...optionalParams); | ||
} | ||
public info(message?: any, ...optionalParams: any[]): void { | ||
console.info(message, ...optionalParams); | ||
} | ||
public trace(message?: any, ...optionalParams: any[]): void { | ||
console.trace(message, ...optionalParams); | ||
} | ||
public warn(message?: any, ...optionalParams: any[]): void { | ||
console.warn(message, ...optionalParams); | ||
} | ||
} | ||
// TODO move this to openapi-request-validator | ||
@@ -114,22 +82,2 @@ type OpenAPIErrorTransformer = ({}, {}) => object; | ||
// export interface OpenAPIFrameworkOperationContext { | ||
// additionalFeatures: any[]; | ||
// allowsFeatures: boolean; | ||
// apiDoc: any; | ||
// basePaths: BasePath[]; | ||
// consumes: string[]; | ||
// features: { | ||
// coercer?: IOpenAPIRequestCoercer; | ||
// // defaultSetter?: IOpenAPIDefaultSetter; | ||
// requestValidator?: IOpenAPIRequestValidator; | ||
// // responseValidator?: IOpenAPIResponseValidator; | ||
// securityHandler?: IOpenAPISecurityHandler; | ||
// }; | ||
// methodName: string; | ||
// methodParameters: any[]; | ||
// operationDoc: any; | ||
// operationHandler: any; | ||
// path: string; | ||
// } | ||
export interface OpenAPIFrameworkVisitor { | ||
@@ -140,1 +88,32 @@ visitApi?(context: OpenAPIFrameworkAPIContext): void; | ||
} | ||
export interface OpenApiRequest extends Request { | ||
openapi; | ||
} | ||
/* istanbul ignore next */ | ||
export class ConsoleDebugAdapterLogger implements Logger { | ||
/** | ||
* `console.debug` is just an alias for `.log()`, and we want debug logging to be optional. | ||
* This class delegates to `console` and overrides `.debug()` to be a no-op. | ||
*/ | ||
public debug(message?: any, ...optionalParams: any[]): void { | ||
// no-op | ||
} | ||
public error(message?: any, ...optionalParams: any[]): void { | ||
console.error(message, ...optionalParams); | ||
} | ||
public info(message?: any, ...optionalParams: any[]): void { | ||
console.info(message, ...optionalParams); | ||
} | ||
public trace(message?: any, ...optionalParams: any[]): void { | ||
console.trace(message, ...optionalParams); | ||
} | ||
public warn(message?: any, ...optionalParams: any[]): void { | ||
console.warn(message, ...optionalParams); | ||
} | ||
} |
@@ -10,3 +10,3 @@ import { OpenAPIV3 } from 'openapi-types'; | ||
throw new Error( | ||
`${framework.name}args.pathSecurity expects an array of tuples.` | ||
`${framework.name}args.pathSecurity expects an array of tuples.`, | ||
); | ||
@@ -17,3 +17,3 @@ } else if (!(tuple[0] instanceof RegExp)) { | ||
framework.name | ||
}args.pathSecurity tuples expect the first argument to be a RegExp.` | ||
}args.pathSecurity tuples expect the first argument to be a RegExp.`, | ||
); | ||
@@ -24,3 +24,3 @@ } else if (!Array.isArray(tuple[1])) { | ||
framework.name | ||
}args.pathSecurity tuples expect the second argument to be a security Array.` | ||
}args.pathSecurity tuples expect the second argument to be a security Array.`, | ||
); | ||
@@ -64,3 +64,3 @@ } | ||
export function getBasePathsFromServers( | ||
servers: OpenAPIV3.ServerObject[] | ||
servers: OpenAPIV3.ServerObject[], | ||
): BasePath[] { | ||
@@ -67,0 +67,0 @@ if (!servers) { |
import * as _ from 'lodash'; | ||
import { ExpressApp } from 'express'; | ||
import { Application, Response, NextFunction } from 'express'; | ||
import { OpenAPIFrameworkArgs } from './framework'; | ||
@@ -7,2 +7,3 @@ import { OpenApiContext } from './openapi.context'; | ||
import ono from 'ono'; | ||
import { OpenApiRequest } from './framework/types'; | ||
@@ -28,3 +29,3 @@ const loggingKey = 'express-openapi-validator'; | ||
OpenApiValidator.prototype.install = function(app: ExpressApp) { | ||
OpenApiValidator.prototype.install = function(app: Application) { | ||
const pathParams = []; | ||
@@ -39,3 +40,3 @@ for (const route of this.context.routes) { | ||
for (const p of _.uniq(pathParams)) { | ||
app.param(p, (req, res, next, value, name) => { | ||
app.param(p, (req: OpenApiRequest, res, next, value, name) => { | ||
if (req.openapi.pathParams) { | ||
@@ -55,4 +56,4 @@ // override path params | ||
enableObjectCoercion: this.opts.enableObjectCoercion, | ||
}) | ||
}), | ||
); | ||
}; |
@@ -17,11 +17,4 @@ import * as pathToRegexp from 'path-to-regexp'; | ||
req.params = pathParams; | ||
} else { | ||
// add openapi object if the route was not matched | ||
// but is contained beneath a base path | ||
for (const bp of openApiContext.basePaths) { | ||
if (req.path.startsWith(bp.path + '/')) { | ||
req.openapi = {}; | ||
break; | ||
} | ||
} | ||
} else if (openApiContext.isManagedRoute(req.path)) { | ||
req.openapi = {}; | ||
} | ||
@@ -28,0 +21,0 @@ next(); |
@@ -10,3 +10,3 @@ import { OpenApiSpecLoader } from './openapi.spec.loader'; | ||
apiDoc; | ||
basePaths: BasePath[]; | ||
private basePaths: Set<string>; | ||
constructor(opts: OpenAPIFrameworkArgs) { | ||
@@ -41,2 +41,9 @@ const openApiRouteDiscovery = new OpenApiSpecLoader(opts); | ||
isManagedRoute(path) { | ||
for (const bp of this.basePaths) { | ||
if (path.startsWith(bp)) return true; | ||
} | ||
return false; | ||
} | ||
routePair(route) { | ||
@@ -43,0 +50,0 @@ const methods = this.methods(route); |
import * as _ from 'lodash'; | ||
import OpenAPIFramework, { | ||
@@ -18,4 +17,8 @@ OpenAPIFrameworkArgs, | ||
const apiDoc = framework.apiDoc || {}; | ||
const basePaths = framework.basePaths || []; | ||
const routes = this.discoverRoutes(framework); | ||
const bps = framework.basePaths || []; | ||
const basePaths = bps.reduce((acc, bp) => { | ||
const all = bp.all().forEach(path => acc.add(path)); | ||
return acc; | ||
}, new Set()); | ||
const routes = this.discoverRoutes(framework, basePaths); | ||
return { | ||
@@ -39,3 +42,3 @@ apiDoc, | ||
private discoverRoutes(framework) { | ||
private discoverRoutes(framework: OpenAPIFramework, basePaths: Set<string>) { | ||
const routes = []; | ||
@@ -46,3 +49,3 @@ const toExpressParams = this.toExpressParams; | ||
const apiDoc = ctx.getApiDoc(); | ||
for (const bp of ctx.basePaths) { | ||
for (const bp of basePaths) { | ||
for (const [path, methods] of Object.entries(apiDoc.paths)) { | ||
@@ -56,3 +59,3 @@ for (const [method, schema] of Object.entries(methods)) { | ||
} | ||
const openApiRoute = `${bp.path}${path}`; | ||
const openApiRoute = `${bp}${path}`; | ||
const expressRoute = `${openApiRoute}` | ||
@@ -59,0 +62,0 @@ .split('/') |
@@ -20,3 +20,3 @@ import * as express from 'express'; | ||
res.json({ | ||
name: `${req.metnod}: /router_1`, | ||
name: `${req.method}: /router_1`, | ||
}); | ||
@@ -26,3 +26,3 @@ }) | ||
res.json({ | ||
name: `${req.metnod}: /router_1`, | ||
name: `${req.method}: /router_1`, | ||
}); | ||
@@ -32,3 +32,3 @@ }) | ||
res.json({ | ||
name: `${req.metnod}: /router_1/${req.params.id}`, | ||
name: `${req.method}: /router_1/${req.params.id}`, | ||
}); | ||
@@ -38,3 +38,3 @@ }) | ||
res.json({ | ||
name: `${req.metnod}: /router_1/${req.params.id}/best/${ | ||
name: `${req.method}: /router_1/${req.params.id}/best/${ | ||
req.params.bid | ||
@@ -74,3 +74,3 @@ }`, | ||
res, | ||
next | ||
next, | ||
) { | ||
@@ -86,3 +86,3 @@ res.json({ | ||
res, | ||
next | ||
next, | ||
) { | ||
@@ -89,0 +89,0 @@ res.json({ |
@@ -6,7 +6,7 @@ import { expect } from 'chai'; | ||
const packageJson = require('../package.json'); | ||
const basePath = app.basePath; | ||
const basePath = (<any>app).basePath; | ||
describe(packageJson.name, () => { | ||
after(() => { | ||
app.server.close(); | ||
(<any>app).server.close(); | ||
}); | ||
@@ -13,0 +13,0 @@ |
@@ -24,5 +24,5 @@ import { expect } from 'chai'; | ||
expect(createMiddleware).to.throw( | ||
'spec could not be read at ./not-found.yaml' | ||
'spec could not be read at ./not-found.yaml', | ||
); | ||
}); | ||
}); |
@@ -6,7 +6,7 @@ import { expect } from 'chai'; | ||
const packageJson = require('../package.json'); | ||
const basePath = app.basePath; | ||
const basePath = (<any>app).basePath; | ||
describe(packageJson.name, () => { | ||
after(() => { | ||
app.server.close(); | ||
(<any>app).server.close(); | ||
}); | ||
@@ -155,3 +155,3 @@ it(`should test something`, () => { | ||
expect(e[0].path).to.equal( | ||
`${basePath}/route_not_defined_within_express` | ||
`${basePath}/route_not_defined_within_express`, | ||
); | ||
@@ -184,3 +184,3 @@ })); | ||
expect(e[0].path).to.equal( | ||
`${basePath}/route_defined_in_express_not_openapi` | ||
`${basePath}/route_defined_in_express_not_openapi`, | ||
); | ||
@@ -198,3 +198,3 @@ })); | ||
expect(e[0].message).to.equal( | ||
'Unsupported Content-Type application/xml' | ||
'Unsupported Content-Type application/xml', | ||
); | ||
@@ -201,0 +201,0 @@ })); |
{ | ||
"compilerOptions": { | ||
"declaration": true, | ||
"target": "es2015", | ||
"lib": ["es6", "dom"], | ||
@@ -5,0 +6,0 @@ "module": "commonjs", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
382248
1982
188
22
Updatedopenapi-types@^1.3.4