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 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 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 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 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
141fn 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#[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}