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