gize_generator/
registry.rs1use anyhow::{Result, bail};
11
12const MODULES_MARKER: &str = "// gize:modules (do not remove this marker)";
13const ROUTES_MARKER: &str = "// gize:module-routes (do not remove this marker)";
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct Edit {
18 pub changed: bool,
20 pub content: String,
22}
23
24pub fn register_module(source: &str, module: &str) -> Result<Edit> {
27 let mod_decl = format!("mod {module};");
28 if source.contains(&mod_decl) {
29 return Ok(Edit {
30 changed: false,
31 content: source.to_string(),
32 });
33 }
34
35 if !source.contains(MODULES_MARKER) || !source.contains(ROUTES_MARKER) {
36 bail!(
37 "src/app/mod.rs is missing gize markers; cannot register `{module}` automatically. \
38 Re-add the `// gize:modules` and `// gize:module-routes` markers or wire the module by hand."
39 );
40 }
41
42 let with_mod = insert_after_marker(source, MODULES_MARKER, &mod_decl);
43 let merge_call = format!(" .merge({module}::routes())");
44 let content = insert_before_marker(&with_mod, ROUTES_MARKER, &merge_call);
45
46 Ok(Edit {
47 changed: true,
48 content,
49 })
50}
51
52fn insert_after_marker(source: &str, marker: &str, new_line: &str) -> String {
54 let mut out: Vec<String> = Vec::new();
55 for line in source.lines() {
56 out.push(line.to_string());
57 if line.contains(marker) {
58 out.push(new_line.to_string());
59 }
60 }
61 finish(out, source)
62}
63
64fn insert_before_marker(source: &str, marker: &str, new_line: &str) -> String {
66 let mut out: Vec<String> = Vec::new();
67 for line in source.lines() {
68 if line.contains(marker) {
69 out.push(new_line.to_string());
70 }
71 out.push(line.to_string());
72 }
73 finish(out, source)
74}
75
76fn finish(lines: Vec<String>, source: &str) -> String {
78 let mut s = lines.join("\n");
79 if source.ends_with('\n') {
80 s.push('\n');
81 }
82 s
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 const APP_MOD: &str = r#"use axum::Router;
90
91use crate::state::AppState;
92
93// gize:modules (do not remove this marker)
94
95pub fn routes() -> Router<AppState> {
96 Router::new()
97 // gize:module-routes (do not remove this marker)
98}
99"#;
100
101 #[test]
102 fn registers_a_new_module() {
103 let edit = register_module(APP_MOD, "users").unwrap();
104 assert!(edit.changed);
105 assert!(edit.content.contains("mod users;"));
106 assert!(edit.content.contains(".merge(users::routes())"));
107 assert!(edit.content.contains(MODULES_MARKER));
109 assert!(edit.content.contains(ROUTES_MARKER));
110 }
111
112 #[test]
113 fn is_idempotent() {
114 let first = register_module(APP_MOD, "users").unwrap();
115 let second = register_module(&first.content, "users").unwrap();
116 assert!(!second.changed);
117 assert_eq!(first.content, second.content);
118 }
119
120 #[test]
121 fn registers_multiple_modules() {
122 let first = register_module(APP_MOD, "users").unwrap();
123 let second = register_module(&first.content, "products").unwrap();
124 assert!(second.changed);
125 assert!(second.content.contains("mod users;"));
126 assert!(second.content.contains("mod products;"));
127 assert!(second.content.contains(".merge(users::routes())"));
128 assert!(second.content.contains(".merge(products::routes())"));
129 }
130
131 #[test]
132 fn fails_without_markers() {
133 assert!(register_module("fn routes() {}", "users").is_err());
134 }
135}