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