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}