From cf49fca928f989c273893ec530d1dbc691320ed4 Mon Sep 17 00:00:00 2001 From: K35 Date: Sun, 2 Jan 2022 22:02:47 +0000 Subject: [PATCH] Add verification and value stripping --- examples/test.ts | 41 ++++++-- lib/jrpc.js | 91 +++++++++++------ package.json | 2 +- src/compile.ts | 1 + src/targets/typescript.ts | 179 +++++++++++++++++++++------------ templates/ts_base.ts | 43 ++++++++ templates/ts_service_base.ts | 22 ---- templates/ts_service_client.ts | 8 +- templates/ts_service_server.ts | 10 ++ 9 files changed, 272 insertions(+), 125 deletions(-) create mode 100644 templates/ts_base.ts diff --git a/examples/test.ts b/examples/test.ts index d36083c..e74bf58 100644 --- a/examples/test.ts +++ b/examples/test.ts @@ -4,6 +4,7 @@ import { AddValueRequest, AddValueResponse, Logging } from "./out/index"; import * as Client from "./out/index_client"; import * as Server from "./out/index_server"; +import { VerificationError } from "./out/ts_base"; const client = new Client.ServiceProvider((msg) => { session.onMessage(msg); @@ -41,7 +42,7 @@ class TestService extends Server.TestService { } async FunctionWithArrayAsParamAndReturn(vals1, vals2) { - return [...vals1, ...vals2]; + return [...vals1, ...vals2]; } } @@ -72,23 +73,49 @@ async function run() { console.log("!!!!This should have failed!!!!"); }) .catch((err) => { - console.log("Found expected error!", err.message); + if (err instanceof VerificationError) + console.log( + "Found expected error!", + { + type: err.type, + field: err.field, + value: err.value + } + ); + else { + console.error(err); + throw new Error("Unexpected Error"); + } }); console.log("Test with arrays:"); - console.log(await test - //@ts-ignore - .FunctionWithArrayAsParamAndReturn([1,2,3], [4,5,6])); + console.log( + await test + //@ts-ignore + .FunctionWithArrayAsParamAndReturn([1, 2, 3], [4, 5, 6]) + ); console.log("Let with Array fail!"); await test //@ts-ignore - .FunctionWithArrayAsParamAndReturn([1,2,3], [4,"asd",6]) + .FunctionWithArrayAsParamAndReturn([1, 2, 3], [4, "asd", 6]) .then(() => { console.log("!!!!This should have failed!!!!"); }) .catch((err) => { - console.log("Found expected error!", err.message); + if (err instanceof VerificationError) + console.log( + "Found expected error!", + { + type: err.type, + field: err.field, + value: err.value + } + ); + else { + console.error(err); + throw new Error("Unexpected Error"); + } }); } diff --git a/lib/jrpc.js b/lib/jrpc.js index 3eee05a..a622b78 100755 --- a/lib/jrpc.js +++ b/lib/jrpc.js @@ -7496,6 +7496,7 @@ var CompileTarget = class { } }; function compile(ir, target) { + target.start(); ir.forEach((step) => { const [type, def] = step; if (type == "type") @@ -7523,13 +7524,15 @@ var TypescriptTarget = class extends CompileTarget { name = "Typescript"; flavour = "node"; start() { + this.writeFormattedFile("ts_base.ts", this.getTemplate("ts_base.ts")); } generateImport(imports, path) { return `import ${imports} from "${path + (this.flavour === "esm" ? ".ts" : "")}"; `; } generateImports(a, def) { - a(0, def.depends.map((dep) => this.generateImport(`${dep}, { verify_${dep} }`, "./" + dep))); + a(0, this.generateImport(`{ VerificationError, verify_number, verify_string, verify_boolean, verify_void, strip_number, strip_string, strip_boolean, strip_void }`, `./ts_base`)); + a(0, def.depends.map((dep) => this.generateImport(`${dep}, { verify_${dep}, strip_${dep} }`, "./" + dep))); } getFileName(typename) { return typename + ".ts"; @@ -7552,7 +7555,7 @@ var TypescriptTarget = class extends CompileTarget { if (field.array) { type = toJSType(field.type) + "[]"; } else if (field.map) { - type = `Map<${toJSType(field.map)}, ${toJSType(field.type)}>`; + type = `{ [key: ${toJSType(field.map)}]: ${toJSType(field.type)} }`; } else { type = toJSType(field.type); } @@ -7569,39 +7572,58 @@ var TypescriptTarget = class extends CompileTarget { a(1, `}`); a(0, ``); a(0, ``); - a(1, `static verify(data: ${def.name}){`); - a(2, `return verify_${def.name}(data);`); + a(1, `static verify(data: ${def.name}) {`); + a(2, `verify_${def.name}(data);`); a(1, `}`); a(0, `}`); a(0, ``); - a(0, `export function verify_${def.name}(data: ${def.name}): boolean {`); + a(0, `export function strip_${def.name}(data: ${def.name}): ${def.name} {`); + { + a(1, `let res = {} as any;`); + def.fields.forEach((field) => { + if (field.array) { + a(1, `res["${field.name}"] = data["${field.name}"].map(elm=>`); + a(2, `strip_${field.type}(elm)`); + a(1, `)`); + } else if (field.map) { + a(1, `res["${field.name}"] = {}`); + a(1, `Object.entries(data["${field.name}"]).forEach(([key, val]) => res["${field.name}"][key] = strip_${field.type}(val))`); + } else { + a(1, `res["${field.name}"] = strip_${field.type}(data["${field.name}"])`); + } + }); + a(1, `return res;`); + } + a(0, `}`); + a(0, ``); + a(0, `export function verify_${def.name}(data: ${def.name}) {`); { def.fields.forEach((field) => { a(1, `if(data["${field.name}"] !== null && data["${field.name}"] !== undefined ) {`); - const verifyType = (varName) => { + const verifyType = (varName, off = 0) => { switch (field.type) { case "string": - a(2, `if(typeof ${varName} !== "string") return false;`); + a(2 + off, `if(typeof ${varName} !== "string") throw new VerificationError("string", "${field.name}", ${varName});`); break; case "number": - a(2, `if(typeof ${varName} !== "number") return false;`); + a(2 + off, `if(typeof ${varName} !== "number") throw new VerificationError("number", "${field.name}", ${varName});`); break; case "boolean": - a(2, `if(typeof ${varName} !== "boolean") return false;`); + a(2 + off, `if(typeof ${varName} !== "boolean") throw new VerificationError("boolean", "${field.name}", ${varName});`); break; default: - a(2, `if(!verify_${field.type}(${varName})) return false;`); + a(2 + off, `if(!verify_${field.type}(${varName})) throw new VerificationError("${field.type}", "${field.name}", ${varName});`); } }; if (field.array) { - a(2, `if(!Array.isArray(data["${field.name}"])) return false`); + a(2, `if(!Array.isArray(data["${field.name}"])) throw new VerificationError("array", "${field.name}", data["${field.name}"]);`); a(2, `for(const elm of data["${field.name}"]) {`); - verifyType("elm"); + verifyType("elm", 1); a(2, `}`); } else if (field.map) { - a(2, `if(typeof data["${field.name}"] !== "object") return false`); + a(2, `if(typeof data["${field.name}"] !== "object") throw new VerificationError("object", ${field.name}, data["${field.name}"]);`); a(2, `for(const key in data["${field.name}"]) {`); - verifyType(`data["${field.name}"][key]`); + verifyType(`data["${field.name}"][key]`, 1); a(2, `}`); } else { verifyType(`data["${field.name}"]`); @@ -7609,7 +7631,7 @@ var TypescriptTarget = class extends CompileTarget { a(1, "}"); a(0, ``); }); - a(1, `return true`); + a(1, `return data`); } a(0, `}`); this.writeFormattedFile(this.getFileName(def.name), lines.join("\n")); @@ -7627,14 +7649,21 @@ var TypescriptTarget = class extends CompileTarget { a(1, `${value.name}=${value.value},`); } a(0, `}`); + a(0, ``); a(0, `export default ${def.name}`); - a(0, `export function verify_${def.name} (data: ${def.name}): boolean {`); - a(1, `return ${def.name}[data] != undefined`); - a(0, "}"); + a(0, ``); + a(0, `export function strip_${def.name} (data: ${def.name}): ${def.name} {`); + a(1, `return data;`); + a(0, `}`); + a(0, ``); + a(0, `export function verify_${def.name} (data: ${def.name}) {`); + a(1, `if(${def.name}[data] == undefined) throw new VerificationError("${def.name}", undefined, data);`); + a(1, `return data`); + a(0, `}`); this.writeFormattedFile(this.getFileName(def.name), lines.join("\n")); } generateServiceClient(def) { - this.writeFile("service_client.ts", this.generateImport("{ RequestObject, ResponseObject, ErrorCodes, Logging }", "./service_base") + "\n\n" + this.getTemplate("ts_service_client.ts")); + this.writeFormattedFile("service_client.ts", this.generateImport("{ RequestObject, ResponseObject, ErrorCodes, Logging }", "./service_base") + this.generateImport(" { VerificationError }", "./ts_base") + "\n\n" + this.getTemplate("ts_service_client.ts")); let lines = []; const a = (i, t) => { if (!Array.isArray(t)) { @@ -7648,7 +7677,6 @@ var TypescriptTarget = class extends CompileTarget { a(1, `${dep},`); }); a(0, `}`); - a(0, this.generateImport("{ verify_number, verify_string, verify_boolean, verify_void }", "./service_base")); a(0, this.generateImport("{ Service, ServiceProvider, getRandomID }", "./service_client")); a(0, ``); a(0, `export class ${def.name} extends Service {`); @@ -7680,10 +7708,10 @@ var TypescriptTarget = class extends CompileTarget { a(2, `}).then(result => {`); if (fnc.return.array) { a(2, `for(const elm of result) {`); - a(3, `if(!verify_${fnc.return.type}(elm)) throw new Error("Invalid result data!");`); + a(3, `verify_${fnc.return.type}(elm);`); a(2, `}`); } else { - a(3, `if(!verify_${fnc.return.type}(result)) throw new Error("Invalid result data!");`); + a(3, `verify_${fnc.return.type}(result);`); } a(3, `return result;`); a(2, `});`); @@ -7703,7 +7731,7 @@ var TypescriptTarget = class extends CompileTarget { } t.forEach((l) => lines.push(" ".repeat(i) + l.trim())); }; - this.writeFile("service_server.ts", this.generateImport("{ RequestObject, ResponseObject, ErrorCodes, Logging }", "./service_base") + "\n\n" + this.getTemplate("ts_service_server.ts")); + this.writeFormattedFile("service_server.ts", this.generateImport("{ RequestObject, ResponseObject, ErrorCodes, Logging }", "./service_base") + this.generateImport(" { VerificationError }", "./ts_base") + "\n\n" + this.getTemplate("ts_service_server.ts")); this.generateImports(a, def); a(0, `export type {`); def.depends.forEach((dep) => { @@ -7711,7 +7739,6 @@ var TypescriptTarget = class extends CompileTarget { }); a(0, `}`); a(0, this.generateImport("{ Service }", "./service_server")); - a(0, this.generateImport("{ verify_number, verify_string, verify_boolean, verify_void }", "./service_base")); a(0, ``); a(0, `export abstract class ${def.name} extends Service {`); a(1, `public name = "${def.name}";`); @@ -7743,16 +7770,16 @@ var TypescriptTarget = class extends CompileTarget { a(2, `if(p[${i}] !== null && p[${i}] !== undefined) {`); if (fnc.inputs[i].array) { a(2, `for(const elm of p[${i}]) {`); - a(3, `if(!verify_${fnc.inputs[i].type}(elm)) throw new Error("Parameter verification failed!")`); + a(3, `verify_${fnc.inputs[i].type}(elm)`); a(2, `}`); } else { - a(2, `if(!verify_${fnc.inputs[i].type}(p[${i}])) throw new Error("Parameter verification failed!")`); + a(2, `verify_${fnc.inputs[i].type}(p[${i}])`); } a(2, `}`); } a(2, ``); a(2, `p.push(ctx);`); - a(2, `return this.${fnc.name}.call(this, ...p)${((_a = fnc.return) == null ? void 0 : _a.type) == "void" ? ".then(res => undefined)" : ""};`); + a(2, `return this.${fnc.name}.call(this, ...p)` + (fnc.return ? `.then(${((_a = fnc.return) == null ? void 0 : _a.array) ? `res => res.map(e => verify_${fnc.return.type}(strip_${fnc.return.type}(e)))` : `res => verify_${fnc.return.type}(strip_${fnc.return.type}(res))`});` : "")); a(1, `}`); a(0, ``); } @@ -7760,7 +7787,7 @@ var TypescriptTarget = class extends CompileTarget { this.writeFormattedFile(this.getFileName(def.name + "_server"), lines.join("\n")); } generateService(def) { - this.writeFile("service_base.ts", this.getTemplate("ts_service_base.ts")); + this.writeFormattedFile("service_base.ts", this.getTemplate("ts_service_base.ts")); this.generateServiceClient(def); this.generateServiceServer(def); } @@ -7790,14 +7817,14 @@ var TypescriptTarget = class extends CompileTarget { steps.forEach(([type, def]) => { switch (type) { case "type": - a(0, this.generateImport(`${def.name}, { verify_${def.name} }`, "./" + def.name)); - a(0, `export { verify_${def.name} }`); + a(0, this.generateImport(`${def.name}, { verify_${def.name}, strip_${def.name} }`, "./" + def.name)); + a(0, `export { verify_${def.name}, strip_${def.name} }`); a(0, `export type { ${def.name} }`); a(0, ``); break; case "enum": - a(0, this.generateImport(`${def.name}, { verify_${def.name} }`, "./" + def.name)); - a(0, `export { ${def.name}, verify_${def.name} }`); + a(0, this.generateImport(`${def.name}, { verify_${def.name}, strip_${def.name} }`, "./" + def.name)); + a(0, `export { ${def.name}, verify_${def.name}, strip_${def.name} }`); a(0, ``); break; case "service": diff --git a/package.json b/package.json index 1d7b13d..c0b684c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hibas123/jrpcgen", - "version": "1.0.5", + "version": "1.0.6", "main": "lib/index.js", "license": "MIT", "packageManager": "yarn@3.1.1", diff --git a/src/compile.ts b/src/compile.ts index 4efc6b2..51ff9a0 100644 --- a/src/compile.ts +++ b/src/compile.ts @@ -63,6 +63,7 @@ export default function compile(ir: IR, target: CompileTarget) { // Types are verified. They are now ready to be compiled to targets // setState("Building for target: " + target.name); + target.start(); ir.forEach((step) => { const [type, def] = step; if (type == "type") target.generateType(def as TypeDefinition); diff --git a/src/targets/typescript.ts b/src/targets/typescript.ts index edf537a..bc03372 100644 --- a/src/targets/typescript.ts +++ b/src/targets/typescript.ts @@ -27,7 +27,9 @@ export class TypescriptTarget extends CompileTarget { flavour: "esm" | "node" = "node"; - start() {} + start() { + this.writeFormattedFile("ts_base.ts", this.getTemplate("ts_base.ts")); + } private generateImport(imports: string, path: string) { return `import ${imports} from "${ @@ -39,10 +41,20 @@ export class TypescriptTarget extends CompileTarget { a: lineAppender, def: TypeDefinition | ServiceDefinition ) { + a( + 0, + this.generateImport( + `{ VerificationError, verify_number, verify_string, verify_boolean, verify_void, strip_number, strip_string, strip_boolean, strip_void }`, + `./ts_base` + ) + ); a( 0, def.depends.map((dep) => - this.generateImport(`${dep}, { verify_${dep} }`, "./" + dep) + this.generateImport( + `${dep}, { verify_${dep}, strip_${dep} }`, + "./" + dep + ) ) ); } @@ -79,7 +91,9 @@ export class TypescriptTarget extends CompileTarget { if (field.array) { type = toJSType(field.type) + "[]"; } else if (field.map) { - type = `Map<${toJSType(field.map)}, ${toJSType(field.type)}>`; + type = `{ [key: ${toJSType(field.map)}]: ${toJSType( + field.type + )} }`; } else { type = toJSType(field.type); } @@ -101,14 +115,44 @@ export class TypescriptTarget extends CompileTarget { a(0, ``); - a(1, `static verify(data: ${def.name}){`); - a(2, `return verify_${def.name}(data);`); + a(1, `static verify(data: ${def.name}) {`); + a(2, `verify_${def.name}(data);`); a(1, `}`); a(0, `}`); a(0, ``); - a(0, `export function verify_${def.name}(data: ${def.name}): boolean {`); + a( + 0, + `export function strip_${def.name}(data: ${def.name}): ${def.name} {` + ); + { + a(1, `let res = {} as any;`); + def.fields.forEach((field) => { + if (field.array) { + a(1, `res["${field.name}"] = data["${field.name}"].map(elm=>`); + a(2, `strip_${field.type}(elm)`); + a(1, `)`); + } else if (field.map) { + a(1, `res["${field.name}"] = {}`); + a( + 1, + `Object.entries(data["${field.name}"]).forEach(([key, val]) => res["${field.name}"][key] = strip_${field.type}(val))` + ); + } else { + a( + 1, + `res["${field.name}"] = strip_${field.type}(data["${field.name}"])` + ); + } + }); + a(1, `return res;`); + } + a(0, `}`); + + a(0, ``); + + a(0, `export function verify_${def.name}(data: ${def.name}) {`); { def.fields.forEach((field) => { a( @@ -116,37 +160,49 @@ export class TypescriptTarget extends CompileTarget { `if(data["${field.name}"] !== null && data["${field.name}"] !== undefined ) {` ); - const verifyType = (varName: string) => { + const verifyType = (varName: string, off = 0) => { switch (field.type) { case "string": - a(2, `if(typeof ${varName} !== "string") return false;`); + a( + 2 + off, + `if(typeof ${varName} !== "string") throw new VerificationError("string", "${field.name}", ${varName});` + ); break; case "number": - a(2, `if(typeof ${varName} !== "number") return false;`); + a( + 2 + off, + `if(typeof ${varName} !== "number") throw new VerificationError("number", "${field.name}", ${varName});` + ); break; case "boolean": - a(2, `if(typeof ${varName} !== "boolean") return false;`); + a( + 2 + off, + `if(typeof ${varName} !== "boolean") throw new VerificationError("boolean", "${field.name}", ${varName});` + ); break; default: a( - 2, - `if(!verify_${field.type}(${varName})) return false;` + 2 + off, + `if(!verify_${field.type}(${varName})) throw new VerificationError("${field.type}", "${field.name}", ${varName});` ); } }; if (field.array) { - a(2, `if(!Array.isArray(data["${field.name}"])) return false`); + a( + 2, + `if(!Array.isArray(data["${field.name}"])) throw new VerificationError("array", "${field.name}", data["${field.name}"]);` + ); a(2, `for(const elm of data["${field.name}"]) {`); - verifyType("elm"); + verifyType("elm", 1); a(2, `}`); } else if (field.map) { a( 2, - `if(typeof data["${field.name}"] !== "object") return false` + `if(typeof data["${field.name}"] !== "object") throw new VerificationError("object", ${field.name}, data["${field.name}"]);` ); a(2, `for(const key in data["${field.name}"]) {`); - verifyType(`data["${field.name}"][key]`); + verifyType(`data["${field.name}"][key]`, 1); a(2, `}`); } else { verifyType(`data["${field.name}"]`); @@ -154,7 +210,7 @@ export class TypescriptTarget extends CompileTarget { a(1, "}"); a(0, ``); }); - a(1, `return true`); + a(1, `return data`); } a(0, `}`); @@ -176,22 +232,35 @@ export class TypescriptTarget extends CompileTarget { } a(0, `}`); + a(0, ``); + a(0, `export default ${def.name}`); - a(0, `export function verify_${def.name} (data: ${def.name}): boolean {`); - a(1, `return ${def.name}[data] != undefined`); - a(0, "}"); + a(0, ``); + + a( + 0, + `export function strip_${def.name} (data: ${def.name}): ${def.name} {` + ); + a(1, `return data;`); + a(0, `}`); + a(0, ``); + a(0, `export function verify_${def.name} (data: ${def.name}) {`); + a(1, `if(${def.name}[data] == undefined) throw new VerificationError("${def.name}", undefined, data);`); + a(1, `return data`); + a(0, `}`); this.writeFormattedFile(this.getFileName(def.name), lines.join("\n")); } generateServiceClient(def: ServiceDefinition) { - this.writeFile( + this.writeFormattedFile( "service_client.ts", this.generateImport( "{ RequestObject, ResponseObject, ErrorCodes, Logging }", "./service_base" ) + + this.generateImport(" { VerificationError }", "./ts_base") + "\n\n" + this.getTemplate("ts_service_client.ts") ); @@ -212,14 +281,6 @@ export class TypescriptTarget extends CompileTarget { }); a(0, `}`); - a( - 0, - this.generateImport( - "{ verify_number, verify_string, verify_boolean, verify_void }", - "./service_base" - ) - ); - a( 0, this.generateImport( @@ -241,6 +302,7 @@ export class TypescriptTarget extends CompileTarget { (e) => `${e.name}: ${toJSType(e.type) + (e.array ? "[]" : "")}` ) .join(", "); + //TODO: Prio 1 : Verify and strip input parameters //TODO: Prio 2 : Add optional parameters to this and the declaration file if (!fnc.return) { a(1, `${fnc.name}(${params}): void {`); @@ -267,16 +329,10 @@ export class TypescriptTarget extends CompileTarget { a(2, `}).then(result => {`); if (fnc.return.array) { a(2, `for(const elm of result) {`); - a( - 3, - `if(!verify_${fnc.return.type}(elm)) throw new Error("Invalid result data!");` - ); + a(3, `verify_${fnc.return.type}(elm);`); a(2, `}`); } else { - a( - 3, - `if(!verify_${fnc.return.type}(result)) throw new Error("Invalid result data!");` - ); + a(3, `verify_${fnc.return.type}(result);`); } a(3, `return result;`); a(2, `});`); @@ -302,12 +358,13 @@ export class TypescriptTarget extends CompileTarget { t.forEach((l) => lines.push(" ".repeat(i) + l.trim())); }; - this.writeFile( + this.writeFormattedFile( "service_server.ts", this.generateImport( "{ RequestObject, ResponseObject, ErrorCodes, Logging }", "./service_base" ) + + this.generateImport(" { VerificationError }", "./ts_base") + "\n\n" + this.getTemplate("ts_service_server.ts") ); @@ -322,14 +379,6 @@ export class TypescriptTarget extends CompileTarget { a(0, this.generateImport("{ Service }", "./service_server")); - a( - 0, - this.generateImport( - "{ verify_number, verify_string, verify_boolean, verify_void }", - "./service_base" - ) - ); - a(0, ``); a(0, `export abstract class ${def.name} extends Service {`); @@ -373,27 +422,27 @@ export class TypescriptTarget extends CompileTarget { a(2, `if(p[${i}] !== null && p[${i}] !== undefined) {`); if (fnc.inputs[i].array) { a(2, `for(const elm of p[${i}]) {`); - a( - 3, - `if(!verify_${fnc.inputs[i].type}(elm)) throw new Error("Parameter verification failed!")` - ); + a(3, `verify_${fnc.inputs[i].type}(elm)`); a(2, `}`); } else { - a( - 2, - `if(!verify_${fnc.inputs[i].type}(p[${i}])) throw new Error("Parameter verification failed!")` - ); + a(2, `verify_${fnc.inputs[i].type}(p[${i}])`); } a(2, `}`); } a(2, ``); a(2, `p.push(ctx);`); + a( 2, - `return this.${fnc.name}.call(this, ...p)${ - fnc.return?.type == "void" ? ".then(res => undefined)" : "" - };` + `return this.${fnc.name}.call(this, ...p)` + //TODO: Refactor. This line is way to compicated for anyone to understand, including me + (fnc.return + ? `.then(${ + fnc.return?.array + ? `res => res.map(e => verify_${fnc.return.type}(strip_${fnc.return.type}(e)))` + : `res => verify_${fnc.return.type}(strip_${fnc.return.type}(res))` + });` + : "") ); a(1, `}`); a(0, ``); @@ -408,7 +457,10 @@ export class TypescriptTarget extends CompileTarget { } generateService(def: ServiceDefinition) { - this.writeFile("service_base.ts", this.getTemplate("ts_service_base.ts")); + this.writeFormattedFile( + "service_base.ts", + this.getTemplate("ts_service_base.ts") + ); this.generateServiceClient(def); this.generateServiceServer(def); } @@ -446,12 +498,12 @@ export class TypescriptTarget extends CompileTarget { a( 0, this.generateImport( - `${def.name}, { verify_${def.name} }`, + `${def.name}, { verify_${def.name}, strip_${def.name} }`, "./" + def.name ) ); - a(0, `export { verify_${def.name} }`); + a(0, `export { verify_${def.name}, strip_${def.name} }`); a(0, `export type { ${def.name} }`); a(0, ``); break; @@ -459,11 +511,14 @@ export class TypescriptTarget extends CompileTarget { a( 0, this.generateImport( - `${def.name}, { verify_${def.name} }`, + `${def.name}, { verify_${def.name}, strip_${def.name} }`, "./" + def.name ) ); - a(0, `export { ${def.name}, verify_${def.name} }`); + a( + 0, + `export { ${def.name}, verify_${def.name}, strip_${def.name} }` + ); a(0, ``); break; diff --git a/templates/ts_base.ts b/templates/ts_base.ts new file mode 100644 index 0000000..b02d79f --- /dev/null +++ b/templates/ts_base.ts @@ -0,0 +1,43 @@ +export class VerificationError extends Error { + constructor( + public readonly type?: string, + public readonly field?: string, + public readonly value?: any + ) { + super("Parameter verification failed!"); + } +} + +export function verify_number(data: any) { + if (typeof data !== "number") throw new VerificationError("number", undefined, data); + return data; +} + +export function strip_number(data: any) { + return data; +} + +export function verify_string(data: any) { + if (typeof data !== "string") throw new VerificationError("string", undefined, data); + return data; +} + +export function strip_string(data: any) { + return data; +} + +export function verify_boolean(data: any) { + if (typeof data !== "boolean") throw new VerificationError("boolean", undefined, data); + return data; +} + +export function strip_boolean(data: any) { + return data; +} + +//TODO: Should it return data? it is kinda undefined actually... +export function verify_void(data: any) {} + +export function strip_void(data: any) { + return undefined; +} diff --git a/templates/ts_service_base.ts b/templates/ts_service_base.ts index 631c968..86e44e4 100644 --- a/templates/ts_service_base.ts +++ b/templates/ts_service_base.ts @@ -28,25 +28,3 @@ export interface ResponseObject { error?: { code: ErrorCodes; message: string; data?: any }; id: string; } - -export function verify_number(data: any) { - if (typeof data !== "number") return false; - - return true; -} - -export function verify_string(data: any) { - if (typeof data !== "string") return false; - - return true; -} - -export function verify_boolean(data: any) { - if (typeof data !== "boolean") return false; - - return true; -} - -export function verify_void(data: any) { - return true; -} diff --git a/templates/ts_service_client.ts b/templates/ts_service_client.ts index 7f29616..296897f 100644 --- a/templates/ts_service_client.ts +++ b/templates/ts_service_client.ts @@ -1,4 +1,6 @@ //@template-ignore +import { VerificationError } from "./ts_base"; +//@template-ignore import { RequestObject, ResponseObject, ErrorCodes, Logging } from "./ts_service_base"; @@ -48,7 +50,11 @@ export class ServiceProvider { let resListener = this.requests.get(msg.id); if(!resListener) return; // Ignore wrong responses if(msg.error) { - resListener.err(new Error(msg.error.message)); + if(msg.error.data && msg.error.data.$ == "verification_error") { + resListener.err(new VerificationError(msg.error.data.type, msg.error.data.field, msg.error.data.value)) + } else { + resListener.err(new Error(msg.error.message)); + } } else { resListener.ok(msg.result); } diff --git a/templates/ts_service_server.ts b/templates/ts_service_server.ts index 900b9dd..c8c3ec9 100644 --- a/templates/ts_service_server.ts +++ b/templates/ts_service_server.ts @@ -1,4 +1,6 @@ //@template-ignore +import { VerificationError } from "./ts_base"; +//@template-ignore import { RequestObject, ResponseObject, ErrorCodes, Logging } from "./ts_service_base"; @@ -109,6 +111,14 @@ class Session { error: { code: ErrorCodes.InternalError, message: err.message, + data: err instanceof VerificationError ? { + $: "verification_error", + type: err.type, + field: err.field, + value: err.value + } : { + $: "unknown_error" + }, }, } as ResponseObject, err