pub const FRAMEWORK_VERSION: &str = "0.1";
pub const GATEWAY_VERSION: &str = "0.1";
pub struct TemplateFile {
pub path: &'static str,
pub contents: &'static str,
}
macro_rules! project_file {
($path:literal) => {
project_file!($path, $path)
};
($path:literal, $stub:literal) => {
TemplateFile {
path: $path,
contents: include_str!(concat!("../templates/project/", $stub, ".stub")),
}
};
}
pub const PROJECT: &[TemplateFile] = &[
project_file!("Cargo.toml"),
project_file!(".env.example"),
project_file!(".gitignore"),
project_file!("README.md"),
project_file!("src/main.rs"),
project_file!("src/app.rs"),
project_file!("src/base/mod.rs"),
project_file!("src/base/config.rs"),
project_file!("src/base/error.rs"),
project_file!("src/base/state.rs"),
project_file!("src/base/auth.rs"),
project_file!("src/base/mcp.rs"),
project_file!("src/modules/mod.rs"),
project_file!("src/modules/user/mod.rs"),
project_file!("src/modules/user/models/mod.rs"),
project_file!("src/modules/user/models/user.rs"),
project_file!("src/modules/user/repositories/mod.rs"),
project_file!("src/modules/user/repositories/user_repository.rs"),
project_file!("src/modules/user/services/mod.rs"),
project_file!("src/modules/user/services/user_service.rs"),
project_file!("src/modules/user/tools.rs"),
project_file!("src/modules/user/dto/mod.rs"),
project_file!("src/modules/user/dto/user.rs"),
project_file!("src/modules/role/mod.rs"),
project_file!("src/modules/role/models/mod.rs"),
project_file!("src/modules/role/models/role.rs"),
project_file!("src/modules/role/repositories/mod.rs"),
project_file!("src/modules/role/repositories/role_repository.rs"),
project_file!("src/modules/role/services/mod.rs"),
project_file!("src/modules/role/services/role_service.rs"),
project_file!("src/modules/role/tools.rs"),
project_file!("src/modules/role/dto/mod.rs"),
project_file!("src/modules/role/dto/role.rs"),
project_file!("src/modules/auth/mod.rs"),
project_file!("src/modules/auth/repositories/mod.rs"),
project_file!("src/modules/auth/repositories/token_repository.rs"),
project_file!("src/modules/auth/services/mod.rs"),
project_file!("src/modules/auth/services/auth_service.rs"),
project_file!("src/modules/auth/tools.rs"),
project_file!("src/modules/auth/dto/mod.rs"),
project_file!("src/modules/auth/dto/auth.rs"),
project_file!("src/bin/migrate.rs"),
project_file!("src/bin/gateway.rs"),
project_file!("config/apps.toml"),
project_file!("database/migrations/mod.rs"),
project_file!(
"database/migrations/{{ ts_users }}_create_users_table.rs",
"database/migrations/create_users_table.rs"
),
project_file!(
"database/migrations/{{ ts_roles }}_create_roles_tables.rs",
"database/migrations/create_roles_tables.rs"
),
project_file!(
"database/migrations/{{ ts_tokens }}_create_auth_tokens_table.rs",
"database/migrations/create_auth_tokens_table.rs"
),
project_file!("tests/mcp.rs"),
];
macro_rules! app_file {
($stack:literal, $path:literal) => {
TemplateFile {
path: $path,
contents: include_str!(concat!("../templates/apps/", $stack, "/", $path, ".stub")),
}
};
}
pub const AXUM_APP: &[TemplateFile] = &[
app_file!("axum", "Cargo.toml"),
app_file!("axum", "src/main.rs"),
app_file!("axum", "src/mcp.rs"),
app_file!("axum", "src/html.rs"),
app_file!("axum", "src/pages.rs"),
];
pub const DIOXUS_APP: &[TemplateFile] = &[
app_file!("dioxus", "Cargo.toml"),
app_file!("dioxus", "Dioxus.toml"),
app_file!("dioxus", "src/main.rs"),
app_file!("dioxus", "src/app.rs"),
app_file!("dioxus", "src/mcp.rs"),
app_file!("dioxus", "src/server.rs"),
];
pub const MODEL: &str = include_str!("../templates/make/model.rs.stub");
pub const TOOLS: &str = include_str!("../templates/make/tools.rs.stub");
pub const MIGRATION: &str = include_str!("../templates/make/migration.rs.stub");
pub const MIGRATION_CREATE_TABLE: &str =
include_str!("../templates/make/migration_create_table.rs.stub");
pub const TOOLS_MARKER: &str = "// slave:tools";
pub const MIGRATION_MODULES_MARKER: &str = "// slave:migration-modules";
pub const MIGRATIONS_MARKER: &str = "// slave:migrations";
pub fn render(template: &str, vars: &[(&str, &str)]) -> String {
vars.iter().fold(template.to_owned(), |acc, (key, value)| {
acc.replace(&format!("{{{{ {key} }}}}"), value)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn renders_placeholders() {
let out = render(
"struct {{ name }}; // {{ name }} {{ other }}",
&[("name", "User")],
);
assert_eq!(out, "struct User; // User {{ other }}");
}
#[test]
fn templates_have_no_unknown_placeholders() {
let project_vars = [
("name", "demo"),
("slave_version", "0.0.0"),
("framework_dependency", "slave-framework = \"0.1\""),
("gateway_dependency", "slave-gateway = \"0.1\""),
("ts_users", "20260101000000"),
("ts_roles", "20260101000001"),
("ts_tokens", "20260101000002"),
];
for file in PROJECT {
let path = render(file.path, &project_vars);
assert!(!path.contains("{{ "), "{path}");
assert!(
!render(file.contents, &project_vars).contains("{{ "),
"{path}"
);
}
let make_vars = [("name", "Post"), ("module", "post"), ("prefix", "post")];
for template in [MODEL, TOOLS] {
assert!(!render(template, &make_vars).contains("{{ "));
}
let app_vars = [
("app", "dashboard"),
("title", "Dashboard"),
("port", "4000"),
];
for file in AXUM_APP.iter().chain(DIOXUS_APP) {
assert!(
!render(file.contents, &app_vars).contains("{{ "),
"{}",
file.path
);
}
let migration_vars = [("struct", "CreatePostsTable"), ("table", "posts")];
for template in [MIGRATION, MIGRATION_CREATE_TABLE] {
assert!(!render(template, &migration_vars).contains("{{ "));
}
}
#[test]
fn migrations_registry_has_markers() {
let registry = PROJECT
.iter()
.find(|file| file.path == "database/migrations/mod.rs")
.expect("migrations registry");
for marker in [MIGRATION_MODULES_MARKER, MIGRATIONS_MARKER] {
assert!(registry.contents.contains(marker), "{marker}");
}
}
#[test]
fn modules_template_has_tools_marker() {
let modules = PROJECT
.iter()
.find(|file| file.path == "src/modules/mod.rs")
.expect("modules template");
assert!(modules.contents.contains(TOOLS_MARKER));
}
#[test]
fn versions_match_workspace_crates() {
for (dir, version) in [
("framework", FRAMEWORK_VERSION),
("gateway", GATEWAY_VERSION),
] {
let manifest = format!("{}/{dir}/Cargo.toml", env!("CARGO_MANIFEST_DIR"));
let Ok(contents) = std::fs::read_to_string(manifest) else {
continue;
};
let expected = format!("version = \"{version}.");
assert!(
contents.contains(&expected),
"update the {dir} version constant"
);
}
}
}