Add verification and value stripping

This commit is contained in:
K35 2022-01-02 22:02:47 +00:00
parent db21c1c42e
commit cf49fca928
9 changed files with 272 additions and 125 deletions

View File

@ -4,6 +4,7 @@ import { AddValueRequest, AddValueResponse, Logging } from "./out/index";
import * as Client from "./out/index_client"; import * as Client from "./out/index_client";
import * as Server from "./out/index_server"; import * as Server from "./out/index_server";
import { VerificationError } from "./out/ts_base";
const client = new Client.ServiceProvider((msg) => { const client = new Client.ServiceProvider((msg) => {
session.onMessage(msg); session.onMessage(msg);
@ -72,23 +73,49 @@ async function run() {
console.log("!!!!This should have failed!!!!"); console.log("!!!!This should have failed!!!!");
}) })
.catch((err) => { .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("Test with arrays:");
console.log(await test console.log(
await test
//@ts-ignore //@ts-ignore
.FunctionWithArrayAsParamAndReturn([1,2,3], [4,5,6])); .FunctionWithArrayAsParamAndReturn([1, 2, 3], [4, 5, 6])
);
console.log("Let with Array fail!"); console.log("Let with Array fail!");
await test await test
//@ts-ignore //@ts-ignore
.FunctionWithArrayAsParamAndReturn([1,2,3], [4,"asd",6]) .FunctionWithArrayAsParamAndReturn([1, 2, 3], [4, "asd", 6])
.then(() => { .then(() => {
console.log("!!!!This should have failed!!!!"); console.log("!!!!This should have failed!!!!");
}) })
.catch((err) => { .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");
}
}); });
} }

View File

