modrpc_codegen/codegen/wasm/
project.rs

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