qaf_build_utils/
lib.rs

1use anyhow::{anyhow, Result};
2use std::path::PathBuf;
3use syn::{ItemFn, LitStr};
4
5#[derive(Debug, Clone)]
6pub struct PageEntry {
7    pub name: String,
8    pub is_dir: bool,
9    pub children: Vec<PageEntry>,
10}
11
12impl PageEntry {
13    pub fn generate(dir: &PathBuf) -> Result<PageEntry> {
14        let mut children: Vec<PageEntry> = Vec::new();
15
16        // TODO: Refactor this
17        for entry in dir.read_dir()? {
18            if let Ok(entry) = entry {
19                let file_type = entry.file_type()?;
20
21                if file_type.is_dir() {
22                    children.push(PageEntry::generate(&entry.path())?);
23                } else if file_type.is_file() {
24                    let file_name = entry
25                        .file_name()
26                        .to_str()
27                        .unwrap()
28                        .split('.')
29                        .collect::<Vec<&str>>()[0]
30                        .to_owned();
31
32                    children.push(PageEntry {
33                        name: file_name,
34                        is_dir: entry.file_type()?.is_dir(),
35                        children: vec![],
36                    })
37                }
38            }
39        }
40
41        // WTF???
42        let dir_name = dir.file_name().unwrap().to_str().unwrap().to_owned();
43        children.sort_by_key(|k| k.is_dir);
44
45        return Ok(PageEntry {
46            name: dir_name,
47            is_dir: true,
48            children,
49        });
50    }
51
52    pub fn get_mods_string(&self) -> Result<String> {
53        let mut out = String::new();
54        if self.is_dir && self.children.len() == 0 {
55            return Ok(out);
56        }
57
58        if self.name.contains(":") || self.name.contains("{") || self.name.contains("}") {
59            out += &format!("#[path = \"{}\"]\n", self.name);
60        }
61        out += &format!(
62            "pub mod {}",
63            self.name
64                .replace(":", "_")
65                .replace("{", "_")
66                .replace("}", "_")
67        );
68
69        if self.children.len() > 0 {
70            out += "{ \n";
71
72            for child in self.children.clone() {
73                out += &child.get_mods_string()?;
74            }
75
76            out += "} \n";
77        } else {
78            out += "; \n";
79        }
80
81        Ok(out)
82    }
83}
84
85#[derive(Debug, Clone)]
86pub struct FunctionRoute {
87    pub function: String,
88    pub route_type: RouteType,
89    pub route: Option<String>,
90}
91
92#[derive(Debug, Clone)]
93pub enum RouteType {
94    Get,
95    Post,
96    Put,
97    Delete,
98    Patch,
99    Head,
100    Options,
101    Any,
102    On,
103}
104
105impl RouteType {
106    pub fn from_str(s: &str) -> Result<RouteType> {
107        match s {
108            "get" => Ok(RouteType::Get),
109            "post" => Ok(RouteType::Post),
110            "put" => Ok(RouteType::Put),
111            "delete" => Ok(RouteType::Delete),
112            "patch" => Ok(RouteType::Patch),
113            "head" => Ok(RouteType::Head),
114            "options" => Ok(RouteType::Options),
115            "any" => Ok(RouteType::Any),
116            "on" => Ok(RouteType::On),
117            _ => Err(anyhow!("Invalid route type")),
118        }
119    }
120
121    pub fn to_str(&self) -> &str {
122        match self {
123            RouteType::Get => "get",
124            RouteType::Post => "post",
125            RouteType::Put => "put",
126            RouteType::Delete => "delete",
127            RouteType::Patch => "patch",
128            RouteType::Head => "head",
129            RouteType::Options => "options",
130            RouteType::Any => "any",
131            RouteType::On => "on",
132        }
133    }
134}
135
136pub fn get_file_routes(path: PathBuf) -> Result<Vec<FunctionRoute>> {
137    let file_content = std::fs::read_to_string(path)?;
138
139    let syntax = syn::parse_file(&file_content).unwrap();
140    let functions: Vec<FunctionRoute> = syntax
141        .items
142        .iter()
143        .filter_map(|item| {
144            if let syn::Item::Fn(item_fn) = item {
145                return get_macro_attr(item_fn);
146            }
147
148            None
149        })
150        .collect();
151
152    return Ok(functions);
153}
154
155const MACROS: [&'static str; 10] = [
156    "get", "post", "put", "delete", "patch", "head", "options", "trace", "any", "on",
157];
158fn get_macro_attr(item: &ItemFn) -> Option<FunctionRoute> {
159    for attr in item.attrs.clone() {
160        for segment in attr.path().segments.clone() {
161            let ident = segment.ident.to_string();
162            if MACROS.contains(&ident.as_str()) {
163                let route: Option<String> = attr.parse_args::<LitStr>().ok().map(|x| x.value());
164
165                return Some(FunctionRoute {
166                    function: item.sig.ident.to_string(),
167                    route_type: RouteType::from_str(&ident).unwrap(),
168                    route,
169                });
170            }
171        }
172    }
173
174    return None;
175}