From 87615616d66e732311cf5548e8dda906a5bd9e1d Mon Sep 17 00:00:00 2001 From: supmiku39 Date: Wed, 8 Apr 2020 20:17:57 +0900 Subject: [PATCH] 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 --- dist/OpenAPI.d.ts | 4 +- dist/OpenAPI.js | 55 +++++++++++------- dist/codegen.js | 33 +++++++---- dist/utils/StrictTypeParser.d.ts | 27 +++++---- dist/utils/StrictTypeParser.js | 98 +++++++++++++++++++++++--------- lib/OpenAPI.ts | 55 +++++++++++------- lib/codegen.ts | 34 +++++++---- lib/utils/StrictTypeParser.ts | 88 ++++++++++++++++++++-------- package.json | 2 +- 9 files changed, 266 insertions(+), 130 deletions(-) diff --git a/dist/OpenAPI.d.ts b/dist/OpenAPI.d.ts index 20b0e0b..5475932 100644 --- a/dist/OpenAPI.d.ts +++ b/dist/OpenAPI.d.ts @@ -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; diff --git a/dist/OpenAPI.js b/dist/OpenAPI.js index c4362f6..fd21066 100644 --- a/dist/OpenAPI.js +++ b/dist/OpenAPI.js @@ -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; }()); diff --git a/dist/codegen.js b/dist/codegen.js index 00a2064..f002c3a 100644 --- a/dist/codegen.js +++ b/dist/codegen.js @@ -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 diff --git a/dist/utils/StrictTypeParser.d.ts b/dist/utils/StrictTypeParser.d.ts index 6ed13c6..d8fd655 100644 --- a/dist/utils/StrictTypeParser.d.ts +++ b/dist/utils/StrictTypeParser.d.ts @@ -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; + 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(x: any, label: string, mapper: (x: any) => T): Array; + function parse(stp: (val: any, label: string) => T, val: any, label: string): T; + function nullableParse(stp: (val: any, label: string) => T, val: any, label: string): T | null; const supportTypes: string[]; } diff --git a/dist/utils/StrictTypeParser.js b/dist/utils/StrictTypeParser.js index e6f7afb..db6eb39 100644 --- a/dist/utils/StrictTypeParser.js +++ b/dist/utils/StrictTypeParser.js @@ -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 = {})); diff --git a/lib/OpenAPI.ts b/lib/OpenAPI.ts index 2149beb..455a111 100644 --- a/lib/OpenAPI.ts +++ b/lib/OpenAPI.ts @@ -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})`; } } diff --git a/lib/codegen.ts b/lib/codegen.ts index 7c90cae..55e1d92 100644 --- a/lib/codegen.ts +++ b/lib/codegen.ts @@ -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 diff --git a/lib/utils/StrictTypeParser.ts b/lib/utils/StrictTypeParser.ts index bc938cf..9aa7bb5 100644 --- a/lib/utils/StrictTypeParser.ts +++ b/lib/utils/StrictTypeParser.ts @@ -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 { - if (x instanceof Array) return x; - throw new BadValueError(attr, 'Array', x); + export function _Array(x: any, label: string, + mapper: (x: any)=>T): Array { + 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( + 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( + 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']; } diff --git a/package.json b/package.json index 3cce05b..97884c8 100644 --- a/package.json +++ b/package.json @@ -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",