modrpc_codegen/codegen/rust/
project.rs1use std::path::Path;
2use std::fs::File;
3use std::io::Write;
4
5use genco::quote;
6use mproto_codegen::codegen::name_util::camel_to_snake_case;
7
8use crate::{
9 ast::Schema,
10 codegen,
11 Database,
12};
13
14const PROTO_CARGO_TOML: &'static str = include_str!("templates/interface/cargo.toml");
15const PROTO_LIB_RS: &'static str = include_str!("templates/interface/lib.rs");
16
17pub fn rust_project_gen(
18 root_dir: impl AsRef<Path>,
19 project_name: &str,
20 schema: &Schema,
21) -> std::io::Result<()> {
22 let pkg_name = &format!("{}-modrpc", project_name);
23 let pkg_root = root_dir.as_ref().join(pkg_name).join("rust");
24
25 std::fs::create_dir_all(&pkg_root)?;
26
27 let _db = rust_proto_package_gen(&pkg_root, pkg_name, schema)?;
28
29 Ok(())
30}
31
32pub fn rust_proto_package_gen(
33 pkg_root: impl AsRef<Path>,
34 pkg_name: &str,
35 schema: &Schema,
36) -> std::io::Result<Database> {
37 let pkg_root = pkg_root.as_ref();
38 let local_mproto_module = mproto_codegen::Module::from_type_defs(
39 schema.type_defs.clone(),
40 );
41 let mproto_db = mproto_codegen::Database::new(local_mproto_module);
42
43 let mut db = Database::new(mproto_db);
44 codegen::load_imports_recursive(&mut db, schema).unwrap();
45
46 for interface in &schema.interfaces {
47 let _ = db.local().add_interface(interface.clone());
48
49 codegen::define_interface_config_and_state_structs(
51 &mut db,
52 interface,
53 );
54 }
55
56 let src_dir = pkg_root.join("src");
57 let roles_dir = src_dir.join("roles");
58 let role_impls_dir = src_dir.join("role_impls");
59
60 std::fs::create_dir_all(&pkg_root)?;
61 std::fs::create_dir_all(&src_dir)?;
62 std::fs::create_dir_all(&roles_dir)?;
63 std::fs::create_dir_all(&role_impls_dir)?;
64
65 let cargo_toml_path = pkg_root.join("Cargo.toml");
67 if !cargo_toml_path.exists() {
68 let mut cargo_toml_file = File::create(cargo_toml_path)?;
69 cargo_toml_file.write_all(
70 PROTO_CARGO_TOML.replace("PKG_NAME", pkg_name).as_bytes()
71 )?;
72 } else {
73 println!(
74 "{} already exists - skipping as it might contain hand-written code.",
75 cargo_toml_path.display(),
76 );
77 }
78
79 let lib_rs_path = src_dir.join("lib.rs");
81 if !lib_rs_path.exists() {
82 let mut lib_rs_file = File::create(&lib_rs_path)?;
83 lib_rs_file.write_all(PROTO_LIB_RS.as_bytes())?;
84 } else {
85 println!(
86 "{} already exists - skipping as it might contain hand-written code.",
87 lib_rs_path.display(),
88 );
89 }
90
91 let fmt = genco::fmt::Config::from_lang::<genco::lang::Rust>()
93 .with_indentation(genco::fmt::Indentation::Space(4));
94 let config = genco::lang::rust::Config::default();
95 let proto_rust_file = File::create(src_dir.join("proto.rs"))?;
96 let mut w = genco::fmt::IoWriter::new(proto_rust_file);
97 let mut tokens = genco::lang::rust::Tokens::new();
98
99 for type_def in db.mproto_db().local().type_defs() {
100 let type_def_tokens = mproto_codegen::codegen::rust::rust_type_def(
101 &mproto_codegen::codegen::CodegenCx::new(db.mproto_db(), None, true),
102 type_def,
103 );
104 tokens = quote! {
105 $tokens
106
107 $type_def_tokens
108 };
109 }
110
111 tokens
112 .format_file(&mut w.as_formatter(&fmt), &config)
113 .expect("format proto.rs file");
114
115 let fmt = genco::fmt::Config::from_lang::<genco::lang::Rust>()
117 .with_indentation(genco::fmt::Indentation::Space(4));
118 let config = genco::lang::rust::Config::default();
119 let interface_rust_file = File::create(src_dir.join("interface.rs"))?;
120 let mut w = genco::fmt::IoWriter::new(interface_rust_file);
121 let mut tokens = genco::lang::rust::Tokens::new();
122
123 for interface in &schema.interfaces {
124 let interface_tokens = codegen::rust::rust_interface(&db, interface);
125 tokens = quote! {
126 $tokens
127
128 $interface_tokens
129 };
130 }
131
132 tokens
133 .format_file(&mut w.as_formatter(&fmt), &config)
134 .expect("format interface.rs file");
135
136 let mut roles_mod_tokens = quote! { };
138 for interface in &schema.interfaces {
139 for role_name in &interface.roles {
140 let snake_interface_role_name = camel_to_snake_case(
141 &format!("{}{}", interface.name, role_name)
142 );
143 write_rust_file(
144 &roles_dir.join(format!("{}.rs", snake_interface_role_name)),
145 &codegen::rust::rust_interface_role(&db, interface, role_name),
146 |f| {
147 f.write_all(b"#![allow(unused_variables)]\n\n")
148 },
149 )?;
150
151 roles_mod_tokens = quote! {
152 mod $(&snake_interface_role_name);
153 pub use $(&snake_interface_role_name)::*;
154 $roles_mod_tokens
155 };
156 }
157 }
158 write_rust_file(&roles_dir.join("mod.rs"), &roles_mod_tokens, |_| Ok(()))?;
159
160 let mut role_impls_mod_tokens = quote! { };
162 for interface in &schema.interfaces {
163 for role_name in &interface.roles {
164 let snake_interface_role_name = camel_to_snake_case(
165 &format!("{}{}", interface.name, role_name)
166 );
167 let path = role_impls_dir.join(&format!("{}.rs", snake_interface_role_name));
168 let tokens = &codegen::rust::rust_interface_role_impl(&db, interface, role_name);
169
170 if !path.exists() {
171 if !tokens.is_empty() {
172 write_rust_file(&path, tokens, |_| Ok(()))?;
173 }
174 } else {
175 println!(
176 "{} already exists - skipping as it might contain hand-written code.",
177 path.display(),
178 );
179 }
180
181 if !tokens.is_empty() {
182 role_impls_mod_tokens = quote! {
183 mod $(&snake_interface_role_name);
184 pub use $(&snake_interface_role_name)::*;
185 $role_impls_mod_tokens
186 };
187 }
188 }
189 }
190 write_rust_file(&role_impls_dir.join("mod.rs"), &role_impls_mod_tokens, |_| Ok(()))?;
191
192 Ok(db)
193}
194
195fn write_rust_file(
196 path: impl AsRef<Path> + std::fmt::Debug,
197 tokens: &genco::lang::rust::Tokens,
198 prepend: impl FnOnce(&mut File) -> std::io::Result<()>,
199) -> std::io::Result<()> {
200 let fmt = genco::fmt::Config::from_lang::<genco::lang::Rust>()
201 .with_indentation(genco::fmt::Indentation::Space(4));
202 let config = genco::lang::rust::Config::default();
203 let mut file = File::create(&path)?;
204 prepend(&mut file)?;
205 let mut w = genco::fmt::IoWriter::new(file);
206
207 tokens
208 .format_file(&mut w.as_formatter(&fmt), &config)
209 .expect(&format!("format {:?} file", path));
210
211 Ok(())
212}
213
214pub fn rust_role_impl_gen(
215 project_name: &str,
216 root_dir: impl AsRef<Path>,
217 schema: &Schema,
218 role_name: &str,
219) -> std::io::Result<()> {
220 let local_mproto_module = mproto_codegen::Module::from_type_defs(
221 schema.type_defs.clone(),
222 );
223 let mproto_db = mproto_codegen::Database::new(local_mproto_module);
224
225 let pkg_name = format!("{}-{}", project_name, &role_name.to_lowercase());
226
227 let pkg_root = root_dir.as_ref().join(pkg_name);
228 let src_dir = pkg_root.join("src");
229
230 std::fs::create_dir_all(&pkg_root)?;
231 std::fs::create_dir_all(&src_dir)?;
232
233 let mut cargo_toml_file = File::create(pkg_root.join("Cargo.toml"))?;
235 cargo_toml_file.write_all(
236 include_str!("templates/server/cargo.toml").replace("PKG_NAME", project_name).as_bytes()
237 )?;
238
239 let fmt = genco::fmt::Config::from_lang::<genco::lang::Rust>()
241 .with_indentation(genco::fmt::Indentation::Space(4));
242 let config = genco::lang::rust::Config::default();
243 let mut main_rs_file = File::create(src_dir.join("main.rs"))?;
244
245 main_rs_file.write("\
246 #![feature(type_alias_impl_trait)]\n\
247 \n\
248 use std::future::Future;\n\
249 \n".as_bytes())?;
250
251 let mut w = genco::fmt::IoWriter::new(main_rs_file);
252
253 let interface = &schema.interfaces[0];
254 let role_impl = codegen::rust::RustInterfaceImpl::generate(
255 &mproto_db, project_name, interface, role_name,
256 );
257
258 role_impl.main_rs
259 .format_file(&mut w.as_formatter(&fmt), &config)
260 .expect("format main.rs file");
261
262 let handlers_dir = src_dir.join("handlers");
263 std::fs::create_dir_all(&handlers_dir)?;
264 for (handler_name, handler_impl_tokens) in role_impl.handlers {
265 write_rust_file(&handlers_dir.join(&format!("{handler_name}.rs")), &handler_impl_tokens, |_| Ok(()))?;
266 }
267
268 Ok(())
269}