Skip to main content

hitbox_derive/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use darling::FromDeriveInput;
4use proc_macro2::TokenStream;
5use quote::quote;
6use syn::{DeriveInput, Error, ItemFn, parse_macro_input};
7
8mod cacheable_request;
9mod cacheable_response;
10mod cached;
11mod generator;
12mod parser;
13
14use crate::generator::Generator;
15use crate::parser::Source;
16
17/// Derive macro for `KeyExtract` trait.
18///
19/// Generates an implementation of `KeyExtract` that creates key parts from struct fields.
20///
21/// # Attributes
22///
23/// - `#[key_extract(name = "...")]` - Override the key part name (default: field name)
24/// - `#[key_extract(skip)]` - Skip this field in key generation
25///
26/// # Example
27///
28/// ```ignore
29/// use hitbox_fn::KeyExtract;
30///
31/// #[derive(KeyExtract)]
32/// struct UserRequest {
33///     user_id: u64,
34///     #[key_extract(skip)]
35///     password: String,
36/// }
37/// ```
38#[proc_macro_derive(KeyExtract, attributes(key_extract))]
39pub fn derive_key_extract(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
40    let input = parse_macro_input!(input as DeriveInput);
41    match derive_impl(&input) {
42        Ok(output) => output.into(),
43        Err(error) => error.to_compile_error().into(),
44    }
45}
46
47fn derive_impl(input: &DeriveInput) -> Result<TokenStream, Error> {
48    let source = Source::from_derive_input(input)?;
49    let fields = source.struct_fields()?.collect::<Vec<_>>();
50    let generator = Generator::new(&source, &fields);
51    Ok(quote! { #generator })
52}
53
54/// Attribute macro for caching async functions.
55///
56/// Transforms an async function to return a builder that can be configured with
57/// backend and policy before execution.
58///
59/// # Attributes
60///
61/// - `prefix = "..."` - Custom prefix for the cache key (default: function name)
62///
63/// # Example
64///
65/// ```ignore
66/// use hitbox_fn::cached;
67///
68/// #[cached]
69/// async fn fetch_user(id: UserId, tenant: TenantId) -> Result<User, Error> {
70///     // expensive operation
71/// }
72///
73/// // With custom prefix
74/// #[cached(prefix = "user_data")]
75/// async fn fetch_user(id: UserId) -> Result<User, Error> {
76///     // expensive operation
77/// }
78///
79/// // Zero-argument function
80/// #[cached(prefix = "app_config")]
81/// async fn get_config() -> Result<Config, Error> {
82///     // expensive operation
83/// }
84///
85/// // Usage with pre-configured cache
86/// let cache = Cache::builder()
87///     .backend(backend)
88///     .policy(policy)
89///     .build();
90///
91/// let user = fetch_user(UserId(42), TenantId("acme".into()))
92///     .cache(&cache)
93///     .await?;
94///
95/// // Usage with context
96/// let (user, ctx) = fetch_user(UserId(42), TenantId("acme".into()))
97///     .cache(&cache)
98///     .with_context()
99///     .await;
100/// println!("Cache status: {:?}", ctx.status);
101///
102/// // Usage with inline configuration
103/// let user = fetch_user(UserId(42), TenantId("acme".into()))
104///     .backend(backend)
105///     .policy(policy)
106///     .await?;
107/// ```
108#[proc_macro_attribute]
109pub fn cached(
110    attr: proc_macro::TokenStream,
111    item: proc_macro::TokenStream,
112) -> proc_macro::TokenStream {
113    let item = parse_macro_input!(item as ItemFn);
114    match cached::expand(attr.into(), item) {
115        Ok(output) => output.into(),
116        Err(error) => error.to_compile_error().into(),
117    }
118}
119
120/// Derive macro for `CacheableResponse` trait.
121///
122/// Generates an implementation of `CacheableResponse` where the type is cached as itself.
123/// The type must implement `Clone + Serialize + DeserializeOwned + Send + 'static`.
124///
125/// # Example
126///
127/// ```ignore
128/// use hitbox_derive::CacheableResponse;
129/// use serde::{Deserialize, Serialize};
130///
131/// #[derive(Clone, Serialize, Deserialize, CacheableResponse)]
132/// struct User {
133///     id: u64,
134///     name: String,
135/// }
136///
137/// // Now User can be used as a return type in cached functions:
138/// #[cached]
139/// async fn fetch_user(id: u64) -> Result<User, Error> {
140///     // ...
141/// }
142/// ```
143#[proc_macro_derive(CacheableResponse, attributes(cacheable_response))]
144pub fn derive_cacheable_response(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
145    let input = parse_macro_input!(input as DeriveInput);
146    match cacheable_response::expand(&input) {
147        Ok(output) => output.into(),
148        Err(error) => error.to_compile_error().into(),
149    }
150}
151
152/// Derive macro for `CacheableRequest` trait.
153///
154/// Generates an implementation of `CacheableRequest` with standard cache policy logic.
155/// The type can then participate in the hitbox caching pipeline.
156///
157/// # Example
158///
159/// ```ignore
160/// use hitbox_derive::CacheableRequest;
161///
162/// #[derive(CacheableRequest)]
163/// struct SearchRequest {
164///     query: String,
165///     page: u32,
166/// }
167/// ```
168#[proc_macro_derive(CacheableRequest, attributes(cacheable_request))]
169pub fn derive_cacheable_request(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
170    let input = parse_macro_input!(input as DeriveInput);
171    match cacheable_request::expand(&input) {
172        Ok(output) => output.into(),
173        Err(error) => error.to_compile_error().into(),
174    }
175}