query_flow_macros/
lib.rs

1//! Procedural macros for query-flow.
2//!
3//! This crate provides attribute macros for defining queries, asset keys,
4//! and asset locators with minimal boilerplate.
5//!
6//! # Query Example
7//!
8//! ```ignore
9//! use query_flow::{query, Db, QueryError};
10//!
11//! #[query]
12//! pub fn add(db: &impl Db, 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//! // Generates:
30//! // impl AssetKey for ConfigFile { type Asset = String; ... }
31//! ```
32//!
33//! # Asset Locator Example
34//!
35//! ```ignore
36//! use query_flow::{asset_locator, Db, LocateResult, QueryError};
37//!
38//! #[asset_locator]
39//! fn pending(_db: &impl Db, _key: &ConfigFile) -> Result<LocateResult<String>, QueryError> {
40//!     Ok(LocateResult::Pending)
41//! }
42//!
43//! // Generates:
44//! // struct Pending;
45//! // impl AssetLocator<ConfigFile> for Pending { ... }
46//! ```
47
48mod asset_key;
49mod asset_locator;
50mod query;
51
52use darling::{ast::NestedMeta, FromMeta as _};
53use proc_macro::TokenStream;
54use syn::{parse_macro_input, Item, ItemFn};
55
56use crate::{
57    asset_key::{generate_asset_key, AssetKeyAttr},
58    asset_locator::generate_asset_locator,
59    query::{generate_query, QueryAttr},
60};
61
62/// Define a query from a function.
63///
64/// # Attributes
65///
66/// - `output_eq = path`: Custom equality function (default: PartialEq)
67/// - `keys(a, b, ...)`: Specify which params form the cache key
68/// - `name = "Name"`: Override generated struct name
69///
70/// # Example
71///
72/// ```ignore
73/// use query_flow::{query, Db, QueryError};
74///
75/// // Basic query - all params are keys
76/// #[query]
77/// fn add(db: &impl Db, a: i32, b: i32) -> Result<i32, QueryError> {
78///     Ok(a + b)
79/// }
80///
81/// // With options
82/// #[query(keys(id))]
83/// pub fn fetch_user(db: &impl Db, id: u64, include_deleted: bool) -> Result<User, QueryError> {
84///     // include_deleted is NOT part of the cache key
85///     Ok(load_user(id, include_deleted))
86/// }
87/// ```
88#[proc_macro_attribute]
89pub fn query(attr: TokenStream, item: TokenStream) -> TokenStream {
90    let attr_args = match NestedMeta::parse_meta_list(attr.into()) {
91        Ok(v) => v,
92        Err(e) => return TokenStream::from(e.to_compile_error()),
93    };
94
95    let attr = match QueryAttr::from_list(&attr_args) {
96        Ok(v) => v,
97        Err(e) => return TokenStream::from(e.write_errors()),
98    };
99
100    let input_fn = parse_macro_input!(item as ItemFn);
101
102    match generate_query(attr, input_fn) {
103        Ok(tokens) => tokens.into(),
104        Err(e) => e.to_compile_error().into(),
105    }
106}
107
108/// Define an asset key type.
109///
110/// # Attributes
111///
112/// - `asset = Type`: The asset type this key loads (required)
113/// - `asset_eq`: Use PartialEq for asset comparison (default)
114/// - `asset_eq = path`: Use custom function for asset comparison
115///
116/// Durability is specified when calling `resolve_asset()`, not on the type.
117///
118/// # Example
119///
120/// ```ignore
121/// use query_flow::{asset_key, DurabilityLevel};
122/// use std::path::PathBuf;
123///
124/// #[asset_key(asset = String)]
125/// pub struct ConfigFile(pub PathBuf);
126///
127/// // Custom equality
128/// #[asset_key(asset = ImageData, asset_eq = image_bytes_eq)]
129/// pub struct TexturePath(pub String);
130///
131/// // When resolving:
132/// runtime.resolve_asset(ConfigFile(path), content, DurabilityLevel::Volatile);
133/// ```
134#[proc_macro_attribute]
135pub fn asset_key(attr: TokenStream, item: TokenStream) -> TokenStream {
136    let attr_args = match NestedMeta::parse_meta_list(attr.into()) {
137        Ok(v) => v,
138        Err(e) => return TokenStream::from(e.to_compile_error()),
139    };
140
141    let attr = match AssetKeyAttr::from_list(&attr_args) {
142        Ok(v) => v,
143        Err(e) => return TokenStream::from(e.write_errors()),
144    };
145
146    let input = parse_macro_input!(item as Item);
147
148    match generate_asset_key(attr, input) {
149        Ok(tokens) => tokens.into(),
150        Err(e) => e.to_compile_error().into(),
151    }
152}
153
154/// Define an asset locator from a function.
155///
156/// This macro generates a struct and `AssetLocator` implementation from a
157/// locator function, making it easier to define inline locators in tests.
158///
159/// The function name is converted to PascalCase for the struct name.
160/// The `db` parameter name can be anything (commonly `_db` if unused).
161/// The key type is inferred from the second parameter.
162///
163/// # Example
164///
165/// ```ignore
166/// use query_flow::{asset_locator, Db, LocateResult, QueryError};
167///
168/// // Basic locator - returns Pending
169/// #[asset_locator]
170/// fn pending(_db: &impl Db, _key: &ConfigFile) -> Result<LocateResult<String>, QueryError> {
171///     Ok(LocateResult::Pending)
172/// }
173///
174/// // Generates:
175/// // struct Pending;
176/// // impl AssetLocator<ConfigFile> for Pending { ... }
177///
178/// // Use it:
179/// runtime.register_asset_locator(Pending);
180/// ```
181#[proc_macro_attribute]
182pub fn asset_locator(_attr: TokenStream, item: TokenStream) -> TokenStream {
183    let input_fn = parse_macro_input!(item as ItemFn);
184
185    match generate_asset_locator(input_fn) {
186        Ok(tokens) => tokens.into(),
187        Err(e) => e.to_compile_error().into(),
188    }
189}