1use proc_macro2::TokenStream as TokenStream2;
7use quote::quote;
8use syn::{punctuated::Punctuated, Expr, MetaNameValue, Token};
9
10static POLICIES: &[&str] = &["fifo", "lru", "lfu", "arc", "random", "tlru", "w_tinylfu"];
12
13#[derive(Default)]
15pub struct CommonAttributes {
16 pub custom_name: Option<String>,
17 pub max_memory: TokenStream2,
18 pub tags: Vec<String>,
19 pub events: Vec<String>,
20 pub dependencies: Vec<String>,
21 pub invalidate_on: Option<syn::Path>,
22 pub cache_if: Option<syn::Path>,
23 pub frequency_weight: TokenStream2,
24 pub window_ratio: TokenStream2,
25 pub sketch_width: TokenStream2,
26 pub sketch_depth: TokenStream2,
27 pub decay_interval: TokenStream2,
28}
29
30impl CommonAttributes {
31 pub fn new() -> Self {
33 Self {
34 custom_name: None,
35 max_memory: quote! { None },
36 tags: Vec::new(),
37 events: Vec::new(),
38 dependencies: Vec::new(),
39 invalidate_on: None,
40 cache_if: None,
41 frequency_weight: quote! { None },
42 window_ratio: quote! { None },
43 sketch_width: quote! { None },
44 sketch_depth: quote! { None },
45 decay_interval: quote! { None },
46 }
47 }
48}
49
50pub fn policies_str_with_separator(separator: &str) -> String {
51 POLICIES
52 .iter()
53 .map(|p| format!("\"{}\"", p))
54 .collect::<Vec<_>>()
55 .join(separator)
56}
57
58pub struct AsyncCacheAttributes {
60 pub limit: TokenStream2,
61 pub policy: TokenStream2,
62 pub ttl: TokenStream2,
63 pub custom_name: Option<String>,
64 pub max_memory: TokenStream2,
65 pub tags: Vec<String>,
66 pub events: Vec<String>,
67 pub dependencies: Vec<String>,
68 pub invalidate_on: Option<syn::Path>,
69 pub cache_if: Option<syn::Path>,
70 pub frequency_weight: TokenStream2,
71 pub window_ratio: TokenStream2,
72 pub sketch_width: TokenStream2,
73 pub sketch_depth: TokenStream2,
74 pub decay_interval: TokenStream2,
75}
76
77impl Default for AsyncCacheAttributes {
78 fn default() -> Self {
79 Self {
80 limit: quote! { Option::<usize>::None },
81 policy: quote! { "fifo" },
82 ttl: quote! { Option::<u64>::None },
83 custom_name: None,
84 max_memory: quote! { Option::<usize>::None },
85 tags: Vec::new(),
86 events: Vec::new(),
87 dependencies: Vec::new(),
88 invalidate_on: None,
89 cache_if: None,
90 frequency_weight: quote! { Option::<f64>::None },
91 window_ratio: quote! { Option::<f64>::None },
92 sketch_width: quote! { Option::<usize>::None },
93 sketch_depth: quote! { Option::<usize>::None },
94 decay_interval: quote! { Option::<u64>::None },
95 }
96 }
97}
98
99impl AsyncCacheAttributes {
100 pub fn builder() -> AsyncCacheAttributesBuilder {
102 AsyncCacheAttributesBuilder::new()
103 }
104}
105
106pub struct AsyncCacheAttributesBuilder {
108 attrs: AsyncCacheAttributes,
109}
110
111impl AsyncCacheAttributesBuilder {
112 pub fn new() -> Self {
113 Self {
114 attrs: AsyncCacheAttributes::default(),
115 }
116 }
117
118 pub fn limit(mut self, limit: TokenStream2) -> Self {
119 self.attrs.limit = limit;
120 self
121 }
122
123 pub fn policy(mut self, policy: TokenStream2) -> Self {
124 self.attrs.policy = policy;
125 self
126 }
127
128 pub fn ttl(mut self, ttl: TokenStream2) -> Self {
129 self.attrs.ttl = ttl;
130 self
131 }
132
133 pub fn custom_name(mut self, name: Option<String>) -> Self {
134 self.attrs.custom_name = name;
135 self
136 }
137
138 pub fn max_memory(mut self, max_memory: TokenStream2) -> Self {
139 self.attrs.max_memory = max_memory;
140 self
141 }
142
143 pub fn tags(mut self, tags: Vec<String>) -> Self {
144 self.attrs.tags = tags;
145 self
146 }
147
148 pub fn events(mut self, events: Vec<String>) -> Self {
149 self.attrs.events = events;
150 self
151 }
152
153 pub fn dependencies(mut self, dependencies: Vec<String>) -> Self {
154 self.attrs.dependencies = dependencies;
155 self
156 }
157
158 pub fn invalidate_on(mut self, invalidate_on: Option<syn::Path>) -> Self {
159 self.attrs.invalidate_on = invalidate_on;
160 self
161 }
162
163 pub fn cache_if(mut self, cache_if: Option<syn::Path>) -> Self {
164 self.attrs.cache_if = cache_if;
165 self
166 }
167
168 pub fn frequency_weight(mut self, frequency_weight: TokenStream2) -> Self {
169 self.attrs.frequency_weight = frequency_weight;
170 self
171 }
172
173 pub fn window_ratio(mut self, window_ratio: TokenStream2) -> Self {
174 self.attrs.window_ratio = window_ratio;
175 self
176 }
177
178 pub fn sketch_width(mut self, sketch_width: TokenStream2) -> Self {
179 self.attrs.sketch_width = sketch_width;
180 self
181 }
182
183 pub fn sketch_depth(mut self, sketch_depth: TokenStream2) -> Self {
184 self.attrs.sketch_depth = sketch_depth;
185 self
186 }
187
188 pub fn decay_interval(mut self, decay_interval: TokenStream2) -> Self {
189 self.attrs.decay_interval = decay_interval;
190 self
191 }
192
193 pub fn with_common(mut self, common: CommonAttributes) -> Self {
195 self.attrs.custom_name = common.custom_name;
196 self.attrs.max_memory = common.max_memory;
197 self.attrs.tags = common.tags;
198 self.attrs.events = common.events;
199 self.attrs.dependencies = common.dependencies;
200 self.attrs.invalidate_on = common.invalidate_on;
201 self.attrs.cache_if = common.cache_if;
202 self.attrs.frequency_weight = common.frequency_weight;
203 self.attrs.window_ratio = common.window_ratio;
204 self.attrs.sketch_width = common.sketch_width;
205 self.attrs.sketch_depth = common.sketch_depth;
206 self.attrs.decay_interval = common.decay_interval;
207 self
208 }
209
210 pub fn build(self) -> AsyncCacheAttributes {
211 self.attrs
212 }
213}
214
215impl Default for AsyncCacheAttributesBuilder {
216 fn default() -> Self {
217 Self::new()
218 }
219}
220
221pub struct SyncCacheAttributes {
223 pub limit: TokenStream2,
224 pub policy: TokenStream2,
225 pub ttl: TokenStream2,
226 pub scope: TokenStream2,
227 pub custom_name: Option<String>,
228 pub max_memory: TokenStream2,
229 pub tags: Vec<String>,
230 pub events: Vec<String>,
231 pub dependencies: Vec<String>,
232 pub invalidate_on: Option<syn::Path>,
233 pub cache_if: Option<syn::Path>,
234 pub frequency_weight: TokenStream2,
235 pub window_ratio: TokenStream2,
236 pub sketch_width: TokenStream2,
237 pub sketch_depth: TokenStream2,
238 pub decay_interval: TokenStream2,
239}
240
241impl Default for SyncCacheAttributes {
242 fn default() -> Self {
243 Self {
244 limit: quote! { None },
245 policy: quote! { cachelito_core::EvictionPolicy::FIFO },
246 ttl: quote! { None },
247 scope: quote! { cachelito_core::CacheScope::Global },
248 custom_name: None,
249 max_memory: quote! { None },
250 tags: Vec::new(),
251 events: Vec::new(),
252 dependencies: Vec::new(),
253 invalidate_on: None,
254 cache_if: None,
255 frequency_weight: quote! { None },
256 window_ratio: quote! { None },
257 sketch_width: quote! { None },
258 sketch_depth: quote! { None },
259 decay_interval: quote! { None },
260 }
261 }
262}
263
264impl SyncCacheAttributes {
265 pub fn builder() -> SyncCacheAttributesBuilder {
267 SyncCacheAttributesBuilder::new()
268 }
269}
270
271pub struct SyncCacheAttributesBuilder {
273 attrs: SyncCacheAttributes,
274}
275
276impl SyncCacheAttributesBuilder {
277 pub fn new() -> Self {
278 Self {
279 attrs: SyncCacheAttributes::default(),
280 }
281 }
282
283 pub fn limit(mut self, limit: TokenStream2) -> Self {
284 self.attrs.limit = limit;
285 self
286 }
287
288 pub fn policy(mut self, policy: TokenStream2) -> Self {
289 self.attrs.policy = policy;
290 self
291 }
292
293 pub fn ttl(mut self, ttl: TokenStream2) -> Self {
294 self.attrs.ttl = ttl;
295 self
296 }
297
298 pub fn scope(mut self, scope: TokenStream2) -> Self {
299 self.attrs.scope = scope;
300 self
301 }
302
303 pub fn custom_name(mut self, name: Option<String>) -> Self {
304 self.attrs.custom_name = name;
305 self
306 }
307
308 pub fn max_memory(mut self, max_memory: TokenStream2) -> Self {
309 self.attrs.max_memory = max_memory;
310 self
311 }
312
313 pub fn tags(mut self, tags: Vec<String>) -> Self {
314 self.attrs.tags = tags;
315 self
316 }
317
318 pub fn events(mut self, events: Vec<String>) -> Self {
319 self.attrs.events = events;
320 self
321 }
322
323 pub fn dependencies(mut self, dependencies: Vec<String>) -> Self {
324 self.attrs.dependencies = dependencies;
325 self
326 }
327
328 pub fn invalidate_on(mut self, invalidate_on: Option<syn::Path>) -> Self {
329 self.attrs.invalidate_on = invalidate_on;
330 self
331 }
332
333 pub fn cache_if(mut self, cache_if: Option<syn::Path>) -> Self {
334 self.attrs.cache_if = cache_if;
335 self
336 }
337
338 pub fn frequency_weight(mut self, frequency_weight: TokenStream2) -> Self {
339 self.attrs.frequency_weight = frequency_weight;
340 self
341 }
342
343 pub fn window_ratio(mut self, window_ratio: TokenStream2) -> Self {
344 self.attrs.window_ratio = window_ratio;
345 self
346 }
347
348 pub fn sketch_width(mut self, sketch_width: TokenStream2) -> Self {
349 self.attrs.sketch_width = sketch_width;
350 self
351 }
352
353 pub fn sketch_depth(mut self, sketch_depth: TokenStream2) -> Self {
354 self.attrs.sketch_depth = sketch_depth;
355 self
356 }
357
358 pub fn decay_interval(mut self, decay_interval: TokenStream2) -> Self {
359 self.attrs.decay_interval = decay_interval;
360 self
361 }
362
363 pub fn with_common(mut self, common: CommonAttributes) -> Self {
365 self.attrs.custom_name = common.custom_name;
366 self.attrs.max_memory = common.max_memory;
367 self.attrs.tags = common.tags;
368 self.attrs.events = common.events;
369 self.attrs.dependencies = common.dependencies;
370 self.attrs.invalidate_on = common.invalidate_on;
371 self.attrs.cache_if = common.cache_if;
372 self.attrs.frequency_weight = common.frequency_weight;
373 self.attrs.window_ratio = common.window_ratio;
374 self.attrs.sketch_width = common.sketch_width;
375 self.attrs.sketch_depth = common.sketch_depth;
376 self.attrs.decay_interval = common.decay_interval;
377 self
378 }
379
380 pub fn build(self) -> SyncCacheAttributes {
381 self.attrs
382 }
383}
384
385impl Default for SyncCacheAttributesBuilder {
386 fn default() -> Self {
387 Self::new()
388 }
389}
390
391pub fn parse_limit_attribute(nv: &MetaNameValue) -> TokenStream2 {
393 match &nv.value {
394 Expr::Lit(expr_lit) => match &expr_lit.lit {
395 syn::Lit::Int(lit_int) => match lit_int.base10_parse::<usize>() {
396 Ok(val) => quote! { Some(#val) },
397 Err(_) => quote! { compile_error!("limit must be a valid positive integer") },
398 },
399 _ => quote! { compile_error!("Invalid literal for `limit`: expected integer") },
400 },
401 _ => quote! { compile_error!("Invalid syntax for `limit`: expected `limit = <integer>`") },
402 }
403}
404
405pub fn parse_policy_attribute(nv: &MetaNameValue) -> Result<String, TokenStream2> {
407 match &nv.value {
408 Expr::Lit(expr_lit) => match &expr_lit.lit {
409 syn::Lit::Str(s) => {
410 let val = s.value();
411 if POLICIES.contains(&val.as_str()) {
413 Ok(val)
414 } else {
415 let policies = policies_str_with_separator(", ");
416 let err_msg = format!("Invalid policy: expected one of {}", policies);
417 Err(quote! { compile_error!(#err_msg) })
418 }
419 }
420 _ => Err(quote! { compile_error!("Invalid literal for `policy`: expected string") }),
421 },
422 _ => {
423 let policies = policies_str_with_separator("|");
424 let err_msg = format!(
425 "Invalid syntax for `policy`: expected `policy = \"{}\"`",
426 policies
427 );
428 Err(quote! {
429 compile_error!(#err_msg)
430 })
431 }
432 }
433}
434
435pub fn parse_ttl_attribute(nv: &MetaNameValue) -> TokenStream2 {
437 match &nv.value {
438 Expr::Lit(expr_lit) => match &expr_lit.lit {
439 syn::Lit::Int(lit_int) => {
440 let val = lit_int
441 .base10_parse::<u64>()
442 .expect("ttl must be a positive integer (seconds)");
443 quote! { Some(#val) }
444 }
445 _ => quote! { compile_error!("Invalid literal for `ttl`: expected integer (seconds)") },
446 },
447 _ => quote! { compile_error!("Invalid syntax for `ttl`: expected `ttl = <integer>`") },
448 }
449}
450
451pub fn parse_frequency_weight_attribute(nv: &MetaNameValue) -> TokenStream2 {
465 match &nv.value {
466 Expr::Lit(expr_lit) => match &expr_lit.lit {
467 syn::Lit::Float(lit_float) => {
468 let val = lit_float
469 .base10_parse::<f64>()
470 .expect("frequency_weight must be a float");
471 if val <= 0.0 {
472 quote! { compile_error!("frequency_weight must be > 0.0 (zero would cause 0^0 undefined behavior and has no semantic meaning)") }
473 } else {
474 quote! { Some(#val) }
475 }
476 }
477 syn::Lit::Int(lit_int) => {
478 let val = lit_int
480 .base10_parse::<u64>()
481 .expect("frequency_weight must be a number");
482 let val_f64 = val as f64;
483 quote! { Some(#val_f64) }
484 }
485 _ => {
486 quote! { compile_error!("Invalid literal for `frequency_weight`: expected float") }
487 }
488 },
489 _ => {
490 quote! { compile_error!("Invalid syntax for `frequency_weight`: expected `frequency_weight = <float>`") }
491 }
492 }
493}
494
495pub fn parse_name_attribute(nv: &MetaNameValue) -> Option<String> {
497 match &nv.value {
498 Expr::Lit(expr_lit) => match &expr_lit.lit {
499 syn::Lit::Str(s) => Some(s.value()),
500 _ => None,
501 },
502 _ => None,
503 }
504}
505
506pub fn parse_max_memory_attribute(nv: &MetaNameValue) -> TokenStream2 {
509 match &nv.value {
510 Expr::Lit(expr_lit) => match &expr_lit.lit {
511 syn::Lit::Str(s) => {
512 let val_str = s.value();
513 let val_str = val_str.to_uppercase();
514
515 let bytes = if val_str.ends_with("GB") {
517 let num_str = val_str.trim_end_matches("GB");
518 match num_str.parse::<usize>() {
519 Ok(n) => n * 1024 * 1024 * 1024,
520 Err(_) => {
521 return quote! { compile_error!("Invalid number format for max_memory") }
522 }
523 }
524 } else if val_str.ends_with("MB") {
525 let num_str = val_str.trim_end_matches("MB");
526 match num_str.parse::<usize>() {
527 Ok(n) => n * 1024 * 1024,
528 Err(_) => {
529 return quote! { compile_error!("Invalid number format for max_memory") }
530 }
531 }
532 } else if val_str.ends_with("KB") {
533 let num_str = val_str.trim_end_matches("KB");
534 match num_str.parse::<usize>() {
535 Ok(n) => n * 1024,
536 Err(_) => {
537 return quote! { compile_error!("Invalid number format for max_memory") }
538 }
539 }
540 } else {
541 match val_str.parse::<usize>() {
543 Ok(n) => n,
544 Err(_) => {
545 return quote! { compile_error!("Invalid format for max_memory: expected \"100MB\", \"1GB\", \"500KB\", or number") }
546 }
547 }
548 };
549
550 quote! { Some(#bytes) }
551 }
552 syn::Lit::Int(lit_int) => {
553 let val = lit_int
554 .base10_parse::<usize>()
555 .expect("max_memory must be a positive integer (bytes)");
556 quote! { Some(#val) }
557 }
558 _ => {
559 quote! { compile_error!("Invalid literal for `max_memory`: expected string (\"100MB\") or integer") }
560 }
561 },
562 _ => {
563 quote! { compile_error!("Invalid syntax for `max_memory`: expected `max_memory = \"100MB\"`") }
564 }
565 }
566}
567
568pub fn parse_scope_attribute(nv: &MetaNameValue) -> Result<String, TokenStream2> {
570 match &nv.value {
571 Expr::Lit(expr_lit) => match &expr_lit.lit {
572 syn::Lit::Str(s) => {
573 let val = s.value();
574 if val == "global" || val == "thread" {
576 Ok(val)
577 } else {
578 Err(
579 quote! { compile_error!("Invalid scope: expected \"global\" or \"thread\"") },
580 )
581 }
582 }
583 _ => Err(quote! { compile_error!("Invalid literal for `scope`: expected string") }),
584 },
585 _ => Err(
586 quote! { compile_error!("Invalid syntax for `scope`: expected `scope = \"global\"|\"thread\"`") },
587 ),
588 }
589}
590
591pub fn generate_key_expr(has_self: bool, arg_pats: &[TokenStream2]) -> TokenStream2 {
593 if has_self {
594 if arg_pats.is_empty() {
595 quote! {{
596 format!("{:?}", self)
597 }}
598 } else {
599 quote! {{
600 let mut __key_parts = Vec::new();
601 __key_parts.push(format!("{:?}", self));
602 #(
603 __key_parts.push(format!("{:?}", #arg_pats));
604 )*
605 __key_parts.join("|")
606 }}
607 }
608 } else if arg_pats.is_empty() {
609 quote! {{ String::new() }}
610 } else {
611 quote! {{
612 let mut __key_parts = Vec::new();
613 #(
614 __key_parts.push(format!("{:?}", #arg_pats));
615 )*
616 __key_parts.join("|")
617 }}
618 }
619}
620
621pub fn generate_key_expr_with_cacheable_key(
623 has_self: bool,
624 arg_pats: &[TokenStream2],
625) -> TokenStream2 {
626 if has_self {
627 if arg_pats.is_empty() {
628 quote! {{
629 use cachelito_core::CacheableKey;
630 self.to_cache_key()
631 }}
632 } else {
633 quote! {{
634 use cachelito_core::CacheableKey;
635 let mut __key_parts = Vec::new();
636 __key_parts.push(self.to_cache_key());
637 #(
638 __key_parts.push((#arg_pats).to_cache_key());
639 )*
640 __key_parts.join("|")
641 }}
642 }
643 } else if arg_pats.is_empty() {
644 quote! {{ String::new() }}
645 } else {
646 quote! {{
647 use cachelito_core::CacheableKey;
648 let mut __key_parts = Vec::new();
649 #(
650 __key_parts.push((#arg_pats).to_cache_key());
651 )*
652 __key_parts.join("|")
653 }}
654 }
655}
656
657pub fn parse_string_array_attribute(nv: &MetaNameValue) -> Result<Vec<String>, TokenStream2> {
664 match &nv.value {
665 Expr::Array(array) => {
666 let mut strings = Vec::new();
667 for elem in &array.elems {
668 match elem {
669 Expr::Lit(expr_lit) => match &expr_lit.lit {
670 syn::Lit::Str(s) => {
671 strings.push(s.value());
672 }
673 _ => {
674 return Err(
675 quote! { compile_error!("Array elements must be string literals") },
676 );
677 }
678 },
679 _ => {
680 return Err(
681 quote! { compile_error!("Array elements must be string literals") },
682 );
683 }
684 }
685 }
686 Ok(strings)
687 }
688 _ => Err(quote! { compile_error!("Expected array of strings like [\"tag1\", \"tag2\"]") }),
689 }
690}
691
692pub fn parse_invalidate_on_attribute(nv: &MetaNameValue) -> Result<syn::Path, TokenStream2> {
695 match &nv.value {
696 Expr::Path(expr_path) => Ok(expr_path.path.clone()),
697 _ => Err(
698 quote! { compile_error!("Invalid syntax for `invalidate_on`: expected `invalidate_on = function_name`") },
699 ),
700 }
701}
702
703pub fn parse_cache_if_attribute(nv: &MetaNameValue) -> Result<syn::Path, TokenStream2> {
706 match &nv.value {
707 Expr::Path(expr_path) => Ok(expr_path.path.clone()),
708 _ => Err(
709 quote! { compile_error!("Invalid syntax for `cache_if`: expected `cache_if = function_name`") },
710 ),
711 }
712}
713
714pub fn parse_window_ratio_attribute(nv: &MetaNameValue) -> TokenStream2 {
717 match &nv.value {
718 Expr::Lit(expr_lit) => match &expr_lit.lit {
719 syn::Lit::Float(lit_float) => {
720 let val = lit_float
721 .base10_parse::<f64>()
722 .expect("window_ratio must be a float");
723 if val <= 0.0 || val >= 1.0 {
724 quote! { compile_error!("window_ratio must be between 0.0 and 1.0 (exclusive)") }
725 } else {
726 quote! { Some(#val) }
727 }
728 }
729 syn::Lit::Int(lit_int) => {
730 let val = lit_int
732 .base10_parse::<u64>()
733 .expect("window_ratio must be a number");
734 if val == 0 {
735 quote! { compile_error!("window_ratio must be between 0.0 and 1.0 (exclusive)") }
736 } else {
737 let val_f64 = val as f64;
738 quote! { Some(#val_f64) }
739 }
740 }
741 _ => {
742 quote! { compile_error!("Invalid literal for `window_ratio`: expected float between 0.0 and 1.0") }
743 }
744 },
745 _ => {
746 quote! { compile_error!("Invalid syntax for `window_ratio`: expected `window_ratio = <float>`") }
747 }
748 }
749}
750
751pub fn parse_sketch_width_attribute(nv: &MetaNameValue) -> TokenStream2 {
753 match &nv.value {
754 Expr::Lit(expr_lit) => match &expr_lit.lit {
755 syn::Lit::Int(lit_int) => {
756 let val = lit_int
757 .base10_parse::<usize>()
758 .expect("sketch_width must be a positive integer");
759 if val == 0 {
760 quote! { compile_error!("sketch_width must be greater than 0") }
761 } else {
762 quote! { Some(#val) }
763 }
764 }
765 _ => quote! { compile_error!("Invalid literal for `sketch_width`: expected integer") },
766 },
767 _ => {
768 quote! { compile_error!("Invalid syntax for `sketch_width`: expected `sketch_width = <integer>`") }
769 }
770 }
771}
772
773pub fn parse_sketch_depth_attribute(nv: &MetaNameValue) -> TokenStream2 {
775 match &nv.value {
776 Expr::Lit(expr_lit) => match &expr_lit.lit {
777 syn::Lit::Int(lit_int) => {
778 let val = lit_int
779 .base10_parse::<usize>()
780 .expect("sketch_depth must be a positive integer");
781 if val == 0 {
782 quote! { compile_error!("sketch_depth must be greater than 0") }
783 } else {
784 quote! { Some(#val) }
785 }
786 }
787 _ => quote! { compile_error!("Invalid literal for `sketch_depth`: expected integer") },
788 },
789 _ => {
790 quote! { compile_error!("Invalid syntax for `sketch_depth`: expected `sketch_depth = <integer>`") }
791 }
792 }
793}
794
795pub fn parse_decay_interval_attribute(nv: &MetaNameValue) -> TokenStream2 {
797 match &nv.value {
798 Expr::Lit(expr_lit) => match &expr_lit.lit {
799 syn::Lit::Int(lit_int) => {
800 let val = lit_int
801 .base10_parse::<u64>()
802 .expect("decay_interval must be a positive integer (number of operations)");
803 if val == 0 {
804 quote! { compile_error!("decay_interval must be greater than 0") }
805 } else {
806 quote! { Some(#val) }
807 }
808 }
809 _ => {
810 quote! { compile_error!("Invalid literal for `decay_interval`: expected integer") }
811 }
812 },
813 _ => {
814 quote! { compile_error!("Invalid syntax for `decay_interval`: expected `decay_interval = <integer>`") }
815 }
816 }
817}
818
819fn parse_common_attribute(
822 nv: &MetaNameValue,
823 common: &mut CommonAttributes,
824) -> Result<bool, TokenStream2> {
825 if nv.path.is_ident("name") {
826 common.custom_name = parse_name_attribute(nv);
827 Ok(true)
828 } else if nv.path.is_ident("max_memory") {
829 common.max_memory = parse_max_memory_attribute(nv);
830 Ok(true)
831 } else if nv.path.is_ident("tags") {
832 common.tags = parse_string_array_attribute(nv)?;
833 Ok(true)
834 } else if nv.path.is_ident("events") {
835 common.events = parse_string_array_attribute(nv)?;
836 Ok(true)
837 } else if nv.path.is_ident("dependencies") {
838 common.dependencies = parse_string_array_attribute(nv)?;
839 Ok(true)
840 } else if nv.path.is_ident("invalidate_on") {
841 common.invalidate_on = Some(parse_invalidate_on_attribute(nv)?);
842 Ok(true)
843 } else if nv.path.is_ident("cache_if") {
844 common.cache_if = Some(parse_cache_if_attribute(nv)?);
845 Ok(true)
846 } else if nv.path.is_ident("frequency_weight") {
847 common.frequency_weight = parse_frequency_weight_attribute(nv);
848 Ok(true)
849 } else if nv.path.is_ident("window_ratio") {
850 common.window_ratio = parse_window_ratio_attribute(nv);
851 Ok(true)
852 } else if nv.path.is_ident("sketch_width") {
853 common.sketch_width = parse_sketch_width_attribute(nv);
854 Ok(true)
855 } else if nv.path.is_ident("sketch_depth") {
856 common.sketch_depth = parse_sketch_depth_attribute(nv);
857 Ok(true)
858 } else if nv.path.is_ident("decay_interval") {
859 common.decay_interval = parse_decay_interval_attribute(nv);
860 Ok(true)
861 } else {
862 Ok(false)
863 }
864}
865
866pub fn parse_async_attributes(attr: TokenStream2) -> Result<AsyncCacheAttributes, TokenStream2> {
868 use syn::parse::Parser;
869
870 let parser = Punctuated::<MetaNameValue, Token![,]>::parse_terminated;
871 let parsed_args = parser.parse2(attr).map_err(|e| {
872 let msg = format!("Failed to parse attributes: {}", e);
873 quote! { compile_error!(#msg) }
874 })?;
875
876 let mut builder = AsyncCacheAttributes::builder();
877 let mut common = CommonAttributes::new();
878
879 for nv in parsed_args {
880 if nv.path.is_ident("limit") {
881 builder = builder.limit(parse_limit_attribute(&nv));
882 } else if nv.path.is_ident("policy") {
883 match parse_policy_attribute(&nv) {
884 Ok(policy_str) => builder = builder.policy(quote! { #policy_str }),
885 Err(err) => return Err(err),
886 }
887 } else if nv.path.is_ident("ttl") {
888 builder = builder.ttl(parse_ttl_attribute(&nv));
889 } else {
890 if !parse_common_attribute(&nv, &mut common)? {
892 let attr_name = nv
894 .path
895 .get_ident()
896 .map(|i| i.to_string())
897 .unwrap_or_else(|| "unknown".to_string());
898 let err_msg = format!(
899 "Unknown attribute: `{}`. Valid attributes are: limit, policy, ttl, name, max_memory, tags, events, dependencies, invalidate_on, cache_if, frequency_weight, window_ratio, sketch_width, sketch_depth, decay_interval",
900 attr_name
901 );
902 return Err(quote! { compile_error!(#err_msg) });
903 }
904 }
905 }
906
907 Ok(builder.with_common(common).build())
909}
910
911pub fn parse_sync_attributes(attr: TokenStream2) -> Result<SyncCacheAttributes, TokenStream2> {
913 use syn::parse::Parser;
914
915 let parser = Punctuated::<MetaNameValue, Token![,]>::parse_terminated;
916 let parsed_args = parser.parse2(attr).map_err(|e| {
917 let msg = format!("Failed to parse attributes: {}", e);
918 quote! { compile_error!(#msg) }
919 })?;
920
921 let mut builder = SyncCacheAttributes::builder();
922 let mut common = CommonAttributes::new();
923
924 for nv in parsed_args {
925 if nv.path.is_ident("limit") {
926 builder = builder.limit(parse_limit_attribute(&nv));
927 } else if nv.path.is_ident("policy") {
928 match parse_policy_attribute(&nv) {
929 Ok(policy_str) => {
930 let policy_token = if policy_str == "fifo" {
931 quote! { cachelito_core::EvictionPolicy::FIFO }
932 } else if policy_str == "lru" {
933 quote! { cachelito_core::EvictionPolicy::LRU }
934 } else if policy_str == "lfu" {
935 quote! { cachelito_core::EvictionPolicy::LFU }
936 } else if policy_str == "arc" {
937 quote! { cachelito_core::EvictionPolicy::ARC }
938 } else if policy_str == "random" {
939 quote! { cachelito_core::EvictionPolicy::Random }
940 } else if policy_str == "tlru" {
941 quote! { cachelito_core::EvictionPolicy::TLRU }
942 } else if policy_str == "w_tinylfu" {
943 quote! { cachelito_core::EvictionPolicy::WTinyLFU }
944 } else {
945 let policies = policies_str_with_separator(", ");
946 let err_msg = format!("Invalid policy: expected one of {}", policies);
947 return Err(quote! { compile_error!(#err_msg) });
948 };
949 builder = builder.policy(policy_token);
950 }
951 Err(err) => return Err(err),
952 }
953 } else if nv.path.is_ident("ttl") {
954 builder = builder.ttl(parse_ttl_attribute(&nv));
955 } else if nv.path.is_ident("scope") {
956 match parse_scope_attribute(&nv) {
957 Ok(scope_str) => {
958 let scope_token = if scope_str == "thread" {
959 quote! { cachelito_core::CacheScope::ThreadLocal }
960 } else if scope_str == "global" {
961 quote! { cachelito_core::CacheScope::Global }
962 } else {
963 return Err(
964 quote! { compile_error!("Invalid scope: expected \"global\" or \"thread\"") },
965 );
966 };
967 builder = builder.scope(scope_token);
968 }
969 Err(err) => return Err(err),
970 }
971 } else {
972 if !parse_common_attribute(&nv, &mut common)? {
974 let attr_name = nv
976 .path
977 .get_ident()
978 .map(|i| i.to_string())
979 .unwrap_or_else(|| "unknown".to_string());
980 let err_msg = format!(
981 "Unknown attribute: `{}`. Valid attributes are: limit, policy, ttl, scope, name, max_memory, tags, events, dependencies, invalidate_on, cache_if, frequency_weight, window_ratio, sketch_width, sketch_depth, decay_interval",
982 attr_name
983 );
984 return Err(quote! { compile_error!(#err_msg) });
985 }
986 }
987 }
988
989 Ok(builder.with_common(common).build())
991}
992
993#[cfg(test)]
994mod tests {
995 use super::*;
996 use quote::quote;
997 use syn::parse_quote;
998
999 #[test]
1000 fn test_policies_str_with_separator() {
1001 let result = policies_str_with_separator(", ");
1002 assert_eq!(
1003 result,
1004 "\"fifo\", \"lru\", \"lfu\", \"arc\", \"random\", \"tlru\", \"w_tinylfu\""
1005 );
1006
1007 let result = policies_str_with_separator("|");
1008 assert_eq!(
1009 result,
1010 "\"fifo\"|\"lru\"|\"lfu\"|\"arc\"|\"random\"|\"tlru\"|\"w_tinylfu\""
1011 );
1012 }
1013
1014 #[test]
1015 fn test_parse_limit_attribute_valid() {
1016 let nv: MetaNameValue = parse_quote! { limit = 100 };
1017 let result = parse_limit_attribute(&nv);
1018 assert_eq!(result.to_string(), "Some (100usize)");
1019 }
1020
1021 #[test]
1022 fn test_parse_policy_attribute_valid() {
1023 let nv: MetaNameValue = parse_quote! { policy = "fifo" };
1024 let result = parse_policy_attribute(&nv);
1025 assert!(result.is_ok());
1026 assert_eq!(result.unwrap(), "fifo");
1027
1028 let nv: MetaNameValue = parse_quote! { policy = "lru" };
1029 let result = parse_policy_attribute(&nv);
1030 assert!(result.is_ok());
1031 assert_eq!(result.unwrap(), "lru");
1032 }
1033
1034 #[test]
1035 fn test_parse_max_memory_attribute() {
1036 let nv: MetaNameValue = parse_quote! { max_memory = "100MB" };
1038 let result = parse_max_memory_attribute(&nv);
1039 let expected = 100 * 1024 * 1024;
1040 assert_eq!(result.to_string(), format!("Some ({}usize)", expected));
1041
1042 let nv: MetaNameValue = parse_quote! { max_memory = "1GB" };
1044 let result = parse_max_memory_attribute(&nv);
1045 let expected = 1024 * 1024 * 1024;
1046 assert_eq!(result.to_string(), format!("Some ({}usize)", expected));
1047
1048 let nv: MetaNameValue = parse_quote! { max_memory = "500KB" };
1050 let result = parse_max_memory_attribute(&nv);
1051 let expected = 500 * 1024;
1052 assert_eq!(result.to_string(), format!("Some ({}usize)", expected));
1053
1054 let nv: MetaNameValue = parse_quote! { max_memory = 1024 };
1056 let result = parse_max_memory_attribute(&nv);
1057 assert_eq!(result.to_string(), "Some (1024usize)");
1058
1059 let nv: MetaNameValue = parse_quote! { max_memory = "2048" };
1061 let result = parse_max_memory_attribute(&nv);
1062 assert_eq!(result.to_string(), "Some (2048usize)");
1063 }
1064
1065 #[test]
1066 fn test_parse_policy_attribute_invalid() {
1067 let nv: MetaNameValue = parse_quote! { policy = "invalid" };
1068 let result = parse_policy_attribute(&nv);
1069 assert!(result.is_err());
1070 }
1071
1072 #[test]
1073 fn test_parse_ttl_attribute_valid() {
1074 let nv: MetaNameValue = parse_quote! { ttl = 60 };
1075 let result = parse_ttl_attribute(&nv);
1076 assert_eq!(result.to_string(), "Some (60u64)");
1077 }
1078
1079 #[test]
1080 fn test_parse_name_attribute() {
1081 let nv: MetaNameValue = parse_quote! { name = "my_cache" };
1082 let result = parse_name_attribute(&nv);
1083 assert_eq!(result, Some("my_cache".to_string()));
1084 }
1085
1086 #[test]
1087 fn test_parse_scope_attribute_valid() {
1088 let nv: MetaNameValue = parse_quote! { scope = "global" };
1089 let result = parse_scope_attribute(&nv);
1090 assert!(result.is_ok());
1091 assert_eq!(result.unwrap(), "global");
1092
1093 let nv: MetaNameValue = parse_quote! { scope = "thread" };
1094 let result = parse_scope_attribute(&nv);
1095 assert!(result.is_ok());
1096 assert_eq!(result.unwrap(), "thread");
1097 }
1098
1099 #[test]
1100 fn test_parse_scope_attribute_invalid() {
1101 let nv: MetaNameValue = parse_quote! { scope = "invalid" };
1102 let result = parse_scope_attribute(&nv);
1103 assert!(result.is_err());
1104 }
1105
1106 #[test]
1107 fn test_generate_key_expr_no_self_no_args() {
1108 let result = generate_key_expr(false, &[]);
1109 assert_eq!(result.to_string(), "{ String :: new () }");
1110 }
1111
1112 #[test]
1113 fn test_generate_key_expr_with_self_no_args() {
1114 let result = generate_key_expr(true, &[]);
1115 let expected = quote! {{ format!("{:?}", self) }};
1116 assert_eq!(result.to_string(), expected.to_string());
1117 }
1118
1119 #[test]
1120 fn test_generate_key_expr_with_args() {
1121 let args = vec![quote! { arg1 }, quote! { arg2 }];
1122 let result = generate_key_expr(false, &args);
1123 assert!(result.to_string().contains("__key_parts"));
1124 }
1125
1126 #[test]
1127 fn test_parse_async_attributes_defaults() {
1128 let attrs = parse_async_attributes(quote! {}).unwrap();
1129 assert_eq!(attrs.limit.to_string(), "Option :: < usize > :: None");
1130 assert_eq!(attrs.policy.to_string(), "\"fifo\"");
1131 assert_eq!(attrs.ttl.to_string(), "Option :: < u64 > :: None");
1132 assert_eq!(attrs.custom_name, None);
1133 }
1134
1135 #[test]
1136 fn test_parse_async_attributes_complete() {
1137 let attrs = parse_async_attributes(quote! {
1138 limit = 50,
1139 policy = "lru",
1140 ttl = 120,
1141 name = "test_cache"
1142 })
1143 .unwrap();
1144
1145 assert_eq!(attrs.limit.to_string(), "Some (50usize)");
1146 assert_eq!(attrs.policy.to_string(), "\"lru\"");
1147 assert_eq!(attrs.ttl.to_string(), "Some (120u64)");
1148 assert_eq!(attrs.custom_name, Some("test_cache".to_string()));
1149 }
1150
1151 #[test]
1152 fn test_parse_sync_attributes_defaults() {
1153 let attrs = parse_sync_attributes(quote! {}).unwrap();
1154 assert_eq!(attrs.limit.to_string(), "None");
1155 assert_eq!(
1156 attrs.policy.to_string(),
1157 "cachelito_core :: EvictionPolicy :: FIFO"
1158 );
1159 assert_eq!(
1160 attrs.scope.to_string(),
1161 "cachelito_core :: CacheScope :: Global"
1162 );
1163 }
1164
1165 #[test]
1166 fn test_parse_sync_attributes_complete() {
1167 let attrs = parse_sync_attributes(quote! {
1168 limit = 100,
1169 policy = "arc",
1170 ttl = 300,
1171 scope = "thread",
1172 name = "sync_cache"
1173 })
1174 .unwrap();
1175
1176 assert_eq!(attrs.limit.to_string(), "Some (100usize)");
1177 assert_eq!(
1178 attrs.policy.to_string(),
1179 "cachelito_core :: EvictionPolicy :: ARC"
1180 );
1181 assert_eq!(
1182 attrs.scope.to_string(),
1183 "cachelito_core :: CacheScope :: ThreadLocal"
1184 );
1185 assert_eq!(attrs.custom_name, Some("sync_cache".to_string()));
1186 }
1187
1188 #[test]
1189 fn test_parse_common_attribute_name() {
1190 let nv: MetaNameValue = parse_quote! { name = "test_cache" };
1191 let mut common = CommonAttributes::new();
1192 let result = parse_common_attribute(&nv, &mut common);
1193
1194 assert!(result.is_ok());
1195 assert_eq!(result.unwrap(), true);
1196 assert_eq!(common.custom_name, Some("test_cache".to_string()));
1197 }
1198
1199 #[test]
1200 fn test_parse_common_attribute_max_memory() {
1201 let nv: MetaNameValue = parse_quote! { max_memory = "100MB" };
1202 let mut common = CommonAttributes::new();
1203 let result = parse_common_attribute(&nv, &mut common);
1204
1205 assert!(result.is_ok());
1206 assert_eq!(result.unwrap(), true);
1207 let expected = 100 * 1024 * 1024;
1208 assert_eq!(
1209 common.max_memory.to_string(),
1210 format!("Some ({}usize)", expected)
1211 );
1212 }
1213
1214 #[test]
1215 fn test_parse_common_attribute_tags() {
1216 let nv: MetaNameValue = parse_quote! { tags = ["tag1", "tag2"] };
1217 let mut common = CommonAttributes::new();
1218 let result = parse_common_attribute(&nv, &mut common);
1219
1220 assert!(result.is_ok());
1221 assert_eq!(result.unwrap(), true);
1222 assert_eq!(common.tags, vec!["tag1".to_string(), "tag2".to_string()]);
1223 }
1224
1225 #[test]
1226 fn test_parse_common_attribute_events() {
1227 let nv: MetaNameValue = parse_quote! { events = ["event1"] };
1228 let mut common = CommonAttributes::new();
1229 let result = parse_common_attribute(&nv, &mut common);
1230
1231 assert!(result.is_ok());
1232 assert_eq!(result.unwrap(), true);
1233 assert_eq!(common.events, vec!["event1".to_string()]);
1234 }
1235
1236 #[test]
1237 fn test_parse_common_attribute_dependencies() {
1238 let nv: MetaNameValue = parse_quote! { dependencies = ["dep1", "dep2"] };
1239 let mut common = CommonAttributes::new();
1240 let result = parse_common_attribute(&nv, &mut common);
1241
1242 assert!(result.is_ok());
1243 assert_eq!(result.unwrap(), true);
1244 assert_eq!(
1245 common.dependencies,
1246 vec!["dep1".to_string(), "dep2".to_string()]
1247 );
1248 }
1249
1250 #[test]
1251 fn test_parse_common_attribute_unknown() {
1252 let nv: MetaNameValue = parse_quote! { unknown = "value" };
1253 let mut common = CommonAttributes::new();
1254 let result = parse_common_attribute(&nv, &mut common);
1255
1256 assert!(result.is_ok());
1257 assert_eq!(result.unwrap(), false); }
1259
1260 #[test]
1261 fn test_parse_common_attribute_invalidate_on() {
1262 let nv: MetaNameValue = parse_quote! { invalidate_on = is_stale };
1263 let mut common = CommonAttributes::new();
1264 let result = parse_common_attribute(&nv, &mut common);
1265
1266 assert!(result.is_ok());
1267 assert_eq!(result.unwrap(), true);
1268 assert!(common.invalidate_on.is_some());
1269 assert_eq!(
1270 common
1271 .invalidate_on
1272 .unwrap()
1273 .segments
1274 .first()
1275 .unwrap()
1276 .ident
1277 .to_string(),
1278 "is_stale"
1279 );
1280 }
1281
1282 #[test]
1283 fn test_parse_string_array_attribute() {
1284 let nv: MetaNameValue = parse_quote! { tags = ["tag1", "tag2", "tag3"] };
1285 let result = parse_string_array_attribute(&nv);
1286 assert!(result.is_ok());
1287 assert_eq!(
1288 result.unwrap(),
1289 vec!["tag1".to_string(), "tag2".to_string(), "tag3".to_string()]
1290 );
1291 }
1292
1293 #[test]
1294 fn test_parse_async_attributes_unknown_attribute() {
1295 let result = parse_async_attributes(quote! {
1296 limit = 100,
1297 unknown_attr = "value"
1298 });
1299
1300 assert!(result.is_err());
1301 if let Err(err) = result {
1302 let err_str = err.to_string();
1303 assert!(err_str.contains("Unknown attribute"));
1304 assert!(err_str.contains("unknown_attr"));
1305 }
1306 }
1307
1308 #[test]
1309 fn test_parse_sync_attributes_unknown_attribute() {
1310 let result = parse_sync_attributes(quote! {
1311 limit = 100,
1312 typo_tag = ["tag1"]
1313 });
1314
1315 assert!(result.is_err());
1316 if let Err(err) = result {
1317 let err_str = err.to_string();
1318 assert!(err_str.contains("Unknown attribute"));
1319 assert!(err_str.contains("typo_tag"));
1320 }
1321 }
1322
1323 #[test]
1324 fn test_parse_async_attributes_typo_in_tags() {
1325 let result = parse_async_attributes(quote! {
1327 tag = ["user_data"]
1328 });
1329
1330 assert!(result.is_err());
1331 if let Err(err) = result {
1332 let err_str = err.to_string();
1333 assert!(err_str.contains("Unknown attribute"));
1334 assert!(err_str.contains("tag"));
1335 }
1336 }
1337
1338 #[test]
1339 fn test_parse_sync_attributes_typo_in_events() {
1340 let result = parse_sync_attributes(quote! {
1342 event = ["user_updated"]
1343 });
1344
1345 assert!(result.is_err());
1346 if let Err(err) = result {
1347 let err_str = err.to_string();
1348 assert!(err_str.contains("Unknown attribute"));
1349 assert!(err_str.contains("event"));
1350 }
1351 }
1352
1353 #[test]
1354 fn test_parse_frequency_weight_valid_float() {
1355 let nv: MetaNameValue = parse_quote! { frequency_weight = 0.3 };
1357 let result = parse_frequency_weight_attribute(&nv);
1358 assert_eq!(result.to_string(), "Some (0.3f64)");
1359
1360 let nv: MetaNameValue = parse_quote! { frequency_weight = 1.0 };
1361 let result = parse_frequency_weight_attribute(&nv);
1362 assert!(result.to_string() == "Some (1f64)" || result.to_string() == "Some (1.0f64)");
1364
1365 let nv: MetaNameValue = parse_quote! { frequency_weight = 1.5 };
1366 let result = parse_frequency_weight_attribute(&nv);
1367 assert_eq!(result.to_string(), "Some (1.5f64)");
1368
1369 let nv: MetaNameValue = parse_quote! { frequency_weight = 2.0 };
1370 let result = parse_frequency_weight_attribute(&nv);
1371 assert!(result.to_string() == "Some (2f64)" || result.to_string() == "Some (2.0f64)");
1372 }
1373
1374 #[test]
1375 fn test_parse_frequency_weight_valid_int() {
1376 let nv: MetaNameValue = parse_quote! { frequency_weight = 1 };
1378 let result = parse_frequency_weight_attribute(&nv);
1379 assert_eq!(result.to_string(), "Some (1f64)");
1380
1381 let nv: MetaNameValue = parse_quote! { frequency_weight = 2 };
1382 let result = parse_frequency_weight_attribute(&nv);
1383 assert_eq!(result.to_string(), "Some (2f64)");
1384 }
1385
1386 #[test]
1387 fn test_parse_frequency_weight_rejects_zero() {
1388 let nv: MetaNameValue = parse_quote! { frequency_weight = 0.0 };
1390 let result = parse_frequency_weight_attribute(&nv);
1391 let result_str = result.to_string();
1392 assert!(result_str.contains("compile_error"));
1393 assert!(result_str.contains("must be > 0.0"));
1394 assert!(result_str.contains("0^0"));
1395 }
1396
1397 #[test]
1398 fn test_parse_frequency_weight_rejects_negative() {
1399 let nv: MetaNameValue = parse_quote! { frequency_weight = -0.5 };
1401 let result = parse_frequency_weight_attribute(&nv);
1402 let result_str = result.to_string();
1403 assert!(result_str.contains("compile_error"));
1404 assert!(result_str.contains("must be > 0.0"));
1405 }
1406
1407 #[test]
1408 fn test_parse_frequency_weight_very_small_positive() {
1409 let nv: MetaNameValue = parse_quote! { frequency_weight = 0.01 };
1411 let result = parse_frequency_weight_attribute(&nv);
1412 assert_eq!(result.to_string(), "Some (0.01f64)");
1413
1414 let nv: MetaNameValue = parse_quote! { frequency_weight = 0.001 };
1415 let result = parse_frequency_weight_attribute(&nv);
1416 assert_eq!(result.to_string(), "Some (0.001f64)");
1417 }
1418
1419 #[test]
1420 fn test_parse_frequency_weight_large_values() {
1421 let nv: MetaNameValue = parse_quote! { frequency_weight = 10.0 };
1423 let result = parse_frequency_weight_attribute(&nv);
1424 assert!(result.to_string() == "Some (10f64)" || result.to_string() == "Some (10.0f64)");
1425
1426 let nv: MetaNameValue = parse_quote! { frequency_weight = 100.0 };
1427 let result = parse_frequency_weight_attribute(&nv);
1428 assert!(result.to_string() == "Some (100f64)" || result.to_string() == "Some (100.0f64)");
1429 }
1430
1431 #[test]
1432 fn test_async_cache_attributes_builder() {
1433 let attrs = AsyncCacheAttributes::builder()
1434 .limit(quote! { Some(100) })
1435 .policy(quote! { "lru" })
1436 .ttl(quote! { Some(60) })
1437 .custom_name(Some("my_cache".to_string()))
1438 .build();
1439
1440 assert_eq!(attrs.limit.to_string(), "Some (100)");
1441 assert_eq!(attrs.policy.to_string(), "\"lru\"");
1442 assert_eq!(attrs.ttl.to_string(), "Some (60)");
1443 assert_eq!(attrs.custom_name, Some("my_cache".to_string()));
1444 }
1445
1446 #[test]
1447 fn test_sync_cache_attributes_builder() {
1448 let attrs = SyncCacheAttributes::builder()
1449 .limit(quote! { Some(200) })
1450 .policy(quote! { cachelito_core::EvictionPolicy::FIFO })
1451 .scope(quote! { cachelito_core::CacheScope::ThreadLocal })
1452 .ttl(quote! { Some(120) })
1453 .build();
1454
1455 assert_eq!(attrs.limit.to_string(), "Some (200)");
1456 assert_eq!(
1457 attrs.policy.to_string(),
1458 "cachelito_core :: EvictionPolicy :: FIFO"
1459 );
1460 assert_eq!(
1461 attrs.scope.to_string(),
1462 "cachelito_core :: CacheScope :: ThreadLocal"
1463 );
1464 assert_eq!(attrs.ttl.to_string(), "Some (120)");
1465 }
1466
1467 #[test]
1468 fn test_builder_with_common_attributes() {
1469 let mut common = CommonAttributes::new();
1470 common.custom_name = Some("test".to_string());
1471 common.tags = vec!["tag1".to_string(), "tag2".to_string()];
1472 common.frequency_weight = quote! { Some(1.5) };
1473
1474 let attrs = AsyncCacheAttributes::builder()
1475 .limit(quote! { Some(50) })
1476 .with_common(common)
1477 .build();
1478
1479 assert_eq!(attrs.custom_name, Some("test".to_string()));
1480 assert_eq!(attrs.tags, vec!["tag1".to_string(), "tag2".to_string()]);
1481 assert_eq!(attrs.frequency_weight.to_string(), "Some (1.5)");
1482 assert_eq!(attrs.limit.to_string(), "Some (50)");
1483 }
1484}