instruct_macros/
lib.rs

1extern crate proc_macro;
2
3mod helpers;
4
5use proc_macro::TokenStream;
6use quote::quote;
7use syn::{parse_macro_input, Attribute, Data, DeriveInput, Expr, ExprLit, Fields, Lit, Meta};
8
9#[proc_macro_derive(InstructMacro, attributes(validate, description))]
10pub fn instruct_validate_derive(input: TokenStream) -> TokenStream {
11    // Parse the input tokens into a syntax tree
12    let input = parse_macro_input!(input as DeriveInput);
13
14    let expanded = match &input.data {
15        Data::Struct(_) => generate_instruct_macro_struct(&input),
16        Data::Enum(_) => generate_instruct_macro_enum(&input),
17        _ => panic!("InstructMacro can only be derived for structs and enums"),
18    };
19
20    // Hand the output tokens back to the compiler
21    TokenStream::from(expanded)
22}
23
24fn generate_instruct_macro_enum(input: &DeriveInput) -> proc_macro2::TokenStream {
25    let name = &input.ident;
26
27    let variants = match &input.data {
28        Data::Enum(data) => &data.variants,
29        _ => panic!("Only enums are supported"),
30    };
31
32    let enum_variants: Vec<String> = variants.iter().map(|v| v.ident.to_string()).collect();
33
34    // Extract struct-level comment
35    let description = extract_attribute_value(&input.attrs, "description");
36
37    let enum_info = quote! {
38        instruct_macros_types::InstructMacroResult::Enum(instruct_macros_types::EnumInfo {
39            title: stringify!(#name).to_string(),
40            r#enum: vec![#(#enum_variants.to_string()),*],
41            r#type: stringify!(#name).to_string(),
42            description: #description.to_string(),
43            is_optional:false,
44            is_list: false
45        })
46    };
47
48    quote! {
49        impl instruct_macros_types::InstructMacro for #name {
50            fn get_info() -> instruct_macros_types::InstructMacroResult {
51                #enum_info
52            }
53
54
55            fn validate(&self) -> Result<(), String> {
56                Ok(())
57            }
58        }
59    }
60}
61
62fn extract_attribute_value(attrs: &[Attribute], attr_name: &str) -> String {
63    attrs
64        .iter()
65        .filter_map(|attr| {
66            if attr.path().is_ident(attr_name) {
67                attr.parse_args::<syn::LitStr>().ok().map(|lit| lit.value())
68            } else {
69                None
70            }
71        })
72        .collect::<Vec<String>>()
73        .join(" ")
74}
75
76fn generate_instruct_macro_struct(input: &DeriveInput) -> proc_macro2::TokenStream {
77    let name = &input.ident;
78
79    let description = extract_attribute_value(&input.attrs, "description");
80
81    let fields = match &input.data {
82        Data::Struct(data) => match &data.fields {
83            Fields::Named(fields) => &fields.named,
84            _ => panic!("Only named fields are supported"),
85        },
86        _ => panic!("Only structs are supported"),
87    };
88
89    let validation_fields: Vec<_> = fields
90        .iter()
91        .filter_map(|f| {
92            let field_name = &f.ident;
93            f.attrs
94                .iter()
95                .find(|attr| attr.path().is_ident("validate"))
96                .map(|attr| {
97                    let meta = attr.parse_args().expect("Unable to parse attribute");
98                    parse_validation_attribute(field_name, &meta)
99                })
100        })
101        .collect();
102
103    // Process each field in the struct
104    let fields = if let Data::Struct(data) = &input.data {
105        if let Fields::Named(fields) = &data.fields {
106            fields
107        } else {
108            panic!("Unnamed fields are not supported");
109        }
110    } else {
111        panic!("Only structs are supported");
112    };
113
114    let parameters = helpers::extract_parameters(fields);
115
116    let expanded = quote! {
117        impl instruct_macros_types::InstructMacro for #name {
118            fn get_info() -> instruct_macros_types::InstructMacroResult {
119                let mut parameters = Vec::new();
120                #(#parameters)*
121
122                instruct_macros_types::InstructMacroResult::Struct(StructInfo {
123                    name: stringify!(#name).to_string(),
124                    description: #description.to_string(),
125                    parameters,
126                    is_optional:false,
127                    is_list: false,
128                })
129            }
130
131            fn validate(&self) -> Result<(), String> {
132                #(#validation_fields)*
133                Ok(())
134            }
135        }
136    };
137
138    expanded
139}
140
141/// Parses the validation attribute and generates corresponding validation code.
142///
143/// This function processes custom validation attributes, expanding them into function calls
144/// that perform the specified validation. It supports custom validators that take a reference
145/// to the field type and return a Result with a string error type.
146fn parse_validation_attribute(
147    field_name: &Option<syn::Ident>,
148    meta: &Meta,
149) -> proc_macro2::TokenStream {
150    let mut output = proc_macro2::TokenStream::new();
151
152    match meta {
153        Meta::NameValue(name_value) if name_value.path.is_ident("custom") => {
154            if let Expr::Lit(ExprLit {
155                lit: Lit::Str(lit_str),
156                ..
157            }) = &name_value.value
158            {
159                let func = syn::Ident::new(&lit_str.value(), proc_macro2::Span::call_site());
160                let tokens = quote! {
161                    if let Err(e) = #func(&self.#field_name) {
162                        return Err(format!("Validation failed for field '{}': {}", stringify!(#field_name), e));
163                    }
164                };
165                output.extend(tokens);
166            } else {
167                panic!("Custom validator must be a string literal");
168            }
169        }
170        _ => panic!("Unsupported validation attribute"),
171    }
172
173    output
174}
175
176/// Custom attribute macro for field validation in structs.
177///
178/// This procedural macro attribute is designed to be applied to structs,
179/// enabling custom validation for their fields. When the `validate` method
180/// is called on an instance of the decorated struct, it triggers the specified
181/// custom validation functions for each annotated field.
182#[proc_macro_attribute]
183pub fn validate(_attr: TokenStream, item: TokenStream) -> TokenStream {
184    let input = parse_macro_input!(item as syn::ItemFn);
185    let syn::ItemFn { sig, block, .. } = input;
186
187    let expanded = quote! {
188        #sig {
189            #block
190        }
191    };
192
193    TokenStream::from(expanded)
194}