use crate::attributes::PipelineAttributes;
use crate::errors::{Error, Result};
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::{
DeriveInput, GenericArgument, Type, TypePath, WherePredicate, parse_quote, spanned::Spanned,
};
pub fn pipeline_derive(input: DeriveInput, attrs: &PipelineAttributes) -> Result<TokenStream> {
let struct_name = &input.ident;
let field = if let syn::Data::Struct(syn::DataStruct {
fields: syn::Fields::Named(fields),
..
}) = &input.data
{
if fields.named.len() == 1 {
fields.named.first().unwrap()
} else {
return Err(Error::spanned(
&input.ident,
"Expected a struct with exactly one named field",
));
}
} else {
return Err(Error::spanned(
&input.ident,
"Expected a struct with named fields",
));
};
let field_ident = field
.ident
.as_ref()
.ok_or_else(|| Error::spanned(field, "Expected named field with identifier"))?;
let inner_type = if let Type::Path(TypePath { path, .. }) = &field.ty {
let last_segment = path
.segments
.last()
.ok_or_else(|| Error::spanned(&field.ty, "Malformed type path in field type"))?;
if last_segment.ident != "Option" {
return Err(Error::spanned(
last_segment,
"Expected field of type Option<T>",
));
}
if let syn::PathArguments::AngleBracketed(angle_bracketed) = &last_segment.arguments {
let Some(GenericArgument::Type(ty)) = angle_bracketed.args.first() else {
return Err(Error::spanned(
angle_bracketed,
"Expected Option<T> with concrete type",
));
};
ty
} else {
return Err(Error::spanned(
last_segment,
"Expected angle-bracketed generic arguments",
));
}
} else {
return Err(Error::spanned(
&field.ty,
"Expected field of type Option<T>",
));
};
let mut generics = input.generics.clone();
let clone_bound: WherePredicate = parse_quote! {
#inner_type: Clone
};
if let Some(ref mut wc) = generics.where_clause {
wc.predicates.push(clone_bound);
} else {
generics.where_clause = Some(syn::WhereClause {
where_token: Default::default(),
predicates: vec![clone_bound].into_iter().collect(),
});
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
if attrs.skip {
return Ok(quote_spanned! { struct_name.span()=>
impl #impl_generics #struct_name #ty_generics #where_clause {
pub fn process3<F, G>(&self, _f1: F, _f2: G) -> Option<#inner_type>
where
F: FnOnce(#inner_type) -> Option<#inner_type>,
G: FnOnce(#inner_type) -> Option<#inner_type>,
{
None
}
pub fn process4<F, G, H>(&self, _f1: F, _f2: G, _f3: H) -> Option<#inner_type>
where
F: FnOnce(#inner_type) -> Option<#inner_type>,
G: FnOnce(#inner_type) -> Option<#inner_type>,
H: FnOnce(#inner_type) -> Option<#inner_type>,
{
None
}
}
});
}
let timeout_code = if let Some(timeout) = attrs.timeout {
quote! {
println!("Pipeline timeout set to {} ms", #timeout);
}
} else {
quote! {}
};
Ok(quote_spanned! { struct_name.span()=>
impl #impl_generics #struct_name #ty_generics #where_clause {
pub fn process3<F, G>(&self, f1: F, f2: G) -> Option<#inner_type>
where
F: FnOnce(#inner_type) -> Option<#inner_type>,
G: FnOnce(#inner_type) -> Option<#inner_type>,
{
#timeout_code
self.#field_ident.as_ref().cloned().and_then(f1).and_then(f2)
}
pub fn process4<F, G, H>(&self, f1: F, f2: G, f3: H) -> Option<#inner_type>
where
F: FnOnce(#inner_type) -> Option<#inner_type>,
G: FnOnce(#inner_type) -> Option<#inner_type>,
H: FnOnce(#inner_type) -> Option<#inner_type>,
{
#timeout_code
self.#field_ident.as_ref().cloned().and_then(f1).and_then(f2).and_then(f3)
}
}
})
}