ferro_cli/commands/
make_middleware.rs1use console::style;
2use std::fs;
3use std::path::Path;
4
5use crate::templates;
6
7pub fn run(name: String) {
8 if !is_valid_identifier(&name) {
10 eprintln!(
11 "{} '{}' is not a valid Rust identifier",
12 style("Error:").red().bold(),
13 name
14 );
15 std::process::exit(1);
16 }
17
18 let struct_name = if name.ends_with("Middleware") {
22 name.clone()
23 } else {
24 format!("{name}Middleware")
25 };
26 let file_name = to_snake_case(name.trim_end_matches("Middleware"));
27
28 let middleware_dir = Path::new("src/middleware");
29 let middleware_file = middleware_dir.join(format!("{file_name}.rs"));
30 let mod_file = middleware_dir.join("mod.rs");
31
32 if !middleware_dir.exists() {
34 if let Err(e) = fs::create_dir_all(middleware_dir) {
35 eprintln!(
36 "{} Failed to create middleware directory: {}",
37 style("Error:").red().bold(),
38 e
39 );
40 std::process::exit(1);
41 }
42 println!("{} Created src/middleware directory", style("✓").green());
43 }
44
45 if middleware_file.exists() {
47 eprintln!(
48 "{} Middleware '{}' already exists at {}",
49 style("Error:").red().bold(),
50 struct_name,
51 middleware_file.display()
52 );
53 std::process::exit(1);
54 }
55
56 let middleware_content = templates::middleware_template(&name, &struct_name);
58
59 if let Err(e) = fs::write(&middleware_file, middleware_content) {
61 eprintln!(
62 "{} Failed to write middleware file: {}",
63 style("Error:").red().bold(),
64 e
65 );
66 std::process::exit(1);
67 }
68 println!(
69 "{} Created {}",
70 style("✓").green(),
71 middleware_file.display()
72 );
73
74 if mod_file.exists() {
76 if let Err(e) = update_mod_file(&mod_file, &file_name, &struct_name) {
77 eprintln!(
78 "{} Failed to update mod.rs: {}",
79 style("Error:").red().bold(),
80 e
81 );
82 std::process::exit(1);
83 }
84 println!("{} Updated src/middleware/mod.rs", style("✓").green());
85 } else {
86 let mod_content = format!(
88 "//! Application middleware\n\nmod {file_name};\n\npub use {file_name}::{struct_name};\n"
89 );
90 if let Err(e) = fs::write(&mod_file, mod_content) {
91 eprintln!(
92 "{} Failed to create mod.rs: {}",
93 style("Error:").red().bold(),
94 e
95 );
96 std::process::exit(1);
97 }
98 println!("{} Created src/middleware/mod.rs", style("✓").green());
99 }
100
101 println!();
102 println!(
103 "Middleware {} created successfully!",
104 style(&struct_name).cyan().bold()
105 );
106 println!();
107 println!("Usage:");
108 println!(" {} Import and use in routes:", style("1.").dim());
109 println!(" use crate::middleware::{struct_name};");
110 println!(" .get(\"/path\", handler).middleware({struct_name})");
111 println!();
112 println!(" {} Or apply globally in main.rs:", style("2.").dim());
113 println!(" .middleware(middleware::{struct_name})");
114 println!();
115}
116
117fn is_valid_identifier(name: &str) -> bool {
118 if name.is_empty() {
119 return false;
120 }
121
122 let mut chars = name.chars();
123
124 match chars.next() {
126 Some(c) if c.is_alphabetic() || c == '_' => {}
127 _ => return false,
128 }
129
130 chars.all(|c| c.is_alphanumeric() || c == '_')
132}
133
134fn to_snake_case(s: &str) -> String {
135 let mut result = String::new();
136 for (i, c) in s.chars().enumerate() {
137 if c.is_uppercase() {
138 if i > 0 {
139 result.push('_');
140 }
141 result.push(c.to_lowercase().next().unwrap());
142 } else {
143 result.push(c);
144 }
145 }
146 result
147}
148
149fn update_mod_file(mod_file: &Path, file_name: &str, struct_name: &str) -> Result<(), String> {
150 let content =
151 fs::read_to_string(mod_file).map_err(|e| format!("Failed to read mod.rs: {e}"))?;
152
153 let mod_decl = format!("mod {file_name};");
155 if content.contains(&mod_decl) {
156 return Err(format!("Module '{file_name}' already declared in mod.rs"));
157 }
158
159 let mut lines: Vec<&str> = content.lines().collect();
161
162 let mut last_mod_idx = None;
164 for (i, line) in lines.iter().enumerate() {
165 if line.trim().starts_with("mod ") {
166 last_mod_idx = Some(i);
167 }
168 }
169
170 let mod_insert_idx = match last_mod_idx {
172 Some(idx) => idx + 1,
173 None => {
174 let mut insert_idx = 0;
176 for (i, line) in lines.iter().enumerate() {
177 if line.starts_with("//!") || line.is_empty() {
178 insert_idx = i + 1;
179 } else {
180 break;
181 }
182 }
183 insert_idx
184 }
185 };
186 lines.insert(mod_insert_idx, &mod_decl);
187
188 let pub_use_decl = format!("pub use {file_name}::{struct_name};");
190 let mut last_pub_use_idx = None;
191 for (i, line) in lines.iter().enumerate() {
192 if line.trim().starts_with("pub use ") {
193 last_pub_use_idx = Some(i);
194 }
195 }
196
197 match last_pub_use_idx {
199 Some(idx) => {
200 lines.insert(idx + 1, &pub_use_decl);
201 }
202 None => {
203 let mut insert_idx = mod_insert_idx + 1;
205 while insert_idx < lines.len() && lines[insert_idx].trim().starts_with("mod ") {
207 insert_idx += 1;
208 }
209 if insert_idx < lines.len() && !lines[insert_idx].is_empty() {
211 lines.insert(insert_idx, "");
212 insert_idx += 1;
213 }
214 lines.insert(insert_idx, &pub_use_decl);
215 }
216 }
217
218 let new_content = lines.join("\n");
219 fs::write(mod_file, new_content).map_err(|e| format!("Failed to write mod.rs: {e}"))?;
220
221 Ok(())
222}