Skip to main content

modrpc_codegen/codegen/wasm/
project.rs

1use std::path::Path;
2use std::fs;
3use std::io::Write;
4
5use mproto_codegen::codegen::name_util::{
6    camel_to_snake_case, camel_to_kebab_case,
7};
8
9use crate::{
10    ast::Schema,
11    codegen,
12    Database,
13};
14
15const RUST_GLUE_CARGO_TOML: &'static str = include_str!("templates/rust-wasm-glue/cargo.toml");
16const RUST_GLUE_BUILD_SH: &'static str = include_str!("templates/rust-wasm-glue/build.sh");
17
18const TS_GLUE_PACKAGE_JSON: &'static str = include_str!("templates/ts-wasm-glue/package.json");
19const TS_GLUE_TSCONFIG_JSON: &'static str = include_str!("templates/ts-wasm-glue/tsconfig.json");
20const TS_GLUE_BUILD_SH: &'static str = include_str!("templates/ts-wasm-glue/build.sh");
21
22pub fn wasm_project_gen(
23    root_dir: impl AsRef<Path>,
24    project_name: &str,
25    schema: &Schema,
26) -> std::io::Result<()> {
27    let pkg_name = &format!("{}-modrpc", project_name);
28    let wasm_root = root_dir.as_ref().join(pkg_name).join("wasm");
29
30    // Setup db for codegen
31    let local_mproto_module = mproto_codegen::Module::from_type_defs(schema.type_defs.clone());
32    let mproto_db = mproto_codegen::Database::new(local_mproto_module);
33
34    let mut db = Database::new(mproto_db);
35    crate::codegen::load_imports_recursive(&mut db, schema).unwrap();
36    for interface in &schema.interfaces {
37        let _ = db.local().add_interface(interface.clone());
38
39        // Define per-interface *InitState and per-role *Config structs
40        codegen::define_interface_config_and_state_structs(
41            &mut db,
42            interface,
43        );
44    }
45
46    for interface in &schema.interfaces {
47        let snake_interface_name = &camel_to_snake_case(&interface.name);
48        let kebab_interface_name = &camel_to_kebab_case(&interface.name);
49
50        for role_name in &interface.roles {
51            let snake_role_name = &camel_to_snake_case(role_name);
52            let kebab_role_name = &camel_to_kebab_case(role_name);
53            let kebab_interface_role_name = &camel_to_kebab_case(
54                &format!("{}{}", interface.name, role_name)
55            );
56
57            let role_root = wasm_root.join(kebab_interface_role_name);
58
59            // Write WIT
60            let wit_dir = role_root.join("wit");
61            fs::create_dir_all(&wit_dir)?;
62            fs::write(
63                wit_dir.join(format!("{}.wit", kebab_interface_role_name)),
64                codegen::wasm::wit_interface(&db, interface, role_name).as_bytes(),
65            )?;
66
67            // Role's rust wasm glue
68
69            let role_rust_root = role_root.join("rust");
70            fs::create_dir_all(&role_rust_root)?;
71
72            // Write Cargo.toml
73            let cargo_toml_path = role_rust_root.join("Cargo.toml");
74            if !cargo_toml_path.exists() {
75                fs::write(
76                    cargo_toml_path,
77                    RUST_GLUE_CARGO_TOML
78                        .replace("PROJECT_NAME", project_name)
79                        .replace("INTERFACE_NAME", kebab_interface_name)
80                        .replace("ROLE_NAME", kebab_role_name)
81                        .as_bytes(),
82                )?;
83            } else {
84                println!(
85                    "{} already exists - skipping as it might contain hand-written code.",
86                    cargo_toml_path.display(),
87                );
88            }
89
90            // Write build.sh
91            fs::write(
92                role_rust_root.join("build.sh"),
93                RUST_GLUE_BUILD_SH
94                    .replace("INTERFACE_NAME", snake_interface_name)
95                    .replace("ROLE_NAME", snake_role_name)
96                    .as_bytes(),
97            )?;
98
99            // Write src/lib.rs
100            let src_dir = role_rust_root.join("src");
101            fs::create_dir_all(&src_dir)?;
102            write_rust_file(
103                &src_dir.join("lib.rs"),
104                &indoc::indoc! {"
105                    #![feature(type_alias_impl_trait)]
106                    #![feature(thread_local)]
107                "},
108                &codegen::wasm::wit_impl_glue_rust(
109                    &format!("{}_modrpc", project_name.replace("-", "_")),
110                    &db, interface, role_name,
111                ),
112            )?;
113
114            // Role's typescript wasm glue
115
116            let role_typescript_root = role_root.join("typescript");
117            fs::create_dir_all(&role_typescript_root)?;
118
119            // Write package.json
120            let package_json_path = role_typescript_root.join("package.json");
121            if !package_json_path.exists() {
122                fs::write(
123                    package_json_path,
124                    TS_GLUE_PACKAGE_JSON
125                        .replace("PROJECT_NAME", project_name)
126                        .replace("PKG_NAME", &format!("{}-{}", kebab_interface_name, kebab_role_name))
127                        .as_bytes(),
128                )?;
129            } else {
130                println!(
131                    "{} already exists - skipping as it might contain hand-written code.",
132                    package_json_path.display(),
133                );
134            }
135
136            // Write tsconfig.json
137            fs::write(
138                role_typescript_root.join("tsconfig.json"),
139                TS_GLUE_TSCONFIG_JSON.as_bytes(),
140            )?;
141
142            // Write build.sh
143            fs::write(
144                role_typescript_root.join("build.sh"),
145                TS_GLUE_BUILD_SH
146                    .replace("INTERFACE_NAME", snake_interface_name)
147                    .replace("ROLE_NAME", snake_role_name)
148                    .as_bytes(),
149            )?;
150
151            // Write src/index.ts
152            let src_dir = role_typescript_root.join("src");
153            fs::create_dir_all(&src_dir)?;
154            crate::codegen::write_js_file(
155                &src_dir.join("index.ts"), "",
156                &codegen::wasm::js_wit_glue(
157                    &format!("{}-modrpc", project_name),
158                    &db, interface, role_name,
159                ),
160            )?;
161        }
162    }
163
164    Ok(())
165}
166
167fn write_rust_file(
168    path: impl AsRef<Path> + std::fmt::Debug,
169    header: &str,
170    tokens: &genco::lang::rust::Tokens,
171) -> std::io::Result<()> {
172    let fmt = genco::fmt::Config::from_lang::<genco::lang::Rust>()
173        .with_indentation(genco::fmt::Indentation::Space(4));
174    let config = genco::lang::rust::Config::default();
175    let mut file = fs::File::create(&path)?;
176
177    file.write_all(header.as_bytes())?;
178
179    let mut w = genco::fmt::IoWriter::new(file);
180    tokens
181        .format_file(&mut w.as_formatter(&fmt), &config)
182        .expect(&format!("format {:?} file", path));
183    
184    Ok(())
185}
186