import chalk from "chalk"; import { CompileTarget } from "../compile"; import { TypeDefinition, EnumDefinition, ServiceDefinition, Step, IR } from "../ir"; import { lineAppender, LineAppender } from "../utils"; const conversion = { boolean: "bool", float: "f64", int: "i64", string: "String", void: "()", bytes: "Vec", }; function toRustType(type: string): string { return (conversion as any)[type] || type; } function toSnake(input: string) { return ( input[0].toLowerCase() + input.slice(1).replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`) ); } export class RustTarget extends CompileTarget<{ rust_crate: string }> { name: string = "rust"; get crate() { return this.options.rust_crate; } start(): void { if (!this.crate) throw new Error( "Setting a crate name is required. Add the following to your jrpc file: 'define rust_crate '" ); if (this.options.allow_bytes == true) { throw new Error("Rust has no support for 'bytes' yet!"); } this.writeFile( "Cargo.toml", this.getTemplate("Rust/Cargo.toml").replace("__name__", this.crate) ); this.writeFile("src/base_lib.rs", this.getTemplate("Rust/src/lib.rs")); } private addDependencies( a: lineAppender, def: TypeDefinition | ServiceDefinition ) { for (const dep of def.depends) { a(0, `use crate::${dep};`); } a(0, ``); a(0, ``); } generateType(definition: TypeDefinition): void { const { a, getResult } = LineAppender(); if (definition.fields.find((e) => e.map)) a(0, `use std::collections::hash_map::HashMap;`); a(0, `use serde::{Deserialize, Serialize};`); this.addDependencies(a, definition); a(0, `#[derive(Clone, Debug, Serialize, Deserialize)]`); a(0, `pub struct ${definition.name} {`); for (const field of definition.fields) { a(1, `#[allow(non_snake_case)]`); let fn = `pub ${field.name}:`; if (field.name == "type") { // TODO: Add other keywords as well! console.log( chalk.yellow("[RUST] WARNING:"), "Field name 'type' is not allowed in Rust. Renaming to 'type_'" ); fn = `pub type_:`; a(1, `#[serde(rename = "type")]`); } let opts = ""; let opte = ""; if (field.optional) { opts = "Option<"; opte = ">"; } if (field.array) { a(1, `${fn} ${opts}Vec<${toRustType(field.type)}>${opte},`); } else if (field.map) { a( 1, `${fn} ${opts}HashMap<${toRustType( field.map )}, ${toRustType(field.type)}>${opte},` ); } else { a(1, `${fn} ${opts}${toRustType(field.type)}${opte},`); } } a(0, `}`); this.writeFile(`src/${toSnake(definition.name)}.rs`, getResult()); } generateEnum(definition: EnumDefinition): void { const { a, getResult } = LineAppender(); a(0, `use int_enum::IntEnum;`); a(0, `use serde::{Deserialize, Serialize};`); a(0, ``); a(0, ``); a(0, `#[repr(i64)]`); a( 0, "#[derive(Clone, Copy, Debug, Eq, PartialEq, IntEnum)]" ); a(0, `pub enum ${definition.name} {`); for (const field of definition.values) { a(1, `${field.name} = ${field.value},`); } a(0, `}`); this.writeFile(`src/${toSnake(definition.name)}.rs`, getResult()); } private generateServiceClient(definition: ServiceDefinition): void { const { a, getResult } = LineAppender(); const typeToRust = (type: string, array: boolean) => { let rt = toRustType(type); return array ? `Vec<${rt}>` : rt; }; this.addDependencies(a, definition); a(0, `use crate::base_lib::{JRPCClient, JRPCRequest, Result};`); a(0, `use serde_json::{json};`); a(0, ``); a(0, `pub struct ${definition.name}{`); a(1, `client: JRPCClient,`); a(0, `}`); a(0, ``); a(0, `impl ${definition.name} {`); a(1, `pub fn new(client: JRPCClient) -> Self {`); a(2, `return Self { client };`); a(1, `}`); a(0, ``); for (const fnc of definition.functions) { let params = fnc.inputs .map((i) => i.name + ": " + typeToRust(i.type, i.array)) .join(", "); let ret = fnc.return ? typeToRust(fnc.return.type, fnc.return.array) : "()"; a(1, `#[allow(non_snake_case)]`); a(1, `pub async fn ${fnc.name}(&self, ${params}) -> Result<${ret}> {`); a(2, `let l_req = JRPCRequest {`); a(3, `jsonrpc: "2.0".to_owned(),`); a(3, `id: None, // 'id' will be set by the send_request function`); a(3, `method: "${definition.name}.${fnc.name}".to_owned(),`); a(3, `params: json!([${fnc.inputs.map((e) => e.name)}])`); a(2, `};`); a(2, ``); if (fnc.return) { a(2, `let l_res = self.client.send_request(l_req).await;`); a(2, `match l_res {`); a(3, `Err(e) => return Err(e),`); if (fnc.return.type == "void") { a(3, `Ok(_) => {`); a(4, `return Ok(());`); a(3, `}`); } else { a(3, `Ok(o) => {`); a( 4, `return serde_json::from_value(o).map_err(|e| Box::from(e));` ); a(3, `}`); } a(2, `}`); } else { a(2, `self.client.send_notification(l_req).await;`); a(2, `return Ok(());`); } a(1, `}`); } a(0, `}`); a(0, ``); this.writeFile(`src/client/${toSnake(definition.name)}.rs`, getResult()); } private generateServiceServer(definition: ServiceDefinition): void { const { a, getResult } = LineAppender(); this.addDependencies(a, definition); a(0, `use crate::base_lib::{JRPCServerService, JRPCRequest, Result};`); a(0, `use serde_json::{Value};`); a(0, `use std::sync::Arc;`); a(0, `use async_trait::async_trait;`); const typeToRust = (type: string, array: boolean) => { let rt = toRustType(type); return array ? `Vec<${rt}>` : rt; }; a(0, `#[async_trait]`); a(0, `pub trait ${definition.name} {`); for (const fnc of definition.functions) { let params = fnc.inputs.length > 0 ? fnc.inputs .map((i) => i.name + ": " + typeToRust(i.type, i.array)) .join(", ") : ""; let ret = fnc.return ? typeToRust(fnc.return.type, fnc.return.array) : "()"; a(1, `#[allow(non_snake_case)]`); a(1, `async fn ${fnc.name}(&self, ${params}) -> Result<${ret}>;`); } a(0, `}`); a(0, ``); a(0, `pub struct ${definition.name}Handler {`); a(1, `implementation: Box,`); a(0, `}`); a(0, ``); a(0, `impl ${definition.name}Handler {`); //TODO: Maybe add a new definition like, pub fn new2(implementation: T) where T: ${definition.name} + Sync + Send + 'static {} a( 1, `pub fn new(implementation: Box) -> Arc {` ); a(2, `return Arc::from(Self { implementation });`); a(1, `}`); a(0, `}`); a(0, ``); a(0, `#[async_trait]`); a( 0, `impl JRPCServerService for ${definition.name}Handler {` ); a(1, `fn get_id(&self) -> String { "${definition.name}".to_owned() }`); a(0, ``); a(1, `#[allow(non_snake_case)]`); a( 1, `async fn handle(&self, msg: &JRPCRequest, function: &str) -> Result<(bool, Value)> {` ); a(2, `match function {`); for (const fnc of definition.functions) { a(3, `"${fnc.name}" => {`); a(4, `if msg.params.is_array() {`); if (fnc.inputs.length > 0) { a( 5, `let arr = msg.params.as_array().unwrap(); //TODO: Check if this can fail.` ); } a(5, `let res = self.implementation.${fnc.name}(`); for (let i = 0; i < fnc.inputs.length; i++) { const inp = fnc.inputs[i]; a(6, `serde_json::from_value(arr[${i}].clone())`); a( 7, `.map_err(|_| "Parameter for field '${inp.name}' should be of type '${inp.type}'!")?${i == fnc.inputs.length - 1 ? "" : ","}` //TODO: Array ); } a(5, `).await?;`); if (fnc.return) { a(5, `return Ok((true, serde_json::to_value(res)?));`); } else { a(5, `_ = res;`); a(5, `return Ok((false, Value::Null))`); } a(4, `} else if msg.params.is_object() {`); a(5, `return Err(Box::from("Not implemented yet".to_owned()));`); a(4, `} else {`); a(5, `return Err(Box::from("Invalid parameters??".to_owned()));`); a(4, `}`); a(3, `},`); } a( 3, `_ => { return Err(Box::from(format!("Invalid function {}", function).to_owned())) ;},` ); a(2, `}`); a(1, `}`); a(0, `}`); this.writeFile(`src/server/${toSnake(definition.name)}.rs`, getResult()); } generateService(definition: ServiceDefinition): void { console.log( chalk.yellow("[RUST] WARNING:"), "Rust support for services is WIP. Use with care!" ); this.generateServiceServer(definition); this.generateServiceClient(definition); } private generateLib(steps: Step[]) { const { a, getResult } = LineAppender(); const lc = LineAppender(); const ls = LineAppender(); const ac = lc.a; const as = ls.a; a(0, `pub mod base_lib;`); a(0, `pub use base_lib::{JRPCServer, JRPCClient, Result};`); for (const [typ, def] of steps) { if (typ == "type" || typ == "enum") { a(0, `mod ${toSnake(def.name)};`); a(0, `pub use ${toSnake(def.name)}::${def.name};`); } else if (typ == "service") { as(0, `mod ${toSnake(def.name)};`); as( 0, `pub use ${toSnake(def.name)}::{${def.name}, ${def.name }Handler};` ); ac(0, `mod ${toSnake(def.name)};`); ac(0, `pub use ${toSnake(def.name)}::${def.name};`); } } a(0, `pub mod server;`); a(0, `pub mod client;`); this.writeFile(`src/lib.rs`, getResult()); this.writeFile(`src/server/mod.rs`, ls.getResult()); this.writeFile(`src/client/mod.rs`, lc.getResult()); } finalize(steps: Step[]): void { this.generateLib(steps); // throw new Error("Method not implemented."); } }