query_flow_macros/lib.rs
1//! Procedural macros for query-flow.
2//!
3//! This crate provides attribute macros for defining queries and asset keys
4//! with minimal boilerplate.
5//!
6//! # Query Example
7//!
8//! ```ignore
9//! use query_flow::{query, QueryContext, QueryError};
10//!
11//! #[query]
12//! pub fn add(ctx: &mut QueryContext, a: i32, b: i32) -> Result<i32, QueryError> {
13//! Ok(a + b)
14//! }
15//!
16//! // Generates:
17//! // pub struct Add { pub a: i32, pub b: i32 }
18//! // impl Query for Add { ... }
19//! ```
20//!
21//! # Asset Key Example
22//!
23//! ```ignore
24//! use query_flow::asset_key;
25//!
26//! #[asset_key(asset = String)]
27//! pub struct ConfigFile(pub PathBuf);
28//!
29//! #[asset_key(asset = String, durability = constant)]
30//! pub struct BundledAsset(pub PathBuf);
31//!
32//! // Generates:
33//! // impl AssetKey for ConfigFile { type Asset = String; ... }
34//! ```
35
36mod asset_key;
37mod query;
38
39use darling::{ast::NestedMeta, FromMeta as _};
40use proc_macro::TokenStream;
41use syn::{parse_macro_input, ItemFn, ItemStruct};
42
43use crate::{
44 asset_key::{generate_asset_key, AssetKeyAttr},
45 query::{generate_query, QueryAttr},
46};
47
48/// Define a query from a function.
49///
50/// # Attributes
51///
52/// - `durability = N`: Set durability level (0-255, default 0)
53/// - `output_eq = path`: Custom equality function (default: PartialEq)
54/// - `keys(a, b, ...)`: Specify which params form the cache key
55/// - `name = "Name"`: Override generated struct name
56///
57/// # Example
58///
59/// ```ignore
60/// use query_flow::{query, QueryContext, QueryError};
61///
62/// // Basic query - all params are keys
63/// #[query]
64/// fn add(ctx: &mut QueryContext, a: i32, b: i32) -> Result<i32, QueryError> {
65/// Ok(a + b)
66/// }
67///
68/// // With options
69/// #[query(durability = 2, keys(id))]
70/// pub fn fetch_user(ctx: &mut QueryContext, id: u64, include_deleted: bool) -> Result<User, QueryError> {
71/// // include_deleted is NOT part of the cache key
72/// Ok(load_user(id, include_deleted))
73/// }
74/// ```
75#[proc_macro_attribute]
76pub fn query(attr: TokenStream, item: TokenStream) -> TokenStream {
77 let attr_args = match NestedMeta::parse_meta_list(attr.into()) {
78 Ok(v) => v,
79 Err(e) => return TokenStream::from(e.to_compile_error()),
80 };
81
82 let attr = match QueryAttr::from_list(&attr_args) {
83 Ok(v) => v,
84 Err(e) => return TokenStream::from(e.write_errors()),
85 };
86
87 let input_fn = parse_macro_input!(item as ItemFn);
88
89 match generate_query(attr, input_fn) {
90 Ok(tokens) => tokens.into(),
91 Err(e) => e.to_compile_error().into(),
92 }
93}
94
95/// Define an asset key type.
96///
97/// # Attributes
98///
99/// - `asset = Type`: The asset type this key loads (required)
100/// - `durability = volatile|session|stable|constant`: Durability level (default: volatile)
101/// - `asset_eq`: Use PartialEq for asset comparison (default)
102/// - `asset_eq = path`: Use custom function for asset comparison
103///
104/// # Example
105///
106/// ```ignore
107/// use query_flow::asset_key;
108/// use std::path::PathBuf;
109///
110/// // Default: volatile durability
111/// #[asset_key(asset = String)]
112/// pub struct ConfigFile(pub PathBuf);
113///
114/// // Explicit constant durability for bundled assets
115/// #[asset_key(asset = String, durability = constant)]
116/// pub struct BundledFile(pub PathBuf);
117///
118/// // Custom equality
119/// #[asset_key(asset = ImageData, asset_eq = image_bytes_eq)]
120/// pub struct TexturePath(pub String);
121/// ```
122#[proc_macro_attribute]
123pub fn asset_key(attr: TokenStream, item: TokenStream) -> TokenStream {
124 let attr_args = match NestedMeta::parse_meta_list(attr.into()) {
125 Ok(v) => v,
126 Err(e) => return TokenStream::from(e.to_compile_error()),
127 };
128
129 let attr = match AssetKeyAttr::from_list(&attr_args) {
130 Ok(v) => v,
131 Err(e) => return TokenStream::from(e.write_errors()),
132 };
133
134 let input_struct = parse_macro_input!(item as ItemStruct);
135
136 match generate_asset_key(attr, input_struct) {
137 Ok(tokens) => tokens.into(),
138 Err(e) => e.to_compile_error().into(),
139 }
140}