1use proc_macro::TokenStream;
2
3use proc_macro_crate::{FoundCrate, crate_name};
4use quote::{format_ident, quote};
5use syn::parse::Parser;
6use syn::punctuated::Punctuated;
7use syn::{
8 Error, Expr, ExprLit, FnArg, GenericArgument, ItemFn, Lit, Meta, MetaNameValue, PathArguments,
9 ReturnType, Signature, Token, Type, TypePath, parse_macro_input, spanned::Spanned,
10};
11
12#[derive(Clone, Copy, PartialEq, Eq)]
14enum OnCacheError {
15 Ignore,
17 Propagate,
19}
20
21struct CacheableArgs {
23 cache: Expr,
24 key: Expr,
25 allow_stale: bool,
26 cache_none: bool,
27 on_cache_error: OnCacheError,
28}
29
30struct CachePutArgs {
32 cache: Expr,
33 key: Expr,
34 value: Expr,
35 on_cache_error: OnCacheError,
36}
37
38struct CacheEvictArgs {
40 cache: Expr,
41 key: Expr,
42 before: bool,
43 on_cache_error: OnCacheError,
44}
45
46struct CacheableBatchArgs {
48 cache: Expr,
49 keys: Expr,
50 allow_stale: bool,
51 on_cache_error: OnCacheError,
52}
53
54struct CacheEvictBatchArgs {
56 cache: Expr,
57 keys: Expr,
58 before: bool,
59 on_cache_error: OnCacheError,
60}
61
62#[proc_macro_attribute]
64pub fn cacheable(attr: TokenStream, item: TokenStream) -> TokenStream {
65 let args = match parse_cacheable_args(attr) {
66 Ok(args) => args,
67 Err(err) => return err.to_compile_error().into(),
68 };
69 let input = parse_macro_input!(item as ItemFn);
70 match expand_cacheable(args, input) {
71 Ok(expanded) => expanded.into(),
72 Err(err) => err.to_compile_error().into(),
73 }
74}
75
76#[proc_macro_attribute]
78pub fn cache_put(attr: TokenStream, item: TokenStream) -> TokenStream {
79 let args = match parse_cache_put_args(attr) {
80 Ok(args) => args,
81 Err(err) => return err.to_compile_error().into(),
82 };
83 let input = parse_macro_input!(item as ItemFn);
84 match expand_cache_put(args, input) {
85 Ok(expanded) => expanded.into(),
86 Err(err) => err.to_compile_error().into(),
87 }
88}
89
90#[proc_macro_attribute]
92pub fn cache_evict(attr: TokenStream, item: TokenStream) -> TokenStream {
93 let args = match parse_cache_evict_args(attr) {
94 Ok(args) => args,
95 Err(err) => return err.to_compile_error().into(),
96 };
97 let input = parse_macro_input!(item as ItemFn);
98 match expand_cache_evict(args, input) {
99 Ok(expanded) => expanded.into(),
100 Err(err) => err.to_compile_error().into(),
101 }
102}
103
104#[proc_macro_attribute]
106pub fn cacheable_batch(attr: TokenStream, item: TokenStream) -> TokenStream {
107 let args = match parse_cacheable_batch_args(attr) {
108 Ok(args) => args,
109 Err(err) => return err.to_compile_error().into(),
110 };
111 let input = parse_macro_input!(item as ItemFn);
112 match expand_cacheable_batch(args, input) {
113 Ok(expanded) => expanded.into(),
114 Err(err) => err.to_compile_error().into(),
115 }
116}
117
118#[proc_macro_attribute]
120pub fn cache_evict_batch(attr: TokenStream, item: TokenStream) -> TokenStream {
121 let args = match parse_cache_evict_batch_args(attr) {
122 Ok(args) => args,
123 Err(err) => return err.to_compile_error().into(),
124 };
125 let input = parse_macro_input!(item as ItemFn);
126 match expand_cache_evict_batch(args, input) {
127 Ok(expanded) => expanded.into(),
128 Err(err) => err.to_compile_error().into(),
129 }
130}
131
132fn expand_cacheable(
133 args: CacheableArgs,
134 mut item: ItemFn,
135) -> syn::Result<proc_macro2::TokenStream> {
136 ensure_async_method(&item.sig, "cacheable")?;
137 let ok_ty = parse_ok_type(&item.sig, "cacheable")?;
138 ensure_option_type(
139 &ok_ty,
140 "cacheable requires a result-like return type where Ok is `Option<T>`",
141 )?;
142
143 let runtime = runtime_crate_path();
144 let on_get_error = on_cache_error_tokens(args.on_cache_error, &runtime, "cacheable", "get");
145 let on_set_error = on_cache_error_tokens(args.on_cache_error, &runtime, "cacheable", "set");
146 let cache_expr = args.cache;
147 let key_expr = args.key;
148 let allow_stale = args.allow_stale;
149 let cache_none = args.cache_none;
150 let original_block = item.block;
151
152 item.block = Box::new(syn::parse_quote!({
153 let __key = #key_expr;
154 let __opts = #runtime::ReadOptions {
155 allow_stale: #allow_stale,
156 disable_load: true,
157 };
158
159 match (#cache_expr).get(&__key, &__opts).await {
160 Ok(Some(__hit)) => return Ok(Some(__hit)),
161 Ok(None) => {}
162 Err(__cache_err) => {
163 #on_get_error
164 }
165 }
166
167 let __result = (async #original_block).await;
168 match __result {
169 Ok(__value) => {
170 if let Some(__present) = __value.as_ref() {
171 if let Err(__cache_err) = (#cache_expr).set(&__key, Some(__present.clone())).await {
172 #on_set_error
173 }
174 } else if #cache_none {
175 if let Err(__cache_err) = (#cache_expr).set(&__key, None).await {
176 #on_set_error
177 }
178 }
179 Ok(__value)
180 }
181 Err(__err) => Err(__err),
182 }
183 }));
184
185 Ok(quote!(#item))
186}
187
188fn expand_cache_put(args: CachePutArgs, mut item: ItemFn) -> syn::Result<proc_macro2::TokenStream> {
189 ensure_async_method(&item.sig, "cache_put")?;
190 parse_ok_type(&item.sig, "cache_put")?;
191
192 let runtime = runtime_crate_path();
193 let on_set_error = on_cache_error_tokens(args.on_cache_error, &runtime, "cache_put", "set");
194 let cache_expr = args.cache;
195 let key_expr = args.key;
196 let value_expr = args.value;
197 let original_block = item.block;
198
199 item.block = Box::new(syn::parse_quote!({
200 let __key = #key_expr;
201
202 let __result = (async #original_block).await;
203 match __result {
204 Ok(__ok) => {
205 let __value = #value_expr;
206 if let Err(__cache_err) = (#cache_expr).set(&__key, __value).await {
207 #on_set_error
208 }
209 Ok(__ok)
210 }
211 Err(__err) => Err(__err),
212 }
213 }));
214
215 Ok(quote!(#item))
216}
217
218fn expand_cache_evict(
219 args: CacheEvictArgs,
220 mut item: ItemFn,
221) -> syn::Result<proc_macro2::TokenStream> {
222 ensure_async_method(&item.sig, "cache_evict")?;
223 parse_ok_type(&item.sig, "cache_evict")?;
224
225 let runtime = runtime_crate_path();
226 let before = args.before;
227 let on_before_error =
228 on_cache_error_tokens(args.on_cache_error, &runtime, "cache_evict", "del_before");
229 let on_after_error =
230 on_cache_error_tokens(args.on_cache_error, &runtime, "cache_evict", "del_after");
231 let cache_expr = args.cache;
232 let key_expr = args.key;
233 let original_block = item.block;
234
235 item.block = Box::new(syn::parse_quote!({
236 let __key = #key_expr;
237
238 if #before {
239 if let Err(__cache_err) = (#cache_expr).del(&__key).await {
240 #on_before_error
241 }
242 }
243
244 let __result = (async #original_block).await;
245 match __result {
246 Ok(__ok) => {
247 if !#before {
248 if let Err(__cache_err) = (#cache_expr).del(&__key).await {
249 #on_after_error
250 }
251 }
252 Ok(__ok)
253 }
254 Err(__err) => Err(__err),
255 }
256 }));
257
258 Ok(quote!(#item))
259}
260
261fn expand_cacheable_batch(
262 args: CacheableBatchArgs,
263 mut item: ItemFn,
264) -> syn::Result<proc_macro2::TokenStream> {
265 ensure_async_method(&item.sig, "cacheable_batch")?;
266 let ok_ty = parse_ok_type(&item.sig, "cacheable_batch")?;
267 ensure_hashmap_option_type(
268 &ok_ty,
269 "cacheable_batch requires a result-like return type where Ok is `HashMap<K, Option<V>>`",
270 )?;
271
272 let runtime = runtime_crate_path();
273 let on_mget_error = on_cache_error_with_fallback_tokens(
274 args.on_cache_error,
275 &runtime,
276 "cacheable_batch",
277 "mget",
278 quote!(::std::collections::HashMap::new()),
279 );
280 let on_mset_error =
281 on_cache_error_tokens(args.on_cache_error, &runtime, "cacheable_batch", "mset");
282 let cache_expr = args.cache;
283 let keys_expr = args.keys;
284 let allow_stale = args.allow_stale;
285 let miss_keys_rebind = build_miss_keys_rebind(&item.sig, &keys_expr, "cacheable_batch")?;
286 let original_block = item.block;
287
288 item.block = Box::new(syn::parse_quote!({
289 let __requested = {
290 let mut keys = ::std::vec::Vec::new();
291 let mut seen = ::std::collections::HashSet::new();
292 for key in (#keys_expr).iter().cloned() {
293 if seen.insert(key.clone()) {
294 keys.push(key);
295 }
296 }
297 keys
298 };
299
300 if __requested.is_empty() {
301 return Ok(::std::collections::HashMap::new());
302 }
303
304 let __opts = #runtime::ReadOptions {
305 allow_stale: #allow_stale,
306 disable_load: true,
307 };
308
309 let mut __values = match (#cache_expr).mget(&__requested, &__opts).await {
310 Ok(cached) => cached,
311 Err(__cache_err) => {
312 #on_mget_error
313 }
314 };
315
316 let __misses = __requested
317 .iter()
318 .filter(|key| match __values.get(*key) {
319 Some(value) => value.is_none(),
320 None => true,
321 })
322 .cloned()
323 .collect::<::std::vec::Vec<_>>();
324
325 if __misses.is_empty() {
326 return Ok(__values);
327 }
328
329 #miss_keys_rebind
330
331 let __result = (async #original_block).await;
332 match __result {
333 Ok(__loaded_map) => {
334 let mut __writes = ::std::collections::HashMap::with_capacity(__misses.len());
335 for key in __misses {
336 let loaded = __loaded_map.get(&key).cloned().unwrap_or(None);
337 __values.insert(key.clone(), loaded.clone());
338 __writes.insert(key, loaded);
339 }
340
341 if !__writes.is_empty() {
342 if let Err(__cache_err) = (#cache_expr).mset(__writes).await {
343 #on_mset_error
344 }
345 }
346
347 Ok(__values)
348 }
349 Err(__err) => Err(__err),
350 }
351 }));
352
353 Ok(quote!(#item))
354}
355
356fn expand_cache_evict_batch(
357 args: CacheEvictBatchArgs,
358 mut item: ItemFn,
359) -> syn::Result<proc_macro2::TokenStream> {
360 ensure_async_method(&item.sig, "cache_evict_batch")?;
361 parse_ok_type(&item.sig, "cache_evict_batch")?;
362
363 let runtime = runtime_crate_path();
364 let before = args.before;
365 let on_before_error = on_cache_error_tokens(
366 args.on_cache_error,
367 &runtime,
368 "cache_evict_batch",
369 "mdel_before",
370 );
371 let on_after_error = on_cache_error_tokens(
372 args.on_cache_error,
373 &runtime,
374 "cache_evict_batch",
375 "mdel_after",
376 );
377 let cache_expr = args.cache;
378 let keys_expr = args.keys;
379 let original_block = item.block;
380
381 item.block = Box::new(syn::parse_quote!({
382 let __keys = {
383 let mut keys = ::std::vec::Vec::new();
384 let mut seen = ::std::collections::HashSet::new();
385 for key in (#keys_expr).iter().cloned() {
386 if seen.insert(key.clone()) {
387 keys.push(key);
388 }
389 }
390 keys
391 };
392
393 if #before && !__keys.is_empty() {
394 if let Err(__cache_err) = (#cache_expr).mdel(&__keys).await {
395 #on_before_error
396 }
397 }
398
399 let __result = (async #original_block).await;
400 match __result {
401 Ok(__ok) => {
402 if !#before && !__keys.is_empty() {
403 if let Err(__cache_err) = (#cache_expr).mdel(&__keys).await {
404 #on_after_error
405 }
406 }
407 Ok(__ok)
408 }
409 Err(__err) => Err(__err),
410 }
411 }));
412
413 Ok(quote!(#item))
414}
415
416fn ensure_async_method(sig: &Signature, macro_name: &str) -> syn::Result<()> {
418 if sig.asyncness.is_none() {
419 return Err(Error::new(
420 sig.span(),
421 format!(
422 "{macro_name} only supports async methods, expected `async fn ...(&self, ...)`"
423 ),
424 ));
425 }
426
427 let Some(first) = sig.inputs.first() else {
428 return Err(Error::new(
429 sig.span(),
430 format!(
431 "{macro_name} requires a method receiver, expected first argument `&self` or `&mut self`"
432 ),
433 ));
434 };
435
436 if !matches!(first, FnArg::Receiver(_)) {
437 return Err(Error::new(
438 first.span(),
439 format!(
440 "{macro_name} only supports methods on impl blocks, expected first argument `&self` or `&mut self`"
441 ),
442 ));
443 }
444
445 Ok(())
446}
447
448fn parse_ok_type(sig: &Signature, macro_name: &str) -> syn::Result<Type> {
450 let ReturnType::Type(_, ty) = &sig.output else {
451 return Err(Error::new(
452 sig.span(),
453 format!("{macro_name} requires a result-like return type"),
454 ));
455 };
456
457 let Type::Path(TypePath { path, .. }) = ty.as_ref() else {
458 return Err(Error::new(
459 ty.span(),
460 format!("{macro_name} requires a result-like return type"),
461 ));
462 };
463
464 let Some(last) = path.segments.last() else {
465 return Err(Error::new(
466 path.span(),
467 format!("{macro_name} requires a result-like return type"),
468 ));
469 };
470
471 let PathArguments::AngleBracketed(args) = &last.arguments else {
472 return Err(Error::new(
473 last.arguments.span(),
474 format!("{macro_name} requires a result-like return type"),
475 ));
476 };
477
478 let mut type_args = args.args.iter().filter_map(|arg| {
479 if let GenericArgument::Type(ty) = arg {
480 Some(ty.clone())
481 } else {
482 None
483 }
484 });
485
486 let Some(ok) = type_args.next() else {
487 return Err(Error::new(
488 args.span(),
489 format!("{macro_name} requires a result-like return type"),
490 ));
491 };
492
493 Ok(ok)
494}
495
496fn ensure_option_type(ty: &Type, message: &str) -> syn::Result<()> {
498 let Type::Path(TypePath { path, .. }) = ty else {
499 return Err(Error::new(ty.span(), message));
500 };
501
502 let Some(last) = path.segments.last() else {
503 return Err(Error::new(path.span(), message));
504 };
505
506 if last.ident != "Option" {
507 return Err(Error::new(last.ident.span(), message));
508 }
509
510 Ok(())
511}
512
513fn ensure_hashmap_option_type(ty: &Type, message: &str) -> syn::Result<()> {
515 let Type::Path(TypePath { path, .. }) = ty else {
516 return Err(Error::new(ty.span(), message));
517 };
518
519 let Some(last) = path.segments.last() else {
520 return Err(Error::new(path.span(), message));
521 };
522
523 if last.ident != "HashMap" {
524 return Err(Error::new(last.ident.span(), message));
525 }
526
527 let PathArguments::AngleBracketed(args) = &last.arguments else {
528 return Err(Error::new(last.arguments.span(), message));
529 };
530
531 let mut type_args = args.args.iter().filter_map(|arg| {
532 if let GenericArgument::Type(ty) = arg {
533 Some(ty)
534 } else {
535 None
536 }
537 });
538
539 let _key_ty = type_args.next();
540 let Some(value_ty) = type_args.next() else {
541 return Err(Error::new(args.span(), message));
542 };
543
544 ensure_option_type(value_ty, message)
545}
546
547fn build_miss_keys_rebind(
549 sig: &Signature,
550 keys_expr: &Expr,
551 macro_name: &str,
552) -> syn::Result<proc_macro2::TokenStream> {
553 let Expr::Path(path) = keys_expr else {
554 return Ok(quote! {});
555 };
556
557 let Some(keys_ident) = path.path.get_ident() else {
558 return Ok(quote! {});
559 };
560
561 let Some(keys_ty) = method_arg_type(sig, keys_ident) else {
562 return Ok(quote! {});
563 };
564
565 match keys_ty {
566 Type::Path(TypePath { path, .. }) => {
567 let Some(last) = path.segments.last() else {
568 return Ok(quote! {});
569 };
570
571 if last.ident == "Vec" {
572 return Ok(quote! {
573 let #keys_ident = __misses.clone();
574 });
575 }
576 }
577 Type::Reference(reference) => match reference.elem.as_ref() {
578 Type::Slice(_) => {
579 return Ok(quote! {
580 let #keys_ident = __misses.as_slice();
581 });
582 }
583 Type::Path(TypePath { path, .. }) => {
584 let Some(last) = path.segments.last() else {
585 return Ok(quote! {});
586 };
587
588 if last.ident == "Vec" {
589 return Ok(quote! {
590 let #keys_ident = &__misses;
591 });
592 }
593 }
594 _ => {}
595 },
596 _ => {}
597 }
598
599 Err(Error::new(
600 keys_expr.span(),
601 format!(
602 "{macro_name} only supports `keys` parameter types `Vec<K>`, `&[K]` or `&Vec<K>` when reusing misses in function body"
603 ),
604 ))
605}
606
607fn method_arg_type(sig: &Signature, ident: &syn::Ident) -> Option<Type> {
609 for arg in &sig.inputs {
610 let FnArg::Typed(typed) = arg else {
611 continue;
612 };
613
614 let syn::Pat::Ident(pat_ident) = typed.pat.as_ref() else {
615 continue;
616 };
617
618 if pat_ident.ident == *ident {
619 return Some((*typed.ty).clone());
620 }
621 }
622
623 None
624}
625
626fn on_cache_error_tokens(
628 mode: OnCacheError,
629 runtime: &proc_macro2::TokenStream,
630 macro_name: &str,
631 op_name: &str,
632) -> proc_macro2::TokenStream {
633 match mode {
634 OnCacheError::Ignore => quote! {
635 #runtime::tracing::warn!(
636 target: "accelerator::macros",
637 cache_macro = #macro_name,
638 op = #op_name,
639 result = "ignored",
640 error = %__cache_err,
641 "cache operation failed, continue business flow"
642 );
643 },
644 OnCacheError::Propagate => quote! {
645 #runtime::tracing::warn!(
646 target: "accelerator::macros",
647 cache_macro = #macro_name,
648 op = #op_name,
649 result = "propagated",
650 error = %__cache_err,
651 "cache operation failed, return error"
652 );
653 return Err(::core::convert::Into::into(__cache_err));
654 },
655 }
656}
657
658fn on_cache_error_with_fallback_tokens(
660 mode: OnCacheError,
661 runtime: &proc_macro2::TokenStream,
662 macro_name: &str,
663 op_name: &str,
664 fallback: proc_macro2::TokenStream,
665) -> proc_macro2::TokenStream {
666 match mode {
667 OnCacheError::Ignore => quote! {
668 #runtime::tracing::warn!(
669 target: "accelerator::macros",
670 cache_macro = #macro_name,
671 op = #op_name,
672 result = "ignored",
673 error = %__cache_err,
674 "cache operation failed, continue business flow"
675 );
676 #fallback
677 },
678 OnCacheError::Propagate => quote! {
679 #runtime::tracing::warn!(
680 target: "accelerator::macros",
681 cache_macro = #macro_name,
682 op = #op_name,
683 result = "propagated",
684 error = %__cache_err,
685 "cache operation failed, return error"
686 );
687 return Err(::core::convert::Into::into(__cache_err));
688 },
689 }
690}
691
692fn parse_cacheable_args(attr: TokenStream) -> syn::Result<CacheableArgs> {
694 let metas = parse_attr_metas(attr)?;
695
696 let mut cache = None;
697 let mut key = None;
698 let mut allow_stale = None;
699 let mut cache_none = None;
700 let mut on_cache_error = None;
701
702 for meta in metas {
703 let (name, value, span) = parse_name_value(meta)?;
704 match name.as_str() {
705 "cache" => set_once(&mut cache, value, "cache", span)?,
706 "key" => set_once(&mut key, value, "key", span)?,
707 "allow_stale" => set_once(
708 &mut allow_stale,
709 parse_bool_expr(&value, span)?,
710 "allow_stale",
711 span,
712 )?,
713 "cache_none" => set_once(
714 &mut cache_none,
715 parse_bool_expr(&value, span)?,
716 "cache_none",
717 span,
718 )?,
719 "on_cache_error" => set_once(
720 &mut on_cache_error,
721 parse_on_cache_error(&value, span)?,
722 "on_cache_error",
723 span,
724 )?,
725 _ => {
726 return Err(Error::new(
727 span,
728 "unknown cacheable argument, supported: cache, key, allow_stale, cache_none, on_cache_error",
729 ));
730 }
731 }
732 }
733
734 Ok(CacheableArgs {
735 cache: required(cache, "cache")?,
736 key: required(key, "key")?,
737 allow_stale: allow_stale.unwrap_or(false),
738 cache_none: cache_none.unwrap_or(true),
739 on_cache_error: on_cache_error.unwrap_or(OnCacheError::Ignore),
740 })
741}
742
743fn parse_cache_put_args(attr: TokenStream) -> syn::Result<CachePutArgs> {
745 let metas = parse_attr_metas(attr)?;
746
747 let mut cache = None;
748 let mut key = None;
749 let mut value = None;
750 let mut on_cache_error = None;
751
752 for meta in metas {
753 let (name, expr, span) = parse_name_value(meta)?;
754 match name.as_str() {
755 "cache" => set_once(&mut cache, expr, "cache", span)?,
756 "key" => set_once(&mut key, expr, "key", span)?,
757 "value" => set_once(&mut value, expr, "value", span)?,
758 "on_cache_error" => set_once(
759 &mut on_cache_error,
760 parse_on_cache_error(&expr, span)?,
761 "on_cache_error",
762 span,
763 )?,
764 _ => {
765 return Err(Error::new(
766 span,
767 "unknown cache_put argument, supported: cache, key, value, on_cache_error",
768 ));
769 }
770 }
771 }
772
773 Ok(CachePutArgs {
774 cache: required(cache, "cache")?,
775 key: required(key, "key")?,
776 value: required(value, "value")?,
777 on_cache_error: on_cache_error.unwrap_or(OnCacheError::Ignore),
778 })
779}
780
781fn parse_cache_evict_args(attr: TokenStream) -> syn::Result<CacheEvictArgs> {
783 let metas = parse_attr_metas(attr)?;
784
785 let mut cache = None;
786 let mut key = None;
787 let mut before = None;
788 let mut on_cache_error = None;
789
790 for meta in metas {
791 let (name, value, span) = parse_name_value(meta)?;
792 match name.as_str() {
793 "cache" => set_once(&mut cache, value, "cache", span)?,
794 "key" => set_once(&mut key, value, "key", span)?,
795 "before" => set_once(&mut before, parse_bool_expr(&value, span)?, "before", span)?,
796 "on_cache_error" => set_once(
797 &mut on_cache_error,
798 parse_on_cache_error(&value, span)?,
799 "on_cache_error",
800 span,
801 )?,
802 _ => {
803 return Err(Error::new(
804 span,
805 "unknown cache_evict argument, supported: cache, key, before, on_cache_error",
806 ));
807 }
808 }
809 }
810
811 Ok(CacheEvictArgs {
812 cache: required(cache, "cache")?,
813 key: required(key, "key")?,
814 before: before.unwrap_or(false),
815 on_cache_error: on_cache_error.unwrap_or(OnCacheError::Ignore),
816 })
817}
818
819fn parse_cacheable_batch_args(attr: TokenStream) -> syn::Result<CacheableBatchArgs> {
821 let metas = parse_attr_metas(attr)?;
822
823 let mut cache = None;
824 let mut keys = None;
825 let mut allow_stale = None;
826 let mut on_cache_error = None;
827
828 for meta in metas {
829 let (name, value, span) = parse_name_value(meta)?;
830 match name.as_str() {
831 "cache" => set_once(&mut cache, value, "cache", span)?,
832 "keys" => set_once(&mut keys, value, "keys", span)?,
833 "allow_stale" => set_once(
834 &mut allow_stale,
835 parse_bool_expr(&value, span)?,
836 "allow_stale",
837 span,
838 )?,
839 "on_cache_error" => set_once(
840 &mut on_cache_error,
841 parse_on_cache_error(&value, span)?,
842 "on_cache_error",
843 span,
844 )?,
845 _ => {
846 return Err(Error::new(
847 span,
848 "unknown cacheable_batch argument, supported: cache, keys, allow_stale, on_cache_error",
849 ));
850 }
851 }
852 }
853
854 Ok(CacheableBatchArgs {
855 cache: required(cache, "cache")?,
856 keys: required(keys, "keys")?,
857 allow_stale: allow_stale.unwrap_or(false),
858 on_cache_error: on_cache_error.unwrap_or(OnCacheError::Ignore),
859 })
860}
861
862fn parse_cache_evict_batch_args(attr: TokenStream) -> syn::Result<CacheEvictBatchArgs> {
864 let metas = parse_attr_metas(attr)?;
865
866 let mut cache = None;
867 let mut keys = None;
868 let mut before = None;
869 let mut on_cache_error = None;
870
871 for meta in metas {
872 let (name, value, span) = parse_name_value(meta)?;
873 match name.as_str() {
874 "cache" => set_once(&mut cache, value, "cache", span)?,
875 "keys" => set_once(&mut keys, value, "keys", span)?,
876 "before" => set_once(&mut before, parse_bool_expr(&value, span)?, "before", span)?,
877 "on_cache_error" => set_once(
878 &mut on_cache_error,
879 parse_on_cache_error(&value, span)?,
880 "on_cache_error",
881 span,
882 )?,
883 _ => {
884 return Err(Error::new(
885 span,
886 "unknown cache_evict_batch argument, supported: cache, keys, before, on_cache_error",
887 ));
888 }
889 }
890 }
891
892 Ok(CacheEvictBatchArgs {
893 cache: required(cache, "cache")?,
894 keys: required(keys, "keys")?,
895 before: before.unwrap_or(false),
896 on_cache_error: on_cache_error.unwrap_or(OnCacheError::Ignore),
897 })
898}
899
900fn parse_attr_metas(attr: TokenStream) -> syn::Result<Vec<Meta>> {
902 let parser = Punctuated::<Meta, Token![,]>::parse_terminated;
903 let metas = parser.parse(attr)?;
904 Ok(metas.into_iter().collect())
905}
906
907fn parse_name_value(meta: Meta) -> syn::Result<(String, Expr, proc_macro2::Span)> {
909 let span = meta.span();
910 let Meta::NameValue(MetaNameValue { path, value, .. }) = meta else {
911 return Err(Error::new(span, "expected `name = value`"));
912 };
913
914 let Some(ident) = path.get_ident() else {
915 return Err(Error::new(path.span(), "expected simple identifier"));
916 };
917
918 Ok((ident.to_string(), value, ident.span()))
919}
920
921fn parse_bool_expr(expr: &Expr, span: proc_macro2::Span) -> syn::Result<bool> {
923 let Expr::Lit(ExprLit {
924 lit: Lit::Bool(value),
925 ..
926 }) = expr
927 else {
928 return Err(Error::new(span, "expected boolean literal"));
929 };
930 Ok(value.value())
931}
932
933fn parse_on_cache_error(expr: &Expr, span: proc_macro2::Span) -> syn::Result<OnCacheError> {
935 let Expr::Lit(ExprLit {
936 lit: Lit::Str(value),
937 ..
938 }) = expr
939 else {
940 return Err(Error::new(span, "expected string literal"));
941 };
942
943 match value.value().as_str() {
944 "ignore" => Ok(OnCacheError::Ignore),
945 "propagate" => Ok(OnCacheError::Propagate),
946 _ => Err(Error::new(
947 span,
948 "on_cache_error must be \"ignore\" or \"propagate\"",
949 )),
950 }
951}
952
953fn set_once<T>(
955 slot: &mut Option<T>,
956 value: T,
957 name: &str,
958 span: proc_macro2::Span,
959) -> syn::Result<()> {
960 if slot.is_some() {
961 return Err(Error::new(span, format!("duplicate `{name}` argument")));
962 }
963 *slot = Some(value);
964 Ok(())
965}
966
967fn required<T>(slot: Option<T>, name: &str) -> syn::Result<T> {
969 slot.ok_or_else(|| Error::new(proc_macro2::Span::call_site(), format!("missing `{name}`")))
970}
971
972fn runtime_crate_path() -> proc_macro2::TokenStream {
974 match crate_name("accelerator") {
975 Ok(FoundCrate::Itself) => quote!(::accelerator),
976 Ok(FoundCrate::Name(name)) => {
977 let ident = format_ident!("{}", name);
978 quote!(::#ident)
979 }
980 Err(_) => quote!(::accelerator),
981 }
982}