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
14use proc_macro::TokenStream;
15use syn::{ItemFn, ItemStruct, parse_macro_input};
16
17mod action;
18mod admin;
19mod api_view;
20mod app_config_attribute;
21mod app_config_derive;
22mod apply_update_attribute;
23mod apply_update_derive;
24mod collect_migrations;
25mod crate_paths;
26mod injectable_common;
27mod injectable_fn;
28mod injectable_struct;
29mod installed_apps;
30mod model_attribute;
31mod model_derive;
32mod orm_reflectable_derive;
33mod path_macro;
34mod permission_macro;
35mod permissions;
36mod query_fields;
37mod receiver;
38mod rel;
39mod routes;
40mod routes_registration;
41mod schema;
42mod settings_compose;
43mod settings_fragment;
44pub(crate) mod settings_parser;
45mod use_inject;
46mod user_attribute;
47mod user_field_mapping;
48mod validate_derive;
49
50use action::action_impl;
51use admin::admin_impl;
52use api_view::api_view_impl;
53use app_config_attribute::app_config_attribute_impl;
54use apply_update_attribute::apply_update_attribute_impl;
55use apply_update_derive::apply_update_derive_impl;
56use injectable_fn::injectable_fn_impl;
57use injectable_struct::injectable_struct_impl;
58use installed_apps::installed_apps_impl;
59use model_attribute::model_attribute_impl;
60use model_derive::model_derive_impl;
61use orm_reflectable_derive::orm_reflectable_derive_impl;
62use path_macro::path_impl;
63use permissions::permission_required_impl;
64use query_fields::derive_query_fields_impl;
65use receiver::receiver_impl;
66use routes::{delete_impl, get_impl, patch_impl, post_impl, put_impl};
67use routes_registration::routes_impl;
68use schema::derive_schema_impl;
69use use_inject::use_inject_impl;
70use user_attribute::user_attribute_impl;
71
72/// Decorator for function-based API views
73#[proc_macro_attribute]
74pub fn api_view(args: TokenStream, input: TokenStream) -> TokenStream {
75 let input = parse_macro_input!(input as ItemFn);
76
77 api_view_impl(args.into(), input)
78 .unwrap_or_else(|e| e.to_compile_error())
79 .into()
80}
81
82/// Decorator for ViewSet custom actions
83#[proc_macro_attribute]
84pub fn action(args: TokenStream, input: TokenStream) -> TokenStream {
85 let input = parse_macro_input!(input as ItemFn);
86
87 action_impl(args.into(), input)
88 .unwrap_or_else(|e| e.to_compile_error())
89 .into()
90}
91
92/// GET method decorator
93#[proc_macro_attribute]
94pub fn get(args: TokenStream, input: TokenStream) -> TokenStream {
95 let input = parse_macro_input!(input as ItemFn);
96
97 get_impl(args.into(), input)
98 .unwrap_or_else(|e| e.to_compile_error())
99 .into()
100}
101
102/// POST method decorator
103#[proc_macro_attribute]
104pub fn post(args: TokenStream, input: TokenStream) -> TokenStream {
105 let input = parse_macro_input!(input as ItemFn);
106
107 post_impl(args.into(), input)
108 .unwrap_or_else(|e| e.to_compile_error())
109 .into()
110}
111
112/// PUT method decorator
113#[proc_macro_attribute]
114pub fn put(args: TokenStream, input: TokenStream) -> TokenStream {
115 let input = parse_macro_input!(input as ItemFn);
116
117 put_impl(args.into(), input)
118 .unwrap_or_else(|e| e.to_compile_error())
119 .into()
120}
121
122/// PATCH method decorator
123#[proc_macro_attribute]
124pub fn patch(args: TokenStream, input: TokenStream) -> TokenStream {
125 let input = parse_macro_input!(input as ItemFn);
126
127 patch_impl(args.into(), input)
128 .unwrap_or_else(|e| e.to_compile_error())
129 .into()
130}
131
132/// DELETE method decorator
133#[proc_macro_attribute]
134pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream {
135 let input = parse_macro_input!(input as ItemFn);
136
137 delete_impl(args.into(), input)
138 .unwrap_or_else(|e| e.to_compile_error())
139 .into()
140}
141
142/// Permission required decorator
143#[proc_macro_attribute]
144pub fn permission_required(args: TokenStream, input: TokenStream) -> TokenStream {
145 let input = parse_macro_input!(input as ItemFn);
146
147 permission_required_impl(args.into(), input)
148 .unwrap_or_else(|e| e.to_compile_error())
149 .into()
150}
151
152/// Defines installed applications with compile-time validation.
153///
154/// Generates an `InstalledApp` enum with variants for each application,
155/// along with `Display`, `FromStr` traits and helper methods.
156///
157/// **Important**: This macro is for **user applications only**. Built-in framework features
158/// (auth, sessions, admin, etc.) are enabled via Cargo feature flags, not through `installed_apps!`.
159///
160/// # Generated Code
161///
162/// The macro generates:
163///
164/// - `enum InstalledApp { ... }` - Type-safe app references with variants for each app
165/// - `impl Display` - Convert enum variants to path strings
166/// - `impl FromStr` - Parse path strings to enum variants
167/// - `fn all_apps() -> Vec<String>` - List all app paths as strings
168/// - `fn path(&self) -> &'static str` - Get app path without allocation
169///
170/// # Example
171///
172/// ```rust,ignore
173/// use reinhardt::installed_apps;
174///
175/// installed_apps! {
176/// users: "users",
177/// posts: "posts",
178/// }
179///
180/// // Use generated enum
181/// let app = InstalledApp::users;
182/// println!("{}", app); // Output: "users"
183///
184/// // Get all apps
185/// let all = InstalledApp::all_apps();
186/// assert_eq!(all, vec!["users".to_string(), "posts".to_string()]);
187///
188/// // Parse from string
189/// use std::str::FromStr;
190/// let app = InstalledApp::from_str("users")?;
191/// assert_eq!(app, InstalledApp::users);
192///
193/// // Get path without allocation
194/// assert_eq!(app.path(), "users");
195/// ```
196///
197/// # Compile-time Validation
198///
199/// Framework modules (starting with `reinhardt.`) are validated at compile time.
200/// Non-existent modules will cause compilation errors:
201///
202/// ```rust,ignore
203/// installed_apps! {
204/// nonexistent: "reinhardt.contrib.nonexistent",
205/// }
206/// // Compile error: cannot find module `nonexistent` in `contrib`
207/// ```
208///
209/// User apps (not starting with `reinhardt.`) skip compile-time validation,
210/// allowing flexible user-defined application names.
211///
212/// # Framework Features
213///
214/// **Do NOT use this macro for built-in framework features.** Instead, enable them
215/// via Cargo feature flags:
216///
217/// ```toml
218/// [dependencies]
219/// reinhardt = { version = "0.1.0-alpha.1", features = ["auth", "sessions", "admin"] }
220/// ```
221///
222/// Then import them directly:
223///
224/// ```rust,ignore
225/// use reinhardt::auth::*;
226/// use reinhardt::auth::sessions::*;
227/// use reinhardt::admin::*;
228/// ```
229///
230/// # See Also
231///
232/// - Module documentation in `installed_apps.rs` for detailed information about
233/// generated code structure, trait implementations, and advanced usage
234/// - `crates/reinhardt-apps/README.md` for comprehensive usage guide
235/// - Tutorial: `docs/tutorials/en/basis/1-project-setup.md`
236///
237#[proc_macro]
238pub fn installed_apps(input: TokenStream) -> TokenStream {
239 installed_apps_impl(input.into())
240 .unwrap_or_else(|e| e.to_compile_error())
241 .into()
242}
243
244/// Register URL patterns for automatic discovery by the framework
245///
246/// This attribute macro automatically registers a function as the URL pattern
247/// provider for the framework. The function will be discovered and used when
248/// running management commands like `runserver`.
249///
250/// # Important: Single Usage Only
251///
252/// **Only one function per project can be annotated with `#[routes]`.**
253/// If multiple `#[routes]` attributes are used, the linker will fail with a
254/// "duplicate symbol" error for `__reinhardt_routes_registration_marker`.
255///
256/// To organize routes across multiple files, use the `.mount()` method:
257///
258/// ```rust,ignore
259/// // In src/config/urls.rs - Only ONE #[routes] in the entire project
260/// #[routes]
261/// pub fn routes() -> UnifiedRouter {
262/// UnifiedRouter::new()
263/// .mount("/api/", api::routes()) // api::routes() returns UnifiedRouter
264/// .mount("/admin/", admin::routes()) // WITHOUT #[routes] attribute
265/// }
266///
267/// // In src/apps/api/urls.rs - NO #[routes] attribute
268/// pub fn routes() -> UnifiedRouter {
269/// UnifiedRouter::new()
270/// .endpoint(views::list)
271/// .endpoint(views::create)
272/// }
273/// ```
274///
275/// # Usage
276///
277/// In your `src/config/urls.rs`:
278///
279/// ```rust,ignore
280/// use reinhardt::prelude::*;
281/// use reinhardt::routes;
282///
283/// #[routes]
284/// pub fn routes() -> UnifiedRouter {
285/// UnifiedRouter::new()
286/// .endpoint(views::index)
287/// .mount("/api/", api::routes())
288/// }
289/// ```
290///
291/// # Notes
292///
293/// - The function can have any name (e.g., `routes`, `app_routes`, `url_patterns`)
294/// - The return type must be `UnifiedRouter` (not `Arc<UnifiedRouter>`)
295/// - The framework automatically wraps the router in `Arc`
296#[proc_macro_attribute]
297pub fn routes(args: TokenStream, input: TokenStream) -> TokenStream {
298 let input = parse_macro_input!(input as ItemFn);
299
300 routes_impl(args.into(), input)
301 .unwrap_or_else(|e| e.to_compile_error())
302 .into()
303}
304
305/// Validate URL patterns at compile time
306///
307/// This macro validates URL pattern syntax at compile time, catching common errors
308/// before they reach runtime. It supports both simple parameters and Django-style
309/// typed parameters.
310///
311/// # Compile-time Validation
312///
313/// The macro will fail to compile if:
314/// - Braces are not properly matched (e.g., `{id` or `id}`)
315/// - Parameter names are empty (e.g., `{}`)
316/// - Parameter names contain invalid characters
317/// - Type specifiers are invalid (valid: `int`, `str`, `uuid`, `slug`, `path`)
318/// - Django-style parameters are used outside braces (e.g., `<int:id>` instead of `{<int:id>}`)
319///
320/// # Supported Type Specifiers
321///
322/// - `int` - Integer values
323/// - `str` - String values
324/// - `uuid` - UUID values
325/// - `slug` - Slug strings (alphanumeric, hyphens, underscores)
326/// - `path` - Path segments (can include slashes)
327///
328#[proc_macro]
329pub fn path(input: TokenStream) -> TokenStream {
330 path_impl(input.into())
331 .unwrap_or_else(|e| e.to_compile_error())
332 .into()
333}
334
335/// Connect a receiver function to a signal automatically
336///
337/// This macro provides Django-style `@receiver` decorator functionality for Rust.
338/// It automatically registers the function as a signal receiver at startup.
339///
340#[proc_macro_attribute]
341pub fn receiver(args: TokenStream, input: TokenStream) -> TokenStream {
342 let input = parse_macro_input!(input as ItemFn);
343
344 receiver_impl(args.into(), input)
345 .unwrap_or_else(|e| e.to_compile_error())
346 .into()
347}
348
349/// Automatic dependency injection macro
350///
351/// This macro enables FastAPI-style dependency injection using parameter attributes.
352/// Parameters marked with `#[inject]` will be automatically resolved from the
353/// `InjectionContext`. Can be used with any function, not just endpoints.
354///
355/// # Generated Code
356///
357/// The macro transforms the function by:
358/// 1. Removing `#[inject]` parameters from the signature
359/// 2. Adding an `InjectionContext` parameter
360/// 3. Injecting dependencies at the start of the function
361///
362#[proc_macro_attribute]
363pub fn use_inject(args: TokenStream, input: TokenStream) -> TokenStream {
364 let input = parse_macro_input!(input as ItemFn);
365
366 use_inject_impl(args.into(), input)
367 .unwrap_or_else(|e| e.to_compile_error())
368 .into()
369}
370
371/// Derive macro for type-safe field lookups
372///
373/// Automatically generates field accessor methods for models, enabling
374/// compile-time validated field lookups.
375///
376/// # Generated Methods
377///
378/// For each field in the struct, the macro generates a static method that
379/// returns a `Field<Model, FieldType>`. The field type determines which
380/// lookup methods are available:
381///
382/// - String fields: `lower()`, `upper()`, `trim()`, `contains()`, etc.
383/// - Numeric fields: `abs()`, `ceil()`, `floor()`, `round()`
384/// - DateTime fields: `year()`, `month()`, `day()`, `hour()`, etc.
385/// - All fields: `eq()`, `ne()`, `gt()`, `gte()`, `lt()`, `lte()`
386///
387#[proc_macro_derive(QueryFields)]
388pub fn derive_query_fields(input: TokenStream) -> TokenStream {
389 let input = parse_macro_input!(input as syn::DeriveInput);
390
391 derive_query_fields_impl(input)
392 .unwrap_or_else(|e| e.to_compile_error())
393 .into()
394}
395
396/// Derive macro for automatic OpenAPI schema generation
397///
398/// Automatically implements the `ToSchema` trait for structs and enums,
399/// generating OpenAPI 3.0 schemas from Rust type definitions.
400///
401/// # Supported Types
402///
403/// - Primitives: `String`, `i32`, `i64`, `f32`, `f64`, `bool`
404/// - `Option<T>`: Makes fields optional in the schema
405/// - `Vec<T>`: Generates array schemas
406/// - Custom types implementing `ToSchema`
407///
408/// # Features
409///
410/// - Automatic field metadata extraction
411/// - Documentation comments become field descriptions
412/// - Required/optional field detection
413/// - Nested schema support
414/// - Enum variant handling
415///
416#[proc_macro_derive(Schema)]
417pub fn derive_schema(input: TokenStream) -> TokenStream {
418 let input = parse_macro_input!(input as syn::DeriveInput);
419
420 derive_schema_impl(input)
421 .unwrap_or_else(|e| e.to_compile_error())
422 .into()
423}
424
425/// Attribute macro for injectable factory/provider functions and structs
426///
427/// This macro can be applied to both functions and structs to enable dependency injection.
428///
429/// # Field Attributes (Struct Only)
430///
431/// All struct fields must have either `#[inject]` or `#[no_inject]` attribute:
432///
433/// - **`#[inject]`**: Inject this field from the DI container
434/// - **`#[inject(cache = false)]`**: Inject without caching
435/// - **`#[inject(scope = Singleton)]`**: Use singleton scope
436/// - **`#[no_inject(default = Default)]`**: Initialize with `Default::default()`
437/// - **`#[no_inject(default = value)]`**: Initialize with specific value
438/// - **`#[no_inject]`**: Initialize with `None` (field must be `Option<T>`)
439///
440/// # Restrictions
441///
442/// **For functions:**
443/// - Function must have an explicit return type
444/// - All parameters must be marked with `#[inject]`
445///
446/// **For structs:**
447/// - Struct must have named fields
448/// - All fields must have either `#[inject]` or `#[no_inject]` attribute
449/// - `#[no_inject]` without default value requires field type to be `Option<T>`
450/// - Struct must be `Clone` (required by `Injectable` trait)
451/// - All `#[inject]` field types must implement `Injectable`
452///
453#[proc_macro_attribute]
454pub fn injectable(args: TokenStream, input: TokenStream) -> TokenStream {
455 // Try to parse as ItemFn first
456 if let Ok(item_fn) = syn::parse::<ItemFn>(input.clone()) {
457 return injectable_fn_impl(proc_macro2::TokenStream::new(), item_fn)
458 .unwrap_or_else(|e| e.to_compile_error())
459 .into();
460 }
461
462 // Try to parse as ItemStruct
463 if let Ok(item_struct) = syn::parse::<ItemStruct>(input.clone()) {
464 // Convert ItemStruct to DeriveInput for compatibility
465 let derive_input = syn::DeriveInput {
466 attrs: item_struct.attrs,
467 vis: item_struct.vis,
468 ident: item_struct.ident,
469 generics: item_struct.generics,
470 data: syn::Data::Struct(syn::DataStruct {
471 struct_token: item_struct.struct_token,
472 fields: item_struct.fields,
473 semi_token: item_struct.semi_token,
474 }),
475 };
476
477 return injectable_struct_impl(args.into(), derive_input)
478 .unwrap_or_else(|e| e.to_compile_error())
479 .into();
480 }
481
482 // Neither ItemFn nor ItemStruct
483 syn::Error::new(
484 proc_macro2::Span::call_site(),
485 "#[injectable] can only be applied to functions or structs",
486 )
487 .to_compile_error()
488 .into()
489}
490
491/// Attribute macro for Django-style model definition with automatic derive
492///
493/// Automatically adds `#[derive(Model)]` and keeps the `#[model(...)]` attribute.
494/// This provides a cleaner syntax by eliminating the need to explicitly write
495/// `#[derive(Model)]` on every model struct.
496///
497/// # Model Attributes
498///
499/// Same as `#[derive(Model)]`. See [`derive_model`] for details.
500///
501#[proc_macro_attribute]
502pub fn model(args: TokenStream, input: TokenStream) -> TokenStream {
503 let input = parse_macro_input!(input as ItemStruct);
504
505 model_attribute_impl(args.into(), input)
506 .unwrap_or_else(|e| e.to_compile_error())
507 .into()
508}
509
510/// Attribute macro for generating auth trait implementations.
511///
512/// Generates `BaseUser`, `FullUser` (when `full = true`), `PermissionsMixin`
513/// (when `user_permissions` and `groups` fields exist), and `AuthIdentity`
514/// trait implementations based on struct fields.
515///
516/// # Arguments
517///
518/// - `hasher`: Type implementing `PasswordHasher + Default` (required)
519/// - `username_field`: Name of the field used as username (required)
520/// - `full`: Generate `FullUser` impl (default: `false`)
521///
522/// # Examples
523///
524/// ```rust,ignore
525/// #[user(hasher = Argon2Hasher, username_field = "email", full = true)]
526/// #[derive(Serialize, Deserialize)]
527/// pub struct MyUser {
528/// pub id: Uuid,
529/// pub email: String,
530/// pub password_hash: Option<String>,
531/// pub last_login: Option<DateTime<Utc>>,
532/// pub is_active: bool,
533/// pub is_superuser: bool,
534/// }
535/// ```
536#[proc_macro_attribute]
537pub fn user(args: TokenStream, input: TokenStream) -> TokenStream {
538 let input = parse_macro_input!(input as ItemStruct);
539
540 user_attribute_impl(args.into(), input)
541 .unwrap_or_else(|e| e.to_compile_error())
542 .into()
543}
544
545/// Derive macro for automatic Model implementation and migration registration
546///
547/// Automatically implements the `Model` trait and registers the model with the global
548/// ModelRegistry for automatic migration generation.
549///
550/// # Model Attributes
551///
552/// - `app_label`: Application label (default: "default")
553/// - `table_name`: Database table name (default: struct name in snake_case)
554/// - `constraints`: List of unique constraints (e.g., `unique(fields = ["field1", "field2"], name = "name")`)
555///
556/// # Field Attributes
557///
558/// - `primary_key`: Mark field as primary key (required for exactly one field)
559/// - `max_length`: Maximum length for String fields (required for String)
560/// - `null`: Allow NULL values (default: inferred from `Option<T>`)
561/// - `blank`: Allow blank values in forms
562/// - `unique`: Enforce uniqueness constraint
563/// - `default`: Default value
564/// - `db_column`: Custom database column name
565/// - `editable`: Whether field is editable (default: true)
566///
567/// # Supported Types
568///
569/// - `i32` → IntegerField
570/// - `i64` → BigIntegerField
571/// - `String` → CharField (requires max_length)
572/// - `bool` → BooleanField
573/// - `DateTime<Utc>` → DateTimeField
574/// - `Date` → DateField
575/// - `Time` → TimeField
576/// - `f32`, `f64` → FloatField
577/// - `Option<T>` → Sets null=true automatically
578///
579/// # Requirements
580///
581/// - Struct must have named fields
582/// - Struct must implement `Serialize` and `Deserialize`
583/// - Exactly one field must be marked with `primary_key = true`
584/// - String fields must specify `max_length`
585///
586#[proc_macro_derive(Model, attributes(model, model_config, field, rel, fk_id_field))]
587pub fn derive_model(input: TokenStream) -> TokenStream {
588 let input = parse_macro_input!(input as syn::DeriveInput);
589
590 model_derive_impl(input)
591 .unwrap_or_else(|e| e.to_compile_error())
592 .into()
593}
594
595/// Derive macro for automatic OrmReflectable implementation
596///
597/// Automatically implements the `OrmReflectable` trait for structs,
598/// enabling reflection-based field and relationship access for association proxies.
599///
600/// ## Type Inference
601///
602/// Fields are automatically classified based on their types:
603/// - `Vec<T>` → Collection relationship
604/// - `Option<T>` (where T is non-primitive) → Scalar relationship
605/// - Primitive types (i32, String, etc.) → Regular fields
606///
607/// ## Attributes
608///
609/// Override automatic inference with explicit attributes:
610///
611/// - `#[orm_field(type = "Integer")]` - Mark as regular field with specific type
612/// - `#[orm_relationship(type = "collection")]` - Mark as collection relationship
613/// - `#[orm_relationship(type = "scalar")]` - Mark as scalar relationship
614/// - `#[orm_ignore]` - Exclude field from reflection
615///
616/// ## Supported Field Types
617///
618/// - **Integer**: i8, i16, i32, i64, i128, u8, u16, u32, u64, u128
619/// - **Float**: f32, f64
620/// - **Boolean**: bool
621/// - **String**: String, str
622///
623#[proc_macro_derive(OrmReflectable, attributes(orm_field, orm_relationship, orm_ignore))]
624pub fn derive_orm_reflectable(input: TokenStream) -> TokenStream {
625 orm_reflectable_derive_impl(input)
626}
627
628/// Attribute macro for Django-style AppConfig definition with automatic derive
629///
630/// Automatically adds `#[derive(AppConfig)]` and keeps the `#[app_config(...)]` attribute.
631/// This provides a cleaner syntax by eliminating the need to explicitly write
632/// `#[derive(AppConfig)]` on every app config struct.
633///
634/// # Example
635///
636/// ```rust,ignore
637/// #[app_config(name = "hello", label = "hello")]
638/// pub struct HelloConfig;
639///
640/// // Generates a config() method:
641/// let config = HelloConfig::config();
642/// assert_eq!(config.name, "hello");
643/// assert_eq!(config.label, "hello");
644/// ```
645///
646/// # Attributes
647///
648/// - `name`: Application name (required, string literal)
649/// - `label`: Application label (required, string literal)
650/// - `verbose_name`: Verbose name (optional, string literal)
651///
652/// # Note
653///
654/// Direct use of `#[derive(AppConfig)]` is not allowed. Always use
655/// `#[app_config(...)]` attribute macro instead.
656///
657#[proc_macro_attribute]
658pub fn app_config(args: TokenStream, input: TokenStream) -> TokenStream {
659 let input = parse_macro_input!(input as ItemStruct);
660
661 app_config_attribute_impl(args.into(), input)
662 .unwrap_or_else(|e| e.to_compile_error())
663 .into()
664}
665
666/// Derive macro for automatic AppConfig factory method generation
667///
668/// **Note**: Do not use this derive macro directly. Use `#[app_config(...)]`
669/// attribute macro instead.
670///
671/// This derive macro is invoked automatically by the `#[app_config(...)]` attribute.
672/// Direct use will result in a compile error.
673///
674#[proc_macro_derive(AppConfig, attributes(app_config, app_config_internal))]
675pub fn derive_app_config(input: TokenStream) -> TokenStream {
676 app_config_derive::derive(input)
677}
678
679/// Collect migrations and register them with the global registry
680///
681/// # Deprecated since 0.2.0
682///
683/// **This macro is deprecated.** Use `FilesystemSource` instead for loading migrations.
684/// `FilesystemSource` scans directories for `.rs` migration files and does not require
685/// compile-time registration. It is consistent with `manage migrate` behavior and
686/// works reliably in Cargo workspaces when using `env!("CARGO_MANIFEST_DIR")`.
687///
688/// This macro generates a `MigrationProvider` implementation and automatically
689/// registers it with the global migration registry using `linkme::distributed_slice`.
690///
691/// # Requirements
692///
693/// - Each migration module must export a `migration()` function returning `Migration`
694/// - The crate must have `reinhardt-migrations` and `linkme` as dependencies
695///
696#[proc_macro]
697pub fn collect_migrations(input: TokenStream) -> TokenStream {
698 collect_migrations::collect_migrations_impl(input.into())
699 .unwrap_or_else(|e| e.to_compile_error())
700 .into()
701}
702
703/// Attribute macro for ModelAdmin configuration
704///
705/// Automatically implements the `ModelAdmin` trait for a struct with compile-time
706/// field validation against the specified model type.
707///
708/// # Attributes
709///
710/// ## Required
711///
712/// - `for = ModelType` - The model type to validate fields against
713/// - `name = "ModelName"` - The display name for the model
714///
715/// ## Optional
716///
717/// - `list_display = [field1, field2, ...]` - Fields to display in list view (default: `[id]`)
718/// - `list_filter = [field1, field2, ...]` - Fields for filtering (default: `[]`)
719/// - `search_fields = [field1, field2, ...]` - Fields for search (default: `[]`)
720/// - `fields = [field1, field2, ...]` - Fields to display in forms (default: all)
721/// - `readonly_fields = [field1, field2, ...]` - Read-only fields (default: `[]`)
722/// - `ordering = [(field1, asc/desc), ...]` - Default ordering (default: `[(id, desc)]`)
723/// - `list_per_page = N` - Items per page (default: site default)
724///
725/// # Compile-time Field Validation
726///
727/// All field names are validated at compile time against the model's `field_xxx()` methods.
728/// If a field doesn't exist, compilation will fail with an error.
729///
730/// # Generated Code
731///
732/// The macro generates:
733/// 1. The struct definition
734/// 2. Compile-time field validation code
735/// 3. `ModelAdmin` trait implementation with `#[async_trait]`
736///
737#[proc_macro_attribute]
738pub fn admin(args: TokenStream, input: TokenStream) -> TokenStream {
739 let input = parse_macro_input!(input as ItemStruct);
740
741 admin_impl(args.into(), input)
742 .unwrap_or_else(|e| e.to_compile_error())
743 .into()
744}
745
746/// Attribute macro for applying partial updates to target structs
747///
748/// Automatically adds `#[derive(ApplyUpdate)]` and creates a helper config attribute.
749/// This provides a cleaner syntax for defining update request structs.
750///
751/// # Attributes
752///
753/// - `target(Type1, Type2, ...)`: Target types to generate `ApplyUpdate` implementations for
754///
755/// # Field Attributes
756///
757/// - `#[apply_update(skip)]`: Skip this field during update application
758/// - `#[apply_update(rename = "field_name")]`: Use a different field name on the target
759///
760#[proc_macro_attribute]
761pub fn apply_update(args: TokenStream, input: TokenStream) -> TokenStream {
762 let input = parse_macro_input!(input as ItemStruct);
763
764 apply_update_attribute_impl(args.into(), input)
765 .unwrap_or_else(|e| e.to_compile_error())
766 .into()
767}
768
769/// Derive macro for automatic `ApplyUpdate` trait implementation
770///
771/// **Note**: Do not use this derive macro directly. Use `#[apply_update(...)]`
772/// attribute macro instead.
773///
774#[proc_macro_derive(ApplyUpdate, attributes(apply_update, apply_update_config))]
775pub fn derive_apply_update(input: TokenStream) -> TokenStream {
776 let input = parse_macro_input!(input as syn::DeriveInput);
777
778 apply_update_derive_impl(input)
779 .unwrap_or_else(|e| e.to_compile_error())
780 .into()
781}
782
783/// Derive macro for struct-level validation
784///
785/// Implements the `Validate` trait using `#[validate(...)]` field attributes
786/// to call Reinhardt's built-in validators.
787///
788/// # Supported Attributes
789///
790/// - `#[validate(email)]` - Validate email format
791/// - `#[validate(url)]` - Validate URL format
792/// - `#[validate(length(min = N, max = M))]` - Validate string length
793/// - `#[validate(range(min = N, max = M))]` - Validate numeric range
794/// - `message = "..."` - Custom error message (inside rule parentheses)
795///
796/// `Option<T>` fields are skipped when `None`.
797///
798#[proc_macro_derive(Validate, attributes(validate))]
799pub fn derive_validate(input: TokenStream) -> TokenStream {
800 let input = parse_macro_input!(input as syn::DeriveInput);
801
802 validate_derive::validate_derive_impl(input)
803 .unwrap_or_else(|e| e.to_compile_error())
804 .into()
805}
806
807/// Settings attribute macro for composable configuration.
808///
809/// # Fragment mode
810///
811/// Marks a struct as a settings fragment:
812///
813/// ```rust,ignore
814/// #[settings(fragment = true, section = "cache")]
815/// pub struct CacheSettings {
816/// pub backend: String,
817/// }
818/// ```
819///
820/// # Composition mode
821///
822/// Composes fragments into a project settings struct.
823///
824/// Supports two syntax forms:
825/// - **Explicit**: `key: Type` — specify field name explicitly
826/// - **Implicit**: `Type` — infer field name from type (requires `Settings` suffix)
827///
828/// Both forms can be mixed freely:
829///
830/// ```rust,ignore
831/// // All implicit (XxxSettings → xxx)
832/// #[settings(CoreSettings | CacheSettings | SessionSettings)]
833/// pub struct ProjectSettings;
834///
835/// // Mixed implicit + explicit
836/// #[settings(CoreSettings | CacheSettings | static_files: StaticSettings)]
837/// pub struct ProjectSettings;
838///
839/// // Explicit only (original syntax, still fully supported)
840/// #[settings(core: CoreSettings | cache: CacheSettings)]
841/// pub struct ProjectSettings;
842/// ```
843///
844/// Types without `Settings` suffix require explicit `key: Type` syntax. Note that
845/// even for `*Settings` types, if the inferred field name would be a Rust keyword
846/// (e.g. `StaticSettings` → `static`), you must use explicit `key: Type` syntax,
847/// as in `static_files: StaticSettings` above.
848#[proc_macro_attribute]
849pub fn settings(args: TokenStream, input: TokenStream) -> TokenStream {
850 let input_struct = parse_macro_input!(input as ItemStruct);
851
852 // Detect mode: if args contain "fragment", use fragment handler
853 let args_str = args.to_string();
854 if args_str.contains("fragment") {
855 settings_fragment::settings_fragment_impl(args.into(), input_struct)
856 .unwrap_or_else(|e| e.to_compile_error())
857 .into()
858 } else {
859 settings_compose::settings_compose_impl(args.into(), input_struct)
860 .unwrap_or_else(|e| e.to_compile_error())
861 .into()
862 }
863}