Compare commits
5 Commits
3ca0893a94
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
e550ed32e2
|
|||
|
12392808f7
|
|||
|
613cf68fc3
|
|||
|
9caeb447c3
|
|||
|
b52b0fcc82
|
@@ -7,7 +7,8 @@ pub enum Types {
|
|||||||
U8, U16, U32, U64,
|
U8, U16, U32, U64,
|
||||||
Array(Box<Types>),
|
Array(Box<Types>),
|
||||||
Optional(Box<Types>),
|
Optional(Box<Types>),
|
||||||
Named(String)
|
Named(String),
|
||||||
|
Generic(String, Vec<Types>)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
@@ -25,7 +26,8 @@ pub struct FieldTy {
|
|||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct StructTy {
|
pub struct StructTy {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub fields: Vec<FieldTy>
|
pub fields: Vec<FieldTy>,
|
||||||
|
pub generic_names: Vec<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
|||||||
@@ -1,9 +1,24 @@
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use crate::data::RPC;
|
use crate::data::RPC;
|
||||||
|
|
||||||
|
pub const JSON_INNER_IMPLS: &[(&str, &str)] = &[
|
||||||
|
("std::string", "String"),
|
||||||
|
("std::int8_t", "Int"),
|
||||||
|
("std::int16_t", "Int"),
|
||||||
|
("std::int32_t", "Int"),
|
||||||
|
("std::int64_t", "Int64"),
|
||||||
|
("std::uint8_t", "Uint"),
|
||||||
|
("std::uint16_t", "Uint"),
|
||||||
|
("std::uint32_t", "Uint"),
|
||||||
|
("std::uint64_t", "Uint64"),
|
||||||
|
("bool", "Bool"),
|
||||||
|
("std::float_t", "Double"),
|
||||||
|
("std::double_t", "Double")
|
||||||
|
];
|
||||||
|
|
||||||
pub fn ty_to_str(ty: &crate::data::Types) -> String {
|
pub fn ty_to_str(ty: &crate::data::Types) -> String {
|
||||||
use crate::data::Types;
|
use crate::data::Types;
|
||||||
match &ty {
|
match ty {
|
||||||
Types::String => "std::string".into(),
|
Types::String => "std::string".into(),
|
||||||
Types::Bool => "bool".into(),
|
Types::Bool => "bool".into(),
|
||||||
Types::F32 => "std::float_t".into(),
|
Types::F32 => "std::float_t".into(),
|
||||||
@@ -18,15 +33,40 @@ pub fn ty_to_str(ty: &crate::data::Types) -> String {
|
|||||||
Types::U64 => "std::uint64_t".into(),
|
Types::U64 => "std::uint64_t".into(),
|
||||||
Types::Named(name) => name.into(),
|
Types::Named(name) => name.into(),
|
||||||
Types::Optional(inner) => format!("std::optional<{}>", ty_to_str(inner)),
|
Types::Optional(inner) => format!("std::optional<{}>", ty_to_str(inner)),
|
||||||
Types::Array(inner) => format!("std::vector<{}>", ty_to_str(inner))
|
Types::Array(inner) => format!("std::vector<{}>", ty_to_str(inner)),
|
||||||
|
Types::Generic(name, types) =>
|
||||||
|
format!("{}<{}>", name, types.iter().map(|ty| ty_to_str(ty)).join(", "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_struct_generics(s: &crate::data::StructTy) -> String {
|
||||||
|
if s.generic_names.is_empty() {
|
||||||
|
"".into()
|
||||||
|
} else {
|
||||||
|
format!("template<{}>\n", generics_brace_inner_typename(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generics_brace_inner_typename(s: &crate::data::StructTy) -> String {
|
||||||
|
s.generic_names.iter().map(|n| String::from("typename ") + n).join(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generics_brace(s: &crate::data::StructTy) -> String {
|
||||||
|
if s.generic_names.is_empty() {
|
||||||
|
"".into()
|
||||||
|
} else {
|
||||||
|
format!("<{}>", generics_brace_inner(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generics_brace_inner(s: &crate::data::StructTy) -> String {
|
||||||
|
s.generic_names.iter().join(", ")
|
||||||
|
}
|
||||||
|
|
||||||
pub fn method_args(method: &crate::data::MethodTy) -> String {
|
pub fn method_args(method: &crate::data::MethodTy) -> String {
|
||||||
method.args.iter()
|
method.args.iter()
|
||||||
.map(|arg| format!("{} &&{}", ty_to_str(&arg.ty), arg.name))
|
.map(|arg| format!("{} &&{}", ty_to_str(&arg.ty), arg.name))
|
||||||
.chain(method.ret_stream.then(|| format!("std::shared_ptr<MRPCStream<{}>>&&", ty_to_str(method.ret.as_ref().unwrap()))))
|
.chain(method.ret_stream.then(|| format!("MRPCStream<{}>&&", ty_to_str(method.ret.as_ref().unwrap()))))
|
||||||
.join(", ")
|
.join(", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,11 +85,23 @@ pub fn call_args(method: &crate::data::MethodTy) -> String {
|
|||||||
.join(", ")
|
.join(", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn gen(file_base_name: &std::path::PathBuf, rpc: &RPC) {
|
pub fn streams_required(rpc: &RPC) -> Vec<String> {
|
||||||
let header_name = file_base_name.with_extension("h");
|
let mut streams = std::collections::HashSet::new();
|
||||||
let header_name = header_name.file_name().unwrap().to_str().unwrap();
|
for s in &rpc.services {
|
||||||
let h = std::fs::File::create(file_base_name.with_extension("h")).unwrap();
|
for m in &s.methods {
|
||||||
let c = std::fs::File::create(file_base_name.with_extension("cpp")).unwrap();
|
if m.ret_stream {
|
||||||
crate::templates::cpp_server_h(h, rpc).unwrap();
|
streams.insert(ty_to_str(m.ret.as_ref().unwrap()));
|
||||||
crate::templates::cpp_server_cpp(c, header_name, rpc).unwrap();
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
streams.into_iter().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gen(file_base_name: &std::path::PathBuf, rpc: &RPC) {
|
||||||
|
let header_name = file_base_name.with_extension("hxx");
|
||||||
|
let h = std::fs::File::create(&header_name).unwrap();
|
||||||
|
let header_name = header_name.file_name().unwrap().to_str().unwrap();
|
||||||
|
let c = std::fs::File::create(file_base_name.with_extension("cxx")).unwrap();
|
||||||
|
crate::templates::cpp_server_hxx(h, rpc).unwrap();
|
||||||
|
crate::templates::cpp_server_cxx(c, header_name, rpc).unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,22 +12,32 @@ pub fn ty_to_str(ty: &crate::data::Types) -> String {
|
|||||||
|Types::U8 | Types::U16 | Types::U32 | Types::U64 => "number".into(),
|
|Types::U8 | Types::U16 | Types::U32 | Types::U64 => "number".into(),
|
||||||
Types::Named(name) => name.into(),
|
Types::Named(name) => name.into(),
|
||||||
Types::Optional(inner) => format!("({}|null)", ty_to_str(inner)),
|
Types::Optional(inner) => format!("({}|null)", ty_to_str(inner)),
|
||||||
Types::Array(inner) => format!("{}[]", ty_to_str(inner))
|
Types::Array(inner) => format!("{}[]", ty_to_str(inner)),
|
||||||
|
Types::Generic(name, types) =>
|
||||||
|
format!("{}<{}>", name, types.iter().map(|ty| ty_to_str(ty)).join(", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_struct_generics(s: &crate::data::StructTy) -> String {
|
||||||
|
if s.generic_names.is_empty() {
|
||||||
|
"".into()
|
||||||
|
} else {
|
||||||
|
format!("<{}>", s.generic_names.join(", "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn method_args(method: &crate::data::MethodTy) -> String {
|
pub fn method_args(method: &crate::data::MethodTy) -> String {
|
||||||
method.args.iter()
|
method.args.iter()
|
||||||
.map(|arg| format!("{}: {}", arg.name, ty_to_str(&arg.ty)))
|
.map(|arg| format!("{}: {}", arg.name, ty_to_str(&arg.ty)))
|
||||||
.chain(method.ret_stream.then(|| format!("__cbk: (v: {}) => void", ty_to_str(method.ret.as_ref().unwrap()))))
|
.chain(method.ret_stream.then(|| format!("__cbk: (v: {}|null) => void", ty_to_str(method.ret.as_ref().unwrap()))))
|
||||||
.join(", ")
|
.join(", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn method_ret(method: &crate::data::MethodTy) -> String {
|
pub fn method_ret(method: &crate::data::MethodTy) -> String {
|
||||||
if method.ret_stream || method.ret.is_none() {
|
if method.ret_stream {
|
||||||
String::new()
|
String::new()
|
||||||
} else {
|
} else {
|
||||||
format!(": Promise<{}>", ty_to_str(method.ret.as_ref().unwrap()))
|
format!(": Promise<{}>", method.ret.as_ref().map(ty_to_str).unwrap_or("void".into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
181
src/main.rs
181
src/main.rs
@@ -1,11 +1,12 @@
|
|||||||
mod data;
|
mod data;
|
||||||
mod generators;
|
mod generators;
|
||||||
mod templates;
|
mod templates;
|
||||||
|
mod parser;
|
||||||
|
|
||||||
|
use std::fmt::Write;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use syn::spanned::Spanned;
|
|
||||||
|
|
||||||
#[derive(Debug, clap::Parser)]
|
#[derive(Debug, clap::Parser)]
|
||||||
struct Args {
|
struct Args {
|
||||||
@@ -28,143 +29,6 @@ struct GenArgs {
|
|||||||
static SOURCE_FILE: once_cell::sync::OnceCell<String> = once_cell::sync::OnceCell::new();
|
static SOURCE_FILE: once_cell::sync::OnceCell<String> = once_cell::sync::OnceCell::new();
|
||||||
static SOURCE: once_cell::sync::OnceCell<String> = once_cell::sync::OnceCell::new();
|
static SOURCE: once_cell::sync::OnceCell<String> = once_cell::sync::OnceCell::new();
|
||||||
|
|
||||||
fn parse_enum(item: &syn::ItemEnum) -> data::EnumTy {
|
|
||||||
data::EnumTy {
|
|
||||||
name: item.ident.to_string(),
|
|
||||||
values: item.variants.iter().enumerate().map(|(i, v) | (v.ident.to_string(), i)).collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_type_string(ty: String) -> data::Types {
|
|
||||||
use data::Types::*;
|
|
||||||
match ty.as_str() {
|
|
||||||
"str" | "String" => String,
|
|
||||||
"bool" => Bool,
|
|
||||||
"f32" => F32,
|
|
||||||
"f64" => F64,
|
|
||||||
"i8" => I8,
|
|
||||||
"i16" => I16,
|
|
||||||
"i32" => I32,
|
|
||||||
"i64" => I64,
|
|
||||||
"u8" => U8,
|
|
||||||
"u16" => U16,
|
|
||||||
"u32" => U32,
|
|
||||||
"u64" => U64,
|
|
||||||
_ => Named(ty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_type(item: &syn::Type) -> data::Types {
|
|
||||||
match item {
|
|
||||||
syn::Type::Path(path) => {
|
|
||||||
let segments = &path.path.segments;
|
|
||||||
if segments.len() != 1 {
|
|
||||||
emit_error(item.span(), "Path segments with len != 1");
|
|
||||||
}
|
|
||||||
let segment = &segments[0];
|
|
||||||
if !segment.arguments.is_empty() {
|
|
||||||
if segment.ident.to_string() != "Option" {
|
|
||||||
emit_error(item.span(), "Only Option are currently allowed to have arguments");
|
|
||||||
}
|
|
||||||
let args = match &segment.arguments {
|
|
||||||
syn::PathArguments::AngleBracketed(v) => v,
|
|
||||||
_ => emit_error(item.span(), "Angle bracketed arguments expected")
|
|
||||||
};
|
|
||||||
if args.args.len() != 1 {
|
|
||||||
emit_error(item.span(), "Expected 1 argument");
|
|
||||||
}
|
|
||||||
let ty = match &args.args[0] {
|
|
||||||
syn::GenericArgument::Type(v) => parse_type(v),
|
|
||||||
_ => emit_error(item.span(), "Type bracketed arguments expected")
|
|
||||||
};
|
|
||||||
data::Types::Optional(ty.into())
|
|
||||||
} else {
|
|
||||||
parse_type_string(segment.ident.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
syn::Type::Slice(slice) => {
|
|
||||||
data::Types::Array(parse_type(&slice.elem).into())
|
|
||||||
}
|
|
||||||
_ => emit_error(item.span(), "Unsupported type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_struct(item: &syn::ItemStruct) -> data::StructTy {
|
|
||||||
let name = item.ident.to_string();
|
|
||||||
let mut fields = vec![];
|
|
||||||
for field in &item.fields {
|
|
||||||
if field.ident.is_none() {
|
|
||||||
emit_error(field.span(), "Missing field name");
|
|
||||||
}
|
|
||||||
let name = field.ident.as_ref().unwrap().to_string();
|
|
||||||
let ty = parse_type(&field.ty);
|
|
||||||
fields.push(data::FieldTy { name, ty });
|
|
||||||
}
|
|
||||||
data::StructTy { name, fields }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_parse_iterator(ty: &syn::Type) -> Option<data::Types> {
|
|
||||||
if let syn::Type::Path(ty) = ty {
|
|
||||||
let seg = ty.path.segments.last()?;
|
|
||||||
if seg.ident.to_string() == "Iterator" {
|
|
||||||
if let syn::PathArguments::AngleBracketed(args) = &seg.arguments {
|
|
||||||
if let Some(syn::GenericArgument::Type(ty)) = args.args.first() {
|
|
||||||
Some(parse_type(ty))
|
|
||||||
} else { None }
|
|
||||||
} else { None }
|
|
||||||
} else { None }
|
|
||||||
} else { None }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_method(item: &syn::Signature) -> data::MethodTy {
|
|
||||||
let mut method = data::MethodTy::default();
|
|
||||||
method.name = item.ident.to_string();
|
|
||||||
|
|
||||||
for arg in &item.inputs {
|
|
||||||
let arg = match arg {
|
|
||||||
syn::FnArg::Typed(v) => v,
|
|
||||||
_ => emit_error(arg.span(), "Unsupported argument")
|
|
||||||
};
|
|
||||||
let ty = parse_type(&arg.ty);
|
|
||||||
let name = match &*arg.pat {
|
|
||||||
syn::Pat::Ident(v) => v.ident.to_string(),
|
|
||||||
_ => emit_error(arg.span(), "Unsupported argument")
|
|
||||||
};
|
|
||||||
method.args.push(data::FieldTy { name, ty });
|
|
||||||
}
|
|
||||||
|
|
||||||
match &item.output {
|
|
||||||
syn::ReturnType::Default => {
|
|
||||||
method.ret = None;
|
|
||||||
method.ret_stream = false;
|
|
||||||
}
|
|
||||||
syn::ReturnType::Type(_, ty) => {
|
|
||||||
if let Some(ty) = try_parse_iterator(ty) {
|
|
||||||
method.ret_stream = true;
|
|
||||||
method.ret = Some(ty);
|
|
||||||
} else {
|
|
||||||
method.ret_stream = false;
|
|
||||||
method.ret = Some(parse_type(ty));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
method
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_service(item: &syn::ItemTrait) -> data::ServiceTy {
|
|
||||||
let name = item.ident.to_string();
|
|
||||||
let mut methods = vec![];
|
|
||||||
for item in &item.items {
|
|
||||||
let item = match item {
|
|
||||||
syn::TraitItem::Fn(v) => v,
|
|
||||||
_ => emit_error(item.span(), "Only functions are supported")
|
|
||||||
};
|
|
||||||
methods.push(parse_method(&item.sig));
|
|
||||||
}
|
|
||||||
data::ServiceTy { name, methods }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
@@ -175,19 +39,19 @@ fn main() {
|
|||||||
SOURCE_FILE.set(args.file.to_string_lossy().to_string()).unwrap();
|
SOURCE_FILE.set(args.file.to_string_lossy().to_string()).unwrap();
|
||||||
SOURCE.set(content).unwrap();
|
SOURCE.set(content).unwrap();
|
||||||
|
|
||||||
let ast = syn::parse_file(SOURCE.get().unwrap()).unwrap();
|
let ast = match syn::parse_file(SOURCE.get().unwrap()) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => emit_error(e.into_iter()
|
||||||
|
.map(|e| {
|
||||||
|
let span = e.span();
|
||||||
|
let mut msg = String::new();
|
||||||
|
write!(msg, "{e}").unwrap();
|
||||||
|
(span, msg)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
let mut rpc = data::RPC::default();
|
let rpc = parser::parse_file(&ast);
|
||||||
|
|
||||||
for item in &ast.items {
|
|
||||||
match item {
|
|
||||||
syn::Item::Enum(v) => rpc.enums.push(parse_enum(v)),
|
|
||||||
syn::Item::Struct(v) => rpc.structs.push(parse_struct(v)),
|
|
||||||
syn::Item::Trait(v) => rpc.services.push(parse_service(v)),
|
|
||||||
syn::Item::Use(_) => {}
|
|
||||||
_ => emit_error(item.span(), "Unsupported item")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for gen in &args.generators.clients {
|
for gen in &args.generators.clients {
|
||||||
gen.generate(&args.rpc_name, &rpc);
|
gen.generate(&args.rpc_name, &rpc);
|
||||||
@@ -198,7 +62,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_error(span: proc_macro2::Span, msg: impl Into<String>) -> ! {
|
pub fn emit_error(errors: impl IntoIterator<Item=(proc_macro2::Span, impl Into<String>)>) -> ! {
|
||||||
use codespan_reporting::{
|
use codespan_reporting::{
|
||||||
diagnostic::{Diagnostic, Label},
|
diagnostic::{Diagnostic, Label},
|
||||||
files::{SimpleFiles, Files},
|
files::{SimpleFiles, Files},
|
||||||
@@ -214,14 +78,17 @@ fn emit_error(span: proc_macro2::Span, msg: impl Into<String>) -> ! {
|
|||||||
|
|
||||||
let file = files.get(file_id).unwrap();
|
let file = files.get(file_id).unwrap();
|
||||||
|
|
||||||
let start = span.start();
|
let errors = errors.into_iter().map(|(span, msg)| {
|
||||||
let start: usize = file.line_range((), start.line-1).unwrap().start + start.column;
|
let start = span.start();
|
||||||
let end = span.end();
|
let start: usize = file.line_range((), start.line-1).unwrap().start + start.column;
|
||||||
let end: usize = file.line_range((), end.line-1).unwrap().start + end.column;
|
let end = span.end();
|
||||||
|
let end: usize = file.line_range((), end.line-1).unwrap().start + end.column;
|
||||||
|
Label::primary(file_id, start..end).with_message(msg)
|
||||||
|
});
|
||||||
|
|
||||||
let diagnostic = Diagnostic::error()
|
let diagnostic = Diagnostic::error()
|
||||||
.with_labels(vec![Label::primary(file_id, start..end).with_message(msg)]);
|
.with_labels(errors.collect());
|
||||||
|
|
||||||
term::emit(&mut writer.lock(), &config, &files, &diagnostic).expect("cannot write error");
|
term::emit(&mut writer.lock(), &config, &files, &diagnostic).expect("cannot write error");
|
||||||
std::process::abort();
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|||||||
178
src/parser.rs
Normal file
178
src/parser.rs
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
use syn::spanned::Spanned;
|
||||||
|
use super::{data, emit_error};
|
||||||
|
|
||||||
|
fn parse_enum(item: &syn::ItemEnum) -> data::EnumTy {
|
||||||
|
data::EnumTy {
|
||||||
|
name: item.ident.to_string(),
|
||||||
|
values: item.variants.iter().enumerate().map(|(i, v) | (v.ident.to_string(), i)).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_type_string(ty: String) -> data::Types {
|
||||||
|
use data::Types::*;
|
||||||
|
match ty.as_str() {
|
||||||
|
"str" | "String" => String,
|
||||||
|
"bool" => Bool,
|
||||||
|
"f32" => F32,
|
||||||
|
"f64" => F64,
|
||||||
|
"i8" => I8,
|
||||||
|
"i16" => I16,
|
||||||
|
"i32" => I32,
|
||||||
|
"i64" => I64,
|
||||||
|
"u8" => U8,
|
||||||
|
"u16" => U16,
|
||||||
|
"u32" => U32,
|
||||||
|
"u64" => U64,
|
||||||
|
_ => Named(ty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_type(item: &syn::Type) -> data::Types {
|
||||||
|
match item {
|
||||||
|
syn::Type::Path(path) => {
|
||||||
|
let segments = &path.path.segments;
|
||||||
|
if segments.len() != 1 {
|
||||||
|
emit_error(vec![(item.span(), "Path segments with len != 1")]);
|
||||||
|
}
|
||||||
|
let segment = &segments[0];
|
||||||
|
if !segment.arguments.is_empty() {
|
||||||
|
let args = match &segment.arguments {
|
||||||
|
syn::PathArguments::AngleBracketed(v) => v,
|
||||||
|
_ => emit_error(vec![(item.span(), "Angle bracketed arguments expected")])
|
||||||
|
};
|
||||||
|
let types = args.args.iter().map(|arg| match arg {
|
||||||
|
syn::GenericArgument::Type(v) => parse_type(v),
|
||||||
|
_ => emit_error(vec![(item.span(), "Only types are supported")])
|
||||||
|
}).collect::<Vec<_>>();
|
||||||
|
let name = segment.ident.to_string();
|
||||||
|
if name == "Option" {
|
||||||
|
if types.len() != 1 { emit_error(Some((segment.span(), "Option needs exactly one argument"))); }
|
||||||
|
data::Types::Optional(types[0].clone().into())
|
||||||
|
} else if name == "Vec" {
|
||||||
|
if types.len() != 1 { emit_error(Some((segment.span(), "Vec needs exactly one argument"))); }
|
||||||
|
data::Types::Array(types[0].clone().into())
|
||||||
|
} else {
|
||||||
|
data::Types::Generic(name, types)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parse_type_string(segment.ident.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
syn::Type::Slice(slice) => {
|
||||||
|
data::Types::Array(parse_type(&slice.elem).into())
|
||||||
|
}
|
||||||
|
_ => emit_error(vec![(item.span(), "Unsupported type")])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_struct(item: &syn::ItemStruct) -> data::StructTy {
|
||||||
|
let name = item.ident.to_string();
|
||||||
|
if let Some(v) = &item.generics.where_clause {
|
||||||
|
emit_error(Some((v.span(), "Where clauses are not allowed")));
|
||||||
|
}
|
||||||
|
if item.generics.params.len() > 1 {
|
||||||
|
emit_error(Some((item.generics.params.span(), "Only one generic parameter is allowed for now")));
|
||||||
|
}
|
||||||
|
let generic_names = item.generics.params.iter().map(|g| {
|
||||||
|
match g {
|
||||||
|
syn::GenericParam::Const(_) |
|
||||||
|
syn::GenericParam::Lifetime(_) => emit_error(Some((g.span(), "Only generic types are allowed"))),
|
||||||
|
syn::GenericParam::Type(ty) => {
|
||||||
|
if !ty.bounds.is_empty() {
|
||||||
|
emit_error(Some((ty.span(), "Bounds are not allowed")));
|
||||||
|
}
|
||||||
|
if let Some(d) = &ty.default {
|
||||||
|
emit_error(Some((d.span(), "Defaults are not allowed")));
|
||||||
|
}
|
||||||
|
ty.ident.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
let fields = item.fields.iter().map(|field| {
|
||||||
|
if field.ident.is_none() {
|
||||||
|
emit_error(vec![(field.span(), "Missing field name")]);
|
||||||
|
}
|
||||||
|
let name = field.ident.as_ref().unwrap().to_string();
|
||||||
|
let ty = parse_type(&field.ty);
|
||||||
|
data::FieldTy { name, ty }
|
||||||
|
}).collect();
|
||||||
|
data::StructTy { name, fields, generic_names }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_parse_iterator(ty: &syn::Type) -> Option<data::Types> {
|
||||||
|
if let syn::Type::Path(ty) = ty {
|
||||||
|
let seg = ty.path.segments.last()?;
|
||||||
|
if seg.ident.to_string() == "Iterator" {
|
||||||
|
if let syn::PathArguments::AngleBracketed(args) = &seg.arguments {
|
||||||
|
if let Some(syn::GenericArgument::Type(ty)) = args.args.first() {
|
||||||
|
Some(parse_type(ty))
|
||||||
|
} else { None }
|
||||||
|
} else { None }
|
||||||
|
} else { None }
|
||||||
|
} else { None }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_method(item: &syn::Signature) -> data::MethodTy {
|
||||||
|
let mut method = data::MethodTy::default();
|
||||||
|
method.name = item.ident.to_string();
|
||||||
|
|
||||||
|
for arg in &item.inputs {
|
||||||
|
let arg = match arg {
|
||||||
|
syn::FnArg::Typed(v) => v,
|
||||||
|
_ => emit_error(vec![(arg.span(), "Unsupported argument")])
|
||||||
|
};
|
||||||
|
let ty = parse_type(&arg.ty);
|
||||||
|
let name = match &*arg.pat {
|
||||||
|
syn::Pat::Ident(v) => v.ident.to_string(),
|
||||||
|
_ => emit_error(vec![(arg.span(), "Unsupported argument")])
|
||||||
|
};
|
||||||
|
method.args.push(data::FieldTy { name, ty });
|
||||||
|
}
|
||||||
|
|
||||||
|
match &item.output {
|
||||||
|
syn::ReturnType::Default => {
|
||||||
|
method.ret = None;
|
||||||
|
method.ret_stream = false;
|
||||||
|
}
|
||||||
|
syn::ReturnType::Type(_, ty) => {
|
||||||
|
if let Some(ty) = try_parse_iterator(ty) {
|
||||||
|
method.ret_stream = true;
|
||||||
|
method.ret = Some(ty);
|
||||||
|
} else {
|
||||||
|
method.ret_stream = false;
|
||||||
|
method.ret = Some(parse_type(ty));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
method
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_service(item: &syn::ItemTrait) -> data::ServiceTy {
|
||||||
|
let name = item.ident.to_string();
|
||||||
|
let mut methods = vec![];
|
||||||
|
for item in &item.items {
|
||||||
|
let item = match item {
|
||||||
|
syn::TraitItem::Fn(v) => v,
|
||||||
|
_ => emit_error(vec![(item.span(), "Only functions are supported")])
|
||||||
|
};
|
||||||
|
methods.push(parse_method(&item.sig));
|
||||||
|
}
|
||||||
|
data::ServiceTy { name, methods }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_file(ast: &syn::File) -> data::RPC {
|
||||||
|
let mut rpc = data::RPC::default();
|
||||||
|
|
||||||
|
for item in &ast.items {
|
||||||
|
match item {
|
||||||
|
syn::Item::Enum(v) => rpc.enums.push(parse_enum(v)),
|
||||||
|
syn::Item::Struct(v) => rpc.structs.push(parse_struct(v)),
|
||||||
|
syn::Item::Trait(v) => rpc.services.push(parse_service(v)),
|
||||||
|
syn::Item::Use(_) => {}
|
||||||
|
_ => emit_error(vec![(item.span(), "Unsupported item")])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc
|
||||||
|
}
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
@use crate::data::RPC;
|
|
||||||
@use crate::generators::cpp_s::*;
|
|
||||||
|
|
||||||
@(header_name: &str, rpc: &RPC)
|
|
||||||
#include "@header_name"
|
|
||||||
using json = nlohmann::json;
|
|
||||||
|
|
||||||
namespace nlohmann @{
|
|
||||||
template <typename T>
|
|
||||||
struct adl_serializer<std::optional<T>> @{
|
|
||||||
static void to_json(json &j, const std::optional<T> &v) @{
|
|
||||||
if (v.has_value())
|
|
||||||
j = v.value();
|
|
||||||
else
|
|
||||||
j = nullptr;
|
|
||||||
@}
|
|
||||||
|
|
||||||
static void from_json(const json &j, std::optional<T> &v) @{
|
|
||||||
if (j.is_null())
|
|
||||||
v.reset();
|
|
||||||
else
|
|
||||||
v = j.get<T>();
|
|
||||||
@}
|
|
||||||
@};
|
|
||||||
@}
|
|
||||||
|
|
||||||
namespace mrpc @{
|
|
||||||
@for s in &rpc.structs {
|
|
||||||
void to_json(nlohmann::json &j, const @s.name &v) @{
|
|
||||||
@for f in &s.fields { j["@f.name"] = v.@f.name;
|
|
||||||
}
|
|
||||||
@}
|
|
||||||
void from_json(const nlohmann::json &j, @s.name &v) @{
|
|
||||||
@for f in &s.fields { j.at("@f.name").get_to(v.@f.name);
|
|
||||||
}
|
|
||||||
@}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
void send_msg(crow::websocket::connection &c, uint64_t id, const T &v) @{
|
|
||||||
c.send_text(json@{@{"id", id@},@{"data", v@}@}.dump());
|
|
||||||
@}
|
|
||||||
|
|
||||||
void mrpc::MRPCStreamImpl::close() noexcept @{
|
|
||||||
if (conn != nullptr) @{
|
|
||||||
send_msg(*conn, id, nullptr);
|
|
||||||
conn = nullptr;
|
|
||||||
@}
|
|
||||||
@}
|
|
||||||
void mrpc::MRPCStreamImpl::abort() noexcept @{ conn = nullptr; @}
|
|
||||||
bool mrpc::MRPCStreamImpl::is_open() noexcept @{ return conn != nullptr; @}
|
|
||||||
|
|
||||||
void mrpc::MRPCServer::install(crow::SimpleApp &app, std::string &&route) @{
|
|
||||||
app.route_dynamic(std::move(route))
|
|
||||||
.websocket()
|
|
||||||
.onclose([&](crow::websocket::connection &c, const std::string&)@{
|
|
||||||
std::lock_guard guard@{__streams_mutex@};
|
|
||||||
auto range = __streams.equal_range(&c);
|
|
||||||
for (auto it = range.first; it != range.second; ++it)
|
|
||||||
it->second->abort();
|
|
||||||
__streams.erase(&c);
|
|
||||||
@})
|
|
||||||
.onmessage([this](auto &&a, auto &&b, auto &&c) @{
|
|
||||||
try @{ msg_handler(a, b, c); @}
|
|
||||||
catch (const std::exception &_) @{@}
|
|
||||||
@});
|
|
||||||
@}
|
|
||||||
void mrpc::MRPCServer::msg_handler(crow::websocket::connection &__c, const std::string &__msg, bool) @{
|
|
||||||
json __j = json::parse(__msg);
|
|
||||||
std::uint64_t __id = __j.at("id");
|
|
||||||
std::string __service = __j.at("service"), __method = __j.at("method");
|
|
||||||
try @{
|
|
||||||
json __data = __j.at("data");
|
|
||||||
@for (si, s) in rpc.services.iter().enumerate() {
|
|
||||||
@if si > 0 {else }if (__service == "@s.name") @{
|
|
||||||
@for (mi, m) in s.methods.iter().enumerate() {
|
|
||||||
@if mi > 0 {else }if (__method == "@m.name") @{
|
|
||||||
@if m.ret_stream {
|
|
||||||
auto __stream = std::make_shared<MRPCStream<@ty_to_str(m.ret.as_ref().unwrap())>>(&__c, __id);
|
|
||||||
@{ std::lock_guard guard@{__streams_mutex@}; __streams.emplace(&__c, __stream); @}
|
|
||||||
}
|
|
||||||
@for (name, ty) in m.args.iter().map(|a| (&a.name, ty_to_str(&a.ty))) { @ty @name = __data.at("@name");
|
|
||||||
}
|
|
||||||
@if m.ret_stream || m.ret.is_none() {@(s.name)_@(m.name)(@call_args(m));}
|
|
||||||
else {send_msg(__c, __id, @(s.name)_@(m.name)(@call_args(m)));}
|
|
||||||
@}
|
|
||||||
}
|
|
||||||
else @{ throw std::exception@{@}; @}
|
|
||||||
@}
|
|
||||||
}
|
|
||||||
else @{ throw std::exception@{@}; @}
|
|
||||||
@} catch (const std::exception &_) @{
|
|
||||||
std::cerr << "Got invalid request " << __id << " for " << __service << "::" << __method << std::endl;
|
|
||||||
@}
|
|
||||||
@}
|
|
||||||
@}
|
|
||||||
100
templates/cpp_server.rs.cxx
Normal file
100
templates/cpp_server.rs.cxx
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
@use crate::data::RPC;
|
||||||
|
@use crate::generators::cpp_s::*;
|
||||||
|
@use super::cpp_server_json_cxx;
|
||||||
|
|
||||||
|
@(header_name: &str, rpc: &RPC)
|
||||||
|
#include "@header_name"
|
||||||
|
#include <corvusoft/restbed/session.hpp>
|
||||||
|
#include <corvusoft/restbed/resource.hpp>
|
||||||
|
#include <corvusoft/restbed/request.hpp>
|
||||||
|
|
||||||
|
using namespace mrpc;
|
||||||
|
|
||||||
|
@:cpp_server_json_cxx(rpc)
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void send_msg(const std::shared_ptr<restbed::Session> &c, const T &v) @{
|
||||||
|
if (c->is_closed())
|
||||||
|
return;
|
||||||
|
rapidjson::StringBuffer s;
|
||||||
|
mrpc::MRPCJWriter writer@{s@};
|
||||||
|
v >> writer;
|
||||||
|
const auto body_ptr = s.GetString();
|
||||||
|
const auto body = restbed::Bytes@{body_ptr, body_ptr+s.GetLength()@};
|
||||||
|
c->yield(
|
||||||
|
200,
|
||||||
|
body,
|
||||||
|
std::multimap<std::string, std::string>@{
|
||||||
|
@{"Content-Type", "application/json"@},
|
||||||
|
@{"Content-Length", std::to_string(body.size())@}
|
||||||
|
@}
|
||||||
|
);
|
||||||
|
@}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void send_sse_msg(const std::shared_ptr<restbed::Session> &c, const T &v) @{
|
||||||
|
if (c->is_closed())
|
||||||
|
return;
|
||||||
|
rapidjson::StringBuffer s;
|
||||||
|
std::memcpy(s.Push(5), "data:", 5);
|
||||||
|
mrpc::MRPCJWriter writer@{s@};
|
||||||
|
v >> writer;
|
||||||
|
std::memcpy(s.Push(2), "\n\n", 2);
|
||||||
|
const auto body_ptr = s.GetString();
|
||||||
|
const auto body = restbed::Bytes@{body_ptr, body_ptr+s.GetLength()@};
|
||||||
|
c->yield(body);
|
||||||
|
@}
|
||||||
|
|
||||||
|
mrpc::MRPCStreamImpl::MRPCStreamImpl(const std::shared_ptr<restbed::Session> &conn) : conn(conn) @{
|
||||||
|
conn->yield(
|
||||||
|
200,
|
||||||
|
std::multimap<std::string, std::string>@{
|
||||||
|
@{"Cache-Control", "no-cache"@},
|
||||||
|
@{"Content-Type", "text/event-stream"@}
|
||||||
|
@}
|
||||||
|
);
|
||||||
|
@}
|
||||||
|
|
||||||
|
void mrpc::MRPCStreamImpl::close() const noexcept @{ conn->close("data:null\n\n"); @}
|
||||||
|
bool mrpc::MRPCStreamImpl::is_open() const noexcept @{ return conn->is_open(); @}
|
||||||
|
@for s in streams_required(rpc) {template<> void MRPCStream<@s>::send(const @s &v) const noexcept @{ send_sse_msg(conn, v); @}
|
||||||
|
}
|
||||||
|
|
||||||
|
mrpc::MRPCServer::MRPCServer(std::shared_ptr<restbed::Resource> &r) @{
|
||||||
|
r->set_method_handler("POST", [this](const std::shared_ptr<restbed::Session>& s) @{
|
||||||
|
const auto req = s->get_request();
|
||||||
|
const auto body_len = req->get_header("Content-Length", 0);
|
||||||
|
s->fetch(body_len, [this](const std::shared_ptr<restbed::Session> &s, auto &&body) @{
|
||||||
|
try @{ msg_handler(s, body); @}
|
||||||
|
catch (const std::exception &_) @{ s->close(400); @}
|
||||||
|
@});
|
||||||
|
@});
|
||||||
|
@}
|
||||||
|
|
||||||
|
void mrpc::MRPCServer::msg_handler(const std::shared_ptr<restbed::Session> __c, const restbed::Bytes &__msg) @{
|
||||||
|
rapidjson::Document __j;
|
||||||
|
__j.Parse((const char*)__msg.data(), __msg.size());
|
||||||
|
if (__j.HasParseError())
|
||||||
|
throw std::exception@{@};
|
||||||
|
std::string __service, __method;
|
||||||
|
__service << json_get(__j, "service");
|
||||||
|
__method << json_get(__j, "method");
|
||||||
|
auto __data_member = __j.FindMember("data");
|
||||||
|
if (__data_member == __j.MemberEnd() || !__data_member->value.IsObject())
|
||||||
|
throw std::exception@{@};
|
||||||
|
auto &__data = __data_member->value;
|
||||||
|
@for (si, s) in rpc.services.iter().enumerate() {@if si > 0 { else }else{ }if (__service == "@s.name") @{
|
||||||
|
@for (mi, m) in s.methods.iter().enumerate() {@if mi > 0 { else }else{ }if (__method == "@m.name") @{
|
||||||
|
@if m.ret_stream {auto __stream = MRPCStream<@ty_to_str(m.ret.as_ref().unwrap())>@{__c@};
|
||||||
|
}
|
||||||
|
@for (name, ty) in m.args.iter().map(|a| (&a.name, ty_to_str(&a.ty))) { @ty @name; @name << json_get(__data, "@name");
|
||||||
|
}
|
||||||
|
@if m.ret_stream {@(s.name)_@(m.name)(@call_args(m));}
|
||||||
|
else if m.ret.is_some() {send_msg(__c, @(s.name)_@(m.name)(@call_args(m)));}
|
||||||
|
else {@(s.name)_@(m.name)(@call_args(m)); send_msg(__c, nullptr);}
|
||||||
|
@}}
|
||||||
|
else @{ throw std::exception@{@}; @}
|
||||||
|
@}}
|
||||||
|
else @{ throw std::exception@{@}; @}
|
||||||
|
@}
|
||||||
|
@}
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
@use itertools::Itertools;
|
|
||||||
@use crate::data::RPC;
|
|
||||||
@use crate::generators::cpp_s::*;
|
|
||||||
|
|
||||||
@(rpc: &RPC)
|
|
||||||
#pragma once
|
|
||||||
#ifndef MRPC_GEN_H
|
|
||||||
#define MRPC_GEN_H
|
|
||||||
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <iosfwd>
|
|
||||||
#include <string>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <crow.h>
|
|
||||||
#include <json.hpp>
|
|
||||||
|
|
||||||
namespace mrpc @{
|
|
||||||
@for e in &rpc.enums {
|
|
||||||
enum struct @e.name : std::uint64_t @{
|
|
||||||
@e.values.iter().map(|(k,v)| format!("{k} = {v}")).join(",\n ")
|
|
||||||
@};
|
|
||||||
}
|
|
||||||
@for s in &rpc.structs {
|
|
||||||
struct @s.name;
|
|
||||||
void to_json(nlohmann::json&, const @s.name&);
|
|
||||||
void from_json(const nlohmann::json&, @s.name&);
|
|
||||||
}
|
|
||||||
@for s in &rpc.structs {
|
|
||||||
struct @s.name @{
|
|
||||||
@for f in &s.fields { @ty_to_str(&f.ty) @f.name;
|
|
||||||
}
|
|
||||||
@};
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MRPCStreamImpl @{
|
|
||||||
virtual void close() noexcept final;
|
|
||||||
virtual void abort() noexcept final;
|
|
||||||
virtual bool is_open() noexcept final;
|
|
||||||
protected:
|
|
||||||
MRPCStreamImpl(crow::websocket::connection *conn, uint64_t id) : conn(conn), id(id) @{@}
|
|
||||||
crow::websocket::connection* conn;
|
|
||||||
std::uint64_t id;
|
|
||||||
@};
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
struct MRPCStream final : MRPCStreamImpl @{
|
|
||||||
MRPCStream(crow::websocket::connection *conn, uint64_t id) : MRPCStreamImpl(conn, id) @{@}
|
|
||||||
bool send(const T &v) noexcept @{
|
|
||||||
if (!conn) return false;
|
|
||||||
try @{
|
|
||||||
conn->send_text(nlohmann::json@{@{"id", id@},@{"data", v@}@}.dump());
|
|
||||||
@} catch (const std::exception &_) @{
|
|
||||||
abort();
|
|
||||||
return false;
|
|
||||||
@}
|
|
||||||
return true;
|
|
||||||
@}
|
|
||||||
@};
|
|
||||||
|
|
||||||
struct MRPCServer @{
|
|
||||||
virtual void install(crow::SimpleApp &app, std::string &&route) final;
|
|
||||||
private:
|
|
||||||
@for s in &rpc.services {@for m in &s.methods { virtual @method_ret(m) @(s.name)_@(m.name)(@method_args(m)) = 0;
|
|
||||||
}}
|
|
||||||
virtual void msg_handler(crow::websocket::connection&, const std::string&, bool) final;
|
|
||||||
|
|
||||||
std::mutex __streams_mutex;
|
|
||||||
std::unordered_multimap<crow::websocket::connection*, std::shared_ptr<MRPCStreamImpl>> __streams;
|
|
||||||
@};
|
|
||||||
@}
|
|
||||||
|
|
||||||
#endif // MRPC_GEN_H
|
|
||||||
70
templates/cpp_server.rs.hxx
Normal file
70
templates/cpp_server.rs.hxx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
@use itertools::Itertools;
|
||||||
|
@use crate::data::RPC;
|
||||||
|
@use crate::generators::cpp_s::*;
|
||||||
|
|
||||||
|
@(rpc: &RPC)
|
||||||
|
#pragma once
|
||||||
|
#ifndef MRPC_GEN_H
|
||||||
|
#define MRPC_GEN_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <optional>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cmath>
|
||||||
|
#include <corvusoft/restbed/byte.hpp>
|
||||||
|
#define RAPIDJSON_HAS_STDSTRING 1
|
||||||
|
#include <rapidjson/stringbuffer.h>
|
||||||
|
#include <rapidjson/writer.h>
|
||||||
|
#include <rapidjson/document.h>
|
||||||
|
|
||||||
|
namespace restbed @{
|
||||||
|
class Resource;
|
||||||
|
class Session;
|
||||||
|
@}
|
||||||
|
|
||||||
|
namespace mrpc @{
|
||||||
|
using MRPCJWriter = rapidjson::Writer<rapidjson::StringBuffer>;
|
||||||
|
@for e in &rpc.enums {
|
||||||
|
enum struct @e.name : std::uint64_t @{
|
||||||
|
@e.values.iter().map(|(k,v)| format!("{k} = {v}")).join(",\n ")
|
||||||
|
@};
|
||||||
|
}
|
||||||
|
@for s in &rpc.structs {@get_struct_generics(s)struct @s.name;
|
||||||
|
}
|
||||||
|
@for s in &rpc.structs {
|
||||||
|
@get_struct_generics(s)struct @s.name @{
|
||||||
|
@for f in &s.fields { @ty_to_str(&f.ty) @f.name;
|
||||||
|
}
|
||||||
|
MRPCJWriter& operator >>(MRPCJWriter&) const;
|
||||||
|
@(s.name)& operator <<(const rapidjson::Value&);
|
||||||
|
@};}
|
||||||
|
@if streams_required(rpc).len() > 0 {
|
||||||
|
struct MRPCStreamImpl @{
|
||||||
|
void close() const noexcept;
|
||||||
|
bool is_open() const noexcept;
|
||||||
|
protected:
|
||||||
|
explicit MRPCStreamImpl(const std::shared_ptr<restbed::Session> &conn);
|
||||||
|
std::shared_ptr<restbed::Session> conn;
|
||||||
|
@};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct MRPCStream final : MRPCStreamImpl @{
|
||||||
|
explicit MRPCStream(const std::shared_ptr<restbed::Session> &conn) : MRPCStreamImpl(conn) @{@}
|
||||||
|
void send(const T &v) const noexcept;
|
||||||
|
@};
|
||||||
|
|
||||||
|
@for s in streams_required(rpc) {template struct MRPCStream<@(s)>;
|
||||||
|
}}
|
||||||
|
struct MRPCServer @{
|
||||||
|
MRPCServer() = delete;
|
||||||
|
explicit MRPCServer(std::shared_ptr<restbed::Resource>&);
|
||||||
|
private:
|
||||||
|
@for s in &rpc.services {@for m in &s.methods { virtual @method_ret(m) @(s.name)_@(m.name)(@method_args(m)) = 0;
|
||||||
|
}}
|
||||||
|
virtual void msg_handler(std::shared_ptr<restbed::Session>, const restbed::Bytes&) final;
|
||||||
|
@};
|
||||||
|
@}
|
||||||
|
|
||||||
|
#endif // MRPC_GEN_H
|
||||||
99
templates/cpp_server_json.rs.cxx
Normal file
99
templates/cpp_server_json.rs.cxx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
@use crate::data::RPC;
|
||||||
|
@use crate::generators::cpp_s::*;
|
||||||
|
|
||||||
|
@(rpc: &RPC)
|
||||||
|
@for (ty, jty) in JSON_INNER_IMPLS {
|
||||||
|
inline @(ty)& operator<<(@ty &v, const rapidjson::Value &j) @{
|
||||||
|
if (!j.Is@(jty)())
|
||||||
|
throw std::exception@{@};
|
||||||
|
v = j.Get@(jty)();
|
||||||
|
return v;
|
||||||
|
@}
|
||||||
|
inline mrpc::MRPCJWriter& operator>>(const @ty &v, mrpc::MRPCJWriter &w) @{
|
||||||
|
w.@(jty)(v);
|
||||||
|
return w;
|
||||||
|
@}}
|
||||||
|
|
||||||
|
@for e in &rpc.enums {
|
||||||
|
inline mrpc::@e.name& operator<<(mrpc::@e.name &v, const rapidjson::Value &j) @{
|
||||||
|
((std::uint64_t&)v) << j;
|
||||||
|
return v;
|
||||||
|
@}
|
||||||
|
mrpc::MRPCJWriter& operator>>(const mrpc::@e.name &v, mrpc::MRPCJWriter &w) @{
|
||||||
|
w.Uint64((std::uint64_t)v);
|
||||||
|
return w;
|
||||||
|
@}
|
||||||
|
}
|
||||||
|
inline mrpc::MRPCJWriter& operator>>(const std::nullptr_t &, mrpc::MRPCJWriter &w) @{
|
||||||
|
w.Null();
|
||||||
|
return w;
|
||||||
|
@}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline std::vector<T>& operator<<(std::vector<T> &v, const rapidjson::Value &j);
|
||||||
|
template<typename T>
|
||||||
|
inline std::optional<T>& operator<<(std::optional<T> &v, const rapidjson::Value &j) @{
|
||||||
|
if (j.IsNull())
|
||||||
|
v = std::nullopt;
|
||||||
|
else @{
|
||||||
|
T t;
|
||||||
|
t << j;
|
||||||
|
v = std::move(t);
|
||||||
|
@}
|
||||||
|
return v;
|
||||||
|
@}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline std::vector<T>& operator<<(std::vector<T> &v, const rapidjson::Value &j) @{
|
||||||
|
if (!j.IsArray())
|
||||||
|
throw std::exception@{@};
|
||||||
|
for (const auto &e : j.GetArray()) @{
|
||||||
|
T t;
|
||||||
|
t << e;
|
||||||
|
v.push_back(std::move(t));
|
||||||
|
@}
|
||||||
|
return v;
|
||||||
|
@}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline mrpc::MRPCJWriter& operator>>(const std::vector<T> &v, mrpc::MRPCJWriter &w);
|
||||||
|
template<typename T>
|
||||||
|
inline mrpc::MRPCJWriter& operator>>(const std::optional<T> &v, mrpc::MRPCJWriter &w) @{
|
||||||
|
if (v.has_value())
|
||||||
|
v.value() >> w;
|
||||||
|
else
|
||||||
|
w.Null();
|
||||||
|
return w;
|
||||||
|
@}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline mrpc::MRPCJWriter& operator>>(const std::vector<T> &v, mrpc::MRPCJWriter &w) @{
|
||||||
|
w.StartArray();
|
||||||
|
for (const auto &e : v)
|
||||||
|
e >> w;
|
||||||
|
w.EndArray();
|
||||||
|
return w;
|
||||||
|
@}
|
||||||
|
|
||||||
|
inline const rapidjson::Value& json_get(const rapidjson::Value &j, const char *key) @{
|
||||||
|
auto member = j.FindMember(key);
|
||||||
|
if (member == j.MemberEnd())
|
||||||
|
throw std::exception@{@};
|
||||||
|
return member->value;
|
||||||
|
@}
|
||||||
|
|
||||||
|
namespace mrpc @{
|
||||||
|
@for s in &rpc.structs {
|
||||||
|
@get_struct_generics(s)MRPCJWriter& @(s.name)@(generics_brace(s))::operator>>(MRPCJWriter &__w) const @{
|
||||||
|
__w.StartObject();
|
||||||
|
@for f in &s.fields { __w.Key("@f.name", @f.name.len());
|
||||||
|
@f.name >> __w;
|
||||||
|
} __w.EndObject();
|
||||||
|
return __w;
|
||||||
|
@}
|
||||||
|
@get_struct_generics(s)@(s.name)@(generics_brace(s))& @(s.name)@(generics_brace(s))::operator<<(const rapidjson::Value &__j) @{
|
||||||
|
using namespace mrpc;
|
||||||
|
@for f in &s.fields { @f.name << json_get(__j, "@f.name");
|
||||||
|
} return *this;
|
||||||
|
@}
|
||||||
|
}
|
||||||
@@ -3,79 +3,43 @@
|
|||||||
@use crate::generators::ts_c::*;
|
@use crate::generators::ts_c::*;
|
||||||
|
|
||||||
@(rpc: &RPC)
|
@(rpc: &RPC)
|
||||||
|
import @{ fetchEventSource @} from '@@microsoft/fetch-event-source';
|
||||||
@for e in &rpc.enums {
|
@for e in &rpc.enums {
|
||||||
export enum @e.name @{
|
export enum @e.name @{
|
||||||
@for (k,v) in &e.values { @k = @v,
|
@for (k,v) in &e.values { @k = @v,
|
||||||
}
|
}@}
|
||||||
@}
|
|
||||||
}
|
}
|
||||||
@for s in &rpc.structs {
|
@for s in &rpc.structs {
|
||||||
export interface @s.name @{
|
export interface @s.name@get_struct_generics(s) @{
|
||||||
@for f in &s.fields { @f.name: @ty_to_str(&f.ty);
|
@for f in &s.fields { @f.name: @ty_to_str(&f.ty);
|
||||||
|
}@}
|
||||||
}
|
}
|
||||||
@}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface _WSResponse @{
|
|
||||||
id: number;
|
|
||||||
data: any;
|
|
||||||
@}
|
|
||||||
|
|
||||||
interface _WSWaitingEntry @{
|
|
||||||
ok: (v: any) => void;
|
|
||||||
err: (reason?: any) => void;
|
|
||||||
@}
|
|
||||||
|
|
||||||
export class MRPCConnector @{
|
export class MRPCConnector @{
|
||||||
url: string;
|
url: string;
|
||||||
socket: WebSocket;
|
|
||||||
nmi: number;
|
|
||||||
waiting: @{ [id: number]: _WSWaitingEntry @};
|
|
||||||
streams: @{ [id: number]: (v: any) => void @};
|
|
||||||
|
|
||||||
private open() @{
|
private __create_msg(service: string, method: string, data: any) @{
|
||||||
this.socket = new WebSocket(this.url);
|
return @{service, method, data@};
|
||||||
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);
|
|
||||||
@}
|
|
||||||
|
|
||||||
private get_prom<T>(id: number): Promise<T> @{
|
|
||||||
return new Promise<T>((ok, err) => @{ this.waiting[id] = @{ok, err@}; @});
|
|
||||||
@}
|
@}
|
||||||
|
|
||||||
public constructor(url: string) @{
|
public constructor(url: string) @{
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.nmi = 0;
|
|
||||||
this.waiting = @{@};
|
|
||||||
this.streams = @{@};
|
|
||||||
this.open();
|
|
||||||
@}
|
@}
|
||||||
|
|
||||||
@for s in &rpc.services { @for m in &s.methods {
|
@for s in &rpc.services { @for m in &s.methods {
|
||||||
public @(s.name)_@(m.name)(@method_args(m))@method_ret(m) @{
|
public @(s.name)_@(m.name)(@method_args(m))@method_ret(m) @{
|
||||||
const __msg = @{
|
const __msg = this.__create_msg('@s.name', '@m.name', @{@m.args.iter().map(|a| &a.name).join(",")@});
|
||||||
id: this.nmi++,
|
@if m.ret.is_some() && !m.ret_stream {return fetch(this.url, @{
|
||||||
service: '@s.name',
|
method: 'POST',
|
||||||
method: '@m.name',
|
body: JSON.stringify(__msg)
|
||||||
data: @{@m.args.iter().map(|a| &a.name).join(",")@}
|
@}).then((__r) => __r.json());}
|
||||||
@};
|
else if m.ret_stream {fetchEventSource(this.url, @{
|
||||||
@if m.ret.is_some() && !m.ret_stream {const __p = this.get_prom<@ty_to_str(m.ret.as_ref().unwrap())>(__msg.id);}
|
method: 'POST',
|
||||||
else if m.ret_stream {this.streams[__msg.id] = __cbk;}
|
body: JSON.stringify(__msg),
|
||||||
this.socket.send(JSON.stringify(__msg));
|
onmessage: __e => __cbk(JSON.parse(__e.data)),
|
||||||
@if m.ret.is_some() && !m.ret_stream {return __p;}
|
onerror: __e => @{throw __e;@},
|
||||||
|
onclose: () => __cbk(null)
|
||||||
|
@});} else {return fetch(this.url, @{method: 'POST', body: JSON.stringify(__msg)@}).then(__r => @{@});}
|
||||||
@}
|
@}
|
||||||
}}
|
}}
|
||||||
@}
|
@}
|
||||||
|
|||||||
Reference in New Issue
Block a user