elif_introspect/
lib.rs

1use elif_core::{ElifError, ResourceSpec};
2use serde::{Deserialize, Serialize};
3use std::path::PathBuf;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct ProjectMap {
7    pub routes: Vec<RouteInfo>,
8    pub models: Vec<ModelInfo>,
9    pub specs: Vec<SpecInfo>,
10}
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct RouteInfo {
14    pub op_id: String,
15    pub method: String,
16    pub path: String,
17    pub file: String,
18    pub marker: Option<String>,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct ModelInfo {
23    pub name: String,
24    pub file: String,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct SpecInfo {
29    pub name: String,
30    pub file: String,
31}
32
33pub struct MapGenerator {
34    project_root: PathBuf,
35}
36
37impl MapGenerator {
38    pub fn new(project_root: PathBuf) -> Self {
39        Self { project_root }
40    }
41
42    pub fn generate(&self) -> Result<ProjectMap, ElifError> {
43        let mut routes = Vec::new();
44        let mut models = Vec::new();
45        let mut specs = Vec::new();
46
47        // Collect resource specifications
48        let resources_dir = self.project_root.join("resources");
49        if resources_dir.exists() {
50            for entry in std::fs::read_dir(&resources_dir)? {
51                let entry = entry?;
52                let path = entry.path();
53
54                if path.extension().is_some_and(|ext| ext == "yaml")
55                    && path
56                        .file_stem()
57                        .and_then(|s| s.to_str())
58                        .is_some_and(|s| s.ends_with(".resource"))
59                {
60                    let content = std::fs::read_to_string(&path)?;
61                    let spec = ResourceSpec::from_yaml(&content)?;
62
63                    // Add spec info
64                    specs.push(SpecInfo {
65                        name: spec.name.clone(),
66                        file: path.to_string_lossy().to_string(),
67                    });
68
69                    // Add model info
70                    models.push(ModelInfo {
71                        name: spec.name.clone(),
72                        file: format!("crates/orm/src/models/{}.rs", spec.name.to_lowercase()),
73                    });
74
75                    // Add route info for each operation
76                    let handler_file =
77                        format!("apps/api/src/routes/{}.rs", spec.name.to_lowercase());
78                    for op in &spec.api.operations {
79                        routes.push(RouteInfo {
80                            op_id: format!("{}.{}", spec.name, op.op),
81                            method: op.method.clone(),
82                            path: format!("{}{}", spec.route.trim_end_matches('/'), op.path),
83                            file: handler_file.clone(),
84                            marker: Some(format!("{}_{}", op.op, spec.name)),
85                        });
86                    }
87                }
88            }
89        }
90
91        Ok(ProjectMap {
92            routes,
93            models,
94            specs,
95        })
96    }
97}