diff --git a/README.md b/README.md index f7a9d89..875bd1d 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@ Type/Service definition language and code generator for json-rpc 2.0. Currently | ------- | --------------------------------- | | ts-node | Typescript for NodeJS | | ts-esm | Typescript in ESM format for Deno | +| rust | Rust | +| dart | Dart | +| c# | C# | ## Usage @@ -23,7 +26,7 @@ enum TestEnum { type Test { testen: TestEnum; someString: string; - someNumber: number; + someNumber?: number; array: string[]; map: {number, TestEnum}; } @@ -44,4 +47,8 @@ Then run the generator like this `jrpc compile test.jrpc -o=ts-node:output/`. This will generate the Client and Server code in the specified folder. -//TODO: Make Documentation better +## TODOS + +1. Documentation +2. Null Checks/Enforcements in all languages +3. More and better tests diff --git a/examples/import.jrpc b/examples/import.jrpc index e44c177..c00e58d 100644 --- a/examples/import.jrpc +++ b/examples/import.jrpc @@ -1,5 +1,5 @@ type TestAtom { - val_number: float; - val_boolean: boolean; - val_string: string; + val_number?: float; + val_boolean?: boolean; + val_string?: string; } diff --git a/package.json b/package.json index 64fc477..3657c80 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hibas123/jrpcgen", - "version": "1.2.3", + "version": "1.2.5", "main": "lib/index.js", "license": "MIT", "packageManager": "yarn@3.1.1", diff --git a/src/ir.ts b/src/ir.ts index 7549d0d..c758f6e 100644 --- a/src/ir.ts +++ b/src/ir.ts @@ -14,6 +14,7 @@ export interface TypeFieldDefinition { name: string; type: string; array: boolean; + optional: boolean; map?: string; } @@ -127,6 +128,7 @@ export default function get_ir(parsed: Parsed): IR { type: field.fieldtype, array: field.array, map: field.map, + optional: field.optional, }; }); steps.push([ diff --git a/src/parser.ts b/src/parser.ts index e9e0d62..2224736 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -16,6 +16,7 @@ export interface ImportStatement extends DefinitionNode { export interface TypeFieldStatement extends DefinitionNode { type: "type_field"; name: string; + optional: boolean; fieldtype: string; array: boolean; map?: string; @@ -134,13 +135,12 @@ export default function parse(tokens: Token[], file: string): Parsed { return val; }; - + const checkTypes = (...types: string[]) => { if (types.indexOf(currentToken.type) < 0) { throw new ParserError( - `Unexpected token value, expected ${types.join(" | ")}, received '${ - currentToken.value + `Unexpected token value, expected ${types.join(" | ")}, received '${currentToken.value }'`, currentToken ); @@ -170,6 +170,11 @@ export default function parse(tokens: Token[], file: string): Parsed { const idx = currentToken.startIdx; let name = currentToken.value; eatToken(); + let optional = false; + if (currentToken.type === "questionmark") { + eatToken("?"); + optional = true; + } eatToken(":"); let array = false; @@ -198,6 +203,7 @@ export default function parse(tokens: Token[], file: string): Parsed { array, map: mapKey, location: { file, idx }, + optional }; }; @@ -303,9 +309,9 @@ export default function parse(tokens: Token[], file: string): Parsed { eatToken("("); let args: string[] = []; let first = true; - while(currentToken.value !== ")") { - if(first) { - first= false; + while (currentToken.value !== ")") { + if (first) { + first = false; } else { eatToken(","); } @@ -399,8 +405,8 @@ export default function parse(tokens: Token[], file: string): Parsed { let functions: ServiceFunctionStatement[] = []; while (currentToken.type !== "curly_close") { - let decorators:Decorators = new Map; - while(currentToken.type == "at") { + let decorators: Decorators = new Map; + while (currentToken.type == "at") { parseFunctionDecorator(decorators); } @@ -427,8 +433,8 @@ export default function parse(tokens: Token[], file: string): Parsed { let [key] = eatText() let value: string = undefined; - if(currentToken.type == "string") { - value = currentToken.value.slice(1,-1); + if (currentToken.type == "string") { + value = currentToken.value.slice(1, -1); eatToken(); } else { [value] = eatText() @@ -437,8 +443,8 @@ export default function parse(tokens: Token[], file: string): Parsed { eatToken(";"); return { - type :"define", - location: {file, idx}, + type: "define", + location: { file, idx }, key, value } diff --git a/src/targets/rust.ts b/src/targets/rust.ts index e8348af..c702080 100644 --- a/src/targets/rust.ts +++ b/src/targets/rust.ts @@ -83,17 +83,24 @@ export class RustTarget extends CompileTarget<{ rust_crate: string }> { fn = `pub type_:`; a(1, `#[serde(rename = "type")]`); } + let opts = ""; + let opte = ""; + if (field.optional) { + opts = "Option<"; + opte = ">"; + } + if (field.array) { - a(1, `${fn} Vec<${toRustType(field.type)}>,`); + a(1, `${fn} ${opts}Vec<${toRustType(field.type)}>${opte},`); } else if (field.map) { a( 1, - `${fn} HashMap<${toRustType( + `${fn} ${opts}HashMap<${toRustType( field.map - )}, ${toRustType(field.type)}>,` + )}, ${toRustType(field.type)}>${opte},` ); } else { - a(1, `${fn} ${toRustType(field.type)},`); + a(1, `${fn} ${opts}${toRustType(field.type)}${opte},`); } } a(0, `}`); @@ -113,7 +120,7 @@ export class RustTarget extends CompileTarget<{ rust_crate: string }> { a(0, `#[repr(i64)]`); a( 0, - "#[derive(Clone, Copy, Debug, Eq, PartialEq, IntEnum, Deserialize, Serialize)]" + "#[derive(Clone, Copy, Debug, Eq, PartialEq, IntEnum)]" ); a(0, `pub enum ${definition.name} {`); for (const field of definition.values) { diff --git a/src/targets/typescript.ts b/src/targets/typescript.ts index a968a07..761dc6f 100644 --- a/src/targets/typescript.ts +++ b/src/targets/typescript.ts @@ -33,9 +33,8 @@ export class TypescriptTarget extends CompileTarget { } private generateImport(imports: string, path: string) { - return `import ${imports} from "${ - path + (this.flavour === "esm" ? ".ts" : "") - }";\n`; + return `import ${imports} from "${path + (this.flavour === "esm" ? ".ts" : "") + }";\n`; } private generateImports( @@ -89,7 +88,7 @@ export class TypescriptTarget extends CompileTarget { } else { type = toJSType(field.type); } - return `${field.name}?: ${type}; `; + return `${field.name}${field.optional ? "?" : ""}: ${type}; `; }) ); @@ -125,10 +124,18 @@ export class TypescriptTarget extends CompileTarget { ); a(1, `let res = new ${def.name}() as any;`); def.fields.forEach((field) => { - a( - 1, - `if(data["${field.name}"] !== null && data["${field.name}"] !== undefined) {` - ); + if (field.optional) { + a( + 1, + `if(data["${field.name}"] !== null && data["${field.name}"] !== undefined) {` + ); + } else { + a( + 1, + `if(data["${field.name}"] === null || data["${field.name}"] === undefined) throw new VerificationError("${def.name}", "${field.name}", data["${field.name}"]);` + ); + a(1, `else {`); + } if (field.array) { a( 2, @@ -203,9 +210,9 @@ export class TypescriptTarget extends CompileTarget { "{ RequestObject, ResponseObject, ErrorCodes, Logging }", "./service_base" ) + - this.generateImport(" { VerificationError }", "./ts_base") + - "\n\n" + - this.getTemplate("ts_service_client.ts") + this.generateImport(" { VerificationError }", "./ts_base") + + "\n\n" + + this.getTemplate("ts_service_client.ts") ); const { a, getResult } = LineAppender(); @@ -300,9 +307,9 @@ export class TypescriptTarget extends CompileTarget { "{ RequestObject, ResponseObject, ErrorCodes, Logging }", "./service_base" ) + - this.generateImport(" { VerificationError }", "./ts_base") + - "\n\n" + - this.getTemplate("ts_service_server.ts") + this.generateImport(" { VerificationError }", "./ts_base") + + "\n\n" + + this.getTemplate("ts_service_server.ts") ); this.generateImports(a, def); @@ -335,9 +342,8 @@ export class TypescriptTarget extends CompileTarget { `ctx: T`, ].join(", "); const retVal = fnc.return - ? `Promise<${ - toJSType(fnc.return.type) + (fnc.return.array ? "[]" : "") - }>` + ? `Promise<${toJSType(fnc.return.type) + (fnc.return.array ? "[]" : "") + }>` : `void`; a(1, `abstract ${fnc.name}(${params}): ${retVal};`); @@ -380,13 +386,12 @@ export class TypescriptTarget extends CompileTarget { a( 2, `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 => apply_${fnc.return.type}(e))` - : `res => apply_${fnc.return.type}(res)` - });` - : "") + (fnc.return + ? `.then(${fnc.return?.array + ? `res => res.map(e => apply_${fnc.return.type}(e))` + : `res => apply_${fnc.return.type}(res)` + });` + : "") ); a(1, `}`); a(0, ``); diff --git a/templates/Rust/Cargo.toml b/templates/Rust/Cargo.toml index e150e28..8ac3329 100644 --- a/templates/Rust/Cargo.toml +++ b/templates/Rust/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -int-enum = "0.5.0" +int-enum = { version ="0.5.0", features = ["serde", "convert"] } serde = { version = "1.0.147", features = ["derive"] } serde_json = "1.0.88" nanoid = "0.4.0"