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