JsonRPC/src/targets/rust.ts
2023-01-02 16:34:13 +01:00

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.");
}
}