diff --git a/examples/test.ts b/examples/test.ts index 7abf57d..7785ad6 100644 --- a/examples/test.ts +++ b/examples/test.ts @@ -1,54 +1,71 @@ -import { AddValueRequest, AddValueResponse, Logging } from "./out/index" - +import { AddValueRequest, AddValueResponse, Logging } from "./out/index"; // Logging.verbose = false; -import * as Client from "./out/index_client" -import * as Server from "./out/index_server" +import * as Client from "./out/index_client"; +import * as Server from "./out/index_server"; -const client = new Client.ServiceProvider(msg=>{ +const client = new Client.ServiceProvider((msg) => { session.onMessage(msg); -}) +}); -const server = new Server.ServiceProvider() +const server = new Server.ServiceProvider(); -const session = server.getSession((msg) => { +const session = server.getSession((msg) => { client.onPacket(msg); -}) +}); class TestService extends Server.TestService { - async AddValuesSingleParam(request: AddValueRequest, ctx: undefined): Promise { + async AddValuesSingleParam( + request: AddValueRequest, + ctx: undefined + ): Promise { return { - value: request.value1 + request.value2 - } + value: request.value1 + request.value2, + }; } - async AddValuesMultipleParams(value1: number, value2: number, ctx: undefined): Promise { + async AddValuesMultipleParams( + value1: number, + value2: number, + ctx: undefined + ): Promise { return value1 + value2; } OnEvent(param1: string, ctx: undefined): void { console.log("Received notification", param1); } - } -server.addService(new TestService()) +server.addService(new TestService()); const test = new Client.TestService(client); async function run() { - console.log("Testing AddValuesSingleParam") - console.log(await test.AddValuesSingleParam({ - value1: 1, - value2: 2 - })); + console.log("Testing AddValuesSingleParam"); + console.log( + await test.AddValuesSingleParam({ + value1: 1, + value2: 2, + }) + ); - console.log("Testing AddValuesMultipleParams") - console.log(await test.AddValuesMultipleParams(1,2)); + console.log("Testing AddValuesMultipleParams"); + console.log(await test.AddValuesMultipleParams(1, 2)); - console.log("Testing Notification") + console.log("Testing Notification"); test.OnEvent("Hi, this is an event"); + + console.log("Let verification fail!"); + await test + //@ts-ignore + .AddValuesMultipleParams("asd", 2) + .then(() => { + console.log("!!!!This should have failed!!!!"); + }) + .catch((err) => { + console.log("Found expected error!", err.message); + }); } run(); - diff --git a/package.json b/package.json index 56d7cc4..c17da21 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "devDependencies": { "@types/debug": "^4.1.7", "@types/node": "^17.0.5", + "@types/prettier": "^2.4.2", "@types/yargs": "^17.0.8", "ts-node": "^10.4.0", "typescript": "^4.5.4" diff --git a/src/process.ts b/src/process.ts index fea6c1e..49380d3 100644 --- a/src/process.ts +++ b/src/process.ts @@ -143,12 +143,16 @@ export default function startCompile(options: CompileOptions) { FS.writeFileSync(options.emitDefinitions, JSON.stringify(ir)); } + if(options.targets.length <= 0) { + console.log(Color.yellow("WARNING:"), "No targets selected!"); + } + options.targets.forEach(target => { const tg = Targets.get(target.type) as any if(!tg) { console.log(Color.red("ERROR:"), "Target not supported!"); return; } - compile(ir, new tg(target.output)); //TODO: implement + compile(ir, new tg(target.output)); }) } diff --git a/src/targets/typescript.ts b/src/targets/typescript.ts index f3b3e17..d258633 100644 --- a/src/targets/typescript.ts +++ b/src/targets/typescript.ts @@ -6,7 +6,8 @@ import { Step, } from "../ir"; -import { CompileTarget, } from "../compile"; +import { CompileTarget } from "../compile"; +import * as prettier from "prettier"; type lineAppender = (ind: number, line: string | string[]) => void; @@ -40,10 +41,7 @@ export class TypescriptTarget extends CompileTarget { a( 0, def.depends.map((dep) => - this.generateImport( - `${dep}, { verify_${dep} }`, - "./" + dep - ) + this.generateImport(`${dep}, { verify_${dep} }`, "./" + dep) ) ); } @@ -53,14 +51,13 @@ export class TypescriptTarget extends CompileTarget { } private writeFormattedFile(file: string, code: string) { - this.writeFile(file, code); - //TODO: Add Prettier back - // const formatted = format(code, { - // parser: "typescript", - // tabWidth: 3, - // }); + // this.writeFile(file, code); + const formatted = prettier.format(code, { + parser: "typescript", + tabWidth: 3, + }); - // this.writeFile(file, formatted); + this.writeFile(file, formatted); } generateType(def: TypeDefinition) { @@ -91,23 +88,24 @@ export class TypescriptTarget extends CompileTarget { a(0, ``); a(1, `constructor(init?:Partial<${def.name}>){`); - a(2, `if(init){`) - def.fields.forEach(field=>{ - a(3, `if(init["${field.name}"])`) - a(4, `this.${field.name} = init["${field.name}"];`) - }) + a(2, `if(init){`); + def.fields.forEach((field) => { + a(3, `if(init["${field.name}"])`); + a(4, `this.${field.name} = init["${field.name}"];`); + }); a(2, `}`); a(1, `}`); a(0, ``); - a(0, ``); a(1, `static verify(data: ${def.name}){`); a(2, `return verify_${def.name}(data);`); - a(1, `}`) - a(0, "}"); + a(1, `}`); + a(0, `}`); + + a(0, ``); a(0, `export function verify_${def.name}(data: ${def.name}): boolean {`); { @@ -117,19 +115,41 @@ export class TypescriptTarget extends CompileTarget { `if(data["${field.name}"] !== null && data["${field.name}"] !== undefined ) {` ); - const ap: lineAppender = (i, l) => a(i + 2, l); - const verifyType = ( )=>{}; - a(2, "// TODO: Implement") - //TODO: Build verification - // if (field.array) { - // a(2, `if(!Array.isArray(data["${field.name}"])) return false`); - // a(2, `if(!(data["${field.name}"].some(e=>))) return false`) - // serializeArray(field, `data["${field.name}"]`, ap); - // } else if (field.map) { - // serializeMap(field, `data["${field.name}"]`, ap); - // } else { - // serializeType(field, `data["${field.name}"]`, true, ap); - // } + const verifyType = (varName: string) => { + switch (field.type) { + case "string": + a(2, `if(typeof ${varName} !== "string") return false;`); + break; + case "number": + a(2, `if(typeof ${varName} !== "number") return false;`); + break; + case "boolean": + a(2, `if(typeof ${varName} !== "boolean") return false;`); + break; + default: + a( + 2, + `if(!verify_${field.type}(${varName})) return false;` + ); + } + }; + + if (field.array) { + a(2, `if(!Array.isArray(data["${field.name}"])) return false`); + a(2, `for(const elm of data["${field.name}"]) {`); + verifyType("elm"); + a(2, `}`); + } else if (field.map) { + a( + 2, + `if(typeof data["${field.name}"] !== "object") return false` + ); + a(2, `for(const key in data["${field.name}"]) {`); + verifyType(`data["${field.name}"][key]`); + a(2, `}`); + } else { + verifyType(`data["${field.name}"]`); + } a(1, "}"); a(0, ``); }); @@ -157,18 +177,25 @@ export class TypescriptTarget extends CompileTarget { a(0, `export default ${def.name}`); - a( - 0, - `export function verify_${def.name} (data: ${def.name}): boolean {` - ); + a(0, `export function verify_${def.name} (data: ${def.name}): boolean {`); a(1, `return ${def.name}[data] != undefined`); a(0, "}"); - this.writeFormattedFile(this.getFileName(def.name), lines.join("\n")); } generateServiceClient(def: ServiceDefinition) { + this.writeFile( + "service_client.ts", + this.generateImport( + "{ RequestObject, ResponseObject, ErrorCodes, Logging }", + "./service_base" + ) + + "\n\n" + + this.getTemplate("ts_service_client.ts") + ); + + let lines: string[] = []; const a: lineAppender = (i, t) => { if (!Array.isArray(t)) { @@ -185,14 +212,12 @@ export class TypescriptTarget extends CompileTarget { }); a(0, `}`); - this.writeFile( - "service_client.ts", + a( + 0, this.generateImport( - "{ RequestObject, ResponseObject, ErrorCodes, Logging }", + "{ verify_number, verify_string, verify_boolean }", "./service_base" - ) + - "\n\n" + - this.getTemplate("ts_service_client.ts") + ) ); a( @@ -203,17 +228,19 @@ export class TypescriptTarget extends CompileTarget { ) ); + a(0, ``); + a(0, `export class ${def.name} extends Service {`); a(1, `constructor(provider: ServiceProvider){`); a(2, `super(provider, "${def.name}");`); a(1, `}`); - for(const fnc of def.functions) { - const params = fnc.inputs.map(e=>`${e.name}: ${toJSType(e.type)}`).join(","); - //TODO: Prio 1 : Verify response! + for (const fnc of def.functions) { + const params = fnc.inputs + .map((e) => `${e.name}: ${toJSType(e.type)}`) + .join(","); //TODO: Prio 2 : Add optional parameters to this and the declaration file - //TODO: Prio 3 : Maybe verify params? But the server will to this regardless so... Maybe not?` - if(!fnc.return) { + if (!fnc.return) { a(1, `${fnc.name}(${params}): void {`); a(2, `this._provider.sendMessage({`); a(3, `jsonrpc: "2.0",`); @@ -224,15 +251,21 @@ export class TypescriptTarget extends CompileTarget { } else { const retType = fnc.return ? toJSType(fnc.return) : "void"; a(1, `${fnc.name}(${params}): Promise<${retType}> {`); - a(2, `return new Promise<${retType}>((ok, err) => {`) + a(2, `return new Promise<${retType}>((ok, err) => {`); a(3, `this._provider.sendMessage({`); a(4, `jsonrpc: "2.0",`); - a(4, `id: getRandomID(16),`); + a(4, `id: getRandomID(16),`); a(4, `method: "${def.name}.${fnc.name}",`); a(4, `params: [...arguments]`); - a(3, `}, {`); - a(4, `ok, err`); + a(3, `}, {`); + a(4, `ok, err`); a(3, `});`); + a(2, `}).then(result => {`); + a( + 3, + `if(!verify_${fnc.return}(result)) throw new Error("Invalid result data!");` + ); + a(3, `return result;`); a(2, `});`); a(1, `}`); } @@ -262,8 +295,8 @@ export class TypescriptTarget extends CompileTarget { "{ RequestObject, ResponseObject, ErrorCodes, Logging }", "./service_base" ) + - "\n\n" + - this.getTemplate("ts_service_server.ts") + "\n\n" + + this.getTemplate("ts_service_server.ts") ); this.generateImports(a, def); @@ -274,49 +307,65 @@ export class TypescriptTarget extends CompileTarget { }); a(0, `}`); + a(0, this.generateImport("{ Service }", "./service_server")); + a( 0, this.generateImport( - "{ Service }", - "./service_server" + "{ verify_number, verify_string, verify_boolean }", + "./service_base" ) ); + a(0, ``); a(0, `export abstract class ${def.name} extends Service {`); a(1, `public name = "${def.name}";`); a(1, `constructor(){`); a(2, `super();`); - for(const fnc of def.functions) { + for (const fnc of def.functions) { a(2, `this.functions.add("${fnc.name}")`); } - a(1, `}`) + a(1, `}`); a(0, ``); - - for(const fnc of def.functions) { - const params = [...fnc.inputs.map(e=>`${e.name}: ${toJSType(e.type)}`), `ctx: T`].join(", "); - const retVal = fnc.return ? `Promise<${toJSType(fnc.return)}>` : `void`; - a(1, `abstract ${fnc.name}(${params}): ${retVal};`) + + for (const fnc of def.functions) { + const params = [ + ...fnc.inputs.map((e) => `${e.name}: ${toJSType(e.type)}`), + `ctx: T`, + ].join(", "); + const retVal = fnc.return + ? `Promise<${toJSType(fnc.return)}>` + : `void`; + a(1, `abstract ${fnc.name}(${params}): ${retVal};`); // a(0, ``); a(1, `_${fnc.name}(params: any[] | any, ctx: T): ${retVal} {`); a(2, `let p: any[] = [];`); a(2, `if(Array.isArray(params)){`); - //TODO: Verify params! a(3, `p = params;`); a(2, `} else {`); - for(const param of fnc.inputs) { + for (const param of fnc.inputs) { a(3, `p.push(params["${param.name}"])`); } a(2, `}`); - a(2, `p.push(ctx);`) //TODO: Either this or [...p, ctx] but idk + a(2, ``); + + for(let i = 0; i < fnc.inputs.length; i++) { + a(2, `if(p[${i}] !== null && p[${i}] !== undefined) {`); + a(2, `if(!verify_${fnc.inputs[i].type}(p[${i}])) throw new Error("Parameter verification failed!")`); + a(2, `}`); + } + + a(2, ``); + a(2, `p.push(ctx);`); a(2, `return this.${fnc.name}.call(this, ...p);`); - a(1, `}`) + a(1, `}`); a(0, ``); } - a(0, `}`) + a(0, `}`); this.writeFormattedFile( this.getFileName(def.name + "_server"), @@ -348,7 +397,7 @@ export class TypescriptTarget extends CompileTarget { t.forEach((l) => linesServer.push(" ".repeat(i) + l.trim())); }; - let lines:string[] = []; + let lines: string[] = []; const a: lineAppender = (i, t) => { if (!Array.isArray(t)) { t = [t]; @@ -356,52 +405,65 @@ export class TypescriptTarget extends CompileTarget { t.forEach((l) => lines.push(" ".repeat(i) + l.trim())); }; - let hasService = false; steps.forEach(([type, def]) => { - switch(type) { + 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} }`, + "./" + def.name + ) + ); + + a(0, `export { verify_${def.name} }`); a(0, `export type { ${def.name} }`); - a(0,``); + 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,``); + a( + 0, + this.generateImport( + `${def.name}, { verify_${def.name} }`, + "./" + def.name + ) + ); + a(0, `export { ${def.name}, verify_${def.name} }`); + a(0, ``); break; case "service": let ext = this.flavour == "esm" ? ".ts" : ""; - if(!hasService) { + if (!hasService) { hasService = true; ac(0, `export * from "./service_client${ext}"`); - ac(0,``); + ac(0, ``); as(0, `export * from "./service_server${ext}"`); - as(0,``); + as(0, ``); a(0, `export * as Client from "./index_client${ext}"`); a(0, `export * as Server from "./index_server${ext}"`); a(0, `export { Logging } from "./service_base${ext}"`); - a(0,``) - //TODO: Export service globals + a(0, ``); } - ac(0, `export { ${def.name} } from "./${def.name}_client${ext}"`); - as(0, `export { ${def.name} } from "./${def.name}_server${ext}"`); - ac(0,``); - as(0,``); + ac( + 0, + `export { ${def.name} } from "./${def.name}_client${ext}"` + ); + as( + 0, + `export { ${def.name} } from "./${def.name}_server${ext}"` + ); + ac(0, ``); + as(0, ``); break; } - }) + }); - this.writeFormattedFile( - this.getFileName("index"), - lines.join("\n") - ); + this.writeFormattedFile(this.getFileName("index"), lines.join("\n")); this.writeFormattedFile( this.getFileName("index_client"), diff --git a/templates/ts_service_base.ts b/templates/ts_service_base.ts index 78f1015..b88bc42 100644 --- a/templates/ts_service_base.ts +++ b/templates/ts_service_base.ts @@ -1,18 +1,18 @@ -export const Logging = { +export const Logging = { verbose: false, log(...args: any[]) { - if(Logging.verbose) { - console.log(...args) + if (Logging.verbose) { + console.log(...args); } - } -} + }, +}; export enum ErrorCodes { - ParseError=-32700, - InvalidRequest=-32700, - MethodNotFound=-32700, - InvalidParams=-32700, - InternalError=-32700, + ParseError = -32700, + InvalidRequest = -32700, + MethodNotFound = -32700, + InvalidParams = -32700, + InternalError = -32700, } export interface RequestObject { @@ -25,6 +25,24 @@ export interface RequestObject { export interface ResponseObject { jsonrpc: "2.0"; result?: any; - error?: { code: ErrorCodes, message:string, data?: any} + 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; +} diff --git a/templates/ts_service_client.ts b/templates/ts_service_client.ts index 9b18c48..7f29616 100644 --- a/templates/ts_service_client.ts +++ b/templates/ts_service_client.ts @@ -34,7 +34,13 @@ export class ServiceProvider { } else { Logging.log("CLIENT: Determined type is Notification"); //Notification. Send to Notification handler - //TODO: Implement + const [srvName, fncName] = msg.method.split("."); + let service = this.services.get(srvName) + if(!service) { + Logging.log("CLIENT: Did not find Service wanted by Notification!", srvName); + } else { + //TODO: Implement Event thingy (or so :)) + } } } else { Logging.log("CLIENT: Determined type is Response"); diff --git a/yarn.lock b/yarn.lock index a314c4c..4304b18 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21,12 +21,13 @@ __metadata: languageName: node linkType: hard -"@hibas123/SimpleRPC@workspace:.": +"@hibas123/jrpcgen@workspace:.": version: 0.0.0-use.local - resolution: "@hibas123/SimpleRPC@workspace:." + resolution: "@hibas123/jrpcgen@workspace:." dependencies: "@types/debug": ^4.1.7 "@types/node": ^17.0.5 + "@types/prettier": ^2.4.2 "@types/yargs": ^17.0.8 chalk: 4 debug: ^4.3.3 @@ -88,6 +89,13 @@ __metadata: languageName: node linkType: hard +"@types/prettier@npm:^2.4.2": + version: 2.4.2 + resolution: "@types/prettier@npm:2.4.2" + checksum: 76e230b2d11028af11fe12e09b2d5b10b03738e9abf819ae6ebb0f78cac13d39f860755ce05ac3855b608222518d956628f5d00322dc206cc6d1f2d8d1519f1e + languageName: node + linkType: hard + "@types/yargs-parser@npm:*": version: 20.2.1 resolution: "@types/yargs-parser@npm:20.2.1"