1use proc_macro::TokenStream;
2use proc_macro2::TokenStream as TokenStream2;
3use quote::quote;
4use syn::{
5 parse::{Parse, ParseStream},
6 parse_macro_input, Ident, ItemFn, LitInt, LitStr, ReturnType, Token, Type,
7};
8
9struct MacroArgs {
12 client: Ident,
13 ttl: u64,
14 namespace: Option<String>,
15 secure: bool,
16}
17
18impl Parse for MacroArgs {
19 fn parse(input: ParseStream) -> syn::Result<Self> {
20 let mut client: Option<Ident> = None;
21 let mut ttl: Option<u64> = None;
22 let mut namespace: Option<String> = None;
23 let mut secure = false;
24
25 while !input.is_empty() {
26 let key: Ident = input.parse()?;
27 match key.to_string().as_str() {
28 "client" => {
29 input.parse::<Token![=]>()?;
30 client = Some(input.parse()?);
31 }
32 "ttl" => {
33 input.parse::<Token![=]>()?;
34 let lit: LitInt = input.parse()?;
35 ttl = Some(lit.base10_parse()?);
36 }
37 "namespace" => {
38 input.parse::<Token![=]>()?;
39 let lit: LitStr = input.parse()?;
40 namespace = Some(lit.value());
41 }
42 "secure" => {
43 secure = true;
44 }
45 other => {
46 return Err(syn::Error::new(
47 key.span(),
48 format!("unknown attribute `{other}`"),
49 ));
50 }
51 }
52
53 if !input.is_empty() {
55 input.parse::<Token![,]>()?;
56 }
57 }
58
59 let client = client
60 .ok_or_else(|| input.error("`client` is required: #[cachekit(client = name, ...)]"))?;
61 let ttl = ttl.ok_or_else(|| {
62 input.error("`ttl` is required: #[cachekit(client = name, ttl = 60)]")
63 })?;
64
65 Ok(MacroArgs {
66 client,
67 ttl,
68 namespace,
69 secure,
70 })
71 }
72}
73
74fn extract_ok_type(ret: &ReturnType) -> syn::Result<Type> {
78 let ty = match ret {
79 ReturnType::Type(_, ty) => ty.as_ref(),
80 ReturnType::Default => {
81 return Err(syn::Error::new_spanned(
82 ret,
83 "#[cachekit] function must return Result<T, CachekitError>",
84 ));
85 }
86 };
87
88 if let Type::Path(type_path) = ty {
90 if let Some(seg) = type_path.path.segments.last() {
91 if seg.ident != "Result" {
92 return Err(syn::Error::new_spanned(
93 ty,
94 "#[cachekit] function must return Result<T, CachekitError>",
95 ));
96 }
97 if let syn::PathArguments::AngleBracketed(args) = &seg.arguments {
98 if let Some(syn::GenericArgument::Type(first)) = args.args.first() {
99 return Ok(first.clone());
100 }
101 }
102 }
103 }
104
105 Err(syn::Error::new_spanned(
106 ty,
107 "#[cachekit] function must return Result<T, CachekitError>",
108 ))
109}
110
111#[proc_macro_attribute]
131pub fn cachekit(attr: TokenStream, item: TokenStream) -> TokenStream {
132 let args = parse_macro_input!(attr as MacroArgs);
133 let input_fn = parse_macro_input!(item as ItemFn);
134
135 match expand(&args, input_fn) {
136 Ok(tokens) => tokens.into(),
137 Err(e) => e.to_compile_error().into(),
138 }
139}
140
141fn expand(args: &MacroArgs, mut func: ItemFn) -> syn::Result<TokenStream2> {
142 let client_ident = &args.client;
143 let ttl_secs = args.ttl;
144 let namespace = args.namespace.as_deref().unwrap_or("");
145 let fn_name = func.sig.ident.to_string();
146
147 let ok_type = extract_ok_type(&func.sig.output)?;
149
150 let non_client_idents: Vec<&Ident> = func
152 .sig
153 .inputs
154 .iter()
155 .filter_map(|arg| {
156 if let syn::FnArg::Typed(pat) = arg {
157 if let syn::Pat::Ident(pi) = pat.pat.as_ref() {
158 if pi.ident != *client_ident {
159 return Some(&pi.ident);
160 }
161 }
162 }
163 None
164 })
165 .collect();
166
167 let args_tuple = if non_client_idents.is_empty() {
169 quote! { () }
170 } else {
171 let refs = non_client_idents.iter().map(|id| quote! { &#id });
172 quote! { (#(#refs,)*) }
173 };
174
175 let (get_expr, set_expr) = if args.secure {
177 (
178 quote! {
179 {
180 let __ck_sec = #client_ident.secure()?;
181 __ck_sec.get::<#ok_type>(&__ck_key).await?
182 }
183 },
184 quote! {
185 let __ck_sec = #client_ident.secure()?;
186 let _ = __ck_sec.set_with_ttl(&__ck_key, __ck_val, std::time::Duration::from_secs(#ttl_secs)).await;
187 },
188 )
189 } else {
190 (
191 quote! { #client_ident.get::<#ok_type>(&__ck_key).await? },
192 quote! {
193 let _ = #client_ident.set_with_ttl(&__ck_key, __ck_val, std::time::Duration::from_secs(#ttl_secs)).await;
194 },
195 )
196 };
197
198 let original_body = &func.block;
200
201 let ret_ty = match &func.sig.output {
203 ReturnType::Type(_, ty) => ty.as_ref(),
204 ReturnType::Default => {
205 return Err(syn::Error::new_spanned(
206 &func.sig,
207 "#[cachekit] function must return Result<T, CachekitError>",
208 ));
209 }
210 };
211
212 let new_body: syn::Block = syn::parse_quote! {
214 {
215 let __ck_args = cachekit::__private::rmp_serde::to_vec(&#args_tuple)
217 .map_err(|e| cachekit::error::CachekitError::Serialization(e.to_string()))?;
218 let __ck_key = cachekit::key::generate_cache_key(#namespace, #fn_name, &__ck_args)?;
219
220 if let Some(__ck_cached) = #get_expr {
222 return Ok(__ck_cached);
223 }
224
225 let __ck_result: #ret_ty = (async #original_body).await;
227
228 if let Ok(ref __ck_val) = __ck_result {
230 #set_expr
231 }
232
233 __ck_result
234 }
235 };
236
237 *func.block = new_body;
238 Ok(quote! { #func })
239}