359 lines
11 KiB
TypeScript
359 lines
11 KiB
TypeScript
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<u8>",
|
|
};
|
|
|
|
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 <name>'"
|
|
);
|
|
|
|
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<dyn ${definition.name} + Sync + Send + 'static>,`);
|
|
a(0, `}`);
|
|
a(0, ``);
|
|
a(0, `impl ${definition.name}Handler {`);
|
|
//TODO: Maybe add a new definition like, pub fn new2<T>(implementation: T) where T: ${definition.name} + Sync + Send + 'static {}
|
|
a(
|
|
1,
|
|
`pub fn new(implementation: Box<dyn ${definition.name} + Sync + Send + 'static>) -> Arc<Self> {`
|
|
);
|
|
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.");
|
|
}
|
|
}
|