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, 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
33mod asset_key;
34mod query;
35
36use darling::{ast::NestedMeta, FromMeta as _};
37use proc_macro::TokenStream;
38use syn::{parse_macro_input, ItemFn, ItemStruct};
39
40use crate::{
41    asset_key::{generate_asset_key, AssetKeyAttr},
42    query::{generate_query, QueryAttr},
43};
44
45/// Define a query from a function.
46///
47/// # Attributes
48///
49/// - `output_eq = path`: Custom equality function (default: PartialEq)
50/// - `keys(a, b, ...)`: Specify which params form the cache key
51/// - `name = "Name"`: Override generated struct name
52///
53/// # Example
54///
55/// ```ignore
56/// use query_flow::{query, Db, QueryError};
57///
58/// // Basic query - all params are keys
59/// #[query]
60/// fn add(db: &impl Db, a: i32, b: i32) -> Result<i32, QueryError> {
61///     Ok(a + b)
62/// }
63///
64/// // With options
65/// #[query(keys(id))]
66/// pub fn fetch_user(db: &impl Db, id: u64, include_deleted: bool) -> Result<User, QueryError> {
67///     // include_deleted is NOT part of the cache key
68///     Ok(load_user(id, include_deleted))
69/// }
70/// ```
71#[proc_macro_attribute]
72pub fn query(attr: TokenStream, item: TokenStream) -> TokenStream {
73    let attr_args = match NestedMeta::parse_meta_list(attr.into()) {
74        Ok(v) => v,
75        Err(e) => return TokenStream::from(e.to_compile_error()),
76    };
77
78    let attr = match QueryAttr::from_list(&attr_args) {
79        Ok(v) => v,
80        Err(e) => return TokenStream::from(e.write_errors()),
81    };
82
83    let input_fn = parse_macro_input!(item as ItemFn);
84
85    match generate_query(attr, input_fn) {
86        Ok(tokens) => tokens.into(),
87        Err(e) => e.to_compile_error().into(),
88    }
89}
90
91/// Define an asset key type.
92///
93/// # Attributes
94///
95/// - `asset = Type`: The asset type this key loads (required)
96/// - `asset_eq`: Use PartialEq for asset comparison (default)
97/// - `asset_eq = path`: Use custom function for asset comparison
98///
99/// Durability is specified when calling `resolve_asset()`, not on the type.
100///
101/// # Example
102///
103/// ```ignore
104/// use query_flow::{asset_key, DurabilityLevel};
105/// use std::path::PathBuf;
106///
107/// #[asset_key(asset = String)]
108/// pub struct ConfigFile(pub PathBuf);
109///
110/// // Custom equality
111/// #[asset_key(asset = ImageData, asset_eq = image_bytes_eq)]
112/// pub struct TexturePath(pub String);
113///
114/// // When resolving:
115/// runtime.resolve_asset(ConfigFile(path), content, DurabilityLevel::Volatile);
116/// ```
117#[proc_macro_attribute]
118pub fn asset_key(attr: TokenStream, item: TokenStream) -> TokenStream {
119    let attr_args = match NestedMeta::parse_meta_list(attr.into()) {
120        Ok(v) => v,
121        Err(e) => return TokenStream::from(e.to_compile_error()),
122    };
123
124    let attr = match AssetKeyAttr::from_list(&attr_args) {
125        Ok(v) => v,
126        Err(e) => return TokenStream::from(e.write_errors()),
127    };
128
129    let input_struct = parse_macro_input!(item as ItemStruct);
130
131    match generate_asset_key(attr, input_struct) {
132        Ok(tokens) => tokens.into(),
133        Err(e) => e.to_compile_error().into(),
134    }
135}