rullst-orm-macros 12.0.0

Procedural macros and derive attributes for the rullst-orm Active Record pattern.
Documentation

rullst-orm-macros

rullst-orm-macros implements the derives re-exported by rullst-orm. Applications should normally depend on rullst-orm, not this proc-macro crate directly, because generated code calls the matching runtime API.

Derive and attribute contracts

Macro Bounded contract
#[derive(Orm)] Generates the SQLx Active Record/query surface for named-field structs, or the explicitly selected bounded Turso profile with #[orm(backend = "turso")].
#[derive(TursoModel)] Generates only the typed Turso/libSQL model contract and rejects SQLx-only relations, soft deletes, hooks, policies, tenants, audit, and search behavior.
#[rullst_orm::test] Runs an async test inside the task-scoped ORM transaction and rolls it back. Code that opens a separate connection is outside that sandbox.
#[derive(PersonalData)] Declares application-selected personal-data fields; it is metadata, not automatic privacy compliance.
#[derive(Enum)] Generates a closed bounded label contract shared by string parsing/display, Serde, RullstValue and SQLx codecs. #[rullst_enum(type_name = "...", rename_all = "snake_case")] and per-variant rename are validated at compile time; schema DDL is owned by Blueprint::native_enum.
#[derive(Nexus)] Generates bounded model metadata consumed by the authenticated Nexus runtime. #[orm(tenant = "organization_id")] or the equivalent #[nexus(...)] opts a text field into Nexus-wide trusted-context scoping and makes it hidden/read-only.

Compile-time safety boundaries

The Orm parser is fail-closed. It uses structured syn nested-meta parsing and rejects unknown or duplicate model/field options instead of silently ignoring them. Every model must expose a persisted id; explicitly configured tenant, soft-delete, and embedding targets must name persisted fields. Table, column, relation-key, and pivot identifiers use the 1–64 byte portable ASCII identifier grammar, while hook, policy, scope, and model names must also be valid Rust identifiers. Persisted Rust field names follow that same portable SQL grammar: raw identifiers, non-ASCII names, and names longer than 64 bytes are rejected before SQL generation. A field that cannot form its generated Rust column-enum variant receives a compile error instead of a macro panic.

Exactly one relation declaration is accepted per relation field. Orphan relation options are rejected, belongs_to_many requires a pivot table, cascade_soft_delete is limited to has-one/has-many, and polymorphic metadata is limited to morph relations. The generated many-to-many foreign/related keys default to the owner and related model names when omitted.

The derive recognizes #[sqlx(skip)], #[sqlx(default)], #[sqlx(json)], and #[sqlx(json(nullable))]. SQLx mappings such as rename, try_from, and flatten fail compilation because the generated persistence SQL cannot honor them safely. The parser also rejects unsupported model shapes, unknown backends, missing or unbindable tenant columns, invalid encrypted field types, unsafe audit fields, malformed polymorphic relations, and SQLx-only behavior on Turso-primary models.

Generated query values remain parameterized by the runtime; raw SQL escape hatches are caller-owned. Soft-delete sentinel expressions are compile-time literals capped at 128 bytes and reject separators, NUL, and SQL comments, but they remain author-supplied SQL fragments rather than parameterized data. Comment rejection covers --, block-comment delimiters, and MySQL's #. Database enums accept 1–64 unit variants with unique labels of at most 63 bytes from the portable ASCII allowlist. PostgreSQL native enums require the strict-postgres runtime profile; SQLx Any cannot decode its custom types.

Randomized encrypted fields cannot be used as ordinary generated filter/order columns. Tenant scope and model policies are generated only when explicitly declared; the macro does not authenticate a principal or authorize unscoped access. Post-commit callbacks are process-local unless the application composes the transactional outbox.

Generated mandatory scopes form separate AND groups around user filters; empty positive IN predicates remain false. Scalar projections and nested queries preserve identifier validation and the managed transaction context. Policy models reject bulk deletion rather than skipping per-row checks. See the ORM query and transaction boundaries for the explicit raw-transaction and streaming limitations.

Verification

The unit suite inspects generated SQL/bind ordering and zero-panic production tokens. Twenty-four trybuild compile-fail cases exercise the actual parser diagnostics, including duplicate/unknown options and cross-field invariants, rather than an unresolved import:

cargo test -p rullst-orm-macros --all-features

Backend runtime behavior is verified in rullst-orm against the corresponding SQLite, PostgreSQL, MySQL/MariaDB, Turso, Redis, vector, and search matrices. This crate alone does not prove those external protocols.