@ -7496,6 +7496,7 @@ var CompileTarget = class {
} }
}; };
function compile(ir, target) { function compile(ir, target) {
target.start();
ir.forEach((step) => { ir.forEach((step) => {
const [type, def] = step; const [type, def] = step;
if (type == "type") if (type == "type")
@ -7523,13 +7524,15 @@ var TypescriptTarget = class extends CompileTarget {
name = "Typescript"; name = "Typescript";
flavour = "node"; flavour = "node";
start() { start() {
this.writeFormattedFile("ts_base.ts", this.getTemplate("ts_base.ts"));
} }
generateImport(imports, path) { generateImport(imports, path) {
return `import ${imports} from "${path + (this.flavour === "esm" ? ".ts" : "")}"; return `import ${imports} from "${path + (this.flavour === "esm" ? ".ts" : "")}";
`; `;
} }
generateImports(a, def) { 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) { getFileName(typename) {
return typename + ".ts"; return typename + ".ts";
@ -7552,7 +7555,7 @@ var TypescriptTarget = class extends CompileTarget {
if (field.array) { if (field.array) {
type = toJSType(field.type) + "[]"; type = toJSType(field.type) + "[]";
} else if (field.map) { } else if (field.map) {
type = `Map<${toJSType(field.map)}, ${toJSType(field.type)}>`; type = `{ [key: ${toJSType(field.map)}]: ${toJSType(field.type)} }`;
} else { } else {
type = toJSType(field.type); type = toJSType(field.type);
} }
@ -7569,39 +7572,58 @@ var TypescriptTarget = class extends CompileTarget {
a(1, `}`); a(1, `}`);
a(0, ``); a(0, ``);
a(0, ``); a(0, ``);
a(1, `static verify(data: ${def.name}){`); a(1, `static verify(data: ${def.name}) {`);
a(2, `return verify_${def.name}(data);`); a(2, `verify_${def.name}(data);`);
a(1, `}`); a(1, `}`);
a(0, `}`); a(0, `}`);
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) => { def.fields.forEach((field) => {
a(1, `if(data["${field.name}"] !== null && data["${field.name}"] !== undefined ) {`); a(1, `if(data["${field.name}"] !== null && data["${field.name}"] !== undefined ) {`);
const verifyType = (varName) => { const verifyType = (varName, off = 0) => {
switch (field.type) { switch (field.type) {
case "string": 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; break;
case "number": 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; break;
case "boolean": 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; break;
default: 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) { 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}"]) {`); a(2, `for(const elm of data["${field.name}"]) {`);
verifyType("elm"); verifyType("elm", 1);
a(2, `}`); a(2, `}`);
} else if (field.map) { } 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}"]) {`); a(2, `for(const key in data["${field.name}"]) {`);
verifyType(`data["${field.name}"][key]`); verifyType(`data["${field.name}"][key]`, 1);
a(2, `}`); a(2, `}`);
} else { } else {
verifyType(`data["${field.name}"]`); verifyType(`data["${field.name}"]`);
@ -7609,7 +7631,7 @@ var TypescriptTarget = class extends CompileTarget {
a(1, "}"); a(1, "}");
a(0, ``); a(0, ``);
}); });
a(1, `return true`); a(1, `return data`);
} }
a(0, `}`); a(0, `}`);
this.writeFormattedFile(this.getFileName(def.name), lines.join("\n")); 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(1, `${value.name}=${value.value},`);
} }
a(0, `}`); a(0, `}`);
a(0, ``);
a(0, `export default ${def.name}`); a(0, `export default ${def.name}`);
a(0, `export function verify_${def.name} (data: ${def.name}): boolean {`); a(0, ``);
a(1, `return ${def.name}[data] != undefined`); a(0, `export function strip_${def.name} (data: ${def.name}): ${def.name} {`);
a(0, "}"); 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")); this.writeFormattedFile(this.getFileName(def.name), lines.join("\n"));
} }
generateServiceClient(def) { 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 = []; let lines = [];
const a = (i, t) => { const a = (i, t) => {
if (!Array.isArray(t)) { if (!Array.isArray(t)) {
@ -7648,7 +7677,6 @@ var TypescriptTarget = class extends CompileTarget {
a(1, `${dep},`); a(1, `${dep},`);
}); });
a(0, `}`); 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, this.generateImport("{ Service, ServiceProvider, getRandomID }", "./service_client"));
a(0, ``); a(0, ``);
a(0, `export class ${def.name} extends Service {`); a(0, `export class ${def.name} extends Service {`);
@ -7680,10 +7708,10 @@ var TypescriptTarget = class extends CompileTarget {
a(2, `}).then(result => {`); a(2, `}).then(result => {`);
if (fnc.return.array) { if (fnc.return.array) {
a(2, `for(const elm of result) {`); 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, `}`); a(2, `}`);
} else { } 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(3, `return result;`);
a(2, `});`); a(2, `});`);
@ -7703,7 +7731,7 @@ var TypescriptTarget = class extends CompileTarget {
} }
t.forEach((l) => lines.push(" ".repeat(i) + l.trim())); 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); this.generateImports(a, def);
a(0, `export type {`); a(0, `export type {`);
def.depends.forEach((dep) => { def.depends.forEach((dep) => {
@ -7711,7 +7739,6 @@ var TypescriptTarget = class extends CompileTarget {
}); });
a(0, `}`); a(0, `}`);
a(0, this.generateImport("{ Service }", "./service_server")); 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, ``);
a(0, `export abstract class ${def.name}<T> extends Service<T> {`); a(0, `export abstract class ${def.name}<T> extends Service<T> {`);
a(1, `public name = "${def.name}";`); a(1, `public name = "${def.name}";`);
@ -7743,16 +7770,16 @@ var TypescriptTarget = class extends CompileTarget {
a(2, `if(p[${i}] !== null && p[${i}] !== undefined) {`); a(2, `if(p[${i}] !== null && p[${i}] !== undefined) {`);
if (fnc.inputs[i].array) { if (fnc.inputs[i].array) {
a(2, `for(const elm of p[${i}]) {`); 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, `}`); a(2, `}`);
} else { } 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, ``); a(2, ``);
a(2, `p.push(ctx);`); 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(1, `}`);
a(0, ``); a(0, ``);
} }
@ -7760,7 +7787,7 @@ var TypescriptTarget = class extends CompileTarget {
this.writeFormattedFile(this.getFileName(def.name + "_server"), lines.join("\n")); this.writeFormattedFile(this.getFileName(def.name + "_server"), lines.join("\n"));
} }
generateService(def) { 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.generateServiceClient(def);
this.generateServiceServer(def); this.generateServiceServer(def);
} }
@ -7790,14 +7817,14 @@ var TypescriptTarget = class extends CompileTarget {
steps.forEach(([type, def]) => { steps.forEach(([type, def]) => {
switch (type) { switch (type) {
case "type": case "type":
a(0, this.generateImport(`${def.name}, { verify_${def.name} }`, "./" + def.name)); a(0, this.generateImport(`${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, `export type { ${def.name} }`);
a(0, ``); a(0, ``);
break; break;
case "enum": case "enum":
a(0, this.generateImport(`${def.name}, { verify_${def.name} }`, "./" + def.name)); a(0, this.generateImport(`${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, ``); a(0, ``);
break; break;
case "service": case "service":

View File

@ -1,6 +1,6 @@
{ {
"name": "@hibas123/jrpcgen", "name": "@hibas123/jrpcgen",
"version": "1.0.5", "version": "1.0.6",
"main": "lib/index.js", "main": "lib/index.js",
"license": "MIT", "license": "MIT",
"packageManager": "yarn@3.1.1", "packageManager": "yarn@3.1.1",

View File

@ -63,6 +63,7 @@ export default function compile(ir: IR, target: CompileTarget) {
// Types are verified. They are now ready to be compiled to targets // Types are verified. They are now ready to be compiled to targets
// setState("Building for target: " + target.name); // setState("Building for target: " + target.name);
target.start();
ir.forEach((step) => { ir.forEach((step) => {
const [type, def] = step; const [type, def] = step;
if (type == "type") target.generateType(def as TypeDefinition); if (type == "type") target.generateType(def as TypeDefinition);

View File

@ -27,7 +27,9 @@ export class TypescriptTarget extends CompileTarget {
flavour: "esm" | "node" = "node"; flavour: "esm" | "node" = "node";
start() {} start() {
this.writeFormattedFile("ts_base.ts", this.getTemplate("ts_base.ts"));
}
private generateImport(imports: string, path: string) { private generateImport(imports: string, path: string) {
return `import ${imports} from "${ return `import ${imports} from "${
@ -39,10 +41,20 @@ export class TypescriptTarget extends CompileTarget {
a: lineAppender, a: lineAppender,
def: TypeDefinition | ServiceDefinition 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( a(
0, 0,
def.depends.map((dep) => 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) { if (field.array) {
type = toJSType(field.type) + "[]"; type = toJSType(field.type) + "[]";
} else if (field.map) { } else if (field.map) {
type = `Map<${toJSType(field.map)}, ${toJSType(field.type)}>`; type = `{ [key: ${toJSType(field.map)}]: ${toJSType(
field.type
)} }`;
} else { } else {
type = toJSType(field.type); type = toJSType(field.type);
} }
@ -101,14 +115,44 @@ export class TypescriptTarget extends CompileTarget {
a(0, ``); a(0, ``);
a(1, `static verify(data: ${def.name}){`); a(1, `static verify(data: ${def.name}) {`);
a(2, `return verify_${def.name}(data);`); a(2, `verify_${def.name}(data);`);
a(1, `}`); a(1, `}`);
a(0, `}`); a(0, `}`);
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) => { def.fields.forEach((field) => {
a( a(
@ -116,37 +160,49 @@ export class TypescriptTarget extends CompileTarget {
`if(data["${field.name}"] !== null && data["${field.name}"] !== undefined ) {` `if(data["${field.name}"] !== null && data["${field.name}"] !== undefined ) {`
); );
const verifyType = (varName: string) => { const verifyType = (varName: string, off = 0) => {
switch (field.type) { switch (field.type) {
case "string": 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; break;
case "number": 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; break;
case "boolean": 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; break;
default: default:
a( a(
2, 2 + off,
`if(!verify_${field.type}(${varName})) return false;` `if(!verify_${field.type}(${varName})) throw new VerificationError("${field.type}", "${field.name}", ${varName});`
); );
} }
}; };
if (field.array) { 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}"]) {`); a(2, `for(const elm of data["${field.name}"]) {`);
verifyType("elm"); verifyType("elm", 1);
a(2, `}`); a(2, `}`);
} else if (field.map) { } else if (field.map) {
a( a(
2, 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}"]) {`); a(2, `for(const key in data["${field.name}"]) {`);
verifyType(`data["${field.name}"][key]`); verifyType(`data["${field.name}"][key]`, 1);
a(2, `}`); a(2, `}`);
} else { } else {
verifyType(`data["${field.name}"]`); verifyType(`data["${field.name}"]`);
@ -154,7 +210,7 @@ export class TypescriptTarget extends CompileTarget {
a(1, "}"); a(1, "}");
a(0, ``); a(0, ``);
}); });
a(1, `return true`); a(1, `return data`);
} }
a(0, `}`); a(0, `}`);
@ -176,22 +232,35 @@ export class TypescriptTarget extends CompileTarget {
} }
a(0, `}`); a(0, `}`);
a(0, ``);
a(0, `export default ${def.name}`); a(0, `export default ${def.name}`);
a(0, `export function verify_${def.name} (data: ${def.name}): boolean {`); a(0, ``);
a(1, `return ${def.name}[data] != undefined`);
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")); this.writeFormattedFile(this.getFileName(def.name), lines.join("\n"));
} }
generateServiceClient(def: ServiceDefinition) { generateServiceClient(def: ServiceDefinition) {
this.writeFile( this.writeFormattedFile(
"service_client.ts", "service_client.ts",
this.generateImport( this.generateImport(
"{ RequestObject, ResponseObject, ErrorCodes, Logging }", "{ RequestObject, ResponseObject, ErrorCodes, Logging }",
"./service_base" "./service_base"
) + ) +
this.generateImport(" { VerificationError }", "./ts_base") +
"\n\n" + "\n\n" +
this.getTemplate("ts_service_client.ts") this.getTemplate("ts_service_client.ts")
); );
@ -212,14 +281,6 @@ export class TypescriptTarget extends CompileTarget {
}); });
a(0, `}`); a(0, `}`);
a(
0,
this.generateImport(
"{ verify_number, verify_string, verify_boolean, verify_void }",
"./service_base"
)
);
a( a(
0, 0,
this.generateImport( this.generateImport(
@ -241,6 +302,7 @@ export class TypescriptTarget extends CompileTarget {
(e) => `${e.name}: ${toJSType(e.type) + (e.array ? "[]" : "")}` (e) => `${e.name}: ${toJSType(e.type) + (e.array ? "[]" : "")}`
) )
.join(", "); .join(", ");
//TODO: Prio 1 : Verify and strip input parameters
//TODO: Prio 2 : Add optional parameters to this and the declaration file //TODO: Prio 2 : Add optional parameters to this and the declaration file
if (!fnc.return) { if (!fnc.return) {
a(1, `${fnc.name}(${params}): void {`); a(1, `${fnc.name}(${params}): void {`);
@ -267,16 +329,10 @@ export class TypescriptTarget extends CompileTarget {
a(2, `}).then(result => {`); a(2, `}).then(result => {`);
if (fnc.return.array) { if (fnc.return.array) {
a(2, `for(const elm of result) {`); a(2, `for(const elm of result) {`);
a( a(3, `verify_${fnc.return.type}(elm);`);
3,
`if(!verify_${fnc.return.type}(elm)) throw new Error("Invalid result data!");`
);
a(2, `}`); a(2, `}`);
} else { } else {
a( a(3, `verify_${fnc.return.type}(result);`);
3,
`if(!verify_${fnc.return.type}(result)) throw new Error("Invalid result data!");`
);
} }
a(3, `return result;`); a(3, `return result;`);
a(2, `});`); a(2, `});`);
@ -302,12 +358,13 @@ export class TypescriptTarget extends CompileTarget {
t.forEach((l) => lines.push(" ".repeat(i) + l.trim())); t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
}; };
this.writeFile( this.writeFormattedFile(
"service_server.ts", "service_server.ts",
this.generateImport( this.generateImport(
"{ RequestObject, ResponseObject, ErrorCodes, Logging }", "{ RequestObject, ResponseObject, ErrorCodes, Logging }",
"./service_base" "./service_base"
) + ) +
this.generateImport(" { VerificationError }", "./ts_base") +
"\n\n" + "\n\n" +
this.getTemplate("ts_service_server.ts") 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("{ Service }", "./service_server"));
a(
0,
this.generateImport(
"{ verify_number, verify_string, verify_boolean, verify_void }",
"./service_base"
)
);
a(0, ``); a(0, ``);
a(0, `export abstract class ${def.name}<T> extends Service<T> {`); a(0, `export abstract class ${def.name}<T> extends Service<T> {`);
@ -373,27 +422,27 @@ export class TypescriptTarget extends CompileTarget {
a(2, `if(p[${i}] !== null && p[${i}] !== undefined) {`); a(2, `if(p[${i}] !== null && p[${i}] !== undefined) {`);
if (fnc.inputs[i].array) { if (fnc.inputs[i].array) {
a(2, `for(const elm of p[${i}]) {`); a(2, `for(const elm of p[${i}]) {`);
a( a(3, `verify_${fnc.inputs[i].type}(elm)`);
3,
`if(!verify_${fnc.inputs[i].type}(elm)) throw new Error("Parameter verification failed!")`
);
a(2, `}`); a(2, `}`);
} else { } else {
a( a(2, `verify_${fnc.inputs[i].type}(p[${i}])`);
2,
`if(!verify_${fnc.inputs[i].type}(p[${i}])) throw new Error("Parameter verification failed!")`
);
} }
a(2, `}`); a(2, `}`);
} }
a(2, ``); a(2, ``);
a(2, `p.push(ctx);`); a(2, `p.push(ctx);`);
a( a(
2, 2,
`return this.${fnc.name}.call(this, ...p)${ `return this.${fnc.name}.call(this, ...p)` + //TODO: Refactor. This line is way to compicated for anyone to understand, including me
fnc.return?.type == "void" ? ".then(res => undefined)" : "" (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(1, `}`);
a(0, ``); a(0, ``);
@ -408,7 +457,10 @@ export class TypescriptTarget extends CompileTarget {
} }
generateService(def: ServiceDefinition) { 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.generateServiceClient(def);
this.generateServiceServer(def); this.generateServiceServer(def);
} }
@ -446,12 +498,12 @@ export class TypescriptTarget extends CompileTarget {
a( a(
0, 0,
this.generateImport( this.generateImport(
`${def.name}, { verify_${def.name} }`, `${def.name}, { verify_${def.name}, strip_${def.name} }`,
"./" + 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, `export type { ${def.name} }`);
a(0, ``); a(0, ``);
break; break;
@ -459,11 +511,14 @@ export class TypescriptTarget extends CompileTarget {
a( a(
0, 0,
this.generateImport( this.generateImport(
`${def.name}, { verify_${def.name} }`, `${def.name}, { verify_${def.name}, strip_${def.name} }`,
"./" + 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, ``); a(0, ``);
break; break;

43
templates/ts_base.ts Normal file
View File

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

View File

@ -28,25 +28,3 @@ export interface ResponseObject {
error?: { code: ErrorCodes; message: string; data?: any }; error?: { code: ErrorCodes; message: string; data?: any };
id: string; 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;
}

View File

@ -1,4 +1,6 @@
//@template-ignore //@template-ignore
import { VerificationError } from "./ts_base";
//@template-ignore
import { RequestObject, ResponseObject, ErrorCodes, Logging } from "./ts_service_base"; import { RequestObject, ResponseObject, ErrorCodes, Logging } from "./ts_service_base";
@ -48,7 +50,11 @@ export class ServiceProvider {
let resListener = this.requests.get(msg.id); let resListener = this.requests.get(msg.id);
if(!resListener) return; // Ignore wrong responses if(!resListener) return; // Ignore wrong responses
if(msg.error) { if(msg.error) {
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)); resListener.err(new Error(msg.error.message));
}
} else { } else {
resListener.ok(msg.result); resListener.ok(msg.result);
} }

View File

@ -1,4 +1,6 @@
//@template-ignore //@template-ignore
import { VerificationError } from "./ts_base";
//@template-ignore
import { RequestObject, ResponseObject, ErrorCodes, Logging } from "./ts_service_base"; import { RequestObject, ResponseObject, ErrorCodes, Logging } from "./ts_service_base";
@ -109,6 +111,14 @@ class Session<T> {
error: { error: {
code: ErrorCodes.InternalError, code: ErrorCodes.InternalError,
message: err.message, message: err.message,
data: err instanceof VerificationError ? {
$: "verification_error",
type: err.type,
field: err.field,
value: err.value
} : {
$: "unknown_error"
},
}, },
} as ResponseObject, } as ResponseObject,
err err