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
This commit is contained in:
parent
8f97095fbc
commit
87615616d6
9 changed files with 266 additions and 130 deletions
4
dist/OpenAPI.d.ts
vendored
4
dist/OpenAPI.d.ts
vendored
|
@ -114,11 +114,11 @@ export declare class SchemaType {
|
||||||
get required(): boolean;
|
get required(): boolean;
|
||||||
get maxSize(): string | number | undefined;
|
get maxSize(): string | number | undefined;
|
||||||
forProp(prop: string): string;
|
forProp(prop: string): string;
|
||||||
stp(prop: string): string;
|
stp(prop: string, label: string, partial?: boolean): string;
|
||||||
private schema;
|
private schema;
|
||||||
constructor(schema: Schema | Reference | string, _required: boolean);
|
constructor(schema: Schema | Reference | string, _required: boolean);
|
||||||
static typeNameOf(schema: Schema | Reference): string;
|
static typeNameOf(schema: Schema | Reference): string;
|
||||||
static gcStp(para: string, schema: Schema | Reference): string;
|
static gcStp(para: string, schema: Schema | Reference, label: string, partial: boolean): string;
|
||||||
}
|
}
|
||||||
export declare type APIFunctions = {
|
export declare type APIFunctions = {
|
||||||
[_: string]: APIFunction;
|
[_: string]: APIFunction;
|
||||||
|
|
55
dist/OpenAPI.js
vendored
55
dist/OpenAPI.js
vendored
|
@ -70,8 +70,9 @@ var SchemaType = /** @class */ (function () {
|
||||||
SchemaType.prototype.forProp = function (prop) {
|
SchemaType.prototype.forProp = function (prop) {
|
||||||
return "" + prop + (this.required ? '' : '?') + ": " + this.typeName;
|
return "" + prop + (this.required ? '' : '?') + ": " + this.typeName;
|
||||||
};
|
};
|
||||||
SchemaType.prototype.stp = function (prop) {
|
SchemaType.prototype.stp = function (prop, label, partial) {
|
||||||
var stp = SchemaType.gcStp(prop, this.schema);
|
if (partial === void 0) { partial = false; }
|
||||||
|
var stp = SchemaType.gcStp(prop, this.schema, label, partial);
|
||||||
return (this.required ? '' : prop + "===undefined ? undefined : ") + stp;
|
return (this.required ? '' : prop + "===undefined ? undefined : ") + stp;
|
||||||
};
|
};
|
||||||
SchemaType.typeNameOf = function (schema) {
|
SchemaType.typeNameOf = function (schema) {
|
||||||
|
@ -118,27 +119,30 @@ var SchemaType = /** @class */ (function () {
|
||||||
sType = "Readonly<" + sType + ">";
|
sType = "Readonly<" + sType + ">";
|
||||||
return sType;
|
return sType;
|
||||||
};
|
};
|
||||||
SchemaType.gcStp = function (para, schema) {
|
SchemaType.gcStp = function (para, schema, label, partial) {
|
||||||
var sPara = "'" + para.replace(/'/g, '\\\'') + "'";
|
// partial: Object only, 1 layer only
|
||||||
// object
|
// object
|
||||||
if (isReference(schema)) {
|
if (isReference(schema)) {
|
||||||
return "new " + new SchemaType(schema, true).typeName + "(" + para + ")";
|
var typeName = new SchemaType(schema, true).typeName;
|
||||||
|
return partial ?
|
||||||
|
typeName + ".Partial(" + para + ")" :
|
||||||
|
"new " + typeName + "(" + para + ")";
|
||||||
}
|
}
|
||||||
// any
|
// any
|
||||||
var code;
|
|
||||||
var type = schema.type, nullable = schema.nullable, format = schema.format;
|
var type = schema.type, nullable = schema.nullable, format = schema.format;
|
||||||
|
var sStp;
|
||||||
if (type === 'any')
|
if (type === 'any')
|
||||||
return para;
|
return para;
|
||||||
if (isArraySchema(schema)) {
|
if (isArraySchema(schema)) {
|
||||||
code = "STP._Array(" + para + ", " + sPara + ").map(o=>" + SchemaType.gcStp('o', schema.items) + ")";
|
sStp = "(v, l)=>STP._Array(v, l, elm=>" + SchemaType.gcStp('elm', schema.items, label + "[]", false) + ")";
|
||||||
}
|
}
|
||||||
else if (isObjectSchema(schema)) {
|
else if (isObjectSchema(schema)) {
|
||||||
code = '{';
|
sStp = '()=>({';
|
||||||
for (var _i = 0, _a = Object.entries(schema.properties); _i < _a.length; _i++) {
|
for (var _i = 0, _a = Object.entries(schema.properties); _i < _a.length; _i++) {
|
||||||
var _b = _a[_i], name_2 = _b[0], sub = _b[1];
|
var _b = _a[_i], name_2 = _b[0], sub = _b[1];
|
||||||
code += name_2 + ": " + SchemaType.gcStp(para + '.' + name_2, sub) + ", ";
|
sStp += name_2 + ": " + SchemaType.gcStp(para + '.' + name_2, sub, label + '.' + name_2, false) + ", ";
|
||||||
}
|
}
|
||||||
code += '}';
|
sStp += '})';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var t = void 0;
|
var t = void 0;
|
||||||
|
@ -148,30 +152,37 @@ var SchemaType = /** @class */ (function () {
|
||||||
else if (format === 'date')
|
else if (format === 'date')
|
||||||
t = 'FullDate';
|
t = 'FullDate';
|
||||||
else if (format === 'byte')
|
else if (format === 'byte')
|
||||||
t = 'string'; // TODO
|
t = 'byte';
|
||||||
else if (format === 'binary')
|
else if (format === 'binary')
|
||||||
t = 'string'; // TODO
|
t = 'binary';
|
||||||
else {
|
else {
|
||||||
if (format)
|
if (format) {
|
||||||
warn("Unknown format " + format + ", use string instead");
|
warn("Unknown string format " + format + ", use string instead");
|
||||||
|
}
|
||||||
t = 'string';
|
t = 'string';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (type === 'integer')
|
else if (type === 'integer') {
|
||||||
t = 'number';
|
if (format === 'int32')
|
||||||
|
t = 'int32';
|
||||||
|
else {
|
||||||
|
warn("Unsupport integer format " + format + ", use number instead");
|
||||||
|
t = 'number'; // TODO int64
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
t = type;
|
t = type;
|
||||||
if (!StrictTypeParser_1.StrictTypeParser.supportTypes.includes(t)) {
|
if (!StrictTypeParser_1.StrictTypeParser.supportTypes.includes(t)) {
|
||||||
warn("Unknown type " + type + ", use any instead");
|
warn("Unsupport type " + type + " " + format + ", use any instead");
|
||||||
return para;
|
return para;
|
||||||
}
|
}
|
||||||
else
|
sStp = "STP._" + t;
|
||||||
code = "STP._" + t + "(" + para + ", " + sPara + ")";
|
|
||||||
}
|
}
|
||||||
// nullable
|
// nullable
|
||||||
if (nullable)
|
var funcName = nullable ? 'nullableParse' : 'parse';
|
||||||
code = para + "===null ? null : " + code;
|
// result
|
||||||
return code;
|
var sLabel = "'" + label.replace(/'/g, '\\\'') + "'"; // escape
|
||||||
|
return "STP." + funcName + "(" + sStp + ", " + para + ", " + sLabel + ")";
|
||||||
};
|
};
|
||||||
return SchemaType;
|
return SchemaType;
|
||||||
}());
|
}());
|
||||||
|
|
33
dist/codegen.js
vendored
33
dist/codegen.js
vendored
|
@ -102,7 +102,7 @@ function codegenIHandler(funcs, config, cp) {
|
||||||
// TODO void -> string or any
|
// TODO void -> string or any
|
||||||
var isValid = validateStatus(status_2);
|
var isValid = validateStatus(status_2);
|
||||||
cp.writeln("case " + status_2 + ": return this." + (isValid ? 'onSuccess' : 'onFail') + "(this.handlers[" + status_2 + "],", 1);
|
cp.writeln("case " + status_2 + ": return this." + (isValid ? 'onSuccess' : 'onFail') + "(this.handlers[" + status_2 + "],", 1);
|
||||||
cp.writeln(schema.stp('data') + ");");
|
cp.writeln(schema.stp('data', 'res.body') + ");");
|
||||||
cp.tab(-1);
|
cp.tab(-1);
|
||||||
if (isValid)
|
if (isValid)
|
||||||
validTypes.add(schema.typeName);
|
validTypes.add(schema.typeName);
|
||||||
|
@ -164,6 +164,7 @@ function codegenRouter(funcs, config, cp) {
|
||||||
for (var _i = 0, _a = Object.entries(funcs); _i < _a.length; _i++) {
|
for (var _i = 0, _a = Object.entries(funcs); _i < _a.length; _i++) {
|
||||||
var _b = _a[_i], funcName = _b[0], func = _b[1];
|
var _b = _a[_i], funcName = _b[0], func = _b[1];
|
||||||
var method = func.method, url = func.url, reqTypes = func.reqTypes, resTypes = func.resTypes;
|
var method = func.method, url = func.url, reqTypes = func.reqTypes, resTypes = func.resTypes;
|
||||||
|
var isPartial = method === 'patch';
|
||||||
var statuses = Object.keys(resTypes);
|
var statuses = Object.keys(resTypes);
|
||||||
// TODO escape
|
// TODO escape
|
||||||
var sURL = url.replace(/{(.*?)}/g, ':$1'); // {a} -> :a
|
var sURL = url.replace(/{(.*?)}/g, ':$1'); // {a} -> :a
|
||||||
|
@ -174,7 +175,7 @@ function codegenRouter(funcs, config, cp) {
|
||||||
mid = "bodyParser(" + config_1 + "), ";
|
mid = "bodyParser(" + config_1 + "), ";
|
||||||
}
|
}
|
||||||
cp.writeln("router." + method + "('" + sURL + "', " + mid + "async ctx => {", 1);
|
cp.writeln("router." + method + "('" + sURL + "', " + mid + "async ctx => {", 1);
|
||||||
// TODO permission check, etc
|
// req
|
||||||
if (Object.keys(reqTypes).length === 0) {
|
if (Object.keys(reqTypes).length === 0) {
|
||||||
cp.writeln('const req = {};');
|
cp.writeln('const req = {};');
|
||||||
}
|
}
|
||||||
|
@ -192,16 +193,15 @@ function codegenRouter(funcs, config, cp) {
|
||||||
for (var _d = 0, _e = Object.entries(paras); _d < _e.length; _d++) {
|
for (var _d = 0, _e = Object.entries(paras); _d < _e.length; _d++) {
|
||||||
var _f = _e[_d], name_1 = _f[0], schema = _f[1];
|
var _f = _e[_d], name_1 = _f[0], schema = _f[1];
|
||||||
var pn = "ctxGetParas." + _in + "(ctx, '" + name_1 + "')";
|
var pn = "ctxGetParas." + _in + "(ctx, '" + name_1 + "')";
|
||||||
cp.writeln(name_1 + ": " + schema.stp(pn) + ",");
|
var label = "req." + _in;
|
||||||
|
cp.writeln(name_1 + ": " + schema.stp(pn, label) + ",");
|
||||||
}
|
}
|
||||||
cp.writeln('},', -1);
|
cp.writeln('},', -1);
|
||||||
}
|
}
|
||||||
// body
|
// body
|
||||||
var body = reqTypes.body;
|
var body = reqTypes.body;
|
||||||
if (body != null) {
|
if (body != null) {
|
||||||
var name_2 = 'body';
|
cp.writeln("body: " + body.stp('reqBody', 'req.body', isPartial));
|
||||||
var pn = 'reqBody';
|
|
||||||
cp.writeln(name_2 + ": " + body.stp(pn));
|
|
||||||
}
|
}
|
||||||
cp.writeln('}} catch(err) {', -1);
|
cp.writeln('}} catch(err) {', -1);
|
||||||
cp.tab(1);
|
cp.tab(1);
|
||||||
|
@ -320,13 +320,13 @@ function codegenSchemas(schemas, config, cp) {
|
||||||
cp.writeln();
|
cp.writeln();
|
||||||
// schema
|
// schema
|
||||||
for (var _i = 0, _a = Object.entries(schemas); _i < _a.length; _i++) {
|
for (var _i = 0, _a = Object.entries(schemas); _i < _a.length; _i++) {
|
||||||
var _b = _a[_i], name_3 = _b[0], schema = _b[1];
|
var _b = _a[_i], typeName = _b[0], schema = _b[1];
|
||||||
if (OpenAPI_1.isObjectSchema(schema)) {
|
if (OpenAPI_1.isObjectSchema(schema)) {
|
||||||
cp.writeln("export class " + name_3 + " {", 1);
|
cp.writeln("export class " + typeName + " {", 1);
|
||||||
var propTypes = [];
|
var propTypes = [];
|
||||||
for (var _c = 0, _d = Object.entries(schema.properties); _c < _d.length; _c++) {
|
for (var _c = 0, _d = Object.entries(schema.properties); _c < _d.length; _c++) {
|
||||||
var _e = _d[_c], propName = _e[0], prop = _e[1];
|
var _e = _d[_c], propName = _e[0], prop = _e[1];
|
||||||
var propType = new OpenAPI_1.SchemaType(prop, true); // TODO required?
|
var propType = new OpenAPI_1.SchemaType(prop, true); // TODO required
|
||||||
propTypes.push([propName, propType]);
|
propTypes.push([propName, propType]);
|
||||||
cp.writeln(propType.forProp(propName) + ';');
|
cp.writeln(propType.forProp(propName) + ';');
|
||||||
}
|
}
|
||||||
|
@ -334,13 +334,24 @@ function codegenSchemas(schemas, config, cp) {
|
||||||
cp.writeln('constructor(o: {[_: string]: any}){', 1);
|
cp.writeln('constructor(o: {[_: string]: any}){', 1);
|
||||||
for (var _f = 0, propTypes_1 = propTypes; _f < propTypes_1.length; _f++) {
|
for (var _f = 0, propTypes_1 = propTypes; _f < propTypes_1.length; _f++) {
|
||||||
var _g = propTypes_1[_f], n = _g[0], t = _g[1];
|
var _g = propTypes_1[_f], n = _g[0], t = _g[1];
|
||||||
cp.writeln("this." + n + " = " + t.stp("o." + n) + ";");
|
cp.writeln("this." + n + " = " + t.stp("o." + n, typeName + '.' + n) + ";");
|
||||||
}
|
}
|
||||||
cp.writeln('}', -1);
|
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);
|
cp.writeln('}', -1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
cp.writeln("export type " + name_3 + " = " + OpenAPI_1.SchemaType.typeNameOf(schema));
|
cp.writeln("export type " + typeName + " = " + OpenAPI_1.SchemaType.typeNameOf(schema));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// return
|
// return
|
||||||
|
|
27
dist/utils/StrictTypeParser.d.ts
vendored
27
dist/utils/StrictTypeParser.d.ts
vendored
|
@ -1,18 +1,25 @@
|
||||||
import { FullDate } from './FullDate';
|
import { FullDate } from './FullDate';
|
||||||
export declare module StrictTypeParser {
|
export declare module StrictTypeParser {
|
||||||
class BadValueError extends Error {
|
class BadValueError extends Error {
|
||||||
attr: string;
|
label: string;
|
||||||
|
constructor(label: string, message: string);
|
||||||
|
}
|
||||||
|
class BadTypeError extends BadValueError {
|
||||||
|
label: string;
|
||||||
type: string;
|
type: string;
|
||||||
value: any;
|
value: any;
|
||||||
constructor(attr: string, type: string, value: any);
|
constructor(label: string, type: string, value: any);
|
||||||
}
|
}
|
||||||
function _number(x: any, attr: string): number;
|
function _int32(x: any, label: string): number;
|
||||||
function _string(x: any, attr: string): string;
|
function _number(x: any, label: string): number;
|
||||||
function _boolean(x: any, attr: string): boolean;
|
function _string(x: any, label: string): string;
|
||||||
function _Date(x: any, attr: string): Date;
|
function _boolean(x: any, label: string): boolean;
|
||||||
function _FullDate(x: any, attr: string): FullDate;
|
function _Date(x: any, label: string): Date;
|
||||||
function _byte(x: any, attr: string): string;
|
function _FullDate(x: any, label: string): FullDate;
|
||||||
function _binary(x: any, attr: string): string;
|
function _byte(x: any, label: string): string;
|
||||||
function _Array(x: any, attr: string): Array<any>;
|
function _binary(x: any, label: string): string;
|
||||||
|
function _Array<T>(x: any, label: string, mapper: (x: any) => T): Array<T>;
|
||||||
|
function parse<T>(stp: (val: any, label: string) => T, val: any, label: string): T;
|
||||||
|
function nullableParse<T>(stp: (val: any, label: string) => T, val: any, label: string): T | null;
|
||||||
const supportTypes: string[];
|
const supportTypes: string[];
|
||||||
}
|
}
|
||||||
|
|
98
dist/utils/StrictTypeParser.js
vendored
98
dist/utils/StrictTypeParser.js
vendored
|
@ -18,87 +18,129 @@ var StrictTypeParser;
|
||||||
(function (StrictTypeParser) {
|
(function (StrictTypeParser) {
|
||||||
var BadValueError = /** @class */ (function (_super) {
|
var BadValueError = /** @class */ (function (_super) {
|
||||||
__extends(BadValueError, _super);
|
__extends(BadValueError, _super);
|
||||||
function BadValueError(attr, type, value) {
|
function BadValueError(label, message) {
|
||||||
var _this = _super.call(this, attr + ": Can not convert `" + (['object', 'array'].includes(typeof value) ?
|
var _this = _super.call(this, message) || this;
|
||||||
JSON.stringify(value) : "" + value) + "` to type " + type) || this;
|
_this.label = label;
|
||||||
_this.attr = attr;
|
|
||||||
_this.type = type;
|
|
||||||
_this.value = value;
|
|
||||||
console.error(_this.message);
|
console.error(_this.message);
|
||||||
Object.setPrototypeOf(_this, BadValueError.prototype);
|
Object.setPrototypeOf(_this, BadTypeError.prototype);
|
||||||
return _this;
|
return _this;
|
||||||
}
|
}
|
||||||
return BadValueError;
|
return BadValueError;
|
||||||
}(Error));
|
}(Error));
|
||||||
StrictTypeParser.BadValueError = BadValueError;
|
StrictTypeParser.BadValueError = BadValueError;
|
||||||
function _number(x, attr) {
|
var BadTypeError = /** @class */ (function (_super) {
|
||||||
|
__extends(BadTypeError, _super);
|
||||||
|
function BadTypeError(label, type, value) {
|
||||||
|
var _this = _super.call(this, label, label + ": Can not convert `" + (['object', 'array'].includes(typeof value) ?
|
||||||
|
JSON.stringify(value) : "" + value) + "` to type " + type) || this;
|
||||||
|
_this.label = label;
|
||||||
|
_this.type = type;
|
||||||
|
_this.value = value;
|
||||||
|
return _this;
|
||||||
|
}
|
||||||
|
return BadTypeError;
|
||||||
|
}(BadValueError));
|
||||||
|
StrictTypeParser.BadTypeError = BadTypeError;
|
||||||
|
function _int32(x, label) {
|
||||||
|
if (typeof x === 'number' && x === (x | 0))
|
||||||
|
return x;
|
||||||
|
if (typeof x === 'string') { // convert from url
|
||||||
|
var r = +x | 0;
|
||||||
|
if (x === r.toString())
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
throw new BadTypeError(label, 'int32', x);
|
||||||
|
}
|
||||||
|
StrictTypeParser._int32 = _int32;
|
||||||
|
function _number(x, label) {
|
||||||
if (typeof x === 'number')
|
if (typeof x === 'number')
|
||||||
return x;
|
return x;
|
||||||
if (typeof x === 'string') {
|
if (typeof x === 'string') { // convert from url
|
||||||
var r = +x;
|
var r = +x;
|
||||||
if (!isNaN(r))
|
if (!isNaN(r))
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
throw new BadValueError(attr, 'number', x);
|
throw new BadTypeError(label, 'number', x);
|
||||||
}
|
}
|
||||||
StrictTypeParser._number = _number;
|
StrictTypeParser._number = _number;
|
||||||
function _string(x, attr) {
|
function _string(x, label) {
|
||||||
if (typeof x === 'string')
|
if (typeof x === 'string')
|
||||||
return x;
|
return x;
|
||||||
if (typeof x === 'object')
|
if (typeof x === 'object')
|
||||||
return x.toString();
|
return x.toString();
|
||||||
throw new BadValueError(attr, 'string', x);
|
throw new BadTypeError(label, 'string', x);
|
||||||
}
|
}
|
||||||
StrictTypeParser._string = _string;
|
StrictTypeParser._string = _string;
|
||||||
function _boolean(x, attr) {
|
function _boolean(x, label) {
|
||||||
if (typeof x === 'boolean')
|
if (typeof x === 'boolean')
|
||||||
return x;
|
return x;
|
||||||
if (x === 'true')
|
if (x === 'true')
|
||||||
return true;
|
return true;
|
||||||
if (x === 'false')
|
if (x === 'false')
|
||||||
return false;
|
return false;
|
||||||
throw new BadValueError(attr, 'boolean', x);
|
throw new BadTypeError(label, 'boolean', x);
|
||||||
}
|
}
|
||||||
StrictTypeParser._boolean = _boolean;
|
StrictTypeParser._boolean = _boolean;
|
||||||
function _Date(x, attr) {
|
function _Date(x, label) {
|
||||||
var r = new Date(x);
|
var r = new Date(x);
|
||||||
if (!isNaN(+r))
|
if (x != null && !isNaN(+r))
|
||||||
return r;
|
return r;
|
||||||
throw new BadValueError(attr, 'Date', x);
|
throw new BadTypeError(label, 'Date', x);
|
||||||
}
|
}
|
||||||
StrictTypeParser._Date = _Date;
|
StrictTypeParser._Date = _Date;
|
||||||
function _FullDate(x, attr) {
|
function _FullDate(x, label) {
|
||||||
var r = new FullDate_1.FullDate(x);
|
var r = new FullDate_1.FullDate(x);
|
||||||
if (!isNaN(+r))
|
if (x != null && !isNaN(+r))
|
||||||
return r;
|
return r;
|
||||||
throw new BadValueError(attr, 'FullDate', x);
|
throw new BadTypeError(label, 'FullDate', x);
|
||||||
}
|
}
|
||||||
StrictTypeParser._FullDate = _FullDate;
|
StrictTypeParser._FullDate = _FullDate;
|
||||||
function _byte(x, attr) {
|
function _byte(x, label) {
|
||||||
if (typeof x === 'string')
|
if (typeof x === 'string')
|
||||||
return x;
|
return x;
|
||||||
if (x instanceof Buffer)
|
if (x instanceof Buffer)
|
||||||
return x.toString('base64');
|
return x.toString('base64');
|
||||||
throw new BadValueError(attr, 'byte', x);
|
throw new BadTypeError(label, 'byte', x);
|
||||||
}
|
}
|
||||||
StrictTypeParser._byte = _byte;
|
StrictTypeParser._byte = _byte;
|
||||||
function _binary(x, attr) {
|
function _binary(x, label) {
|
||||||
if (typeof x === 'string')
|
if (typeof x === 'string')
|
||||||
return x;
|
return x;
|
||||||
if (x instanceof Buffer)
|
if (x instanceof Buffer)
|
||||||
return x.toString('hex');
|
return x.toString('hex');
|
||||||
if ((x === null || x === void 0 ? void 0 : x.buffer) instanceof Buffer)
|
if ((x === null || x === void 0 ? void 0 : x.buffer) instanceof Buffer)
|
||||||
return x.toString('hex');
|
return x.toString('hex');
|
||||||
throw new BadValueError(attr, 'binary', x);
|
throw new BadTypeError(label, 'binary', x);
|
||||||
}
|
}
|
||||||
StrictTypeParser._binary = _binary;
|
StrictTypeParser._binary = _binary;
|
||||||
function _Array(x, attr) {
|
function _Array(x, label, mapper) {
|
||||||
if (x instanceof Array)
|
if (x instanceof Array)
|
||||||
return x;
|
return x.map(mapper);
|
||||||
throw new BadValueError(attr, 'Array', x);
|
throw new BadTypeError(label, 'Array', x);
|
||||||
}
|
}
|
||||||
StrictTypeParser._Array = _Array;
|
StrictTypeParser._Array = _Array;
|
||||||
|
function undefinedCheck(val, label) {
|
||||||
|
if (val === undefined) {
|
||||||
|
throw new BadValueError(label, label + " is required, but got undefined");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function parse(stp, val, label) {
|
||||||
|
// body
|
||||||
|
undefinedCheck(val, label);
|
||||||
|
if (val === null) {
|
||||||
|
throw new BadValueError(label, label + " is not nullable, but got null");
|
||||||
|
}
|
||||||
|
return stp(val, label);
|
||||||
|
}
|
||||||
|
StrictTypeParser.parse = parse;
|
||||||
|
function nullableParse(stp, val, label) {
|
||||||
|
// body
|
||||||
|
undefinedCheck(val, label);
|
||||||
|
return val === null ? null : stp(val, label);
|
||||||
|
}
|
||||||
|
StrictTypeParser.nullableParse = nullableParse;
|
||||||
StrictTypeParser.supportTypes = [
|
StrictTypeParser.supportTypes = [
|
||||||
'number', 'string', 'boolean', 'Date', 'FullDate', 'byte', 'binary'
|
'int32', 'number', 'string', 'boolean',
|
||||||
|
'Date', 'FullDate', 'byte', 'binary'
|
||||||
];
|
];
|
||||||
})(StrictTypeParser = exports.StrictTypeParser || (exports.StrictTypeParser = {}));
|
})(StrictTypeParser = exports.StrictTypeParser || (exports.StrictTypeParser = {}));
|
||||||
|
|
|
@ -145,8 +145,8 @@ export class SchemaType {
|
||||||
forProp(prop: string): string {
|
forProp(prop: string): string {
|
||||||
return `${prop}${this.required ? '' : '?'}: ${this.typeName}`;
|
return `${prop}${this.required ? '' : '?'}: ${this.typeName}`;
|
||||||
}
|
}
|
||||||
stp(prop: string): string {
|
stp(prop: string, label: string, partial: boolean=false): string {
|
||||||
const stp = SchemaType.gcStp(prop, this.schema);
|
const stp = SchemaType.gcStp(prop, this.schema, label, partial);
|
||||||
return (this.required ? '' : `${prop}===undefined ? undefined : `)+stp;
|
return (this.required ? '' : `${prop}===undefined ? undefined : `)+stp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,46 +189,61 @@ export class SchemaType {
|
||||||
if (readOnly) sType = `Readonly<${sType}>`;
|
if (readOnly) sType = `Readonly<${sType}>`;
|
||||||
return sType;
|
return sType;
|
||||||
}
|
}
|
||||||
static gcStp(para: string, schema: Schema | Reference): string {
|
static gcStp(para: string, schema: Schema | Reference,
|
||||||
const sPara = `'${para.replace(/'/g, '\\\'')}'`;
|
label: string, partial: boolean): string {
|
||||||
|
// partial: Object only, 1 layer only
|
||||||
// object
|
// object
|
||||||
if (isReference(schema)) {
|
if (isReference(schema)) {
|
||||||
return `new ${new SchemaType(schema, true).typeName}(${para})`;
|
const typeName = new SchemaType(schema, true).typeName;
|
||||||
|
return partial ?
|
||||||
|
`${typeName}.Partial(${para})` :
|
||||||
|
`new ${typeName}(${para})`;
|
||||||
}
|
}
|
||||||
// any
|
// any
|
||||||
let code;
|
|
||||||
const {type, nullable, format} = schema;
|
const {type, nullable, format} = schema;
|
||||||
|
let sStp;
|
||||||
if (type === 'any') return para;
|
if (type === 'any') return para;
|
||||||
if (isArraySchema(schema)) {
|
if (isArraySchema(schema)) {
|
||||||
code = `STP._Array(${para}, ${sPara}).map(o=>${
|
sStp = `(v, l)=>STP._Array(v, l, elm=>${
|
||||||
SchemaType.gcStp('o', schema.items)})`;
|
SchemaType.gcStp('elm', schema.items, `${label}[]`, false)})`;
|
||||||
} else if (isObjectSchema(schema)) {
|
} else if (isObjectSchema(schema)) {
|
||||||
code = '{';
|
sStp = '()=>({';
|
||||||
for (const [name, sub] of Object.entries(schema.properties)) {
|
for (const [name, sub] of Object.entries(schema.properties)) {
|
||||||
code += `${name}: ${SchemaType.gcStp(para+'.'+name, sub)}, `;
|
sStp += `${name}: ${
|
||||||
|
SchemaType.gcStp(para+'.'+name, sub, label+'.'+name, false)}, `;
|
||||||
}
|
}
|
||||||
code += '}';
|
sStp += '})';
|
||||||
} else {
|
} else {
|
||||||
let t;
|
let t;
|
||||||
if (type === 'string') {
|
if (type === 'string') {
|
||||||
if (format === 'date-time') t = 'Date';
|
if (format === 'date-time') t = 'Date';
|
||||||
else if (format === 'date') t = 'FullDate';
|
else if (format === 'date') t = 'FullDate';
|
||||||
else if (format === 'byte') t = 'string'; // TODO
|
else if (format === 'byte') t = 'byte';
|
||||||
else if (format === 'binary') t = 'string'; // TODO
|
else if (format === 'binary') t = 'binary';
|
||||||
else {
|
else {
|
||||||
if (format) warn(`Unknown format ${format}, use string instead`);
|
if (format) {
|
||||||
|
warn(`Unknown string format ${format}, use string instead`);
|
||||||
|
}
|
||||||
t = 'string';
|
t = 'string';
|
||||||
}
|
}
|
||||||
} else if (type === 'integer') t = 'number';
|
} else if (type === 'integer') {
|
||||||
else t = type;
|
if (format === 'int32') t = 'int32';
|
||||||
|
else {
|
||||||
|
warn(`Unsupport integer format ${format}, use number instead`);
|
||||||
|
t = 'number'; // TODO int64
|
||||||
|
}
|
||||||
|
} else t = type;
|
||||||
if (!STP.supportTypes.includes(t)) {
|
if (!STP.supportTypes.includes(t)) {
|
||||||
warn(`Unknown type ${type}, use any instead`);
|
warn(`Unsupport type ${type} ${format}, use any instead`);
|
||||||
return para;
|
return para;
|
||||||
} else code = `STP._${t}(${para}, ${sPara})`;
|
}
|
||||||
|
sStp = `STP._${t}`;
|
||||||
}
|
}
|
||||||
// nullable
|
// nullable
|
||||||
if (nullable) code = `${para}===null ? null : ${code}`;
|
const funcName = nullable ? 'nullableParse' : 'parse';
|
||||||
return code;
|
// result
|
||||||
|
const sLabel = `'${label.replace(/'/g, '\\\'')}'`; // escape
|
||||||
|
return `STP.${funcName}(${sStp}, ${para}, ${sLabel})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -101,7 +101,7 @@ function codegenIHandler(funcs: APIFuncs, config: Config, cp: CodePrinter) {
|
||||||
cp.writeln(`case ${status}: return this.${
|
cp.writeln(`case ${status}: return this.${
|
||||||
isValid ? 'onSuccess' : 'onFail'
|
isValid ? 'onSuccess' : 'onFail'
|
||||||
}(this.handlers[${status}],`, 1);
|
}(this.handlers[${status}],`, 1);
|
||||||
cp.writeln(`${schema.stp('data')});`);
|
cp.writeln(`${schema.stp('data', 'res.body')});`);
|
||||||
cp.tab(-1);
|
cp.tab(-1);
|
||||||
if (isValid) validTypes.add(schema.typeName);
|
if (isValid) validTypes.add(schema.typeName);
|
||||||
}
|
}
|
||||||
|
@ -167,6 +167,7 @@ function codegenRouter(funcs: APIFuncs, config: Config, cp: CodePrinter) {
|
||||||
const {
|
const {
|
||||||
method, url, reqTypes, resTypes,
|
method, url, reqTypes, resTypes,
|
||||||
} = func;
|
} = func;
|
||||||
|
const isPartial = method === 'patch';
|
||||||
const statuses = Object.keys(resTypes);
|
const statuses = Object.keys(resTypes);
|
||||||
// TODO escape
|
// TODO escape
|
||||||
const sURL = url.replace(/{(.*?)}/g, ':$1'); // {a} -> :a
|
const sURL = url.replace(/{(.*?)}/g, ':$1'); // {a} -> :a
|
||||||
|
@ -177,7 +178,7 @@ function codegenRouter(funcs: APIFuncs, config: Config, cp: CodePrinter) {
|
||||||
mid = `bodyParser(${config}), `;
|
mid = `bodyParser(${config}), `;
|
||||||
}
|
}
|
||||||
cp.writeln(`router.${method}('${sURL}', ${mid}async ctx => {`, 1);
|
cp.writeln(`router.${method}('${sURL}', ${mid}async ctx => {`, 1);
|
||||||
// TODO permission check, etc
|
// req
|
||||||
if (Object.keys(reqTypes).length === 0) {
|
if (Object.keys(reqTypes).length === 0) {
|
||||||
cp.writeln('const req = {};');
|
cp.writeln('const req = {};');
|
||||||
} else {
|
} else {
|
||||||
|
@ -191,16 +192,15 @@ function codegenRouter(funcs: APIFuncs, config: Config, cp: CodePrinter) {
|
||||||
cp.writeln(`${_in}: {`, 1);
|
cp.writeln(`${_in}: {`, 1);
|
||||||
for (const [name, schema] of Object.entries(paras)) {
|
for (const [name, schema] of Object.entries(paras)) {
|
||||||
const pn = `ctxGetParas.${_in}(ctx, '${name}')`;
|
const pn = `ctxGetParas.${_in}(ctx, '${name}')`;
|
||||||
cp.writeln(`${name}: ${schema.stp(pn)},`);
|
const label = `req.${_in}`;
|
||||||
|
cp.writeln(`${name}: ${schema.stp(pn, label)},`);
|
||||||
}
|
}
|
||||||
cp.writeln('},', -1);
|
cp.writeln('},', -1);
|
||||||
}
|
}
|
||||||
// body
|
// body
|
||||||
const {body} = reqTypes;
|
const {body} = reqTypes;
|
||||||
if (body != null) {
|
if (body != null) {
|
||||||
const name = 'body';
|
cp.writeln(`body: ${body.stp('reqBody', 'req.body', isPartial)}`);
|
||||||
const pn = 'reqBody';
|
|
||||||
cp.writeln(`${name}: ${body.stp(pn)}`);
|
|
||||||
}
|
}
|
||||||
cp.writeln('}} catch(err) {', -1); cp.tab(1);
|
cp.writeln('}} catch(err) {', -1); cp.tab(1);
|
||||||
cp.writeln('if(err instanceof STP.BadValueError)', 1);
|
cp.writeln('if(err instanceof STP.BadValueError)', 1);
|
||||||
|
@ -316,24 +316,36 @@ function codegenSchemas(schemas: Schemas, config: Config, cp: CodePrinter) {
|
||||||
`import {FullDate, StrictTypeParser as STP} from '${utilsTSPath}'`);
|
`import {FullDate, StrictTypeParser as STP} from '${utilsTSPath}'`);
|
||||||
cp.writeln();
|
cp.writeln();
|
||||||
// schema
|
// schema
|
||||||
for (const [name, schema] of Object.entries(schemas)) {
|
for (const [typeName, schema] of Object.entries(schemas)) {
|
||||||
if (isObjectSchema(schema)) {
|
if (isObjectSchema(schema)) {
|
||||||
cp.writeln(`export class ${name} {`, 1);
|
cp.writeln(`export class ${typeName} {`, 1);
|
||||||
const propTypes: [string, SchemaType][] = [];
|
const propTypes: [string, SchemaType][] = [];
|
||||||
for (const [propName, prop] of Object.entries(schema.properties)) {
|
for (const [propName, prop] of Object.entries(schema.properties)) {
|
||||||
const propType = new SchemaType(prop, true); // TODO required?
|
const propType = new SchemaType(prop, true); // TODO required
|
||||||
propTypes.push([propName, propType]);
|
propTypes.push([propName, propType]);
|
||||||
cp.writeln(propType.forProp(propName)+';');
|
cp.writeln(propType.forProp(propName)+';');
|
||||||
}
|
}
|
||||||
// method
|
// method
|
||||||
cp.writeln('constructor(o: {[_: string]: any}){', 1);
|
cp.writeln('constructor(o: {[_: string]: any}){', 1);
|
||||||
for (const [n, t] of propTypes) {
|
for (const [n, t] of propTypes) {
|
||||||
cp.writeln(`this.${n} = ${t.stp(`o.${n}`)};`);
|
cp.writeln(`this.${n} = ${t.stp(`o.${n}`, typeName+'.'+n)};`);
|
||||||
}
|
}
|
||||||
cp.writeln('}', -1);
|
cp.writeln('}', -1);
|
||||||
|
// Partial
|
||||||
|
cp.writeln(
|
||||||
|
`static Partial(o: {[_: string]: any}): Partial<${typeName}> {`, 1);
|
||||||
|
cp.writeln(`const r: Partial<${typeName}> = {};`);
|
||||||
|
const locPartial = `Partial<${typeName}>`;
|
||||||
|
for (const [n, t] of propTypes) {
|
||||||
|
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);
|
cp.writeln('}', -1);
|
||||||
} else {
|
} else {
|
||||||
cp.writeln(`export type ${name} = ${SchemaType.typeNameOf(schema)}`);
|
cp.writeln(`export type ${typeName} = ${SchemaType.typeNameOf(schema)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// return
|
// return
|
||||||
|
|
|
@ -2,60 +2,98 @@ import {FullDate} from './FullDate';
|
||||||
|
|
||||||
export module StrictTypeParser {
|
export module StrictTypeParser {
|
||||||
export class BadValueError extends Error {
|
export class BadValueError extends Error {
|
||||||
constructor(public attr: string, public type: string, public value: any) {
|
constructor(public label: string, message: string) {
|
||||||
super(`${attr}: Can not convert \`${
|
super(message);
|
||||||
|
console.error(this.message);
|
||||||
|
Object.setPrototypeOf(this, BadTypeError.prototype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class BadTypeError extends BadValueError {
|
||||||
|
constructor(public label: string, public type: string, public value: any) {
|
||||||
|
super(label, `${label}: Can not convert \`${
|
||||||
['object', 'array'].includes(typeof value) ?
|
['object', 'array'].includes(typeof value) ?
|
||||||
JSON.stringify(value) : `${value}`
|
JSON.stringify(value) : `${value}`
|
||||||
}\` to type ${type}`);
|
}\` to type ${type}`);
|
||||||
console.error(this.message);
|
|
||||||
Object.setPrototypeOf(this, BadValueError.prototype);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function _number(x: any, attr: string): number {
|
export function _int32(x: any, label: string): number {
|
||||||
|
if (typeof x === 'number' && x === (x|0)) return x;
|
||||||
|
if (typeof x === 'string') { // convert from url
|
||||||
|
const r = +x|0;
|
||||||
|
if (x === r.toString()) return r;
|
||||||
|
}
|
||||||
|
throw new BadTypeError(label, 'int32', x);
|
||||||
|
}
|
||||||
|
export function _number(x: any, label: string): number {
|
||||||
if (typeof x === 'number') return x;
|
if (typeof x === 'number') return x;
|
||||||
if (typeof x === 'string') {
|
if (typeof x === 'string') { // convert from url
|
||||||
const r = +x;
|
const r = +x;
|
||||||
if (!isNaN(r)) return r;
|
if (!isNaN(r)) return r;
|
||||||
}
|
}
|
||||||
throw new BadValueError(attr, 'number', x);
|
throw new BadTypeError(label, 'number', x);
|
||||||
}
|
}
|
||||||
export function _string(x: any, attr: string): string {
|
export function _string(x: any, label: string): string {
|
||||||
if (typeof x === 'string') return x;
|
if (typeof x === 'string') return x;
|
||||||
if (typeof x === 'object') return x.toString();
|
if (typeof x === 'object') return x.toString();
|
||||||
throw new BadValueError(attr, 'string', x);
|
throw new BadTypeError(label, 'string', x);
|
||||||
}
|
}
|
||||||
export function _boolean(x: any, attr: string): boolean {
|
export function _boolean(x: any, label: string): boolean {
|
||||||
if (typeof x === 'boolean') return x;
|
if (typeof x === 'boolean') return x;
|
||||||
if (x==='true') return true;
|
if (x==='true') return true;
|
||||||
if (x==='false') return false;
|
if (x==='false') return false;
|
||||||
throw new BadValueError(attr, 'boolean', x);
|
throw new BadTypeError(label, 'boolean', x);
|
||||||
}
|
}
|
||||||
export function _Date(x: any, attr: string): Date {
|
export function _Date(x: any, label: string): Date {
|
||||||
const r = new Date(x);
|
const r = new Date(x);
|
||||||
if (!isNaN(+r)) return r;
|
if (x != null && !isNaN(+r)) return r;
|
||||||
throw new BadValueError(attr, 'Date', x);
|
throw new BadTypeError(label, 'Date', x);
|
||||||
}
|
}
|
||||||
export function _FullDate(x: any, attr: string): FullDate {
|
export function _FullDate(x: any, label: string): FullDate {
|
||||||
const r = new FullDate(x);
|
const r = new FullDate(x);
|
||||||
if (!isNaN(+r)) return r;
|
if (x != null && !isNaN(+r)) return r;
|
||||||
throw new BadValueError(attr, 'FullDate', x);
|
throw new BadTypeError(label, 'FullDate', x);
|
||||||
}
|
}
|
||||||
export function _byte(x: any, attr: string): string {
|
export function _byte(x: any, label: string): string {
|
||||||
if (typeof x === 'string') return x;
|
if (typeof x === 'string') return x;
|
||||||
if (x instanceof Buffer) return x.toString('base64');
|
if (x instanceof Buffer) return x.toString('base64');
|
||||||
throw new BadValueError(attr, 'byte', x);
|
throw new BadTypeError(label, 'byte', x);
|
||||||
}
|
}
|
||||||
export function _binary(x: any, attr: string): string {
|
export function _binary(x: any, label: string): string {
|
||||||
if (typeof x === 'string') return x;
|
if (typeof x === 'string') return x;
|
||||||
if (x instanceof Buffer) return x.toString('hex');
|
if (x instanceof Buffer) return x.toString('hex');
|
||||||
if (x?.buffer instanceof Buffer) return x.toString('hex');
|
if (x?.buffer instanceof Buffer) return x.toString('hex');
|
||||||
throw new BadValueError(attr, 'binary', x);
|
throw new BadTypeError(label, 'binary', x);
|
||||||
}
|
}
|
||||||
export function _Array(x: any, attr: string): Array<any> {
|
export function _Array<T>(x: any, label: string,
|
||||||
if (x instanceof Array) return x;
|
mapper: (x: any)=>T): Array<T> {
|
||||||
throw new BadValueError(attr, 'Array', x);
|
if (x instanceof Array) return x.map(mapper);
|
||||||
|
throw new BadTypeError(label, 'Array', x);
|
||||||
|
}
|
||||||
|
|
||||||
|
function undefinedCheck(val: any, label: string) {
|
||||||
|
if (val === undefined) {
|
||||||
|
throw new BadValueError(label,
|
||||||
|
`${label} is required, but got undefined`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function parse<T>(
|
||||||
|
stp: (val: any, label: string)=>T, val: any, label: string): T {
|
||||||
|
// body
|
||||||
|
undefinedCheck(val, label);
|
||||||
|
if (val === null) {
|
||||||
|
throw new BadValueError(label,
|
||||||
|
`${label} is not nullable, but got null`);
|
||||||
|
}
|
||||||
|
return stp(val, label);
|
||||||
|
}
|
||||||
|
export function nullableParse<T>(
|
||||||
|
stp: (val: any, label: string)=>T, val: any, label: string): T | null {
|
||||||
|
// body
|
||||||
|
undefinedCheck(val, label);
|
||||||
|
return val === null ? null : stp(val, label);
|
||||||
}
|
}
|
||||||
export const supportTypes = [
|
export const supportTypes = [
|
||||||
'number', 'string', 'boolean', 'Date', 'FullDate', 'byte', 'binary'];
|
'int32', 'number', 'string', 'boolean',
|
||||||
|
'Date', 'FullDate', 'byte', 'binary'];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "api-codegen-ts",
|
"name": "api-codegen-ts",
|
||||||
"version": "1.0.1",
|
"version": "1.1.0",
|
||||||
"description": "OpenAPI code generator for TypeScript",
|
"description": "OpenAPI code generator for TypeScript",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|
Reference in a new issue