baobao_codegen/
handlers.rs1use std::{
4 collections::HashSet,
5 path::{Path, PathBuf},
6};
7
8use baobao_manifest::Schema;
9use eyre::Result;
10
11use crate::commands::flatten_commands;
12
13#[derive(Debug, Clone)]
20pub struct HandlerPaths {
21 base_dir: PathBuf,
23 extension: String,
25}
26
27impl HandlerPaths {
28 pub fn new(base_dir: impl Into<PathBuf>, extension: impl Into<String>) -> Self {
30 Self {
31 base_dir: base_dir.into(),
32 extension: extension.into(),
33 }
34 }
35
36 pub fn handler_path(&self, command_path: &[&str]) -> PathBuf {
48 let mut path = self.base_dir.clone();
49 if command_path.len() > 1 {
50 for segment in &command_path[..command_path.len() - 1] {
52 path.push(segment);
53 }
54 }
55 if let Some(last) = command_path.last() {
57 path.push(format!("{}.{}", last, self.extension));
58 }
59 path
60 }
61
62 pub fn mod_path(&self, command_path: &[&str]) -> PathBuf {
74 let mut path = self.base_dir.clone();
75 for segment in command_path {
76 path.push(segment);
77 }
78 path.push(format!("mod.{}", self.extension));
79 path
80 }
81
82 pub fn exists(&self, command_path: &[&str]) -> bool {
84 self.handler_path(command_path).exists()
85 }
86
87 pub fn find_orphans(&self, expected_paths: &HashSet<String>) -> Result<Vec<String>> {
91 let mut orphans = Vec::new();
92 self.scan_for_orphans(&self.base_dir, "", expected_paths, &mut orphans)?;
93 Ok(orphans)
94 }
95
96 fn scan_for_orphans(
97 &self,
98 dir: &Path,
99 prefix: &str,
100 expected: &HashSet<String>,
101 orphans: &mut Vec<String>,
102 ) -> Result<()> {
103 if !dir.exists() {
104 return Ok(());
105 }
106
107 for entry in std::fs::read_dir(dir)? {
108 let entry = entry?;
109 let path = entry.path();
110 let file_name = path.file_name().unwrap().to_string_lossy();
111
112 if file_name == format!("mod.{}", self.extension) {
114 continue;
115 }
116
117 if path.is_dir() {
118 let new_prefix = if prefix.is_empty() {
119 file_name.to_string()
120 } else {
121 format!("{}/{}", prefix, file_name)
122 };
123
124 if !expected.contains(&new_prefix) {
126 orphans.push(new_prefix.clone());
127 } else {
128 self.scan_for_orphans(&path, &new_prefix, expected, orphans)?;
129 }
130 } else if path
131 .extension()
132 .is_some_and(|ext| ext == self.extension.as_str())
133 {
134 let stem = path.file_stem().unwrap().to_string_lossy();
135 let handler_path = if prefix.is_empty() {
136 stem.to_string()
137 } else {
138 format!("{}/{}", prefix, stem)
139 };
140
141 if !expected.contains(&handler_path) {
142 orphans.push(handler_path);
143 }
144 }
145 }
146
147 Ok(())
148 }
149}
150
151pub fn collect_handler_paths(schema: &Schema) -> HashSet<String> {
155 let commands = flatten_commands(schema);
156 let mut paths = HashSet::new();
157
158 for cmd in commands {
159 let path_str = cmd.path_str("/");
160 paths.insert(path_str);
161 }
162
163 paths
164}
165
166pub fn collect_leaf_handler_paths(schema: &Schema) -> HashSet<String> {
168 let commands = flatten_commands(schema);
169 let mut paths = HashSet::new();
170
171 for cmd in commands {
172 if cmd.is_leaf {
173 paths.insert(cmd.path_str("/"));
174 }
175 }
176
177 paths
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn test_handler_path_simple() {
186 let paths = HandlerPaths::new("src/handlers", "rs");
187 assert_eq!(
188 paths.handler_path(&["hello"]),
189 PathBuf::from("src/handlers/hello.rs")
190 );
191 }
192
193 #[test]
194 fn test_handler_path_nested() {
195 let paths = HandlerPaths::new("src/handlers", "rs");
196 assert_eq!(
197 paths.handler_path(&["db", "migrate"]),
198 PathBuf::from("src/handlers/db/migrate.rs")
199 );
200 }
201
202 #[test]
203 fn test_handler_path_deeply_nested() {
204 let paths = HandlerPaths::new("src/handlers", "rs");
205 assert_eq!(
206 paths.handler_path(&["config", "user", "set"]),
207 PathBuf::from("src/handlers/config/user/set.rs")
208 );
209 }
210
211 #[test]
212 fn test_mod_path() {
213 let paths = HandlerPaths::new("src/handlers", "rs");
214 assert_eq!(
215 paths.mod_path(&["db"]),
216 PathBuf::from("src/handlers/db/mod.rs")
217 );
218 }
219
220 #[test]
221 fn test_typescript_extension() {
222 let paths = HandlerPaths::new("src/handlers", "ts");
223 assert_eq!(
224 paths.handler_path(&["hello"]),
225 PathBuf::from("src/handlers/hello.ts")
226 );
227 }
228}