kinetics_parser/
parser.rs

1use crate::{environment::Environment, Cron, Endpoint, Worker};
2use syn::{parse::Parse, visit::Visit, Attribute, ItemFn};
3
4/// Represents a function in the source code
5#[derive(Debug, Clone)]
6pub struct ParsedFunction {
7    /// Name of the function, parsed from the function definition
8    pub rust_function_name: String,
9
10    /// Path to the file where function is defined
11    pub relative_path: String,
12
13    /// Parsed from kinetics_macro macro definition
14    pub role: Role,
15}
16
17impl ParsedFunction {
18    /// Generate lambda function name out of Rust function name or macro attribute
19    ///
20    /// By default use the Rust function plus crate path as the function name. Convert
21    /// some-name to SomeName, and do other transformations in order to comply with Lambda
22    /// function name requirements.
23    pub fn func_name(&self, is_local: bool) -> String {
24        let rust_name = &self.rust_function_name;
25        let full_path = format!("{}/{rust_name}", self.relative_path);
26
27        let default_func_name = full_path
28            .as_str()
29            .replace("_", "Undrscr")
30            .replace("_", "Dash")
31            .split(&['.', '/'])
32            .filter(|s| !s.eq(&"rs"))
33            .map(|s| match s.chars().next() {
34                Some(first) => first.to_uppercase().collect::<String>() + &s[1..],
35                None => String::new(),
36            })
37            .collect::<String>()
38            .replacen("Src", "", 1);
39
40        // TODO Check the name for uniqueness
41        format!(
42            "{}{}",
43            self.role.name().unwrap_or(&default_func_name),
44            if is_local { "Local" } else { "" }
45        )
46    }
47}
48
49#[derive(Debug, Clone)]
50pub enum Role {
51    Endpoint(Endpoint),
52    Cron(Cron),
53    Worker(Worker),
54}
55
56impl Role {
57    pub fn name(&self) -> Option<&String> {
58        match self {
59            Role::Endpoint(params) => params.name.as_ref(),
60            Role::Cron(params) => params.name.as_ref(),
61            Role::Worker(params) => params.name.as_ref(),
62        }
63    }
64
65    pub fn environment(&self) -> &Environment {
66        match self {
67            Role::Endpoint(params) => &params.environment,
68            Role::Cron(params) => &params.environment,
69            Role::Worker(params) => &params.environment,
70        }
71    }
72}
73
74#[derive(Debug, Default)]
75pub struct Parser {
76    /// All found functions in the source code
77    pub functions: Vec<ParsedFunction>,
78
79    /// Relative path to currently processing file
80    pub relative_path: String,
81}
82
83impl Parser {
84    pub fn new() -> Self {
85        Default::default()
86    }
87
88    pub fn set_relative_path(&mut self, file_path: Option<&str>) {
89        self.relative_path = file_path.map_or_else(|| "".to_string(), |s| s.to_string());
90    }
91
92    fn parse_endpoint(&mut self, attr: &Attribute) -> syn::Result<Endpoint> {
93        attr.parse_args_with(Endpoint::parse)
94    }
95
96    fn parse_worker(&mut self, attr: &Attribute) -> syn::Result<Worker> {
97        attr.parse_args_with(Worker::parse)
98    }
99
100    fn parse_cron(&mut self, attr: &Attribute) -> syn::Result<Cron> {
101        attr.parse_args_with(Cron::parse)
102    }
103
104    /// Checks if the input is a valid kinetics_macro definition and returns its role
105    /// Checks if the input is a valid kinetics_macro definition
106    /// Known definitions:
107    /// kinetics_macro::<role> or <role>
108    fn parse_attr_role(&self, input: &Attribute) -> String {
109        let path = input.path();
110
111        if path.segments.len() == 1 {
112            let ident = &path.segments[0].ident;
113            return ident.to_string();
114        }
115
116        if path.segments.len() == 2 && &path.segments[0].ident == "kinetics_macro" {
117            let ident = &path.segments[1].ident;
118            return ident.to_string();
119        }
120
121        "".to_string()
122    }
123}
124
125impl Visit<'_> for Parser {
126    /// Visits function definitions
127    fn visit_item_fn(&mut self, item: &ItemFn) {
128        for attr in &item.attrs {
129            // Skip non-endpoint or non-worker attributes
130            let role = match self.parse_attr_role(attr).as_str() {
131                "endpoint" => {
132                    let params = self.parse_endpoint(attr).unwrap();
133                    Role::Endpoint(params)
134                }
135                "worker" => {
136                    let params = self.parse_worker(attr).unwrap();
137                    Role::Worker(params)
138                }
139                "cron" => {
140                    let params = self.parse_cron(attr).unwrap();
141                    Role::Cron(params)
142                }
143                _ => continue,
144            };
145
146            self.functions.push(ParsedFunction {
147                role,
148                rust_function_name: item.sig.ident.to_string(),
149                relative_path: self.relative_path.clone(),
150            });
151        }
152
153        // We don't need to parse the function body (in case nested functions), so just exit here
154    }
155}