mrpc/src/generators/ts_c.rs
2023-09-27 17:51:03 +02:00

158 lines
5.1 KiB
Rust

use std::io::Write;
use itertools::Itertools;
use crate::data::RPC;
use super::IndentedWriter;
fn output_common(f: &mut IndentedWriter) {
f.f.write_all(
b"interface _WSResponse {
id: number;
data: any;
}
interface _WSWaitingEntry {
ok: (v: any) => void;
err: (reason?: any) => void;
}
export class MRPCConnector {
url: string;
socket: WebSocket;
next_message_id: number;
waiting: { [id: number]: _WSWaitingEntry };
streams: { [id: number]: (v: any) => void };
private open() {
this.socket = new WebSocket(this.url);
this.socket.onmessage = ev => {
const data = JSON.parse(ev.data) as _WSResponse;
if (data.id in this.streams) {
this.streams[data.id](data.data);
if (data.data == null)
delete this.streams[data.id];
} else if (data.id in this.waiting) {
this.waiting[data.id].ok(data.data);
delete this.waiting[data.id];
} else {
console.log(`Got unexpected message: ${data}`);
}
}
this.socket.onerror = () => setTimeout(() => {this.open();}, 2500);
this.socket.onclose = () => setTimeout(() => {this.open();}, 2500);
}
public constructor(url: string) {
this.url = url;
this.next_message_id = 0;
this.waiting = {};
this.streams = {};
this.open();
}\n\n").unwrap();
f.ident = 1;
}
fn field_ty_to_ty_str(ty: &crate::data::FieldTy, with_name: bool) -> String {
use crate::data::Types;
let mut ret = String::new();
if with_name {
ret += &ty.name;
if ty.optional {
ret += "?";
}
ret += ": ";
}
ret += match &ty.ty {
Types::String => "string",
Types::Bool => "boolean",
Types::F32 | Types::F64
|Types::I8 | Types::I16 | Types::I32 | Types::I64
|Types::U8 | Types::U16 | Types::U32 | Types::U64 => "number",
Types::Named(name) => name
};
if ty.array {
ret += "[]";
}
ret
}
fn output_services(f: &mut IndentedWriter, rpc: &RPC) {
for service in &rpc.services {
for method in &service.methods {
write!(f, "public {}_{}(", service.name, method.name).unwrap();
f.write_all(method.args.iter()
.map(|arg| field_ty_to_ty_str(arg, true))
.chain(method.ret_stream.then(|| format!("cbk: (v: {}) => void", field_ty_to_ty_str(method.ret.as_ref().unwrap(), false))))
.join(", ")
.as_bytes()
).unwrap();
f.write_all(b")").unwrap();
if let Some(ret) = &method.ret {
if ret.optional {
unimplemented!("Optional return value is current not supported in typescript client");
}
if !method.ret_stream {
write!(f, ": Promise<{}>", field_ty_to_ty_str(ret, false)).unwrap();
}
}
f.write_all(b" {\nconst msg = {id:this.next_message_id++,").unwrap();
write!(f, "service:'{}',method:'{}',data:{{", service.name, method.name).unwrap();
f.write_all(method.args.iter()
.map(|arg| {
if arg.optional {
format!("{0}:{0}||null", arg.name)
} else {
arg.name.clone()
}
})
.join(",")
.as_bytes()
).unwrap();
f.write_all(b"}};\n").unwrap();
if let Some(ret) = &method.ret {
if !method.ret_stream {
writeln!(f, "const p = new Promise<{}>((ok,err) => {{ this.waiting[msg.id] = {{ ok, err }}; }});", field_ty_to_ty_str(ret, false)).unwrap();
} else {
f.write_all(b"this.streams[msg.id] = cbk;\n").unwrap();
}
}
f.write_all(b"this.socket.send(JSON.stringify(msg));\n").unwrap();
if method.ret.is_some() && !method.ret_stream{
f.write_all(b"return p;\n").unwrap();
}
f.write_all(b"}\n\n").unwrap();
}
}
f.write_all(b"}").unwrap();
}
fn output_enums(f: &mut IndentedWriter, rpc: &RPC) {
for e in &rpc.enums {
writeln!(f, "export enum {} {{", e.name).unwrap();
for (k, v) in &e.values {
writeln!(f, "{k} = {v},").unwrap();
}
f.write_all(b"}\n\n").unwrap();
}
}
fn output_structs(f: &mut IndentedWriter, rpc: &RPC) {
for s in &rpc.structs {
writeln!(f, "export interface {} {{", s.name).unwrap();
for field in &s.fields {
writeln!(f, "{};", field_ty_to_ty_str(field, true)).unwrap();
}
f.write_all(b"}\n\n").unwrap();
}
}
pub fn gen(file_base_name: &std::path::PathBuf, rpc: &RPC) {
let f = std::fs::File::create(file_base_name.with_extension("ts")).unwrap();
let mut f = IndentedWriter::new(f);
let f = &mut f;
output_enums(f, rpc);
output_structs(f, rpc);
output_common(f);
output_services(f, rpc);
}