UNPKG

@36node/swagen

Version:

A module boilerplate for nodejs and web.

1,058 lines (873 loc) 29 kB
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var hbsHelper = _interopDefault(require('handlebars-helpers')); var SwaggerParser = _interopDefault(require('swagger-parser')); var jsonSchemaToTypescript = require('json-schema-to-typescript'); var Handlebars = _interopDefault(require('handlebars')); var prettier = _interopDefault(require('prettier')); var lodash = require('lodash'); var postmanCollection = require('postman-collection'); var jsonfile = _interopDefault(require('jsonfile')); var fs = _interopDefault(require('fs')); var path = _interopDefault(require('path')); function objectWithoutProperties (obj, exclude) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; } hbsHelper({ handlebars: Handlebars }); /** * Get schema type * * @param {*} schema swagger schema object * @returns {String} parsed type */ function getSchemaType(schema) { if ( schema === void 0 ) schema = {}; var type = schema.type; var $ref = schema.$ref; var items = schema.items; var properties = schema.properties; if ( properties === void 0 ) properties = {}; var oneOf = schema.oneOf; if (oneOf) { return oneOf.map(function (i) { return getSchemaType(i); }).join("|"); } if (schema.enum) { if (type === "string") { return schema.enum.map(function (e) { return ("\"" + e + "\""); }).join("|"); } else { return schema.enum.join("|"); } } if (type === "integer") { return "number"; } if (type === "float") { return "number"; } if (type === "array") { return ("[" + (getSchemaType(items)) + "]"); } if (type === "object") { var ret = []; for (var key in properties) { ret.push((key + ":" + (getSchemaType(properties[key])))); } return ("{\n " + (ret.join("\n")) + "\n }"); } if ($ref) { var segs = $ref.split("/"); if (segs.length < 2) { throw new Error(("ref pattern is wrong: " + $ref)); } return lodash.upperFirst(segs[segs.length - 1]); } return type; } /** * Determine whether the type parameter is included * * @param {Array} parameters the params * @param {string} type parameter type * @returns {boolean} return true if type in parameters */ function hasParamType(parameters, type) { if ( parameters === void 0 ) parameters = []; for (var i = 0, list = parameters; i < list.length; i += 1) { var p = list[i]; if (p.in === type) { return true; } } return false; } /** * Determin whether the response json body is included * @param {*} operation swagger operation object */ function hasJsonBody(operation) { var schema = lodash.get(operation, ["response", "content", "schema"], null); if (schema) { return true; } return false; } /** * Get json body from response of operation definition * @param {*} operation swagger operation object * @returns {object} return json response body schema */ function getJsonBodySchema(operation) { var bodySchema = lodash.get(operation, ["response", "content", "schema"], null); if (bodySchema) { return JSON.stringify(bodySchema, null, 2); } return null; } /** * Determine whether the request has status * @param {*} operation swagger operation object */ function hasResponseStatus(operation) { var status = lodash.get(operation, ["response", "status"], null); if (status) { return true; } return false; } /** * Determine whether the type parameter is required * * @param {Array} parameters the params * @param {string} type parameter type * @returns {boolean} return true if {type} in parameters */ function requireParamType(parameters, type) { if ( parameters === void 0 ) parameters = []; for (var i = 0, list = parameters; i < list.length; i += 1) { var p = list[i]; if (p.in === type && p.required) { return true; } } return false; } /** * 移除不必要的字段,确保 ajv 校验不失败 * * @param {Object} obj schema */ function cutSchema(obj) { if (typeof obj !== "object") { return obj; } if (lodash.isArray(obj)) { return obj.map(cutSchema); } var rest = objectWithoutProperties( obj, ["description", "example", "tsType", "additionalProperties", "writeOnly", "readOnly"] ); var newObj = rest; Object.keys(newObj).map(function (key) { return newObj[key] = cutSchema(newObj[key]); }); return newObj; } /** * Handlebars helpers */ Handlebars.registerHelper("decapitalize", function (str) { return str.charAt(0).toLowerCase() + str.slice(1); }); Handlebars.registerHelper("schemaType", function (schema) { return getSchemaType(schema); }); Handlebars.registerHelper("jsonBodySchema", function (operation) { return getJsonBodySchema(operation); }); Handlebars.registerHelper("withParamPath", function (parameters, options) { return hasParamType(parameters, "path") ? options.fn(this) : options.inverse(this); }); Handlebars.registerHelper("withParamQuery", function (parameters, options) { return hasParamType(parameters, "query") ? options.fn(this) : options.inverse(this); }); Handlebars.registerHelper("withParamHeader", function (parameters, options) { return hasParamType(parameters, "header") ? options.fn(this) : options.inverse(this); }); Handlebars.registerHelper("withParamCookie", function (parameters, options) { return hasParamType(parameters, "cookie") ? options.fn(this) : options.inverse(this); }); Handlebars.registerHelper("requireParamQuery", function (parameters, options) { return requireParamType(parameters, "query") ? options.fn(this) : options.inverse(this); }); Handlebars.registerHelper("requireParamHeader", function (parameters, options) { return requireParamType(parameters, "header") ? options.fn(this) : options.inverse(this); }); Handlebars.registerHelper("requireParamCookie", function (parameters, options) { return requireParamType(parameters, "cookie") ? options.fn(this) : options.inverse(this); }); Handlebars.registerHelper("withJsonBody", function (operation, options) { return hasJsonBody(operation) ? options.fn(this) : options.inverse(this); }); Handlebars.registerHelper("withResponseStatus", function (operation, options) { return hasResponseStatus(operation) ? options.fn(this) : options.inverse(this); }); Handlebars.registerHelper("toRoute", function (path$$1) { return path$$1.replace(/{(.*?)}/g, ":$1"); }); Handlebars.registerHelper("toDollar", function (path$$1) { return path$$1.replace(/{(.*?)}/g, "$$$&"); }); Handlebars.registerHelper("toUpperCase", function (str) { return str.toUpperCase(); }); Handlebars.registerHelper("postmanAjvSchema", function (schema) { return JSON.stringify(cutSchema(schema)); }); // A type of promise-like that resolves synchronously and supports only one observer var _Pact = /*#__PURE__*/function () { function _Pact() {} _Pact.prototype.then = function (onFulfilled, onRejected) { var result = new _Pact(); var state = this.s; if (state) { var callback = state & 1 ? onFulfilled : onRejected; if (callback) { try { _settle(result, 1, callback(this.v)); } catch (e) { _settle(result, 2, e); } return result; } else { return this; } } this.o = function (_this) { try { var value = _this.v; if (_this.s & 1) { _settle(result, 1, onFulfilled ? onFulfilled(value) : value); } else if (onRejected) { _settle(result, 1, onRejected(value)); } else { _settle(result, 2, value); } } catch (e) { _settle(result, 2, e); } }; return result; }; return _Pact; }(); // Settles a pact synchronously function _settle(pact, state, value) { if (!pact.s) { if (value instanceof _Pact) { if (value.s) { if (state & 1) { state = value.s; } value = value.v; } else { value.o = _settle.bind(null, pact, state); return; } } if (value && value.then) { value.then(_settle.bind(null, pact, state), _settle.bind(null, pact, 2)); return; } pact.s = state; pact.v = value; var observer = pact.o; if (observer) { observer(pact); } } } function _isSettledPact(thenable) { return thenable instanceof _Pact && thenable.s & 1; } // Converts argument to a function that always returns a Promise function _forTo(array, body, check) { var i = -1, pact, reject; function _cycle(result) { try { while (++i < array.length && (!check || !check())) { result = body(i); if (result && result.then) { if (_isSettledPact(result)) { result = result.v; } else { result.then(_cycle, reject || (reject = _settle.bind(null, pact = new _Pact(), 2))); return; } } } if (pact) { _settle(pact, 1, result); } else { pact = result; } } catch (e) { _settle(pact || (pact = new _Pact()), 2, e); } } _cycle(); return pact; } // Asynchronously iterate through an object's properties (including properties inherited from the prototype) // Uses a snapshot of the object's properties function _forIn(target, body, check) { var keys = []; for (var key in target) { keys.push(key); } return _forTo(keys, function (i) { return body(keys[i]); }, check); } // Asynchronously iterate through an object's own properties (excluding properties inherited from the prototype) var _iteratorSymbol = /*#__PURE__*/typeof Symbol !== "undefined" ? Symbol.iterator || (Symbol.iterator = Symbol("Symbol.iterator")) : "@@iterator"; // Asynchronously iterate through an object's values var _asyncIteratorSymbol = /*#__PURE__*/typeof Symbol !== "undefined" ? Symbol.asyncIterator || (Symbol.asyncIterator = Symbol("Symbol.asyncIterator")) : "@@asyncIterator"; // Asynchronously iterate on a value using it's async iterator if present, or its synchronous iterator if missing /** * parse swagger file to expected structure * * @param {string} source file path or remote url * @returns { Object } { info, servers, api, components, security } */ var parse = function (source, options) { if ( options === void 0 ) options = {}; try { return Promise.resolve(SwaggerParser.dereference(source)).then(function (swagger) { return Promise.resolve(_parse(swagger)); }); } catch (e) { return Promise.reject(e); } }; /** * parse swagger * * @param {Object} swagger openapi object * @returns {Object} { info, servers, api, components, security } */ var _parse = function (swagger) { try { var _exit = false; function _temp4(_result2) { function _temp2() { return { info: info, servers: servers, api: api, components: components, schemaDefs: schemaDefs, security: security }; } var schemaDefs = []; var _temp = _forIn(components.schemas, function (k) { var s = components.schemas[k]; return Promise.resolve(getDef(_transSchema(s), k)).then(function (def) { if (def) { schemaDefs.push(def); } }); }); return _temp && _temp.then ? _temp.then(_temp2) : _temp2(_temp); } var paths = swagger.paths; var components = swagger.components; var info = swagger.info; var servers = swagger.servers; var security = swagger.security; var api = {}; var _temp3 = _forIn(paths, function (path$$1) { return _forIn(paths[path$$1], function (method) { var ref = paths[path$$1][method]; var operationId = ref.operationId; var summary = ref.summary; var tags = ref.tags; var parameters = ref.parameters; var requestBody = ref.requestBody; var responses = ref.responses; var security = ref.security; var roles = ref["x-roles"]; // make sure some props must exist if (!operationId) { throw new Error(("missing operationId for " + method + " " + path$$1)); } if (!tags && !tags[0]) { throw new Error(("missing operation tag for " + method + " " + path$$1)); } // use 200/204 response, https://github.com/jshttp/http-errors var candidates = ["200", "201", "202", "204", 200, 201, 202, 204]; var status = candidates.find(function (k) { return responses[k]; }); if (!status) { throw new Error(("missing 20X response for " + method + " " + path$$1)); } var response = responses[status]; response.status = status; response.content = lodash.get(response, ["content", "application/json", "schema"]); // requestBody if (requestBody) { requestBody.content = lodash.get(requestBody, ["content", "application/json", "schema"]); } // schema var name = lodash.camelCase(operationId); var requestSchema = getRequestSchema({ parameters: parameters, requestBody: requestBody }); var responseSchema = getResponseSchema(response); return Promise.resolve(getDef(requestSchema, (name + "Request"))).then(function (requestDef) { return Promise.resolve(getDef(responseSchema, (name + "Response"))).then(function (responseDef) { // use tags[0] as api's name api[tags[0]] = api[tags[0]] || { name: tags[0], operations: [], ref: [] }; api[tags[0]].operations.push({ method: method, name: name, operationId: operationId, parameters: parameters, path: path$$1, requestBody: requestBody, response: response, summary: summary, security: security, requestSchema: requestSchema, responseSchema: responseSchema, requestDef: requestDef, responseDef: responseDef, roles: roles }); }); }); }, function () { return _exit; }); }, function () { return _exit; }); return Promise.resolve(_temp3 && _temp3.then ? _temp3.then(_temp4) : _temp4(_temp3)); } catch (e) { return Promise.reject(e); } }; var initObjSchema = function (properties) { if ( properties === void 0 ) properties = {}; return ({ type: "object", required: [], properties: properties }); }; /** * 对 schema 做一定的补充和变化,使得 ajv 和 compile to typedef 能正确工作 */ function _transSchema(obj, isRequest) { if ( isRequest === void 0 ) isRequest = false; if (typeof obj !== "object") { return obj; } var newobj = lodash.isArray(obj) ? [] : {}; if (obj.format === "date-time") { newobj.tsType = "Date"; } if (obj.type === "object") { if (obj.additionalProperties) { newobj.additionalProperties = true; } else { newobj.additionalProperties = false; } } for (var prop in obj) { if (isRequest && !obj[prop].readOnly || !isRequest && !obj[prop].writeOnly) { newobj[prop] = _transSchema(obj[prop], isRequest); } if (prop === "type" && obj.nullable) { newobj[prop] = [obj.type, "null"]; } } return newobj; } /** * get request schema from operation * * @param {object} operation */ function getRequestSchema(ref) { var parameters = ref.parameters; if ( parameters === void 0 ) parameters = []; var requestBody = ref.requestBody; var schema = initObjSchema(); for (var i = 0, list = parameters; i < list.length; i += 1) { var param = list[i]; var seg = schema; if (param.in && param.in !== "path") { schema.properties[param.in] = schema.properties[param.in] || initObjSchema(); seg = schema.properties[param.in]; } if (param.required) { seg.required.push(param.name); } seg.properties[param.name] = param.schema; } if (requestBody) { schema.properties.body = requestBody.content; schema.required.push("body"); } if (!lodash.isEmpty(schema.properties)) { return _transSchema(schema, true); } } /** * get response schema from operation.response * * @param {object} response */ function getResponseSchema(ref) { var headers = ref.headers; var content = ref.content; var schema = initObjSchema(); if (content) { schema.properties.body = content; schema.required.push("body"); } if (!lodash.isEmpty(headers)) { schema.properties.headers = initObjSchema(); schema.required.push("headers"); } for (var key in headers) { var lowerCasedKey = key.toLowerCase(); if (headers[key].required) { schema.properties.headers.required.push(lowerCasedKey); } schema.properties.headers.properties[lowerCasedKey] = headers[key].schema; } // if no response, just return undefined if (!lodash.isEmpty(schema.properties)) { return _transSchema(schema); } } /** * get request definition from schema * * @param {object} schema */ function getDef(schema, name) { if (schema) { return jsonSchemaToTypescript.compile(schema, name, { unknownAny: false, bannerComment: null }); } } /** * Make directory * * @param {*} dir target directory */ function mkdir(dir) { if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } } /** * @param {string} path */ function formatPath(path$$1) { var result = path$$1; if (path$$1.charAt(0) === "/") { result = path$$1.substr(1); } // format path parameter /{petId} to /:petId var rxp = /{([^}]+)}/g; var params = []; var curMatch; while ((curMatch = rxp.exec(path$$1)) != null) { params.push(curMatch[1]); } for (var i = 0, list = params; i < list.length; i += 1) { var p = list[i]; result = result.replace(("{" + p + "}"), (":" + p)); } return result; } /** * Generate template content through handlebars * * @param {*} tplFile template file * @param {*} data data for handlebars * * @returns {string} parsed template content */ function build(tplFile, data) { var content = fs.readFileSync(tplFile, "utf8"); var template = Handlebars.compile(content); var parsed_content = template(data); return parsed_content; } /** * Generate file through handlebars * * @param {string} tplFile template file * @param {string} toFile target file * @param {object} data data for handlebars * @param {object} prettierOpts prettier opts */ function generate(tplFile, toFile, data, prettierOpts) { if ( prettierOpts === void 0 ) prettierOpts = {}; var parsed_content = build(tplFile, data); var defaultPrettierOpts = { parser: "babel", printWidth: 100, trailingComma: "es5" }; fs.writeFileSync(toFile, prettier.format(parsed_content, Object.assign({}, defaultPrettierOpts, prettierOpts)), "utf8"); } /** * Get api roles * * @param {*} api */ function getApiRoles(api) { var roles = []; for (var name in api) { for (var i = 0, list = api[name]["operations"]; i < list.length; i += 1) { var operation = list[i]; roles.push({ operationId: operation["operationId"], roles: operation["roles"] || [] }); } } return roles; } /** * Generate code for koa server api * @param {object} opts options * @param {string} opts.dist code dist * @param {string} opts.yamlFile openapi file path */ function genKoa(ref) { var dist = ref.dist; if ( dist === void 0 ) dist = process.cwd(); var yamlFile = ref.yamlFile; var templatePath = ref.templatePath; mkdir(dist); return parse(yamlFile).then(function (swagger) { var api = swagger.api; // 生成 roles.js 文件 var tplRole = path.join(templatePath, "koa", "role.hbs"); generate(tplRole, path.join(dist, "_roles.js"), { value: getApiRoles(api) }); // 生成 api 文件 var tplAPI = path.join(templatePath, "koa", "api.hbs"); var tplDef = path.join(templatePath, "koa", "definitions.hbs"); var tplSchema = path.join(templatePath, "koa", "schema.hbs"); for (var name in api) { generate(tplAPI, path.join(dist, (name + ".js")), api[name]); generate(tplSchema, path.join(dist, (name + ".schema.js")), api[name]); generate(tplDef, path.join(dist, (name + ".d.ts")), api[name], { parser: "typescript" }); } }).catch(function (err) { return console.error(err); }); } var PostmanGenerator = function PostmanGenerator(swagger, templatePath) { this._variable = {}; this.swagger = swagger; this.templatePath = templatePath; }; /** * Gen postman collection headers * @param {object} operation swaggen operation node */ PostmanGenerator.prototype.genHeaders = function genHeaders (operation) { var headerList = new postmanCollection.HeaderList(); headerList.append({ key: "Accept", value: "application/json" }); if (operation.requestBody) { headerList.append({ key: "Content-Type", value: "application/json" }); } return headerList; }; /** * Genreate postman collection url object * @param {object} operation swagger operation object */ PostmanGenerator.prototype.genUrl = function genUrl (operation) { var operationPath = lodash.get(operation, "path", "/"); var url = new postmanCollection.Url(); url.host = "{{baseUrl}}"; url.path = [formatPath(operationPath)]; return url; }; /** * Generate postman request object * @param {*} operation swagger operation */ PostmanGenerator.prototype.genRequest = function genRequest (operation) { var request = new postmanCollection.Request(); request.headers = this.genHeaders(operation); request.url = this.genUrl(operation); request.method = lodash.toUpper(operation.method); // security config, preferred to use operation security config var security = lodash.get(operation, ["security"], null) || lodash.get(this.swagger, ["security"], null); if (security && Array.isArray(security)) { for (var i = 0, list = security; i < list.length; i += 1) { var s = list[i]; var applySecurity = Object.keys(s)[0]; var securitySchema = lodash.get(this.swagger, ["components", "securitySchemes", applySecurity], null); // TODO: support multi-type security schema if (securitySchema && securitySchema.type === "http" && securitySchema.scheme === "bearer") { request.auth = new postmanCollection.RequestAuth({ type: "bearer", bearer: [{ key: "token", value: "{{token}}", type: "string" }] }); } } } // generate request body var body = lodash.get(operation, ["requestBody", "content", "properties"], null); if (body) { var raw = {}; for (var k in body) { raw[k] = "{{" + k + "}}"; this._variable[k] = { key: k }; } request.body = new postmanCollection.RequestBody({ mode: "raw", raw: JSON.stringify(raw, null, 2) }); } if (operation.parameters) { for (var i$1 = 0, list$1 = operation.parameters; i$1 < list$1.length; i$1 += 1) { var param = list$1[i$1]; if (param.in === "query") { request.url.addQueryParams({ key: param.name, value: lodash.toString(param.default) }); } if (param.in === "path") { request.url.variables.add({ id: param.name, value: ("{{" + (param.name) + "}}"), type: lodash.get(param, "schema.type", "string") }); } if (param.in === "header") { request.headers.add({ key: param.name, value: lodash.toString(param.default) }); } } } return request; }; /** * Generate postman response object * @param {*} operation swager operation definitions */ PostmanGenerator.prototype.genResponse = function genResponse (operation) { var response = operation.response; if (!response) { return undefined; } var description = response.description; var headers = response.headers; var content = response.content; var status = response.status; var resp = new postmanCollection.Response(); resp.name = "Response_" + status; resp.originalRequest = this.genRequest(operation); resp.code = Number(status); resp.status = description || "OK"; var headerList = new postmanCollection.HeaderList(); if (content) { headerList.append({ key: "Content-type", value: "application/json" }); } if (headers) { for (var i = 0, list = Object.keys(headers); i < list.length; i += 1) { var k = list[i]; headerList.append({ key: k, value: "unset" }); } } resp.headers = headerList; var examples = lodash.get(content, "examples"); // only default example if (examples && examples.default) { resp.body = lodash.get(examples, ["default", "value"]); } return resp; }; /** * Generate postman item, one reqeust one item * @param {*} operation swagger operation definition */ PostmanGenerator.prototype.genItem = function genItem (operation) { var name = operation.summary || operation.name || operation.operationId; var request = this.genRequest(operation); var item = new postmanCollection.Item(); item.request = request; item.name = name; item.description = name; item.responses.append(this.genResponse(operation)); var tplTest = path.join(this.templatePath, "postman", "test-script.hbs"); var testContent = build(tplTest, operation); // console.log(testContent); // init test script item.events.add({ listen: "test", script: { type: "text/javascript", exec: testContent.split("\n") } }); return item; }; /** * Gernate postman collection folder * @param api swagger api definition */ PostmanGenerator.prototype.genFolder = function genFolder (api) { var folder = new postmanCollection.ItemGroup({ name: api.name }); var operations = api.operations; if ( operations === void 0 ) operations = []; for (var i = 0, list = operations; i < list.length; i += 1) { var operation = list[i]; folder.items.add(this.genItem(operation)); } return folder; }; PostmanGenerator.prototype.genCollection = function genCollection (name) { var ref = this.swagger; var api = ref.api; if ( api === void 0 ) api = {}; var components = ref.components; if ( components === void 0 ) components = {}; var collection = new postmanCollection.Collection({ name: name }); for (var n in api) { collection.items.add(this.genFolder(api[n], components)); } collection.variable = Object.values(this._variable); return collection; }; function genPostman(ref) { var yamlFile = ref.yamlFile; var targetFile = ref.targetFile; var templatePath = ref.templatePath; parse(yamlFile, { dereference: true }).then(function (swagger) { var generator = new PostmanGenerator(swagger, templatePath); var name = lodash.get(swagger, ["info", "title"], null); if (!name) { throw new Error("Openapi must have title"); } var collection = generator.genCollection(name).toJSON(); var finalTargetFile = targetFile || name + ".postman_collection.json"; // if file exist merge generated collection to original collection if (fs.existsSync(finalTargetFile)) { var originCollection = jsonfile.readFileSync(finalTargetFile); lodash.mergeWith(collection, originCollection, function (objValue, srcValue, key) { // test script not merge if (key === "exec") { return srcValue; } return undefined; }); } jsonfile.writeFileSync(finalTargetFile, collection, { spaces: 2 }); }); } /** * Genereate code for sdk * * @param {object} opts options * @param {string} opts.target code dist * @param {string} opts.name sdk name */ function genSDK(ref) { var yamlFile = ref.yamlFile; var dist = ref.dist; if ( dist === void 0 ) dist = "./"; var templatePath = ref.templatePath; // put sdk generation code here parse(yamlFile).then(function (swagger) { var tplSdk = path.join(templatePath, "sdk", "sdk.hbs"); var tplDef = path.join(templatePath, "sdk", "definitions.hbs"); var servers = swagger.servers; if ( servers === void 0 ) servers = []; var tplData = Object.assign({}, {server: servers[0] || { url: "" }}, swagger); if (dist) { mkdir(dist); } var finalDist = dist || process.cwd(); generate(tplDef, path.join(finalDist, "typings", "index.d.ts"), tplData, { parser: "typescript" }); generate(tplSdk, path.join(finalDist, "src", "index.js"), tplData); }).catch(function (err) { return console.error(err); }); } exports.koa = genKoa; exports.postman = genPostman; exports.sdk = genSDK; //# sourceMappingURL=index.js.map