Skip to main content

uxar_macros/
lib.rs

1
2mod schemable;
3mod route;
4mod bundle;
5mod validate;
6mod filterable;
7mod bindable;
8mod scannable;
9mod bitrole;
10mod cron;
11mod periodic;
12mod pgnotify;
13mod signal;
14mod task;
15mod flow;
16mod assets;
17mod openapi;
18mod bundlepart;
19mod service;
20
21
22use proc_macro::TokenStream;
23extern crate proc_macro;
24
25
26/// Derives the Validate trait for data validation.
27/// 
28/// Generates validation logic based on `#[validate(...)]` attributes.
29/// 
30/// # Attributes
31/// 
32/// ## `#[validate(...)]`
33/// - `delegate` - Delegate validation to the field's type (must implement `Validate`)
34/// - `custom = "path"` - Call a custom validation function: `fn(&T) -> Result<(), ValidationReport>`
35/// - String: `min_length`, `max_length`, `exact_length`, `pattern`
36/// - String formats: `email`, `url`, `uuid`, `phone_e164`, `ipv4`, `ipv6`
37/// - Numeric: `min`, `max`, `exclusive_min`, `exclusive_max`, `multiple_of`
38/// - Array: `min_items`, `max_items`, `unique_items`
39#[proc_macro_derive(Validate, attributes(validate))]
40pub fn derive_validate(input: TokenStream) -> TokenStream {
41    validate::derive_validate_impl(input)
42}
43
44
45
46/// Defines a route handler with metadata for routing and OpenAPI documentation.
47/// 
48/// Can be applied to free functions or methods in impl blocks.
49/// 
50/// # Required Attributes
51/// 
52/// - `method` - HTTP method: `"get"`, `"post"`, `"put"`, `"patch"`, `"delete"`, `"head"`, `"options"`, or `"trace"`
53/// - `url` - Path pattern with optional parameters in braces: `"/users/{id}"`
54/// 
55/// # Optional Attributes
56/// 
57/// - `tags` - Array of OpenAPI tags: `tags = ["users", "api"]`
58/// - `name` - Route name for reverse routing (defaults to function name)
59/// - `summary` - Short description for OpenAPI (defaults to first doc comment line)
60/// - `description` - Detailed description for OpenAPI (defaults to remaining doc comments)
61/// 
62/// # Examples
63/// 
64/// ```ignore
65/// // Free function
66/// #[route(method = "get", url = "/users/{id}", tags = ["users"])]
67/// async fn get_user(Path(id): Path<i32>) -> Json<User> {
68///     // ...
69/// }
70/// 
71/// // Method in impl block
72/// impl UserApi {
73///     #[route(method = "post", url = "/users", tags = ["users"])]
74///     async fn create_user(Json(data): Json<CreateUser>) -> Json<User> {
75///         // ...
76///     }
77/// }
78/// ```
79#[proc_macro_attribute]
80pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
81    route::parse_route(attr, item)
82}
83
84/// Collects bundle parts (routes, tasks, signals) into a Bundle for composition and registration.
85///
86/// Bundles are the primary unit for organizing and composing application components.
87/// Each handler must be annotated with appropriate macros (`#[route]`, `#[cron]`, `#[periodic]`, etc.).
88/// 
89/// # Syntax
90/// 
91/// ```ignore
92/// bundle! {
93///     handler1,
94///     handler2,
95///     ...,
96///     tags = ["tag1", "tag2"]  // optional, applies only to routes
97/// }
98/// ```
99/// 
100/// # Options
101/// 
102/// - `tags` - Optional array of tags to apply to all routes in the bundle.
103///            These tags extend (not replace) any tags defined on individual routes.
104///            Note: tags only apply to route parts, not other bundle parts.
105/// 
106/// # Examples
107/// 
108/// ```ignore
109/// // Bundle without tags
110/// let user_bundle = bundle! {
111///     get_user,        // #[route]
112///     create_user,     // #[route]
113///     sync_users,      // #[cron]
114/// };
115/// 
116/// // Bundle with tags - extends individual route tags
117/// let api_bundle = bundle! {
118///     tags = ["api", "v1"],
119///     get_user,
120///     create_user,
121/// };
122/// 
123/// // Compose bundles
124/// let all_bundles = bundle! {
125///     user_bundle,
126///     api_bundle,
127/// };
128/// ```
129/// 
130/// # Notes
131/// 
132/// - Handlers must be annotated with `#[route]`, `#[cron]`, `#[periodic]`, `#[pgnotify]`, or `#[signal]`
133/// - Handlers can be free functions or references to IntoBundle types
134/// - Tags are additive and only apply to route parts
135/// - Returns a `Bundle` that implements `IntoBundle`
136#[proc_macro]
137pub fn bundle(input: TokenStream) -> TokenStream {
138    bundle::parse_bundle(input)
139}
140
141
142#[proc_macro_derive(Filterable, attributes(filterable, filter))]
143pub fn derive_filterable(input: TokenStream) -> TokenStream {
144    filterable::derive_filterable(input)
145}
146
147#[proc_macro_derive(Bindable, attributes(field, column))]
148pub fn derive_bindable(input: TokenStream) -> TokenStream {
149    bindable::derive_bindable(input)
150}
151
152#[proc_macro_derive(Scannable, attributes(field, column))]
153pub fn derive_scannable(input: TokenStream) -> TokenStream {
154    scannable::derive_scannable(input)
155}
156
157/// Derives the BitRole trait for role-based access control.
158/// 
159/// Automatically implements BitRole for enums with unit variants only.
160/// Each variant is assigned a bit position (0, 1, 2, ...) for role masking.
161/// 
162/// # Requirements
163/// - Only unit variants allowed (no tuple or struct variants)
164/// - Explicit discriminants must be > 0
165/// - Enum must derive Copy, Debug, and implement IntoEnumIterator (from strum)
166/// 
167/// # Example
168/// ```ignore
169/// #[derive(Debug, Copy, Clone, BitRole, EnumIter)]
170/// enum UserRole {
171///     Viewer = 1,
172///     Editor = 2,
173///     Admin = 3,
174/// }
175/// ```
176#[proc_macro_derive(BitRole, attributes(bitrole))]
177pub fn derive_bitrole(input: TokenStream) -> TokenStream {
178    bitrole::derive_bitrole(input)
179}
180
181/// Schedules a function to run periodically based on a cron expression.
182/// 
183/// Annotated functions will be registered as cron jobs in the bundle.
184/// The function must accept a `Site` parameter and return a type that can be
185/// wrapped in `SignalPayload`.
186/// 
187/// # Attributes
188/// 
189/// - `expr` - Cron expression (required): `"0 0 * * *"` (daily at midnight)
190/// 
191/// # Examples
192/// 
193/// ```ignore
194/// // Free function
195/// #[cron(expr = "0 0 * * *")]
196/// fn sync_daily(site: Site) -> SyncResult {
197///     // runs daily at midnight
198/// }
199/// 
200/// // Method in impl block
201/// impl SyncTasks {
202///     #[cron(expr = "*/5 * * * *")]
203///     fn sync_frequent(site: Site) -> SyncResult {
204///         // runs every 5 minutes
205///     }
206/// }
207/// ```
208#[proc_macro_attribute]
209pub fn cron(attr: TokenStream, item: TokenStream) -> TokenStream {
210    cron::parse_cron(attr, item)
211}
212
213/// Schedules a function to run periodically at fixed intervals.
214/// 
215/// Annotated functions will be registered as periodic tasks in the bundle.
216/// The function must accept a `Site` parameter and return a type that can be
217/// wrapped in `SignalPayload`.
218/// 
219/// # Attributes
220/// 
221/// - `secs` - Interval in seconds (optional)
222/// - `millis` - Interval in milliseconds (optional)
223/// 
224/// At least one of `secs` or `millis` must be specified. Both can be used together.
225/// 
226/// # Examples
227/// 
228/// ```ignore
229/// // Free function - runs every 30 seconds
230/// #[periodic(secs = 30)]
231/// fn health_check(site: Site) -> CheckResult {
232///     // ...
233/// }
234/// 
235/// // Method - runs every 500ms
236/// impl Monitor {
237///     #[periodic(millis = 500)]
238///     fn monitor_metrics(site: Site) -> Metrics {
239///         // ...
240///     }
241/// }
242/// 
243/// // Combined - runs every 1.5 seconds
244/// #[periodic(secs = 1, millis = 500)]
245/// fn poll_queue(site: Site) -> QueueStatus {
246///     // ...
247/// }
248/// ```
249#[proc_macro_attribute]
250pub fn periodic(attr: TokenStream, item: TokenStream) -> TokenStream {
251    periodic::parse_periodic(attr, item)
252}
253
254/// Registers a function as a PostgreSQL NOTIFY/LISTEN handler.
255/// 
256/// Annotated functions will listen for notifications on a PostgreSQL channel.
257/// The function must accept a `&str` payload and return `Result<T, SignalError>`
258/// where T can be wrapped in `SignalPayload`.
259/// 
260/// # Attributes
261/// 
262/// - `channel` - PostgreSQL channel name (required): `"user_updates"`
263/// 
264/// # Examples
265/// 
266/// ```ignore
267/// // Free function
268/// #[pgnotify(channel = "user_updates")]
269/// fn handle_user_update(payload: &str) -> Result<UserUpdate, SignalError> {
270///     serde_json::from_str(payload)
271///         .map_err(|_| SignalError::PayloadTypeMismatch)
272/// }
273/// 
274/// // Method in impl block
275/// impl UserHandlers {
276///     #[pgnotify(channel = "notifications")]
277///     fn handle_notification(payload: &str) -> Result<Notification, SignalError> {
278///         // parse and return notification
279///     }
280/// }
281/// ```
282#[proc_macro_attribute]
283pub fn pgnotify(attr: TokenStream, item: TokenStream) -> TokenStream {
284    pgnotify::parse_pgnotify(attr, item)
285}
286
287/// Registers a function as a generic signal handler.
288/// 
289/// Annotated functions will be registered to handle any signal events.
290/// The function must accept `Site` and `Arc<dyn Any + Send + Sync>` parameters
291/// and return a Future.
292/// 
293/// # Examples
294/// 
295/// ```ignore
296/// // Free function
297/// #[signal]
298/// async fn handle_signal(site: Site, payload: Arc<dyn Any + Send + Sync>) {
299///     // handle generic signal
300/// }
301/// 
302/// // Method in impl block
303/// impl SignalHandlers {
304///     #[signal]
305///     async fn process_event(site: Site, payload: Arc<dyn Any + Send + Sync>) {
306///         // process event
307///     }
308/// }
309/// ```
310#[proc_macro_attribute]
311pub fn signal(attr: TokenStream, item: TokenStream) -> TokenStream {
312    signal::parse_signal(attr, item)
313}
314
315/// Registers a function as a unit task handler.
316/// 
317/// Unit tasks are async operations that execute once and complete. The function
318/// must accept `Site` and a deserializable input type, returning a TaskUnitOutput.
319/// 
320/// # Attributes
321/// 
322/// - `name` - Optional task name (defaults to function name)
323/// 
324/// # Examples
325/// 
326/// ```ignore
327/// // Free function with default name
328/// #[task]
329/// async fn send_email(site: Site, input: EmailData) -> Result<TaskUnitOutput, TaskError> {
330///     // send email
331/// }
332/// 
333/// // Method with custom name
334/// impl TaskHandlers {
335///     #[task(name = "custom_task_name")]
336///     async fn process_order(site: Site, order: Order) -> Result<TaskUnitOutput, TaskError> {
337///         // process order
338///     }
339/// }
340/// ```
341#[proc_macro_attribute]
342pub fn task(attr: TokenStream, item: TokenStream) -> TokenStream {
343    task::parse_task(attr, item)
344}
345
346/// Registers a function as a flow task handler.
347/// 
348/// Flow tasks are synchronous operations that can spawn child tasks. The function
349/// accepts a deserializable input type and returns TaskFlowOutput.
350/// 
351/// # Attributes
352/// 
353/// - `name` - Optional task name (defaults to function name)
354/// 
355/// # Examples
356/// 
357/// ```ignore
358/// // Free function with default name
359/// #[flow]
360/// fn process_batch(input: BatchData) -> Result<TaskFlowOutput, TaskError> {
361///     // process and potentially spawn child tasks
362/// }
363/// 
364/// // Method with custom name
365/// impl FlowHandlers {
366///     #[flow(name = "workflow_step")]
367///     fn execute_workflow(data: WorkflowData) -> Result<TaskFlowOutput, TaskError> {
368///         // execute workflow step
369///     }
370/// }
371/// ```
372#[proc_macro_attribute]
373pub fn flow(attr: TokenStream, item: TokenStream) -> TokenStream {
374    flow::parse_flow(attr, item)
375}
376
377
378
379// #[proc_macro_attribute]
380// pub fn fnspec(attr: TokenStream, item: TokenStream) -> TokenStream {
381//     fnspec::parse_fnspec_input(attr, item, "fnspec")
382// }
383
384
385#[proc_macro_attribute]
386pub fn openapi(attr: TokenStream, item: TokenStream) -> TokenStream {
387    openapi::parse_openapi(attr, item)
388}
389
390
391#[proc_macro_attribute]
392pub fn service(attr: TokenStream, item: TokenStream) -> TokenStream {
393    service::parse_service(attr, item)
394}
395
396
397#[proc_macro_attribute]
398pub fn asset_dir(attr: TokenStream, item: TokenStream) -> TokenStream {
399    assets::parse_asset_dir(attr, item)
400}