Archived
1
0
Fork 0

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:
supmiku39 2020-04-08 20:17:57 +09:00
parent 8f97095fbc
commit 87615616d6
9 changed files with 266 additions and 130 deletions

4
dist/OpenAPI.d.ts vendored
View file

@ -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
View file

@ -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
View file

@ -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

View file

@ -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[];
} }

View file

@ -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 = {}));

View file

@ -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})`;
} }
} }

View file

@ -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

View file

@ -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'];
} }

View file

@ -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",