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}