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);
46 let merge_call = format!(" .merge({module}::routes())");
47 let content = insert_before_marker(&with_mod, ROUTES_MARKER, &merge_call);
48
49 Ok(Edit {
50 changed: true,
51 content,
52 })
53}
54
55fn insert_after_marker(source: &str, marker: &str, new_line: &str) -> String {
57 let mut out: Vec<String> = Vec::new();
58 for line in source.lines() {
59 out.push(line.to_string());
60 if line.contains(marker) {
61 out.push(new_line.to_string());
62 }
63 }
64 finish(out, source)
65}
66
67fn insert_before_marker(source: &str, marker: &str, new_line: &str) -> String {
69 let mut out: Vec<String> = Vec::new();
70 for line in source.lines() {
71 if line.contains(marker) {
72 out.push(new_line.to_string());
73 }
74 out.push(line.to_string());
75 }
76 finish(out, source)
77}
78
79fn finish(lines: Vec<String>, source: &str) -> String {
81 let mut s = lines.join("\n");
82 if source.ends_with('\n') {
83 s.push('\n');
84 }
85 s
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 const APP_MOD: &str = r#"use axum::Router;
93
94use crate::state::AppState;
95
96// gize:modules (do not remove this marker)
97
98pub fn routes() -> Router<AppState> {
99 Router::new()
100 // gize:module-routes (do not remove this marker)
101}
102"#;
103
104 #[test]
105 fn registers_a_new_module() {
106 let edit = register_module(APP_MOD, "users").unwrap();
107 assert!(edit.changed);
108 assert!(edit.content.contains("mod users;"));
109 assert!(edit.content.contains(".merge(users::routes())"));
110 assert!(edit.content.contains(MODULES_MARKER));
112 assert!(edit.content.contains(ROUTES_MARKER));
113 }
114
115 #[test]
116 fn is_idempotent() {
117 let first = register_module(APP_MOD, "users").unwrap();
118 let second = register_module(&first.content, "users").unwrap();
119 assert!(!second.changed);
120 assert_eq!(first.content, second.content);
121 }
122
123 #[test]
124 fn registers_multiple_modules() {
125 let first = register_module(APP_MOD, "users").unwrap();
126 let second = register_module(&first.content, "products").unwrap();
127 assert!(second.changed);
128 assert!(second.content.contains("mod users;"));
129 assert!(second.content.contains("mod products;"));
130 assert!(second.content.contains(".merge(users::routes())"));
131 assert!(second.content.contains(".merge(products::routes())"));
132 }
133
134 #[test]
135 fn fails_without_markers() {
136 assert!(register_module("fn routes() {}", "users").is_err());
137 }
138}