floxide_macros_support/
lib.rs

1pub fn add(left: u64, right: u64) -> u64 {
2    left + right
3}
4
5#[cfg(test)]
6mod tests {
7    use super::*;
8
9    #[test]
10    fn it_works() {
11        let result = add(2, 2);
12        assert_eq!(result, 4);
13    }
14}
15
16/// AST and parsing logic for floxide-macros
17use syn::{
18    braced, bracketed,
19    parse::{Parse, ParseStream},
20    Generics, Ident, Result, Token, Type, Visibility,
21};
22
23#[derive(Debug, Clone)]
24pub struct CompositeArm {
25    pub action_path: Ident,
26    pub variant: Ident,
27    pub binding: Option<Ident>,
28    pub is_wildcard: bool,
29    pub guard: Option<syn::Expr>,
30    pub succs: Vec<Ident>,
31}
32
33#[derive(Debug, Clone)]
34pub enum EdgeKind {
35    Direct {
36        succs: Vec<Ident>,
37        on_failure: Option<Vec<Ident>>,
38    },
39    Composite(Vec<CompositeArm>),
40}
41
42#[derive(Debug, Clone)]
43pub struct WorkflowDef {
44    pub vis: Visibility,
45    pub name: Ident,
46    pub generics: Generics,
47    pub fields: Vec<(Ident, Type, Option<Ident>)>,
48    pub start: Ident,
49    pub context: Type,
50    pub edges: Vec<(Ident, EdgeKind)>,
51}
52
53// Parsing logic for WorkflowDef (copy from floxide-macros/src/workflow.rs, adapted to use these types)
54impl Parse for WorkflowDef {
55    fn parse(input: ParseStream) -> Result<Self> {
56        // parse optional visibility
57        let vis: Visibility = if input.peek(Token![pub]) {
58            input.parse()? // pub or pub(...) etc
59        } else {
60            Visibility::Inherited
61        };
62        // parse struct definition
63        input.parse::<Token![struct]>()?;
64        let name: Ident = input.parse()?;
65        let generics: Generics = input.parse()?;
66        // parse struct fields
67        let content;
68        braced!(content in input);
69        let mut fields = Vec::new();
70        while !content.is_empty() {
71            // Optional retry annotation: #[retry = policy]
72            let mut retry_policy: Option<Ident> = None;
73            if content.peek(Token![#]) {
74                content.parse::<Token![#]>()?;
75                let inner;
76                bracketed!(inner in content);
77                let attr_name: Ident = inner.parse()?;
78                if attr_name == "retry" {
79                    inner.parse::<Token![=]>()?;
80                    let pol: Ident = inner.parse()?;
81                    retry_policy = Some(pol);
82                } else {
83                    return Err(inner.error("unknown attribute, expected `retry`"));
84                }
85            }
86            // Field name and type
87            let fld: Ident = content.parse()?;
88            content.parse::<Token![:]>()?;
89            let ty: Type = content.parse()?;
90            if content.peek(Token![,]) {
91                content.parse::<Token![,]>()?;
92            }
93            fields.push((fld, ty, retry_policy));
94        }
95        // Now parse the rest in any order: start, context, edges
96        let mut start: Option<Ident> = None;
97        let mut context: Option<Type> = None;
98        let mut edges: Option<Vec<(Ident, EdgeKind)>> = None;
99        let mut seen = std::collections::HashSet::new();
100        while !input.is_empty() {
101            if input.peek(Ident) {
102                let fork = input.fork();
103                let kw = fork.parse::<Ident>()?;
104                match kw.to_string().as_str() {
105                    "start" => {
106                        if seen.contains("start") {
107                            return Err(
108                                input.error("Duplicate 'start' field in workflow definition.")
109                            );
110                        }
111                        input.parse::<Ident>()?; // start
112                        input.parse::<Token![=]>()?;
113                        let s: Ident = input.parse()?;
114                        input.parse::<Token![;]>()?;
115                        start = Some(s);
116                        seen.insert("start");
117                    }
118                    "context" => {
119                        if seen.contains("context") {
120                            return Err(
121                                input.error("Duplicate 'context' field in workflow definition.")
122                            );
123                        }
124                        input.parse::<Ident>()?; // context
125                        input.parse::<Token![=]>()?;
126                        let ty: Type = input.parse()?;
127                        input.parse::<Token![;]>()?;
128                        context = Some(ty);
129                        seen.insert("context");
130                    }
131                    "edges" => {
132                        if seen.contains("edges") {
133                            return Err(
134                                input.error("Duplicate 'edges' field in workflow definition.")
135                            );
136                        }
137                        input.parse::<Ident>()?; // edges
138                        let edges_content;
139                        braced!(edges_content in input);
140                        // Collect direct-success, direct-failure, and composite arms
141                        let mut direct_success =
142                            std::collections::HashMap::<Ident, Vec<Ident>>::new();
143                        let mut direct_failure =
144                            std::collections::HashMap::<Ident, Vec<Ident>>::new();
145                        let mut composite_map =
146                            std::collections::HashMap::<Ident, Vec<CompositeArm>>::new();
147                        while !edges_content.is_empty() {
148                            let src: Ident = edges_content.parse()?;
149                            if edges_content.peek(Ident) {
150                                // on_failure clause
151                                let kw: Ident = edges_content.parse()?;
152                                if kw == "on_failure" {
153                                    edges_content.parse::<Token![=>]>()?;
154                                    let nested;
155                                    braced!(nested in edges_content);
156                                    // expect bracketed fallback list
157                                    let succs_content;
158                                    bracketed!(succs_content in nested);
159                                    let fails: Vec<Ident> = succs_content
160                                        .parse_terminated(Ident::parse, Token![,])?
161                                        .into_iter()
162                                        .collect();
163                                    edges_content.parse::<Token![;]>()?;
164                                    direct_failure.insert(src.clone(), fails);
165                                    continue;
166                                } else {
167                                    return Err(edges_content.error(
168                                        "Unexpected identifier. Expected `on_failure` or `=>`.",
169                                    ));
170                                }
171                            }
172                            // success or composite entry
173                            edges_content.parse::<Token![=>]>()?;
174                            if edges_content.peek(syn::token::Bracket) {
175                                // direct edge: foo => [bar];
176                                let succs_content;
177                                bracketed!(succs_content in edges_content);
178                                let succs: Vec<Ident> = succs_content
179                                    .parse_terminated(Ident::parse, Token![,])?
180                                    .into_iter()
181                                    .collect();
182                                edges_content.parse::<Token![;]>()?;
183                                direct_success.insert(src.clone(), succs);
184                            } else if edges_content.peek(syn::token::Brace) {
185                                // direct or composite edge: foo => { ... };
186                                let nested;
187                                braced!(nested in edges_content);
188                                // Empty braces => direct edge with no successors
189                                if nested.is_empty() {
190                                    edges_content.parse::<Token![;]>()?;
191                                    direct_success.insert(src.clone(), Vec::new());
192                                } else if nested.peek(syn::token::Bracket) {
193                                    // legacy: foo => {[bar]};
194                                    let succs_content;
195                                    bracketed!(succs_content in nested);
196                                    let succs: Vec<Ident> = succs_content
197                                        .parse_terminated(Ident::parse, Token![,])?
198                                        .into_iter()
199                                        .collect();
200                                    edges_content.parse::<Token![;]>()?;
201                                    direct_success.insert(src.clone(), succs);
202                                } else {
203                                    // composite arms
204                                    let mut arms = Vec::new();
205                                    while !nested.is_empty() {
206                                        let action_path: Ident = nested.parse()?;
207                                        nested.parse::<Token![::]>()?;
208                                        let variant: Ident = nested.parse()?;
209                                        let mut binding = None;
210                                        let mut is_wildcard = false;
211                                        if nested.peek(syn::token::Paren) {
212                                            let inner;
213                                            syn::parenthesized!(inner in nested);
214                                            if inner.peek(Token![_]) {
215                                                inner.parse::<Token![_]>()?;
216                                                is_wildcard = true;
217                                            } else if inner.peek(Ident) {
218                                                binding = Some(inner.parse()?);
219                                            } else {
220                                                return Err(inner.error(
221                                                    "Expected identifier or _ in variant binding",
222                                                ));
223                                            }
224                                        } else {
225                                            // No parens: always treat as wildcard for macro ergonomics
226                                            is_wildcard = true;
227                                            binding = None;
228                                        }
229                                        // Optional guard
230                                        let guard = if nested.peek(Token![if]) {
231                                            nested.parse::<Token![if]>()?;
232                                            Some(nested.parse()?)
233                                        } else {
234                                            None
235                                        };
236                                        nested.parse::<Token![=>]>()?;
237                                        let succs_content;
238                                        bracketed!(succs_content in nested);
239                                        let succs: Vec<Ident> = succs_content
240                                            .parse_terminated(Ident::parse, Token![,])?
241                                            .into_iter()
242                                            .collect();
243                                        nested.parse::<Token![;]>()?;
244                                        let arm = CompositeArm {
245                                            action_path: action_path.clone(),
246                                            variant: variant.clone(),
247                                            binding: binding.clone(),
248                                            is_wildcard,
249                                            guard: guard.clone(),
250                                            succs: succs.clone(),
251                                        };
252                                        // Debug log removed: parsed arm
253                                        arms.push(arm);
254                                    }
255                                    edges_content.parse::<Token![;]>()?;
256                                    // After parsing all arms for a composite edge, print them for debugging
257                                    // Debug log removed: parsed composite arms for source
258                                    composite_map.insert(src.clone(), arms);
259                                }
260                            } else {
261                                return Err(edges_content
262                                    .error("Expected [ or { after => in edge definition"));
263                            }
264                        }
265                        // Merge into final edges vector
266                        let mut edges_vec = Vec::new();
267                        // direct-success entries
268                        for (src, succs) in direct_success.into_iter() {
269                            let failure = direct_failure.remove(&src);
270                            edges_vec.push((
271                                src,
272                                EdgeKind::Direct {
273                                    succs,
274                                    on_failure: failure,
275                                },
276                            ));
277                        }
278                        // direct-failure-only entries
279                        for (src, fails) in direct_failure.into_iter() {
280                            edges_vec.push((
281                                src,
282                                EdgeKind::Direct {
283                                    succs: Vec::new(),
284                                    on_failure: Some(fails),
285                                },
286                            ));
287                        }
288                        // composite entries
289                        for (src, arms) in composite_map.into_iter() {
290                            edges_vec.push((src, EdgeKind::Composite(arms)));
291                        }
292                        edges = Some(edges_vec);
293                        seen.insert("edges");
294                    }
295                    other => {
296                        return Err(input.error(format!(
297                            "Unexpected identifier '{}'. Expected one of: start, context, edges.",
298                            other
299                        )));
300                    }
301                }
302            } else {
303                return Err(input.error("Unexpected token in workflow definition. Expected 'start', 'context', or 'edges'."));
304            }
305        }
306        // Check required fields
307        let start = start
308            .ok_or_else(|| input.error("Missing required 'start' field in workflow definition."))?;
309        let context = context.unwrap_or_else(|| syn::parse_quote! { () });
310        let edges = edges
311            .ok_or_else(|| input.error("Missing required 'edges' field in workflow definition."))?;
312        Ok(WorkflowDef {
313            vis,
314            name,
315            generics,
316            fields,
317            start,
318            context,
319            edges,
320        })
321    }
322}