pipeit_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::{quote, format_ident};
3use syn::{parse_macro_input, ItemFn, LitStr};
4
5// FNV-1a 64-bit hash
6const fn hash_str(s: &str) -> u64 {
7    let mut hash = 0xcbf29ce484222325;
8    let bytes = s.as_bytes();
9    let mut i = 0;
10    while i < bytes.len() {
11        hash ^= bytes[i] as u64;
12        hash = hash.wrapping_mul(0x100000001b3);
13        i += 1;
14    }
15    hash
16}
17
18/// Attribute macro to tag a function as a pipeline node.
19/// Usage: #[node("name")]
20#[proc_macro_attribute]
21pub fn node(attr: TokenStream, item: TokenStream) -> TokenStream {
22    let name_lit = parse_macro_input!(attr as LitStr);
23    let name_str = name_lit.value();
24    let hash = hash_str(&name_str);
25    let tag_ident = format_ident!("__pipeline_tag_{}", hash);
26    
27    let input_fn = parse_macro_input!(item as ItemFn);
28    let fn_name = &input_fn.sig.ident;
29    let fn_vis = &input_fn.vis;
30
31    // Analyze inputs to deduce types.
32    let args = &input_fn.sig.inputs;
33    let mut arg_types = Vec::new();
34    for arg in args {
35        if let syn::FnArg::Typed(pat_type) = arg {
36            arg_types.push(pat_type.ty.clone());
37        }
38    }
39    
40    let ret_type = match &input_fn.sig.output {
41        syn::ReturnType::Default => quote! { () },
42        syn::ReturnType::Type(_, ty) => quote! { #ty },
43    };
44
45    let expanded = quote! {
46        #input_fn
47
48        #[allow(non_camel_case_types)]
49        #[derive(Clone, Copy)]
50        #fn_vis struct #tag_ident;
51
52        impl<I> pipe_it::handler::Handler<I, #ret_type, (#(#arg_types),*)> 
53        for #tag_ident
54        where
55             I: Clone + Send + Sync + 'static,
56             #ret_type: Send + 'static,
57             #(
58                #arg_types: pipe_it::FromContext<I>,
59             )*
60        {
61            fn call(&self, ctx: pipe_it::Context<I>) -> impl std::future::Future<Output = #ret_type> + Send {
62                async move {
63                    #fn_name(
64                        #(
65                            <#arg_types as pipe_it::FromContext<I>>::from(ctx.clone()).await
66                        ),*
67                    ).await
68                }
69            }
70        }
71    };
72
73    TokenStream::from(expanded)
74}
75
76/// Function-like macro to generate the tag struct name.
77/// Usage: tag_struct!("name") -> __pipeline_tag_HASH
78#[proc_macro]
79pub fn tag_struct(input: TokenStream) -> TokenStream {
80    let lit = parse_macro_input!(input as LitStr);
81    let hash = hash_str(&lit.value());
82    let ident = format_ident!("__pipeline_tag_{}", hash);
83    quote! { #ident }.into()
84}
85
86/// Function-like macro to get the hash ID of a tag name.
87#[proc_macro]
88pub fn tag_id(input: TokenStream) -> TokenStream {
89    let lit = parse_macro_input!(input as LitStr);
90    let hash = hash_str(&lit.value());
91    quote! { #hash }.into()
92}