Add Target: Rust
This commit is contained in:
@ -11,6 +11,7 @@ import {
|
||||
NodeJSTypescriptTarget,
|
||||
} from "./targets/typescript";
|
||||
import { CSharpTarget } from "./targets/csharp";
|
||||
import { RustTarget } from "./targets/rust";
|
||||
|
||||
class CatchedError extends Error {}
|
||||
|
||||
@ -21,6 +22,7 @@ export const Targets = new Map<string, typeof CompileTarget>();
|
||||
Targets.set("ts-esm", ESMTypescriptTarget);
|
||||
Targets.set("ts-node", NodeJSTypescriptTarget);
|
||||
Targets.set("c#", CSharpTarget as typeof CompileTarget);
|
||||
Targets.set("rust", RustTarget as typeof CompileTarget);
|
||||
|
||||
function indexToLineAndCol(src: string, index: number) {
|
||||
let line = 1;
|
||||
|
377
src/targets/rust.ts
Normal file
377
src/targets/rust.ts
Normal file
@ -0,0 +1,377 @@
|
||||
import chalk from "chalk";
|
||||
import { CompileTarget } from "../compile";
|
||||
import { TypeDefinition, EnumDefinition, ServiceDefinition, Step } from "../ir";
|
||||
|
||||
type lineAppender = (ind: number, line: string | string[]) => void;
|
||||
|
||||
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 {
|
||||
let lines: string[] = [];
|
||||
const a: lineAppender = (i, t) => {
|
||||
if (!Array.isArray(t)) {
|
||||
t = [t];
|
||||
}
|
||||
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
|
||||
};
|
||||
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) {
|
||||
if (field.array) {
|
||||
a(1, `pub ${field.name}: Vec<${toRustType(field.type)}>,`);
|
||||
} else if (field.map) {
|
||||
a(
|
||||
1,
|
||||
`pub ${field.name}: HashMap<${toRustType(
|
||||
field.map
|
||||
)}, ${toRustType(field.type)}>,`
|
||||
);
|
||||
} else {
|
||||
a(1, `pub ${field.name}: ${toRustType(field.type)},`);
|
||||
}
|
||||
}
|
||||
a(0, `}`);
|
||||
|
||||
this.writeFile(`src/${toSnake(definition.name)}.rs`, lines.join("\n"));
|
||||
}
|
||||
|
||||
generateEnum(definition: EnumDefinition): void {
|
||||
let lines: string[] = [];
|
||||
const a: lineAppender = (i, t) => {
|
||||
if (!Array.isArray(t)) {
|
||||
t = [t];
|
||||
}
|
||||
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
|
||||
};
|
||||
|
||||
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, Deserialize, Serialize)]"
|
||||
);
|
||||
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`, lines.join("\n"));
|
||||
}
|
||||
|
||||
private generateServiceClient(definition: ServiceDefinition): void {
|
||||
let lines: string[] = [];
|
||||
const a: lineAppender = (i, t) => {
|
||||
if (!Array.isArray(t)) {
|
||||
t = [t];
|
||||
}
|
||||
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
|
||||
};
|
||||
|
||||
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, `pub 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);`);
|
||||
a(2, `if let Err(e) = l_res {`);
|
||||
a(3, `return Err(e);`);
|
||||
a(2, `} else if let Ok(o) = l_res {`);
|
||||
if (fnc.return.type == "void") {
|
||||
a(3, `return ();`);
|
||||
} else {
|
||||
a(
|
||||
3,
|
||||
`return serde_json::from_value(o).map_err(|e| Box::from(e));`
|
||||
);
|
||||
}
|
||||
a(2, `} else { panic!("What else cases could there be?"); }`);
|
||||
} else {
|
||||
a(2, `self.client.send_notification(l_req);`);
|
||||
a(2, `return Ok(());`);
|
||||
}
|
||||
a(1, `}`);
|
||||
}
|
||||
a(0, `}`);
|
||||
a(0, ``);
|
||||
|
||||
this.writeFile(
|
||||
`src/client/${toSnake(definition.name)}.rs`,
|
||||
lines.join("\n")
|
||||
);
|
||||
}
|
||||
|
||||
private generateServiceServer(definition: ServiceDefinition): void {
|
||||
let lines: string[] = [];
|
||||
const a: lineAppender = (i, t) => {
|
||||
if (!Array.isArray(t)) {
|
||||
t = [t];
|
||||
}
|
||||
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
|
||||
};
|
||||
|
||||
this.addDependencies(a, definition);
|
||||
a(0, `use crate::base_lib::{JRPCServiceHandler,JRPCRequest,Result};`);
|
||||
a(0, `use serde_json::{Value};`);
|
||||
|
||||
const typeToRust = (type: string, array: boolean) => {
|
||||
let rt = toRustType(type);
|
||||
return array ? `Vec<${rt}>` : rt;
|
||||
};
|
||||
|
||||
const CTX_TYPE = "'static + Sync + Send + Copy";
|
||||
|
||||
a(0, ``);
|
||||
a(0, `pub trait ${definition.name}<C: ${CTX_TYPE}> {`);
|
||||
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, `fn ${fnc.name}(&self, ${params}context: &C) -> Result<${ret}>;`);
|
||||
}
|
||||
a(0, `}`);
|
||||
|
||||
a(0, ``);
|
||||
a(0, `pub struct ${definition.name}Handler<C: ${CTX_TYPE}> {`);
|
||||
a(1, `implementation: Box<dyn ${definition.name}<C> + Sync + Send>,`);
|
||||
a(0, `}`);
|
||||
a(0, ``);
|
||||
a(0, `impl<C: ${CTX_TYPE}> ${definition.name}Handler<C> {`);
|
||||
a(
|
||||
1,
|
||||
`pub fn new(implementation: Box<dyn ${definition.name}<C> + Sync + Send>) -> Box<Self> {`
|
||||
);
|
||||
a(2, `return Box::from(Self { implementation });`);
|
||||
a(1, `}`);
|
||||
a(0, `}`);
|
||||
|
||||
a(0, ``);
|
||||
|
||||
a(
|
||||
0,
|
||||
`impl<C: ${CTX_TYPE}> JRPCServiceHandler<C> for ${definition.name}Handler<C> {`
|
||||
);
|
||||
|
||||
a(1, `fn get_name(&self) -> String { "${definition.name}".to_owned() }`);
|
||||
a(0, ``);
|
||||
|
||||
a(
|
||||
1,
|
||||
`fn on_message(&self, msg: JRPCRequest, function: String, ctx: &C) -> Result<(bool, Value)> {`
|
||||
);
|
||||
a(2, `match function.as_str() {`);
|
||||
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}'!")?,` //TODO: Array
|
||||
);
|
||||
}
|
||||
a(5, `ctx)?;`);
|
||||
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`,
|
||||
lines.join("\n")
|
||||
);
|
||||
}
|
||||
|
||||
generateService(definition: ServiceDefinition): void {
|
||||
console.log(
|
||||
chalk.yellow("WARNING:"),
|
||||
"Rust support for services is WIP. Use with care!"
|
||||
);
|
||||
this.generateServiceServer(definition);
|
||||
this.generateServiceClient(definition);
|
||||
}
|
||||
|
||||
private generateLib(steps: Step[]) {
|
||||
let lines: string[] = [];
|
||||
const a: lineAppender = (i, t) => {
|
||||
if (!Array.isArray(t)) {
|
||||
t = [t];
|
||||
}
|
||||
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
|
||||
};
|
||||
|
||||
let linesServer: string[] = [];
|
||||
const as: lineAppender = (i, t) => {
|
||||
if (!Array.isArray(t)) {
|
||||
t = [t];
|
||||
}
|
||||
t.forEach((l) => linesServer.push(" ".repeat(i) + l.trim()));
|
||||
};
|
||||
|
||||
let linesClient: string[] = [];
|
||||
const ac: lineAppender = (i, t) => {
|
||||
if (!Array.isArray(t)) {
|
||||
t = [t];
|
||||
}
|
||||
t.forEach((l) => linesClient.push(" ".repeat(i) + l.trim()));
|
||||
};
|
||||
|
||||
a(0, `pub mod base_lib;`);
|
||||
a(0, `pub use base_lib::{JRPCServer,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`, lines.join("\n"));
|
||||
this.writeFile(`src/server/mod.rs`, linesServer.join("\n"));
|
||||
this.writeFile(`src/client/mod.rs`, linesClient.join("\n"));
|
||||
}
|
||||
|
||||
finalize(steps: Step[]): void {
|
||||
this.generateLib(steps);
|
||||
// throw new Error("Method not implemented.");
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user