Archived
1
0
Fork 0
This repository has been archived on 2024-02-06. You can view files and clone it, but cannot push or open issues or pull requests.
api-ts-gen/dist/codegen.js
supmiku39 87615616d6 fix Partial constructor, enhance error msg
add more info to BadValueError
  special message on undefined and null
  trace the location where the error is thrown
add int32 STP
fix STP null argument bug
2020-04-08 20:17:57 +09:00

385 lines
17 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var fs = require("fs");
var path = require("path");
var Config_1 = require("./Config");
var OpenAPI_1 = require("./OpenAPI");
var CodePrinter_1 = require("./CodePrinter");
function codegenIServerAPI(funcs, config, cp) {
var apiDirTSPath = config.apiDirTSPath, IHandlerName = config.IHandlerName;
// import
cp.writeln("import * as IHandler from '" + apiDirTSPath + "/" + IHandlerName + "'");
// export default
cp.writeln('\nexport default interface IAPI {', 1);
for (var _i = 0, _a = Object.keys(funcs); _i < _a.length; _i++) {
var funcName = _a[_i];
cp.writeln(funcName + ": IHandler." + funcName + ".IServerHandler;");
}
cp.writeln('};', -1);
return cp.end();
}
function codegenIHandler(funcs, config, cp) {
var apiDirTSPath = config.apiDirTSPath, schemasName = config.schemasName, utilsTSPath = config.utilsTSPath, responsePrefix = config.responsePrefix, validateStatus = config.validateStatus, stateTSPath = config.stateTSPath;
// import
cp.writeln("import * as Schemas from '" + apiDirTSPath + "/" + schemasName + "'");
cp.writeln('import {FullDate, StrictTypeParser as STP, APIPromise} ' +
("from '" + utilsTSPath + "'"));
cp.writeln('import {RouterContext as Context} from \'@koa/router\'');
cp.writeln('import {AxiosResponse} from \'axios\'');
cp.writeln(stateTSPath ?
"import IState from '" + stateTSPath + "'" : 'type IState = any');
// handler types
for (var _i = 0, _a = Object.entries(funcs); _i < _a.length; _i++) {
var _b = _a[_i], funcName = _b[0], func = _b[1];
var reqTypes = func.reqTypes, resTypes = func.resTypes, method = func.method;
cp.writeln("export namespace " + funcName + " {", 1);
// req
var sReqTypes = [];
// paras
for (var _c = 0, ELParameterIn_1 = OpenAPI_1.ELParameterIn; _c < ELParameterIn_1.length; _c++) {
var _in = ELParameterIn_1[_c];
var paras = reqTypes[_in];
if (paras == null)
continue;
cp.writeln("export type T_" + _in + " = {", 1);
for (var _d = 0, _e = Object.entries(paras); _d < _e.length; _d++) {
var _f = _e[_d], propName = _f[0], schemaType = _f[1];
cp.writeln(schemaType.forProp(propName) + ';');
}
cp.writeln('};', -1);
sReqTypes.push(_in + ": T_" + _in);
}
// body
var body = reqTypes.body;
if (body != null) {
// PATCH's req body: Partial
var typeName = body.typeName;
if (method == 'patch')
typeName = "Partial<" + typeName + ">";
cp.writeln("export type T_body = " + typeName + ";");
sReqTypes.push("body" + (body.required ? '' : '?') + ": T_body");
}
// IRequest
if (sReqTypes.length > 0) {
cp.writeln('interface IRequest {', 1);
for (var _g = 0, sReqTypes_1 = sReqTypes; _g < sReqTypes_1.length; _g++) {
var sReqType = sReqTypes_1[_g];
cp.writeln(sReqType + ";");
}
cp.writeln('}', -1);
}
else
cp.writeln('interface IRequest {}');
// res
cp.writeln('interface IResponses<T> {', 1);
for (var _h = 0, _j = Object.entries(resTypes); _h < _j.length; _h++) {
var _k = _j[_h], status_1 = _k[0], schema = _k[1];
cp.writeln("" + responsePrefix + status_1 + ": " + ("(" + schema.forProp('body') + ") => T;"));
}
cp.writeln('}', -1);
cp.writeln('export interface IServerHandler {', 1);
cp.writeln('(req: IRequest, res: IResponses<void>, ' +
'state: IState, ctx: Context): void;');
cp.writeln('}', -1);
// class _ResponsePromise
var validTypes = new Set();
cp.writeln('export class ResponsePromise<T> extends ' +
'APIPromise<T|T_ValidResponse> {', 1);
// handler
cp.writeln('private handlers: Partial<IResponses<T>> = {};');
// on
cp.writeln('on<K extends keyof IResponses<T>, U>(', 1);
cp.writeln('k: K, h: IResponses<U>[K]): ResponsePromise<T|U>');
cp.tab(-1);
cp.writeln('{ const e: ResponsePromise<T|U> = this; ' +
'e.handlers[k] = h; return e; }');
// onResponse
cp.writeln('onResponse(res: AxiosResponse<any>){', 1);
cp.writeln('const {status, data} = res');
cp.writeln('switch(status){', 1);
for (var _l = 0, _m = Object.entries(resTypes); _l < _m.length; _l++) {
var _o = _m[_l], status_2 = _o[0], schema = _o[1];
// TODO void -> string or any
var isValid = validateStatus(status_2);
cp.writeln("case " + status_2 + ": return this." + (isValid ? 'onSuccess' : 'onFail') + "(this.handlers[" + status_2 + "],", 1);
cp.writeln(schema.stp('data', 'res.body') + ");");
cp.tab(-1);
if (isValid)
validTypes.add(schema.typeName);
}
cp.writeln('}', -1); // end switch
cp.writeln('throw new Error(\'Unexpect status code: \'+status);');
cp.writeln('}', -1); // end onResponse
cp.writeln('}', -1); // end class
// valid type
var sValidTypes = Array.from(validTypes.values()).join(' | ');
cp.writeln("export type T_ValidResponse = " + sValidTypes + ";");
// export client handler
cp.writeln('export interface IClientHandler {', 1);
cp.writeln("(" + sReqTypes.join(', ') + "): ResponsePromise<never>;");
cp.writeln('}', -1); // end client handler
cp.writeln('}', -1); // end namespace
}
return cp.end();
}
function codegenRouter(funcs, config, cp) {
var apiDirTSPath = config.apiDirTSPath, schemasName = config.schemasName, responsePrefix = config.responsePrefix, ServerAPITSPath = config.ServerAPITSPath, utilsTSPath = config.utilsTSPath, stateTSPath = config.stateTSPath;
// import
cp.writeln("import * as Schemas from '" + apiDirTSPath + "/" + schemasName + "'");
cp.writeln("import * as Router from '@koa/router'");
cp.writeln("import {FullDate, StrictTypeParser as STP} from '" + utilsTSPath + "'");
cp.writeln("import * as bodyParser from 'koa-body'");
cp.writeln(stateTSPath ?
"import IState from '" + stateTSPath + "'" : 'type IState = any');
cp.writeln("type CTX = Router.RouterContext<IState>;");
// router
cp.writeln("\nconst router = new Router<IState>();");
cp.writeln('');
// function
cp.writeln('function isEmpty(x: any): boolean {', 1);
cp.writeln('if(x == null || x === \'\') return true;');
cp.writeln('if(typeof x === \'object\') return Object.keys(x).length===0');
cp.writeln('return false;');
cp.writeln('}', -1);
cp.writeln('function nullableParse<T>(v: any, ' +
'p: (x: any)=>T): T | undefined {', 1);
cp.writeln('return isEmpty(v) ? undefined : p(v);');
cp.writeln('}', -1);
cp.writeln('const ctxGetParas = {', 1);
cp.writeln('path: (ctx: CTX, attr: string) => ctx.params[attr],');
cp.writeln('query: (ctx: CTX, attr: string) => ctx.query[attr],');
cp.writeln('header: (ctx: CTX, attr: string) => ctx.headers[attr],');
cp.writeln('cookie: (ctx: CTX, attr: string) => ctx.cookies.get(attr),');
cp.writeln('};', -1);
// response generator
cp.writeln('function g_res<T>(ctx: CTX, ' +
'status: number, dft: string = \'\'){', 1);
cp.writeln('return (body: T) => {', 1);
cp.writeln('ctx.status = status;');
cp.writeln('ctx.body = body ?? dft;');
cp.writeln('}', -1);
cp.writeln('}', -1);
// route
cp.writeln("\nimport api from '" + ServerAPITSPath + "'");
for (var _i = 0, _a = Object.entries(funcs); _i < _a.length; _i++) {
var _b = _a[_i], funcName = _b[0], func = _b[1];
var method = func.method, url = func.url, reqTypes = func.reqTypes, resTypes = func.resTypes;
var isPartial = method === 'patch';
var statuses = Object.keys(resTypes);
// TODO escape
var sURL = url.replace(/{(.*?)}/g, ':$1'); // {a} -> :a
var mid = '';
if (reqTypes.body) {
var maxSize = reqTypes.body.maxSize;
var config_1 = maxSize == null ? '' : "{jsonLimit: '" + maxSize + "'}";
mid = "bodyParser(" + config_1 + "), ";
}
cp.writeln("router." + method + "('" + sURL + "', " + mid + "async ctx => {", 1);
// req
if (Object.keys(reqTypes).length === 0) {
cp.writeln('const req = {};');
}
else {
cp.writeln('let req;');
cp.writeln('const {body: reqBody} = ctx.request;');
cp.writeln('try { req = {', 1);
// paras
for (var _c = 0, ELParameterIn_2 = OpenAPI_1.ELParameterIn; _c < ELParameterIn_2.length; _c++) {
var _in = ELParameterIn_2[_c];
var paras = reqTypes[_in];
if (paras == null)
continue;
cp.writeln(_in + ": {", 1);
for (var _d = 0, _e = Object.entries(paras); _d < _e.length; _d++) {
var _f = _e[_d], name_1 = _f[0], schema = _f[1];
var pn = "ctxGetParas." + _in + "(ctx, '" + name_1 + "')";
var label = "req." + _in;
cp.writeln(name_1 + ": " + schema.stp(pn, label) + ",");
}
cp.writeln('},', -1);
}
// body
var body = reqTypes.body;
if (body != null) {
cp.writeln("body: " + body.stp('reqBody', 'req.body', isPartial));
}
cp.writeln('}} catch(err) {', -1);
cp.tab(1);
cp.writeln('if(err instanceof STP.BadValueError)', 1);
cp.writeln('return ctx.throw(400, err.toString());');
cp.tab(-1);
cp.writeln('throw err;');
cp.writeln('}', -1);
}
// res
cp.writeln('const res = {', 1);
for (var _g = 0, statuses_1 = statuses; _g < statuses_1.length; _g++) {
var status_3 = statuses_1[_g];
cp.writeln("" + responsePrefix + status_3 + ": g_res(ctx, " + status_3 + "),");
}
cp.writeln('};', -1);
// call
cp.writeln("await api." + funcName + "(req, res, ctx.state, ctx);");
cp.writeln('})', -1);
}
cp.writeln('\nexport default router;');
return cp.end();
}
function codegenIClientAPI(funcs, config, cp) {
var apiDirTSPath = config.apiDirTSPath, IHandlerName = config.IHandlerName;
// import
cp.writeln("import * as IHandler from '" + apiDirTSPath + "/" + IHandlerName + "'");
// export default
cp.writeln('\nexport default interface IAPI {', 1);
cp.writeln('$baseURL: string;');
for (var _i = 0, _a = Object.keys(funcs); _i < _a.length; _i++) {
var funcName = _a[_i];
cp.writeln(funcName + ": IHandler." + funcName + ".IClientHandler;");
}
cp.writeln('}', -1);
return cp.end();
}
function codegenClientAPI(funcs, config, cp) {
var apiDirTSPath = config.apiDirTSPath, IClientAPIName = config.IClientAPIName, IHandlerName = config.IHandlerName;
// import
cp.writeln("import * as _IAPI from '" + apiDirTSPath + "/" + IClientAPIName + "'");
cp.writeln("import IAPI from '" + apiDirTSPath + "/" + IClientAPIName + "'");
cp.writeln("import * as IHandler from '" + apiDirTSPath + "/" + IHandlerName + "'");
cp.writeln('import axios from \'axios\'');
// axios
cp.writeln('\nconst $http = axios.create({', 1);
cp.writeln('validateStatus: ()=>true,');
cp.writeln('});', -1);
// function
cp.writeln('\nfunction urlReplacer(url: string, ' +
'rules: {[_: string]: any}): string {', 1);
cp.writeln('for(const [attr, value] of Object.entries(rules))', 1);
cp.writeln('url = url.replace(\'{\'+attr+\'}\', value)');
cp.writeln('return url;', -1);
cp.writeln('};', -1);
// implementation
// export default
cp.writeln('\nexport default {', 1);
// set $baseURL
cp.writeln('set $baseURL(url: string) {', 1);
cp.writeln('$http.interceptors.request.use(async config => {', 1);
cp.writeln('config.baseURL = url;');
cp.writeln('return config;', -1);
cp.writeln('}, err => Promise.reject(err));', -1);
cp.writeln('},');
// functions
for (var _i = 0, _a = Object.entries(funcs); _i < _a.length; _i++) {
var _b = _a[_i], funcName = _b[0], func = _b[1];
var ncHandler = "IHandler." + funcName;
var method = func.method, url = func.url, reqTypes = func.reqTypes;
var query = reqTypes.query, header = reqTypes.header, path_1 = reqTypes.path, body = reqTypes.body; // TODO cookie
// name
cp.writeln(funcName + "(", 1);
// paras
for (var _c = 0, ELParameterIn_3 = OpenAPI_1.ELParameterIn; _c < ELParameterIn_3.length; _c++) {
var _in = ELParameterIn_3[_c];
var paras = reqTypes[_in];
if (paras == null)
continue;
var _required = false;
for (var _d = 0, _e = Object.values(paras); _d < _e.length; _d++) {
var required = _e[_d].required;
if (required) {
_required = true;
break;
}
}
cp.writeln(_in + ": " + ncHandler + ".T_" + _in + (_required ? '' : '={}') + ",");
}
// body
if (body != null) {
cp.writeln("body" + (body.required ? '' : '?') + ": " + ncHandler + ".T_body,");
}
// function body
cp.tab(-1);
cp.writeln("){return new " + ncHandler +
'.ResponsePromise<never>($http({', 1);
cp.writeln("method: '" + method + "',");
var sURL = "'" + url + "'";
cp.writeln("url: " + (path_1 ? "urlReplacer(" + sURL + ", path)" : sURL) + ",");
if (query)
cp.writeln('params: query,');
if (header)
cp.writeln('header: header,');
if (body != null)
cp.writeln('data: body,');
cp.writeln('}));},', -1);
}
cp.writeln('} as IAPI', -1);
return cp.end();
}
function codegenSchemas(schemas, config, cp) {
var utilsTSPath = config.utilsTSPath;
// import
cp.writeln("import {FullDate, StrictTypeParser as STP} from '" + utilsTSPath + "'");
cp.writeln();
// schema
for (var _i = 0, _a = Object.entries(schemas); _i < _a.length; _i++) {
var _b = _a[_i], typeName = _b[0], schema = _b[1];
if (OpenAPI_1.isObjectSchema(schema)) {
cp.writeln("export class " + typeName + " {", 1);
var propTypes = [];
for (var _c = 0, _d = Object.entries(schema.properties); _c < _d.length; _c++) {
var _e = _d[_c], propName = _e[0], prop = _e[1];
var propType = new OpenAPI_1.SchemaType(prop, true); // TODO required
propTypes.push([propName, propType]);
cp.writeln(propType.forProp(propName) + ';');
}
// method
cp.writeln('constructor(o: {[_: string]: any}){', 1);
for (var _f = 0, propTypes_1 = propTypes; _f < propTypes_1.length; _f++) {
var _g = propTypes_1[_f], n = _g[0], t = _g[1];
cp.writeln("this." + n + " = " + t.stp("o." + n, typeName + '.' + n) + ";");
}
cp.writeln('}', -1);
// Partial
cp.writeln("static Partial(o: {[_: string]: any}): Partial<" + typeName + "> {", 1);
cp.writeln("const r: Partial<" + typeName + "> = {};");
var locPartial = "Partial<" + typeName + ">";
for (var _h = 0, propTypes_2 = propTypes; _h < propTypes_2.length; _h++) {
var _j = propTypes_2[_h], n = _j[0], t = _j[1];
cp.writeln("if (o." + n + " !== undefined) r." + n + " = " + t.stp("o." + n, locPartial + '.' + n) + ";");
}
cp.writeln('return r;');
cp.writeln('}', -1);
// end of class
cp.writeln('}', -1);
}
else {
cp.writeln("export type " + typeName + " = " + OpenAPI_1.SchemaType.typeNameOf(schema));
}
}
// return
return cp.end();
}
function codegen(openAPI, configUser) {
var _a;
var config = Object.assign({}, configUser, Config_1.configDefault);
// prepare
fs.mkdirSync(config.outputDir, { recursive: true });
var apiFuncs = OpenAPI_1.apiFunctionsOf(openAPI);
var gCP = function (fn) { return new CodePrinter_1.CodePrinter(fs.createWriteStream(path.join(config.outputDir, fn + '.ts')), config.indentString); };
var ps = [];
// write files
// handler
ps.push(codegenIHandler(apiFuncs, config, gCP(config.IHandlerName)));
// server
ps.push(codegenIServerAPI(apiFuncs, config, gCP(config.IServerAPIName)));
ps.push(codegenRouter(apiFuncs, config, gCP(config.routerName)));
// client
ps.push(codegenIClientAPI(apiFuncs, config, gCP(config.IClientAPIName)));
ps.push(codegenClientAPI(apiFuncs, config, gCP(config.ClientAPIName)));
// schema
var schemas = (_a = openAPI.components) === null || _a === void 0 ? void 0 : _a.schemas;
if (schemas != null) {
ps.push(codegenSchemas(schemas, config, gCP(config.schemasName)));
}
// return
return Promise.all(ps);
}
exports.default = codegen;