Skip to main content

reinhardt_macros/
lib.rs

1//! # Reinhardt Procedural Macros
2//!
3//! Provides Django-style decorators as Rust procedural macros.
4//!
5//! ## Macros
6//!
7//! - `#[routes]` - Register URL pattern function for automatic discovery
8//! - `#[api_view]` - Convert function to API view
9//! - `#[action]` - Define custom ViewSet action
10//! - `#[get]`, `#[post]`, etc. - HTTP method decorators
11//! - `#[permission_required]` - Permission decorator
12//!
13
14#![warn(missing_docs)]
15
16use proc_macro::TokenStream;
17use syn::{ItemFn, ItemStruct, parse_macro_input};
18
19mod action;
20mod admin;
21mod api_view;
22mod app_config_attribute;
23mod app_config_derive;
24mod apply_update_attribute;
25mod apply_update_derive;
26mod collect_migrations;
27mod crate_paths;
28mod dto;
29mod flatten_imports;
30mod hook;
31mod http_error_derive;
32mod injectable_common;
33mod injectable_fn;
34mod injectable_struct;
35mod installed_apps;
36mod macro_state;
37mod model_attribute;
38mod model_derive;
39mod orm_reflectable_derive;
40mod pascal_case;
41mod path_macro;
42mod permission_macro;
43mod permissions;
44mod pk_shape;
45mod query_fields;
46mod receiver;
47mod rel;
48mod routes;
49mod routes_registration;
50mod schema;
51mod settings_compose;
52mod settings_fragment;
53pub(crate) mod settings_parser;
54mod settings_schema;
55mod streaming;
56mod streaming_patterns;
57mod use_inject;
58mod user_attribute;
59mod user_field_mapping;
60mod validate_derive;
61
62use action::action_impl;
63use admin::admin_impl;
64use api_view::api_view_impl;
65use app_config_attribute::app_config_attribute_impl;
66use apply_update_attribute::apply_update_attribute_impl;
67use apply_update_derive::apply_update_derive_impl;
68use http_error_derive::derive_http_error_impl;
69use injectable_fn::injectable_fn_impl;
70use injectable_struct::injectable_struct_impl;
71use installed_apps::installed_apps_impl;
72use model_attribute::model_attribute_impl;
73use model_derive::model_derive_impl;
74use orm_reflectable_derive::orm_reflectable_derive_impl;
75use path_macro::path_impl;
76use permissions::permission_required_impl;
77use query_fields::derive_query_fields_impl;
78use receiver::receiver_impl;
79use routes::{delete_impl, get_impl, patch_impl, post_impl, put_impl};
80use routes_registration::routes_impl;
81mod viewset_macro;
82mod websocket;
83use schema::derive_schema_impl;
84use use_inject::use_inject_impl;
85use user_attribute::user_attribute_impl;
86
87/// Decorator for function-based API views
88#[proc_macro_attribute]
89pub fn api_view(args: TokenStream, input: TokenStream) -> TokenStream {
90	let input = parse_macro_input!(input as ItemFn);
91
92	api_view_impl(args.into(), input)
93		.unwrap_or_else(|e| e.to_compile_error())
94		.into()
95}
96
97/// Decorator for ViewSet custom actions
98#[proc_macro_attribute]
99pub fn action(args: TokenStream, input: TokenStream) -> TokenStream {
100	let input = parse_macro_input!(input as ItemFn);
101
102	action_impl(args.into(), input)
103		.unwrap_or_else(|e| e.to_compile_error())
104		.into()
105}
106
107/// GET method decorator.
108///
109/// # Route name (`name = "..."`)
110///
111/// The optional `name` selects the identifier used for URL reversal
112/// (`reverse(...)`). Prefer kebab-case (e.g. `name = "users-list"`) to match
113/// ViewSet-generated names; a non-kebab name (snake_case / camelCase) emits a
114/// warning at compile time and again when routes are registered. Prefix the
115/// name with `!` (e.g. `name = "!legacy_name"`) to opt out — the sigil is
116/// stripped before storage, so reverse lookups use the clean name — or set
117/// `REINHARDT_URL_NAME_WARNINGS=0` to silence all such warnings. When omitted,
118/// the route name defaults to the function name and is exempt from the warning.
119/// The same convention applies to `#[post]`, `#[put]`, `#[patch]`, and
120/// `#[delete]`. Refs Issue #4901.
121#[proc_macro_attribute]
122pub fn get(args: TokenStream, input: TokenStream) -> TokenStream {
123	let input = parse_macro_input!(input as ItemFn);
124
125	get_impl(args.into(), input)
126		.unwrap_or_else(|e| e.to_compile_error())
127		.into()
128}
129
130/// POST method decorator
131#[proc_macro_attribute]
132pub fn post(args: TokenStream, input: TokenStream) -> TokenStream {
133	let input = parse_macro_input!(input as ItemFn);
134
135	post_impl(args.into(), input)
136		.unwrap_or_else(|e| e.to_compile_error())
137		.into()
138}
139
140/// PUT method decorator
141#[proc_macro_attribute]
142pub fn put(args: TokenStream, input: TokenStream) -> TokenStream {
143	let input = parse_macro_input!(input as ItemFn);
144
145	put_impl(args.into(), input)
146		.unwrap_or_else(|e| e.to_compile_error())
147		.into()
148}
149
150/// PATCH method decorator
151#[proc_macro_attribute]
152pub fn patch(args: TokenStream, input: TokenStream) -> TokenStream {
153	let input = parse_macro_input!(input as ItemFn);
154
155	patch_impl(args.into(), input)
156		.unwrap_or_else(|e| e.to_compile_error())
157		.into()
158}
159
160/// DELETE method decorator
161#[proc_macro_attribute]
162pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream {
163	let input = parse_macro_input!(input as ItemFn);
164
165	delete_impl(args.into(), input)
166		.unwrap_or_else(|e| e.to_compile_error())
167		.into()
168}
169
170/// Producer handler decorator — auto-publishes return value to a Kafka topic.
171///
172/// # Arguments
173///
174/// - `topic` — Kafka topic to publish to
175/// - `name` — handler identifier for topic resolution via `resolve_streaming_topic()`
176///
177/// # Example
178///
179/// ```rust,ignore
180/// #[producer(topic = "orders", name = "create_order")]
181/// pub async fn create_order(cmd: CreateOrderCommand) -> Result<Order, StreamingError> {
182///     Ok(Order::from(cmd))
183/// }
184/// ```
185#[proc_macro_attribute]
186pub fn producer(args: TokenStream, input: TokenStream) -> TokenStream {
187	let input = parse_macro_input!(input as ItemFn);
188	streaming::producer_impl(args.into(), input)
189		.unwrap_or_else(|e| e.to_compile_error())
190		.into()
191}
192
193/// Consumer handler decorator — receives messages from a Kafka topic.
194///
195/// # Arguments
196///
197/// - `topic` — Kafka topic to consume from
198/// - `group` — Consumer group id
199/// - `name` — handler identifier for topic resolution via `resolve_streaming_topic()`
200///
201/// # Example
202///
203/// ```rust,ignore
204/// #[consumer(topic = "orders", group = "order-processor", name = "handle_order")]
205/// pub async fn handle_order(msg: Message<Order>) -> Result<(), StreamingError> {
206///     Ok(())
207/// }
208/// ```
209#[proc_macro_attribute]
210pub fn consumer(args: TokenStream, input: TokenStream) -> TokenStream {
211	let input = parse_macro_input!(input as ItemFn);
212	streaming::consumer_impl(args.into(), input)
213		.unwrap_or_else(|e| e.to_compile_error())
214		.into()
215}
216
217/// Streaming patterns attribute — generates per-app streaming topic resolver structs.
218///
219/// Apply to the function that builds and returns the app's `StreamingRouter`.
220/// The function body must contain `streaming_routes![handler1, handler2, ...]`.
221///
222/// # Arguments
223///
224/// - First positional arg: `InstalledApp::<Variant>` — the app's label (or any path/ident)
225///
226/// # Example
227///
228/// ```rust,ignore
229/// #[streaming_patterns(InstalledApp::Orders)]
230/// pub fn streaming_routes() -> reinhardt_streaming::StreamingRouter {
231///     streaming_routes![create_order, handle_order]
232/// }
233/// ```
234///
235/// After this macro expands:
236/// - `OrdersStreamingUrls` struct is generated with `.create_order()` and `.handle_order()` methods
237/// - Each method returns the Kafka topic name as `&'static str`
238#[proc_macro_attribute]
239pub fn streaming_patterns(args: TokenStream, input: TokenStream) -> TokenStream {
240	streaming_patterns::streaming_patterns_impl(args.into(), input.into())
241		.unwrap_or_else(|e| e.to_compile_error())
242		.into()
243}
244
245/// Permission required decorator
246#[proc_macro_attribute]
247pub fn permission_required(args: TokenStream, input: TokenStream) -> TokenStream {
248	let input = parse_macro_input!(input as ItemFn);
249
250	permission_required_impl(args.into(), input)
251		.unwrap_or_else(|e| e.to_compile_error())
252		.into()
253}
254
255/// Defines installed applications with compile-time validation.
256///
257/// Generates an `InstalledApp` enum with variants for each application,
258/// along with `Display`, `FromStr` traits and helper methods.
259///
260/// **Important**: This macro is for **user applications only**. Built-in framework features
261/// (auth, sessions, admin, etc.) are enabled via Cargo feature flags, not through `installed_apps!`.
262///
263/// # Generated Code
264///
265/// The macro generates:
266///
267/// - `enum InstalledApp { ... }` - Type-safe app references with variants for each app
268/// - `impl Display` - Convert enum variants to path strings
269/// - `impl FromStr` - Parse path strings to enum variants
270/// - `fn all_apps() -> Vec<String>` - List all app paths as strings
271/// - `fn path(&self) -> &'static str` - Get app path without allocation
272///
273/// # Example
274///
275/// ```rust,ignore
276/// use reinhardt::installed_apps;
277///
278/// installed_apps! {
279///     users: "users",
280///     posts: "posts",
281/// }
282///
283/// // Use generated enum
284/// let app = InstalledApp::users;
285/// println!("{}", app);  // Output: "users"
286///
287/// // Get all apps
288/// let all = InstalledApp::all_apps();
289/// assert_eq!(all, vec!["users".to_string(), "posts".to_string()]);
290///
291/// // Parse from string
292/// use std::str::FromStr;
293/// let app = InstalledApp::from_str("users")?;
294/// assert_eq!(app, InstalledApp::users);
295///
296/// // Get path without allocation
297/// assert_eq!(app.path(), "users");
298/// ```
299///
300/// # Compile-time Validation
301///
302/// Framework modules (starting with `reinhardt.`) are validated at compile time.
303/// Non-existent modules will cause compilation errors:
304///
305/// ```rust,ignore
306/// installed_apps! {
307///     nonexistent: "reinhardt.contrib.nonexistent",
308/// }
309/// // Compile error: cannot find module `nonexistent` in `contrib`
310/// ```
311///
312/// User apps (not starting with `reinhardt.`) skip compile-time validation,
313/// allowing flexible user-defined application names.
314///
315/// # Framework Features
316///
317/// **Do NOT use this macro for built-in framework features.** Instead, enable them
318/// via Cargo feature flags:
319///
320/// ```toml
321/// [dependencies]
322/// reinhardt = { version = "0.1.0-alpha.1", features = ["auth", "sessions", "admin"] }
323/// ```
324///
325/// Then import them directly:
326///
327/// ```rust,ignore
328/// use reinhardt::auth::*;
329/// use reinhardt::auth::sessions::*;
330/// use reinhardt::admin::*;
331/// ```
332///
333/// # See Also
334///
335/// - Module documentation in `installed_apps.rs` for detailed information about
336///   generated code structure, trait implementations, and advanced usage
337/// - `crates/reinhardt-apps/README.md` for comprehensive usage guide
338/// - Tutorial: `docs/tutorials/en/basis/1-project-setup.md`
339///
340#[proc_macro]
341pub fn installed_apps(input: TokenStream) -> TokenStream {
342	installed_apps_impl(input.into())
343		.unwrap_or_else(|e| e.to_compile_error())
344		.into()
345}
346
347/// Register URL patterns for automatic discovery by the framework
348///
349/// This attribute macro automatically registers a function as the URL pattern
350/// provider for the framework. The function will be discovered and used when
351/// running management commands like `runserver`.
352///
353/// # Important: Single Usage Only
354///
355/// **Only one function per project can be annotated with `#[routes]`.**
356/// If multiple `#[routes]` attributes are used, the linker will fail with a
357/// "duplicate symbol" error for `__reinhardt_routes_registration_marker`.
358///
359/// To organize routes across multiple files, use the `.mount()` method:
360///
361/// ```rust,ignore
362/// // In src/config/urls.rs - Only ONE #[routes] in the entire project
363/// #[routes]
364/// pub fn routes() -> UnifiedRouter {
365///     UnifiedRouter::new()
366///         .mount("/api/", api::routes())   // api::routes() returns UnifiedRouter
367///         .mount("/admin/", admin::routes())  // WITHOUT #[routes] attribute
368/// }
369///
370/// // In src/apps/api/urls.rs - NO #[routes] attribute
371/// pub fn routes() -> UnifiedRouter {
372///     UnifiedRouter::new()
373///         .endpoint(views::list)
374///         .endpoint(views::create)
375/// }
376/// ```
377///
378/// # Supported Function Signatures
379///
380/// ## 1. Sync function (standard)
381///
382/// ```rust,ignore
383/// #[routes]
384/// pub fn routes() -> UnifiedRouter {
385///     UnifiedRouter::new()
386///         .endpoint(views::index)
387///         .mount("/api/", api::routes())
388/// }
389/// ```
390///
391/// ## 2. Async function (no DI)
392///
393/// ```rust,ignore
394/// #[routes]
395/// pub async fn routes() -> UnifiedRouter {
396///     UnifiedRouter::new()
397///         .mount("/api/", api::routes())
398/// }
399/// ```
400///
401/// ## 3. Async function with `#[inject]` (DI-aware)
402///
403/// ```rust,ignore
404/// #[routes]
405/// pub async fn routes(#[inject] router: UnifiedRouter) -> UnifiedRouter {
406///     router
407/// }
408/// ```
409///
410/// When `#[inject]` parameters are present, the macro automatically creates
411/// a DI context (`SingletonScope` + `InjectionContext`) and resolves each
412/// injected dependency before calling the function.
413///
414/// # Arguments
415///
416/// The `#[routes]` macro does not accept any arguments. It only emits
417/// `inventory::submit!(UrlPatternsRegistration)` and a linker marker.
418///
419/// Previous flags (`standalone`, `client_inventory`, `server_only`,
420/// `no_client_resolvers`, `no_ws_resolvers`) were removed as part of
421/// the URL routing simplification (Issue #4784).
422///
423/// # Notes
424///
425/// - The function can have any name (e.g., `routes`, `app_routes`, `url_patterns`)
426/// - The return type must be `UnifiedRouter` (not `Arc<UnifiedRouter>`)
427/// - The framework automatically wraps the router in `Arc`
428/// - Sync functions cannot use `#[inject]` (DI resolution is inherently async)
429#[proc_macro_attribute]
430pub fn routes(args: TokenStream, input: TokenStream) -> TokenStream {
431	let input = parse_macro_input!(input as ItemFn);
432
433	routes_impl(args.into(), input)
434		.unwrap_or_else(|e| e.to_compile_error())
435		.into()
436}
437
438/// Generate URL resolver traits for a ViewSet function.
439///
440/// When applied to a function returning a ViewSet (e.g., `ModelViewSet`), extracts
441/// the basename from the function body and generates `__url_resolver_{basename}_list`
442/// and `__url_resolver_{basename}_detail` modules.
443///
444/// # Example
445///
446/// ```rust,ignore
447/// #[viewset]
448/// pub fn viewset() -> ModelViewSet<Snippet, SnippetSerializer> {
449///     ModelViewSet::new("snippet")
450/// }
451/// // Generates: __url_resolver_snippet_list, __url_resolver_snippet_detail
452/// ```
453///
454#[doc = include_str!("upstream_workaround_note.md")]
455#[proc_macro_attribute]
456pub fn viewset(args: TokenStream, input: TokenStream) -> TokenStream {
457	viewset_macro::viewset_macro_impl(args.into(), input.into())
458		.unwrap_or_else(|e| e.to_compile_error())
459		.into()
460}
461
462/// Validate URL patterns at compile time
463///
464/// This macro validates URL pattern syntax at compile time, catching common errors
465/// before they reach runtime. It supports both simple parameters and Django-style
466/// typed parameters.
467///
468/// # Compile-time Validation
469///
470/// The macro will fail to compile if:
471/// - Braces are not properly matched (e.g., `{id` or `id}`)
472/// - Parameter names are empty (e.g., `{}`)
473/// - Parameter names contain invalid characters
474/// - Type specifiers are invalid (valid: `int`, `str`, `uuid`, `slug`, `path`)
475/// - Django-style parameters are used outside braces (e.g., `<int:id>` instead of `{<int:id>}`)
476///
477/// # Supported Type Specifiers
478///
479/// - `int` - Integer values
480/// - `str` - String values
481/// - `uuid` - UUID values
482/// - `slug` - Slug strings (alphanumeric, hyphens, underscores)
483/// - `path` - Path segments (can include slashes)
484///
485#[proc_macro]
486pub fn path(input: TokenStream) -> TokenStream {
487	path_impl(input.into())
488		.unwrap_or_else(|e| e.to_compile_error())
489		.into()
490}
491
492/// Connect a receiver function to a signal automatically
493///
494/// This macro provides Django-style `@receiver` decorator functionality for Rust.
495/// It automatically registers the function as a signal receiver at startup.
496///
497#[proc_macro_attribute]
498pub fn receiver(args: TokenStream, input: TokenStream) -> TokenStream {
499	let input = parse_macro_input!(input as ItemFn);
500
501	receiver_impl(args.into(), input)
502		.unwrap_or_else(|e| e.to_compile_error())
503		.into()
504}
505
506/// Attribute macro for registering lifecycle hooks.
507///
508/// Currently supports `runserver` hooks for extending server startup behavior.
509///
510/// # Usage
511///
512/// ```rust,ignore
513/// use reinhardt::commands::{RunserverHook, RunserverContext};
514///
515/// #[reinhardt::hook(on = runserver)]
516/// struct MyValidationHook;
517///
518/// #[async_trait]
519/// impl RunserverHook for MyValidationHook {
520///     async fn validate(&self) -> Result<(), Box<dyn Error + Send + Sync>> {
521///         // Fail-fast validation before server starts
522///         Ok(())
523///     }
524/// }
525/// ```
526#[proc_macro_attribute]
527pub fn hook(args: TokenStream, input: TokenStream) -> TokenStream {
528	let input = parse_macro_input!(input as ItemStruct);
529	hook::hook_impl(args.into(), input)
530		.unwrap_or_else(|e| e.to_compile_error())
531		.into()
532}
533
534/// Automatic dependency injection macro
535///
536/// This macro enables FastAPI-style dependency injection using parameter attributes.
537/// Parameters marked with `#[inject]` will be automatically resolved from the
538/// `InjectionContext`. Can be used with any function, not just endpoints.
539///
540/// # Generated Code
541///
542/// The macro transforms the function by:
543/// 1. Removing `#[inject]` parameters from the signature
544/// 2. Adding an `InjectionContext` parameter
545/// 3. Injecting dependencies at the start of the function
546///
547#[proc_macro_attribute]
548pub fn use_inject(args: TokenStream, input: TokenStream) -> TokenStream {
549	let input = parse_macro_input!(input as ItemFn);
550
551	use_inject_impl(args.into(), input)
552		.unwrap_or_else(|e| e.to_compile_error())
553		.into()
554}
555
556/// Derive macro for type-safe field lookups
557///
558/// Automatically generates field accessor methods for models, enabling
559/// compile-time validated field lookups.
560///
561/// # Generated Methods
562///
563/// For each field in the struct, the macro generates a static method that
564/// returns a `Field<Model, FieldType>`. The field type determines which
565/// lookup methods are available:
566///
567/// - String fields: `lower()`, `upper()`, `trim()`, `contains()`, etc.
568/// - Numeric fields: `abs()`, `ceil()`, `floor()`, `round()`
569/// - DateTime fields: `year()`, `month()`, `day()`, `hour()`, etc.
570/// - All fields: `eq()`, `ne()`, `gt()`, `gte()`, `lt()`, `lte()`
571///
572#[proc_macro_derive(QueryFields)]
573pub fn derive_query_fields(input: TokenStream) -> TokenStream {
574	let input = parse_macro_input!(input as syn::DeriveInput);
575
576	derive_query_fields_impl(input)
577		.unwrap_or_else(|e| e.to_compile_error())
578		.into()
579}
580
581/// Derive macro for automatic OpenAPI schema generation
582///
583/// Automatically implements the `ToSchema` trait for structs and enums,
584/// generating OpenAPI 3.0 schemas from Rust type definitions.
585///
586/// # Supported Types
587///
588/// - Primitives: `String`, `i32`, `i64`, `f32`, `f64`, `bool`
589/// - `Option<T>`: Makes fields optional in the schema
590/// - `Vec<T>`: Generates array schemas
591/// - Custom types implementing `ToSchema`
592///
593/// # Features
594///
595/// - Automatic field metadata extraction
596/// - Documentation comments become field descriptions
597/// - Required/optional field detection
598/// - Nested schema support
599/// - Enum variant handling
600///
601#[proc_macro_derive(Schema)]
602pub fn derive_schema(input: TokenStream) -> TokenStream {
603	let input = parse_macro_input!(input as syn::DeriveInput);
604
605	derive_schema_impl(input)
606		.unwrap_or_else(|e| e.to_compile_error())
607		.into()
608}
609
610/// Implements HTTP status and client-message mapping for application error enums.
611#[proc_macro_derive(HttpError, attributes(http_error))]
612pub fn derive_http_error(input: TokenStream) -> TokenStream {
613	let input = parse_macro_input!(input as syn::DeriveInput);
614
615	derive_http_error_impl(input)
616		.unwrap_or_else(|e| e.to_compile_error())
617		.into()
618}
619
620/// Attribute macro for injectable factory/provider functions and structs
621///
622/// This macro can be applied to both functions and structs to enable dependency injection.
623///
624/// # Field Attributes (Struct Only)
625///
626/// All struct fields must have either `#[inject]` or `#[no_inject]` attribute:
627///
628/// - **`#[inject]`**: Inject this field from the DI container
629/// - **`#[inject(cache = false)]`**: Inject without caching
630/// - **`#[inject(scope = Singleton)]`**: Use singleton scope
631/// - **`#[no_inject(default = Default)]`**: Initialize with `Default::default()`
632/// - **`#[no_inject(default = value)]`**: Initialize with specific value
633/// - **`#[no_inject]`**: Initialize with `None` (field must be `Option<T>`)
634///
635/// # Restrictions
636///
637/// **For functions:**
638/// - Function must have an explicit return type
639/// - All parameters must be marked with `#[inject]`
640///
641/// **For structs:**
642/// - Struct must have named fields
643/// - All fields must have either `#[inject]` or `#[no_inject]` attribute
644/// - `#[no_inject]` without default value requires field type to be `Option<T>`
645/// - `Clone` is auto-derived if not already present (used by generated injection and caching paths)
646/// - All `#[inject]` field types must implement `Injectable` or `InjectableType`
647///
648/// # Attribute Ordering
649///
650/// **`#[injectable]` must be placed above `#[derive(...)]` attributes.**
651///
652/// In Rust 2024 edition, attribute macros can only see attributes listed
653/// below them. If `#[derive(Clone)]` appears above `#[injectable]`, the
654/// macro cannot detect it and will add a duplicate `#[derive(Clone)]`,
655/// causing a compilation error.
656///
657/// ```ignore
658/// // Correct
659/// #[injectable]
660/// #[derive(Default, Debug)]
661/// struct MyService { /* ... */ }
662///
663/// // Incorrect — may cause duplicate Clone derive
664/// #[derive(Default, Debug)]
665/// #[injectable]
666/// struct MyService { /* ... */ }
667/// ```
668///
669#[proc_macro_attribute]
670pub fn injectable(args: TokenStream, input: TokenStream) -> TokenStream {
671	// Try to parse as ItemFn first
672	if let Ok(item_fn) = syn::parse::<ItemFn>(input.clone()) {
673		return injectable_fn_impl(proc_macro2::TokenStream::new(), item_fn)
674			.unwrap_or_else(|e| e.to_compile_error())
675			.into();
676	}
677
678	// Try to parse as ItemStruct
679	if let Ok(item_struct) = syn::parse::<ItemStruct>(input.clone()) {
680		// Convert ItemStruct to DeriveInput for compatibility
681		let derive_input = syn::DeriveInput {
682			attrs: item_struct.attrs,
683			vis: item_struct.vis,
684			ident: item_struct.ident,
685			generics: item_struct.generics,
686			data: syn::Data::Struct(syn::DataStruct {
687				struct_token: item_struct.struct_token,
688				fields: item_struct.fields,
689				semi_token: item_struct.semi_token,
690			}),
691		};
692
693		return injectable_struct_impl(args.into(), derive_input)
694			.unwrap_or_else(|e| e.to_compile_error())
695			.into();
696	}
697
698	// Neither ItemFn nor ItemStruct
699	syn::Error::new(
700		proc_macro2::Span::call_site(),
701		"#[injectable] can only be applied to functions or structs",
702	)
703	.to_compile_error()
704	.into()
705}
706
707/// Attribute macro for Django-style model definition with automatic derive
708///
709/// Automatically adds `#[derive(Model)]` and keeps the `#[model(...)]` attribute.
710/// This provides a cleaner syntax by eliminating the need to explicitly write
711/// `#[derive(Model)]` on every model struct.
712///
713/// # Info Companion Type (Issues #4194, #5272)
714///
715/// By default, generates a `{Model}Info` companion struct with `pub` fields,
716/// bidirectional `From` conversions, and a typestate builder. Relationship
717/// fields use lightweight `RelationInfo<T>` and `ManyToManyInfo<Source, Target>`
718/// payloads instead of ORM marker fields or flattened `*_id` fields. FK and
719/// OneToOne builder setters accept `impl IntoPrimaryKey<T>`. Validation
720/// attributes are derived from `#[field(...)]` config. Opt out with
721/// `#[model(info = false)]`. Exclude individual fields with
722/// `#[field(skip_info = true)]`.
723///
724/// # Model Attributes
725///
726/// Same as `#[derive(Model)]`. See [`derive_model`] for details.
727///
728#[proc_macro_attribute]
729pub fn model(args: TokenStream, input: TokenStream) -> TokenStream {
730	let input = parse_macro_input!(input as ItemStruct);
731
732	model_attribute_impl(args.into(), input)
733		.unwrap_or_else(|e| e.to_compile_error())
734		.into()
735}
736
737/// Attribute macro for generating auth trait implementations.
738///
739/// Generates `BaseUser`, `FullUser` (when `full = true`), `PermissionsMixin`
740/// (when `user_permissions` and `groups` fields exist), and `AuthIdentity`
741/// trait implementations based on struct fields.
742///
743/// # Arguments
744///
745/// - `hasher`: Type implementing `PasswordHasher + Default` (required)
746/// - `username_field`: Name of the field used as username (required)
747/// - `full`: Generate `FullUser` impl (default: `false`)
748///
749/// # Examples
750///
751/// ```rust,ignore
752/// #[user(hasher = reinhardt::Argon2Hasher, username_field = "email", full = true)]
753/// #[derive(Serialize, Deserialize)]
754/// pub struct MyUser {
755///     pub id: Uuid,
756///     pub email: String,
757///     pub password_hash: Option<String>,
758///     pub last_login: Option<DateTime<Utc>>,
759///     pub is_active: bool,
760///     pub is_superuser: bool,
761/// }
762/// ```
763#[proc_macro_attribute]
764pub fn user(args: TokenStream, input: TokenStream) -> TokenStream {
765	let input = parse_macro_input!(input as ItemStruct);
766
767	user_attribute_impl(args.into(), input)
768		.unwrap_or_else(|e| e.to_compile_error())
769		.into()
770}
771
772/// Derive macro for automatic Model implementation and migration registration
773///
774/// Automatically implements the `Model` trait and registers the model with the global
775/// ModelRegistry for automatic migration generation.
776///
777/// # Model Attributes
778///
779/// - `app_label`: Application label (default: "default")
780/// - `table_name`: Database table name (default: struct name in snake_case)
781/// - `constraints`: List of unique constraints (e.g., `unique(fields = ["field1", "field2"], name = "name")`)
782///
783/// # Field Attributes
784///
785/// - `primary_key`: Mark field as primary key (required for exactly one field)
786/// - `max_length`: Maximum length for String fields (required for String)
787/// - `null`: Allow NULL values (default: inferred from `Option<T>`)
788/// - `blank`: Allow blank values in forms
789/// - `unique`: Enforce uniqueness constraint
790/// - `default`: Default value
791/// - `db_column`: Custom database column name
792/// - `editable`: Whether field is editable (default: true)
793///
794/// # Supported Types
795///
796/// - `i32` → IntegerField
797/// - `i64` → BigIntegerField
798/// - `String` → CharField (requires max_length)
799/// - `bool` → BooleanField
800/// - `DateTime<Utc>` → DateTimeField
801/// - `Date` → DateField
802/// - `Time` → TimeField
803/// - `f32`, `f64` → FloatField
804/// - `Option<T>` → Sets null=true automatically
805///
806/// # Requirements
807///
808/// - Struct must have named fields
809/// - Struct must implement `Serialize` and `Deserialize`
810/// - Exactly one field must be marked with `primary_key = true`
811/// - String fields must specify `max_length`
812///
813#[proc_macro_derive(
814	Model,
815	attributes(
816		model,
817		model_config,
818		field,
819		rel,
820		fk_id_field,
821		reinhardt_internal_relation_serde_skip
822	)
823)]
824pub fn derive_model(input: TokenStream) -> TokenStream {
825	let input = parse_macro_input!(input as syn::DeriveInput);
826
827	model_derive_impl(input)
828		.unwrap_or_else(|e| e.to_compile_error())
829		.into()
830}
831
832/// Derive macro for automatic OrmReflectable implementation
833///
834/// Automatically implements the `OrmReflectable` trait for structs,
835/// enabling reflection-based field and relationship access for association proxies.
836///
837/// ## Type Inference
838///
839/// Fields are automatically classified based on their types:
840/// - `Vec<T>` → Collection relationship
841/// - `Option<T>` (where T is non-primitive) → Scalar relationship
842/// - Primitive types (i32, String, etc.) → Regular fields
843///
844/// ## Attributes
845///
846/// Override automatic inference with explicit attributes:
847///
848/// - `#[orm_field(type = "Integer")]` - Mark as regular field with specific type
849/// - `#[orm_relationship(type = "collection")]` - Mark as collection relationship
850/// - `#[orm_relationship(type = "scalar")]` - Mark as scalar relationship
851/// - `#[orm_ignore]` - Exclude field from reflection
852///
853/// ## Supported Field Types
854///
855/// - **Integer**: i8, i16, i32, i64, i128, u8, u16, u32, u64, u128
856/// - **Float**: f32, f64
857/// - **Boolean**: bool
858/// - **String**: String, str
859///
860#[proc_macro_derive(OrmReflectable, attributes(orm_field, orm_relationship, orm_ignore))]
861pub fn derive_orm_reflectable(input: TokenStream) -> TokenStream {
862	orm_reflectable_derive_impl(input)
863}
864
865/// Attribute macro for Django-style AppConfig definition with automatic derive
866///
867/// Automatically adds `#[derive(AppConfig)]` and keeps the `#[app_config(...)]` attribute.
868/// This provides a cleaner syntax by eliminating the need to explicitly write
869/// `#[derive(AppConfig)]` on every app config struct.
870///
871/// # Example
872///
873/// ```rust,ignore
874/// #[app_config(name = "hello", label = "hello")]
875/// pub struct HelloConfig;
876///
877/// // Generates a config() method:
878/// let config = HelloConfig::config();
879/// assert_eq!(config.name, "hello");
880/// assert_eq!(config.label, "hello");
881/// ```
882///
883/// # Attributes
884///
885/// - `name`: Application name (required, string literal)
886/// - `label`: Application label (required, string literal)
887/// - `verbose_name`: Verbose name (optional, string literal)
888///
889/// # Note
890///
891/// Direct use of `#[derive(AppConfig)]` is not allowed. Always use
892/// `#[app_config(...)]` attribute macro instead.
893///
894#[proc_macro_attribute]
895pub fn app_config(args: TokenStream, input: TokenStream) -> TokenStream {
896	let input = parse_macro_input!(input as ItemStruct);
897
898	app_config_attribute_impl(args.into(), input)
899		.unwrap_or_else(|e| e.to_compile_error())
900		.into()
901}
902
903/// Derive macro for automatic AppConfig factory method generation
904///
905/// **Note**: Do not use this derive macro directly. Use `#[app_config(...)]`
906/// attribute macro instead.
907///
908/// This derive macro is invoked automatically by the `#[app_config(...)]` attribute.
909/// Direct use will result in a compile error.
910///
911#[proc_macro_derive(AppConfig, attributes(app_config, app_config_internal))]
912pub fn derive_app_config(input: TokenStream) -> TokenStream {
913	app_config_derive::derive(input)
914}
915
916/// Collect migrations and register them with the global registry
917///
918/// # Deprecated since 0.2.0
919///
920/// **This macro is deprecated.** Use `FilesystemSource` instead for loading migrations.
921/// `FilesystemSource` scans directories for `.rs` migration files and does not require
922/// compile-time registration. It is consistent with `manage migrate` behavior and
923/// works reliably in Cargo workspaces when using `env!("CARGO_MANIFEST_DIR")`.
924///
925/// This macro generates a `MigrationProvider` implementation and automatically
926/// registers it with the global migration registry using `linkme::distributed_slice`.
927///
928/// # Requirements
929///
930/// - Each migration module must export a `migration()` function returning `Migration`
931/// - The crate must have `reinhardt-migrations` and `linkme` as dependencies
932///
933#[proc_macro]
934pub fn collect_migrations(input: TokenStream) -> TokenStream {
935	collect_migrations::collect_migrations_impl(input.into())
936		.unwrap_or_else(|e| e.to_compile_error())
937		.into()
938}
939
940/// Attribute macro for ModelAdmin configuration
941///
942/// Automatically implements the `ModelAdmin` trait for a struct with compile-time
943/// field validation against the specified model type.
944///
945/// # Attributes
946///
947/// ## Required
948///
949/// - `for = ModelType` - The model type to validate fields against
950/// - `name = "ModelName"` - The display name for the model
951///
952/// ## Optional
953///
954/// - `list_display = [field1, field2, ...]` - Fields to display in list view (default: `[id]`)
955/// - `list_filter = [field1, field2, ...]` - Fields for filtering (default: `[]`)
956/// - `search_fields = [field1, field2, ...]` - Fields for search (default: `[]`)
957/// - `fields = [field1, field2, ...]` - Fields to display in forms (default: all)
958/// - `readonly_fields = [field1, field2, ...]` - Read-only fields (default: `[]`)
959/// - `ordering = [(field1, asc/desc), ...]` - Default ordering (default: `[(id, desc)]`)
960/// - `list_per_page = N` - Items per page (default: site default)
961///
962/// # Compile-time Field Validation
963///
964/// All field names are validated at compile time against the model's `field_xxx()` methods.
965/// If a field doesn't exist, compilation will fail with an error.
966///
967/// # Generated Code
968///
969/// The macro generates:
970/// 1. The struct definition
971/// 2. Compile-time field validation code
972/// 3. `ModelAdmin` trait implementation with `#[async_trait]`
973///
974#[proc_macro_attribute]
975pub fn admin(args: TokenStream, input: TokenStream) -> TokenStream {
976	let input = parse_macro_input!(input as ItemStruct);
977
978	admin_impl(args.into(), input)
979		.unwrap_or_else(|e| e.to_compile_error())
980		.into()
981}
982
983/// Attribute macro for applying partial updates to target structs
984///
985/// Automatically adds `#[derive(ApplyUpdate)]` and creates a helper config attribute.
986/// This provides a cleaner syntax for defining update request structs.
987///
988/// # Attributes
989///
990/// - `target(Type1, Type2, ...)`: Target types to generate `ApplyUpdate` implementations for
991///
992/// # Field Attributes
993///
994/// - `#[apply_update(skip)]`: Skip this field during update application
995/// - `#[apply_update(rename = "field_name")]`: Use a different field name on the target
996///
997#[proc_macro_attribute]
998pub fn apply_update(args: TokenStream, input: TokenStream) -> TokenStream {
999	let input = parse_macro_input!(input as ItemStruct);
1000
1001	apply_update_attribute_impl(args.into(), input)
1002		.unwrap_or_else(|e| e.to_compile_error())
1003		.into()
1004}
1005
1006/// Derive macro for automatic `ApplyUpdate` trait implementation
1007///
1008/// **Note**: Do not use this derive macro directly. Use `#[apply_update(...)]`
1009/// attribute macro instead.
1010///
1011#[proc_macro_derive(ApplyUpdate, attributes(apply_update, apply_update_config))]
1012pub fn derive_apply_update(input: TokenStream) -> TokenStream {
1013	let input = parse_macro_input!(input as syn::DeriveInput);
1014
1015	apply_update_derive_impl(input)
1016		.unwrap_or_else(|e| e.to_compile_error())
1017		.into()
1018}
1019
1020/// Derive macro for struct-level validation
1021///
1022/// Implements the `Validate` trait using `#[validate(...)]` field attributes
1023/// to call Reinhardt's built-in validators.
1024///
1025/// # Supported Attributes
1026///
1027/// - `#[validate(email)]` - Validate email format
1028/// - `#[validate(url)]` - Validate URL format
1029/// - `#[validate(length(min = N, max = M))]` - Validate string length
1030/// - `#[validate(range(min = N, max = M))]` - Validate numeric range
1031/// - `message = "..."` - Custom error message (inside rule parentheses)
1032///
1033/// `Option<T>` fields are skipped when `None`.
1034///
1035#[proc_macro_derive(Validate, attributes(validate))]
1036pub fn derive_validate(input: TokenStream) -> TokenStream {
1037	let input = parse_macro_input!(input as syn::DeriveInput);
1038
1039	validate_derive::validate_derive_impl(input)
1040		.unwrap_or_else(|e| e.to_compile_error())
1041		.into()
1042}
1043
1044/// Attribute macro that absorbs the `cfg_attr(native, ...)` boilerplate for
1045/// DTOs shared between the server (`native` cfg) and client (`wasm`) builds.
1046///
1047/// The macro:
1048///
1049/// 1. Emits `#[cfg_attr(native, derive(::reinhardt::Validate))]`
1050///    on the struct so the server build gets validation while the wasm build
1051///    sees a plain serializable type.
1052/// 2. Wraps every `#[validate(...)]` field attribute in `#[cfg_attr(native, ...)]`
1053///    so the same source compiles unchanged for `wasm32-unknown-unknown`.
1054/// 3. Is idempotent: if the user already wrote
1055///    `#[cfg_attr(native, derive(Validate))]` on the struct, that derive is
1056///    not duplicated.
1057///
1058/// # `#[dto]` vs [`macro@model`]
1059///
1060/// Both are struct attributes, but they describe different things and live in
1061/// different files:
1062///
1063/// | | `#[model]` (ORM) | `#[dto]` (this macro) |
1064/// |---|---|---|
1065/// | What | A persistent record | A wire-level data shape |
1066/// | Where it lives | `apps/<app>/models/*.rs` | `apps/<app>/shared/types.rs` |
1067/// | Where it runs | Server only (`native`) | Both server (`native`) and client (`wasm`) |
1068/// | What it adds | Table mapping, primary key, FK fields, migrations | `Validate` derive (native-only), wraps `#[validate(...)]` |
1069/// | Boundary it crosses | Rust ↔ database | Server ↔ client (via `#[server_fn]`, REST handlers, WebSocket payloads) |
1070///
1071/// "DTO" is the industry-standard term for the second row — a data-transfer
1072/// object that is serialized on one side, sent over the wire, and
1073/// deserialized on the other side.
1074///
1075/// # Example
1076///
1077/// ```rust,ignore
1078/// use reinhardt::dto;
1079/// use serde::{Deserialize, Serialize};
1080///
1081/// #[dto]
1082/// #[derive(Debug, Clone, Serialize, Deserialize)]
1083/// pub struct LoginRequest {
1084///     #[validate(email(message = "Invalid email address"))]
1085///     pub email: String,
1086///
1087///     #[validate(length(min = 1, message = "Password is required"))]
1088///     pub password: String,
1089/// }
1090/// ```
1091///
1092/// Expands (conceptually) to:
1093///
1094/// ```rust,ignore
1095/// #[cfg_attr(native, derive(::reinhardt::Validate))]
1096/// #[derive(Debug, Clone, Serialize, Deserialize)]
1097/// pub struct LoginRequest {
1098///     #[cfg_attr(native, validate(email(message = "Invalid email address")))]
1099///     pub email: String,
1100///
1101///     #[cfg_attr(native, validate(length(min = 1, message = "Password is required")))]
1102///     pub password: String,
1103/// }
1104/// ```
1105///
1106/// # Requirements
1107///
1108/// - OpenAPI schema generation is not implicit. If a DTO should be part of
1109///   generated OpenAPI documentation, explicitly add a `Schema` derive in a
1110///   build that enables the OpenAPI feature graph.
1111/// - Applies only to `struct` items (named, tuple, or unit). Enums and unions
1112///   produce a compile error.
1113/// - Does not accept arguments in this version. Passing any tokens (e.g.
1114///   `#[dto(no_schema)]`) is a compile error.
1115/// - Unconditional `#[derive(Validate)]` on the same struct is a compile
1116///   error. `Validate` lives behind the `native` cfg, so an unconditional
1117///   derive cannot resolve on wasm and would duplicate the macro's emission on
1118///   native. Either delete the derive (and let `#[dto]` emit it) or wrap it in
1119///   `#[cfg_attr(native, derive(Validate))]` yourself.
1120/// - Any pre-existing `#[cfg_attr(native, derive(Validate))]` MUST be written
1121///   *below* `#[dto]`,
1122///   not above it. Attribute proc macros only observe attributes that appear
1123///   under them in source order, so a `cfg_attr` placed above `#[dto]` is
1124///   invisible to the macro and would cause `#[dto]` to emit a duplicate
1125///   `cfg_attr(native, derive(...))` on native. Example of the supported
1126///   ordering:
1127///
1128/// ```rust,ignore
1129/// #[dto]
1130/// #[cfg_attr(native, derive(Validate))]
1131/// pub struct LoginRequest { /* ... */ }
1132/// ```
1133#[proc_macro_attribute]
1134pub fn dto(args: TokenStream, input: TokenStream) -> TokenStream {
1135	let input = parse_macro_input!(input as syn::DeriveInput);
1136
1137	dto::dto_impl(args.into(), input)
1138		.unwrap_or_else(|e| e.to_compile_error())
1139		.into()
1140}
1141
1142/// Settings attribute macro for composable configuration.
1143///
1144/// # Fragment mode
1145///
1146/// Marks a struct as a root settings fragment:
1147///
1148/// ```rust,ignore
1149/// #[settings(fragment = true, section = "cache")]
1150/// pub struct CacheSettings {
1151///     pub backend: String,
1152/// }
1153/// ```
1154///
1155/// Omitting `section = "..."` creates an embedded settings node instead of a
1156/// root fragment. Embedded nodes participate in recursive schema metadata and
1157/// required-field validation below a root fragment, but they do not implement
1158/// `SettingsFragment` and cannot be composed directly:
1159///
1160/// ```rust,ignore
1161/// #[settings(fragment = true, default_policy = "required")]
1162/// pub struct DatabaseConfig {
1163///     pub engine: String,
1164///     pub host: String,
1165/// }
1166/// ```
1167///
1168/// # Composition mode
1169///
1170/// Composes fragments into a project settings struct.
1171///
1172/// Supports two syntax forms:
1173/// - **Explicit**: `key: Type` — specify field name explicitly
1174/// - **Implicit**: `Type` — infer field name from type (requires `Settings` suffix)
1175///
1176/// Both forms can be mixed freely:
1177///
1178/// ```rust,ignore
1179/// // All implicit (XxxSettings → xxx)
1180/// #[settings(CoreSettings | CacheSettings | SessionSettings)]
1181/// pub struct ProjectSettings;
1182///
1183/// // Mixed implicit + explicit
1184/// #[settings(CoreSettings | CacheSettings | static_files: StaticSettings)]
1185/// pub struct ProjectSettings;
1186///
1187/// // Explicit only (original syntax, still fully supported)
1188/// #[settings(core: CoreSettings | cache: CacheSettings)]
1189/// pub struct ProjectSettings;
1190/// ```
1191///
1192/// Types without `Settings` suffix require explicit `key: Type` syntax. Note that
1193/// even for `*Settings` types, if the inferred field name would be a Rust keyword
1194/// (e.g. `StaticSettings` → `static`), you must use explicit `key: Type` syntax,
1195/// as in `static_files: StaticSettings` above.
1196#[proc_macro_attribute]
1197pub fn settings(args: TokenStream, input: TokenStream) -> TokenStream {
1198	let input_struct = parse_macro_input!(input as ItemStruct);
1199
1200	// Detect mode: if args contain "fragment", use fragment handler
1201	let args_str = args.to_string();
1202	if args_str.contains("fragment") {
1203		settings_fragment::settings_fragment_impl(args.into(), input_struct)
1204			.unwrap_or_else(|e| e.to_compile_error())
1205			.into()
1206	} else {
1207		settings_compose::settings_compose_impl(args.into(), input_struct)
1208			.unwrap_or_else(|e| e.to_compile_error())
1209			.into()
1210	}
1211}
1212
1213/// WebSocket consumer macro. Parallel to `#[get]` / `#[post]`.
1214///
1215/// Annotates an `async fn` that handles WebSocket messages (`on_message`).
1216/// Generates a `{FnName}Consumer` struct implementing `WebSocketConsumer`,
1217/// a factory function, inventory metadata, and URL resolver extension traits.
1218///
1219/// # Example
1220///
1221/// ```ignore
1222/// use reinhardt::websocket;
1223/// use reinhardt_websockets::consumers::{ConsumerContext, WebSocketResult};
1224/// use reinhardt_websockets::connection::Message;
1225///
1226/// #[websocket("/ws/chat/{room_id}/", name = "chat_ws")]
1227/// pub async fn chat_ws(
1228///     context: &mut ConsumerContext,
1229///     message: Message,
1230/// ) -> WebSocketResult<()> {
1231///     context.send_text("pong".to_string()).await
1232/// }
1233/// ```
1234#[proc_macro_attribute]
1235pub fn websocket(args: TokenStream, input: TokenStream) -> TokenStream {
1236	let input = parse_macro_input!(input as ItemFn);
1237	websocket::websocket_impl(args.into(), input)
1238		.unwrap_or_else(|e| e.to_compile_error())
1239		.into()
1240}
1241
1242/// Function-like proc macro for multi-file view modules.
1243///
1244/// When a view module uses per-file endpoint organization (one file per view in
1245/// `views/`), the URL resolver modules generated by `#[get]`/`#[post]`/etc.
1246/// live inside the submodules. This macro generates `pub use submod::*;`
1247/// for each `pub mod` declaration, bringing endpoint functions and their
1248/// resolver modules into the parent module scope.
1249///
1250/// This enables resolvers to be discovered using the standard
1251/// parent-module path convention (e.g., `.endpoint(views::login)`).
1252///
1253/// # Usage
1254///
1255/// ```rust,ignore
1256/// // views.rs (multi-file pattern)
1257/// use reinhardt::flatten_imports;
1258///
1259/// flatten_imports! {
1260///     pub mod login;
1261///     pub mod register;
1262/// }
1263///
1264/// // Generates:
1265/// //   pub mod login;
1266/// //   pub mod register;
1267/// //   pub use login::*;
1268/// //   pub use register::*;
1269/// ```
1270///
1271/// For single-file views where all functions are defined directly in
1272/// `views.rs`, this macro is not needed.
1273#[proc_macro]
1274pub fn flatten_imports(input: TokenStream) -> TokenStream {
1275	flatten_imports::flatten_imports_impl(input.into())
1276		.unwrap_or_else(|e| e.to_compile_error())
1277		.into()
1278}