swagger-typed-express-docs
Version:
Simple express runtime parser and documentation swagger generator with 100% support of Typescript static types
239 lines (238 loc) • 14.3 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.mock_apiDoc = exports.getMock_apiDocInstance = exports.initApiDocs = exports.apiDoc = exports.getApiDocInstance = exports.__expressOpenAPIHack__ = exports.__expressTypedHack_key__ = void 0;
var utils_1 = require("./utils");
var schemaBuilder_1 = require("./schemaBuilder");
var openAPIFromSchema_1 = require("./openAPIFromSchema");
var runtimeSchemaValidation_1 = require("./runtimeSchemaValidation");
var expressRegExUrlParser_1 = require("./expressRegExUrlParser");
var jsValueToSchema_1 = require("./jsValueToSchema");
exports.__expressTypedHack_key__ = '__expressTypedHack_key__';
exports.__expressOpenAPIHack__ = Symbol('__expressOpenAPIHack__');
var getApiDocInstance = function (_a) {
var _b = _a === void 0 ? {} : _a, _c = _b.errorFormatter, errorFormatter = _c === void 0 ? (function (e) { return e; }) : _c;
return function (docs) {
return function (handle) {
var headersSchema = docs.headers ? docs.headers : null;
var paramsSchema = docs.params ? schemaBuilder_1.T.object(docs.params) : null;
var querySchema = docs.query ? schemaBuilder_1.T.object(docs.query) : null;
var bodySchema = docs.body ? docs.body : null;
var returnsSchema = docs.returns ? docs.returns : null;
var headersValidator = headersSchema ? (0, runtimeSchemaValidation_1.getTSchemaValidator)(headersSchema) : null;
var paramsValidator = paramsSchema
? (0, runtimeSchemaValidation_1.getTSchemaValidator)(paramsSchema, { transformTypeMode: 'decode' })
: null;
var queryValidator = querySchema
? (0, runtimeSchemaValidation_1.getTSchemaValidator)(querySchema, { transformTypeMode: 'decode' })
: null;
var bodyValidator = bodySchema
? (0, runtimeSchemaValidation_1.getTSchemaValidator)(bodySchema, { transformTypeMode: 'decode' })
: null;
var returnsValidator = returnsSchema
? (0, runtimeSchemaValidation_1.getTSchemaValidator)(returnsSchema, { transformTypeMode: 'encode' })
: null;
var lazyInitializeHandler = function (message) {
if (message !== exports.__expressOpenAPIHack__) {
throw new Error('You probably forget to call `initApiDocs()` for typed-express library');
}
var handleRouteWithRuntimeValidations = function (req, res, next) { return __awaiter(void 0, void 0, void 0, function () {
var _a, headersValidationRes, paramValidationRes, queryValidationRes, bodyValidationRes, headersErrors, paramsErrors, queryErrors, bodyErrors, errObj, tSend;
return __generator(this, function (_b) {
_a = __read((0, utils_1.syncAllSettled)([
function () { return headersValidator === null || headersValidator === void 0 ? void 0 : headersValidator.validate(req.headers); },
function () { return paramsValidator === null || paramsValidator === void 0 ? void 0 : paramsValidator.validate(req.params); },
function () { return queryValidator === null || queryValidator === void 0 ? void 0 : queryValidator.validate(req.query); },
function () { return bodyValidator === null || bodyValidator === void 0 ? void 0 : bodyValidator.validate(req.body); },
]), 4), headersValidationRes = _a[0], paramValidationRes = _a[1], queryValidationRes = _a[2], bodyValidationRes = _a[3];
if (headersValidationRes.status === 'rejected' ||
paramValidationRes.status === 'rejected' ||
queryValidationRes.status === 'rejected' ||
bodyValidationRes.status === 'rejected') {
headersErrors = headersValidationRes.status === 'rejected' ? headersValidationRes.reason : null;
paramsErrors = paramValidationRes.status === 'rejected' ? paramValidationRes.reason : null;
queryErrors = queryValidationRes.status === 'rejected' ? queryValidationRes.reason : null;
bodyErrors = bodyValidationRes.status === 'rejected' ? bodyValidationRes.reason : null;
errObj = {
errors: {
headers: (0, runtimeSchemaValidation_1.normalizeYupError)(headersErrors),
params: (0, runtimeSchemaValidation_1.normalizeYupError)(paramsErrors),
query: (0, runtimeSchemaValidation_1.normalizeYupError)(queryErrors),
body: (0, runtimeSchemaValidation_1.normalizeYupError)(bodyErrors),
},
};
res.status(400).send(errorFormatter(errObj));
return [2];
}
if (headersValidator)
req.headers = headersValidationRes.value;
if (paramsValidator)
req.params = paramValidationRes.value;
if (queryValidator)
req.query = queryValidationRes.value;
if (bodyValidator)
req.body = bodyValidationRes.value;
tSend = function (data) { return __awaiter(void 0, void 0, void 0, function () {
var transformedData;
return __generator(this, function (_a) {
try {
transformedData = returnsValidator ? returnsValidator.validate(data) : data;
res.send(transformedData);
}
catch (errObj) {
res.status(500).send({
type: 'invalid data came from app handler',
error: errorFormatter({ errors: { returns: (0, runtimeSchemaValidation_1.normalizeYupError)(errObj) } }),
});
}
return [2];
});
}); };
res.tSend = tSend;
return [2, handle(req, res, next)];
});
}); };
return {
apiRouteSchema: {
headersSchema: headersSchema,
paramsSchema: paramsSchema,
querySchema: querySchema,
bodySchema: bodySchema,
returnsSchema: returnsSchema,
},
handle: handleRouteWithRuntimeValidations,
};
};
lazyInitializeHandler[exports.__expressTypedHack_key__] = exports.__expressOpenAPIHack__;
return lazyInitializeHandler;
};
};
};
exports.getApiDocInstance = getApiDocInstance;
exports.apiDoc = (0, exports.getApiDocInstance)();
var resolveRouteHandlersAndExtractAPISchema = function (route, path, urlsMethodDocsPointer) {
if (path === void 0) { path = ''; }
if (urlsMethodDocsPointer === void 0) { urlsMethodDocsPointer = {}; }
route.stack.forEach(function (r) {
var _a, _b;
if (r.name === 'router') {
var stack = r;
var parsedRouterRelativePath = (0, expressRegExUrlParser_1.parseUrlFromExpressRegexp)(stack.regexp.toString(), (_a = stack.keys) !== null && _a !== void 0 ? _a : []);
var routerFullPath = (0, utils_1.mergePaths)(path, parsedRouterRelativePath);
resolveRouteHandlersAndExtractAPISchema((_b = stack.handle) !== null && _b !== void 0 ? _b : stack.__handle, routerFullPath, urlsMethodDocsPointer);
}
else if (r.name === 'bound dispatch') {
r.route.stack.forEach(function (s) {
var _a;
var shouldInitTypedRoute = ((_a = s.handle) === null || _a === void 0 ? void 0 : _a[exports.__expressTypedHack_key__]) === exports.__expressOpenAPIHack__;
var isInitTypedRoute = s._swaggerTypedExpressDocs__route_cache !== undefined;
if (shouldInitTypedRoute === false && isInitTypedRoute === false)
return;
var endpointPath = (0, utils_1.mergePaths)(path, r.route.path);
var routeMetadataDocs;
if (s._swaggerTypedExpressDocs__route_cache) {
routeMetadataDocs = s._swaggerTypedExpressDocs__route_cache;
}
else {
routeMetadataDocs = s.handle(exports.__expressOpenAPIHack__);
s.handle = routeMetadataDocs.handle;
s._swaggerTypedExpressDocs__route_cache = routeMetadataDocs;
}
if (!urlsMethodDocsPointer[endpointPath]) {
urlsMethodDocsPointer[endpointPath] = {};
}
urlsMethodDocsPointer[endpointPath][s.method] = {
headersSchema: routeMetadataDocs.apiRouteSchema.headersSchema,
pathSchema: routeMetadataDocs.apiRouteSchema.paramsSchema,
querySchema: routeMetadataDocs.apiRouteSchema.querySchema,
bodySchema: routeMetadataDocs.apiRouteSchema.bodySchema,
returnsSchema: routeMetadataDocs.apiRouteSchema.returnsSchema,
};
});
}
});
return urlsMethodDocsPointer;
};
var initApiDocs = function (expressApp, customOpenAPIType) {
if (customOpenAPIType === void 0) { customOpenAPIType = {}; }
var mutDefinitions = {};
var openApiTypes = (0, utils_1.deepMerge)({
openapi: '3.0.0',
info: {
version: '1.0.0',
title: 'openapi documentation',
},
servers: [
{
url: 'http://localhost/',
},
],
paths: (0, openAPIFromSchema_1.convertUrlsMethodsSchemaToOpenAPI)(resolveRouteHandlersAndExtractAPISchema(expressApp._router), mutDefinitions),
}, customOpenAPIType);
openApiTypes.components = { schemas: mutDefinitions };
return openApiTypes;
};
exports.initApiDocs = initApiDocs;
var getMock_apiDocInstance = function (_a) {
var _b = _a === void 0 ? {} : _a, _c = _b.errorFormatter, errorFormatter = _c === void 0 ? (function (e) { return e; }) : _c;
return function (a) {
return function (_handler) {
return (0, exports.getApiDocInstance)({ errorFormatter: errorFormatter })(a)(function (_req, res) {
res.send(a.returns ? (0, jsValueToSchema_1.tSchemaToJSValue)(a.returns) : undefined);
});
};
};
};
exports.getMock_apiDocInstance = getMock_apiDocInstance;
exports.mock_apiDoc = (0, exports.getMock_apiDocInstance)();
;