Skip to main content

modo_db_macros/
lib.rs

1use proc_macro::TokenStream;
2
3mod entity;
4mod migration;
5
6/// Attribute macro for declaring a SeaORM database entity with auto-registration.
7///
8/// The macro wraps the annotated struct in a SeaORM entity module and submits an
9/// `EntityRegistration` to the `inventory` collector so `modo_db::sync_and_migrate`
10/// can discover it at startup.
11///
12/// # Required argument
13///
14/// - `table = "<name>"` — SQL table name.
15///
16/// # Optional argument
17///
18/// - `group = "<name>"` — assigns the entity to a named group (default: `"default"`).
19///   Entities in a group can be synced separately via `modo_db::sync_and_migrate_group`.
20///
21/// # Struct-level options (applied as a second `#[entity(...)]` attribute)
22///
23/// - `timestamps` — injects `created_at` and `updated_at` columns of type
24///   `DateTime<Utc>`; both are set automatically via `Record::apply_auto_fields`
25///   on every insert and update.
26/// - `soft_delete` — injects a `deleted_at: Option<DateTime<Utc>>` column. The
27///   `delete` method becomes a soft-delete (sets `deleted_at`). Extra methods
28///   generated on the struct: `with_deleted`, `only_deleted`, `restore`,
29///   `force_delete`, `force_delete_by_id`, `delete_many` (bulk soft-delete),
30///   `force_delete_many` (bulk hard-delete). `find_all` and `query` are overridden
31///   to exclude soft-deleted rows automatically.
32/// - `framework` — marks the entity as framework-internal (non-user schema).
33/// - `index(columns = ["col1", "col2"])` — creates a composite index. Add `unique`
34///   inside to make it a unique index.
35///
36/// # Field-level options (applied as `#[entity(...)]` on individual fields)
37///
38/// - `primary_key` — marks the field as the primary key.
39/// - `auto_increment = true|false` — overrides SeaORM's default auto-increment behaviour.
40/// - `auto = "ulid"|"nanoid"` — generates a ULID or NanoID before insert; only valid
41///   on `primary_key` fields.
42/// - `unique` — adds a unique constraint.
43/// - `indexed` — creates a single-column index.
44/// - `nullable` — accepted but has no effect (SeaORM infers nullability from `Option<T>`).
45/// - `column_type = "<type>"` — overrides the inferred SeaORM column type string.
46/// - `default_value = <literal>` — sets a default value (passed to SeaORM).
47/// - `default_expr = "<expr>"` — sets a default SQL expression string.
48/// - `belongs_to = "<Entity>"` — declares a `BelongsTo` relation to the named entity.
49///   Pair with `on_delete` / `on_update` as needed.
50/// - `to_column = "<Column>"` — overrides the target column for a `belongs_to` FK
51///   (default: `"Id"`).
52/// - `on_delete = "<action>"` — FK action on delete. One of: `Cascade`, `SetNull`,
53///   `Restrict`, `NoAction`, `SetDefault`.
54/// - `on_update = "<action>"` — FK action on update. Same values as `on_delete`.
55/// - `has_many` — declares a `HasMany` relation (field is excluded from the model).
56/// - `has_one` — declares a `HasOne` relation (field is excluded from the model).
57/// - `via = "<JoinEntity>"` — used with `has_many` / `has_one` for many-to-many
58///   relations through a join entity.
59/// - `target = "<Entity>"` — overrides the inferred target entity name for `has_many`
60///   / `has_one` relations when the field name does not match the entity name.
61/// - `renamed_from = "<old_name>"` — records a rename hint as a column comment.
62///
63/// # Example
64///
65/// ```rust,ignore
66/// #[modo_db::entity(table = "users")]
67/// #[entity(timestamps, soft_delete)]
68/// pub struct User {
69///     #[entity(primary_key, auto = "ulid")]
70///     pub id: String,
71///     #[entity(unique)]
72///     pub email: String,
73///     pub name: String,
74/// }
75/// ```
76#[proc_macro_attribute]
77pub fn entity(attr: TokenStream, item: TokenStream) -> TokenStream {
78    entity::expand(attr.into(), item.into())
79        .unwrap_or_else(|e| e.to_compile_error())
80        .into()
81}
82
83/// Attribute macro for registering an escape-hatch SQL migration function.
84///
85/// The annotated async function is kept as-is and a `MigrationRegistration` is submitted
86/// to the `inventory` collector so `modo_db::sync_and_migrate` runs it in version order.
87///
88/// # Required arguments
89///
90/// - `version = <u64>` — monotonically increasing migration version number.
91/// - `description = "<text>"` — human-readable description shown in logs.
92///
93/// # Optional argument
94///
95/// - `group = "<name>"` — assigns the migration to a named group (default: `"default"`).
96///   Migrations in a group run only when `modo_db::sync_and_migrate_group` is called
97///   with the matching group name.
98///
99/// # Function signature
100///
101/// The annotated function must be `async` and accept a single `&sea_orm::DatabaseConnection`
102/// parameter. Return type must be `Result<(), modo::Error>`.
103///
104/// # Example
105///
106/// ```rust,ignore
107/// #[modo_db::migration(version = 1, description = "seed default roles")]
108/// async fn seed_roles(db: &sea_orm::DatabaseConnection) -> Result<(), modo::Error> {
109///     // run raw SQL or SeaORM operations
110///     Ok(())
111/// }
112/// ```
113#[proc_macro_attribute]
114pub fn migration(attr: TokenStream, item: TokenStream) -> TokenStream {
115    migration::expand(attr.into(), item.into())
116        .unwrap_or_else(|e| e.to_compile_error())
117        .into()
118}