trident_syn/parser/
trident_flow_executor.rs

1use syn::parse::Error as ParseError;
2use syn::parse::Result as ParseResult;
3use syn::spanned::Spanned;
4use syn::Attribute;
5use syn::ItemImpl;
6use syn::Meta;
7
8use crate::types::trident_flow_executor::FlowConstraints;
9use crate::types::trident_flow_executor::FlowMethod;
10use crate::types::trident_flow_executor::TridentFlowExecutorImpl;
11
12pub fn parse_trident_flow_executor(input: &ItemImpl) -> ParseResult<TridentFlowExecutorImpl> {
13    // Extract just the path without any generics
14    let type_name = if let syn::Type::Path(type_path) = &*input.self_ty {
15        let mut cleaned_path = type_path.clone();
16        // Clear any generic arguments from the last segment
17        if let Some(last_segment) = cleaned_path.path.segments.last_mut() {
18            last_segment.arguments = syn::PathArguments::None;
19        }
20        Box::new(syn::Type::Path(cleaned_path))
21    } else {
22        input.self_ty.clone()
23    };
24    let generics = input.generics.clone();
25
26    let mut init_method = None;
27    let mut end_method = None;
28    let mut flow_methods = Vec::new();
29
30    // Collect init, end, and flow methods
31    for item in &input.items {
32        if let syn::ImplItem::Fn(method) = item {
33            // First check for init methods
34            if method.attrs.iter().any(|attr| attr.path().is_ident("init")) {
35                if init_method.is_some() {
36                    return Err(ParseError::new(
37                        method.span(),
38                        "Multiple #[init] methods found. Only one is allowed.",
39                    ));
40                }
41                init_method = Some(method.sig.ident.clone());
42                continue;
43            }
44
45            // Then check for end methods
46            if method.attrs.iter().any(|attr| attr.path().is_ident("end")) {
47                if end_method.is_some() {
48                    return Err(ParseError::new(
49                        method.span(),
50                        "Multiple #[end] methods found. Only one is allowed.",
51                    ));
52                }
53                end_method = Some(method.sig.ident.clone());
54                continue;
55            }
56
57            // Then check for flow methods
58            if let Some(flow_attr) = method
59                .attrs
60                .iter()
61                .find(|attr| attr.path().is_ident("flow"))
62            {
63                let constraints = parse_flow_constraints(flow_attr)?;
64                let flow_method = FlowMethod {
65                    ident: method.sig.ident.clone(),
66                    constraints,
67                };
68                flow_methods.push(flow_method);
69            }
70        }
71    }
72
73    // Validate weight consistency
74    let flows_with_weights: Vec<_> = flow_methods
75        .iter()
76        .filter(|f| f.constraints.weight.is_some())
77        .collect();
78    let flows_without_weights: Vec<_> = flow_methods
79        .iter()
80        .filter(|f| f.constraints.weight.is_none())
81        .collect();
82
83    if !flows_with_weights.is_empty() && !flows_without_weights.is_empty() {
84        return Err(ParseError::new(
85            proc_macro2::Span::call_site(),
86            format!("Weight consistency error: If any flow has a weight specified, all flows must have weights. Flows without weights: {}",
87                flows_without_weights.iter().map(|f| f.ident.to_string()).collect::<Vec<_>>().join(", "))
88        ));
89    }
90
91    // Validate that total weight equals exactly 100
92    if !flows_with_weights.is_empty() {
93        let total_weight: u32 = flows_with_weights
94            .iter()
95            .map(|f| f.constraints.weight.unwrap())
96            .sum();
97
98        if total_weight != 100 {
99            return Err(ParseError::new(
100                proc_macro2::Span::call_site(),
101                format!("Total weight must equal exactly 100: The sum of all flow weights is {} but must be exactly 100 to represent clear percentages.", total_weight)
102            ));
103        }
104    }
105
106    Ok(TridentFlowExecutorImpl {
107        type_name,
108        impl_block: input.items.clone(),
109        flow_methods,
110        init_method,
111        end_method,
112        generics,
113    })
114}
115
116fn parse_flow_constraints(attr: &Attribute) -> ParseResult<FlowConstraints> {
117    let mut constraints = FlowConstraints::default();
118
119    // Handle both #[flow] (no args) and #[flow(ignore)] (with args)
120    match &attr.meta {
121        Meta::Path(_) => {
122            // #[flow] with no arguments - use defaults
123            Ok(constraints)
124        }
125        Meta::List(_) => {
126            // #[flow(...)] with arguments
127            attr.parse_nested_meta(|meta| {
128                if let Some(ident) = meta.path.get_ident() {
129                    match ident.to_string().as_str() {
130                        "ignore" => {
131                            constraints.ignore = true;
132                            Ok(())
133                        }
134                        "weight" => {
135                            if meta.input.peek(syn::Token![=]) {
136                                meta.input.parse::<syn::Token![=]>()?;
137                                let weight_lit: syn::LitInt = meta.input.parse()?;
138                                let weight_val = weight_lit.base10_parse::<u32>()?;
139
140                                if weight_val > 100 {
141                                    return Err(meta.error("Weight must be between 0 and 100"));
142                                }
143
144                                constraints.weight = Some(weight_val);
145                            }
146                            Ok(())
147                        }
148                        _ => Err(meta.error("unsupported flow constraint")),
149                    }
150                } else {
151                    Err(meta.error("unsupported flow constraint"))
152                }
153            })?;
154            Ok(constraints)
155        }
156        _ => Err(ParseError::new_spanned(attr, "Invalid flow attribute")),
157    }
158}