3 releases
new 0.1.2 | May 14, 2025 |
---|---|
0.1.1 | May 12, 2025 |
0.1.0 | May 12, 2025 |
#1999 in Procedural macros
81 downloads per month
48KB
341 lines
Rusticx Derive
rusticx-derive
is a procedural macro crate that provides the #[derive(Model)]
attribute for the rusticx
ORM. This macro automatically generates the necessary SQLModel
trait implementation for your Rust structs, enabling them to be easily mapped to database tables.
Purpose
Writing database mapping code manually can be repetitive and error-prone. The #[derive(Model)]
macro automates this process by inspecting your struct definition and its attributes to generate:
-
- The database table name.
-
- The primary key field name and logic to retrieve its value.
-
- The SQL
CREATE TABLE
statement, including column definitions, constraints, and defaults.
- The SQL
-
- Logic to extract field names and values for SQL queries (like
INSERT
orUPDATE
).
- Logic to extract field names and values for SQL queries (like
-
- Logic to deserialize database rows (typically represented as a JSON object) back into struct instances.
This allows you to define your database models in a declarative way using standard Rust structs and attributes, reducing boilerplate code and potential errors.
Installation
Add rusticx-derive
and the main rusticx
crate to your Cargo.toml
. Remember to enable the feature flags for the database drivers you intend to use in the rusticx
crate (e.g., "postgres"
, "mysql"
, "rusqlite"
).
[dependencies]
rusticx = { version = "x.y.z", features = ["<database-driver>"] } # Replace x.y.z with the actual version and specify features
rusticx-derive = "x.y.z" # Replace x.y.z with the actual version
serde = { version = "1.0", features = ["derive"] } # Required for deserialization
serde_json = "1.0" # Required for row deserialization via JSON
# Add uuid and chrono if you use Uuid or date/time types in your models
# uuid = { version = "1.0", features = ["serde", "v4"] }
# chrono = { version = "0.4", features = ["serde"] }
Usage
Apply the #[derive(Model)]
attribute to your struct definition. Your struct must have named fields. You can customize the table name and field mappings using #[model(...)]
attributes on the struct and its fields. The struct typically needs to derive serde::Deserialize
for the from_row
method generated by the macro.
use rusticx_derive::Model;
use serde::{Serialize, Deserialize};
use uuid::Uuid; // Example using uuid crate
use chrono::NaiveDateTime; // Example using chrono crate
#[derive(Model, Debug, Serialize, Deserialize)]
#[model(table = "my_application_users")] // Optional: Customize table name
struct User {
#[model(primary_key, auto_increment)] // Marks 'id' as primary key with auto-increment
id: Option<i32>, // Use Option<i32> for nullable auto-increment PKs
#[model(column = "username")] // Optional: Specify a custom column name
name: String, // Maps to a text/varchar column
#[model(nullable)] // Explicitly marks the 'email' column as nullable
email: Option<String>, // Option<T> fields are automatically treated as nullable
#[model(default = "'pending'")] // Sets a SQL default value (note the single quotes for the string literal)
status: String,
#[model(sql_type = "JSONB")] // Specify a custom SQL data type
metadata: serde_json::Value, // serde_json::Value maps well to JSON/JSONB types
#[model(skip)] // This field will be ignored by the ORM
transient_data: String,
#[model(primary_key, uuid)] // Alternative primary key: UUID with default generation (only one PK per struct!)
// user_uuid: Uuid, // Use Uuid or Option<Uuid> for UUID primary keys
created_at: NaiveDateTime, // Maps to an appropriate DateTime/Timestamp type
}
Attributes
The #[derive(Model)]
macro recognizes #[model(...)]
attributes on the struct and its fields.
Struct Attributes (#[model(...)]
on the struct)
-
#[model(table = "custom_name")]
: Specifies the database table name for this model. If omitted, the macro defaults to the struct name converted to lowercase and pluralized (e.g.,User
->users
).
Field Attributes (#[model(...)]
on fields)
-
#[model(primary_key)]
: Designates this field as the primary key for the table. Exactly one field should be marked with this attribute.
-
#[model(column = "custom_name")]
: Specifies the database column name for this field. If omitted, the macro defaults to the field name converted to lowercase.
-
#[model(default = "SQL_DEFAULT_VALUE")]
: Sets a SQLDEFAULT
constraint for the column. The value provided inside the quotes is inserted directly into theCREATE TABLE
statement. Ensure the value is correctly formatted for SQL (e.g., enclose string literals in single quotes:"'active'"
).
-
#[model(nullable)]
: Explicitly marks the column as allowingNULL
values (NULL
in SQL). Fields withOption<T>
type are automatically treated as nullable, but this attribute can be used on non-Option types that should still allowNULL
.
-
#[model(sql_type = "SQL_TYPE_STRING")]
: Specifies a custom SQL data type for the column. This overrides the default type mapping based on the Rust type. The string provided is used directly in theCREATE TABLE
statement.
-
#[model(skip)]
: Excludes this field entirely from the generatedSQLModel
implementation. It will not be included inCREATE TABLE
statements,INSERT
/UPDATE
queries, or deserialized byfrom_row
. Useful for transient or computed fields.
-
#[model(auto_increment)]
: Applicable only to fields also marked with#[model(primary_key)]
and having an integer type (likei32
,i64
, etc., oftenOption<i32>
). Adds the database-specific syntax for auto-incrementing integer primary keys (SERIAL
orGENERATED ALWAYS AS IDENTITY
for PostgreSQL,AUTO_INCREMENT
for MySQL,AUTOINCREMENT
for SQLite).
-
#[model(uuid)]
: Applicable only to fields also marked with#[model(primary_key)]
and having auuid::Uuid
orOption<uuid::Uuid>
type. Adds database-specific default value generation for UUID primary keys (gen_random_uuid()
for PostgreSQL,UUID()
for MySQL, and a standard UUID generation expression for SQLite).
Automatic SQL Type Mapping
The macro attempts to infer appropriate SQL data types for columns based on the Rust type of the field. This mapping is then translated to the specific syntax for the target database type in the CREATE TABLE
statement.
Rust Type | Mapped SqlType | Typical SQL Type Examples (PostgreSQL, MySQL, SQLite) |
---|---|---|
i8 , i16 , i32 , u8 , u16 , u32 |
Integer |
INTEGER , INT |
i64 , u64 |
BigInt |
BIGINT |
f32 , f64 |
Float |
REAL , DOUBLE PRECISION , FLOAT |
bool |
Boolean |
BOOLEAN , TINYINT(1) |
String , str |
Text |
TEXT , VARCHAR |
Uuid (from uuid crate`) |
Text |
TEXT , VARCHAR(36) (often stored as text) |
NaiveDate (from chrono crate) |
Date |
DATE |
NaiveTime (from chrono crate) |
Time |
TIME |
NaiveDateTime , DateTime (from chrono crate) |
DateTime |
TIMESTAMP , DATETIME |
Vec<u8> |
Blob |
BYTEA , BLOB |
Option<T> |
(Maps T ) |
Adds NULL constraint |
serde_json::Value |
Blob |
BLOB (Consider using #[model(sql_type = "JSONB")] or similar for JSON types) |
You can always override this automatic mapping using the #[model(sql_type = "...")
attribute on a field.
Requirements & Notes
-
- The derived struct must have named fields (
struct MyStruct { field1: Type, field2: Type, ... }
). Tuple structs and unit structs are not supported.
- The derived struct must have named fields (
-
- The struct needs to derive
serde::Deserialize
for thefrom_row
method to function correctly, as row data is currently processed by converting it to aserde_json::Value
object before deserialization.
- The struct needs to derive
-
- Ensure that the types used in your struct fields (like
Uuid
,NaiveDateTime
,serde_json::Value
) are included as dependencies in yourCargo.toml
with necessary features (e.g.,serde
feature for integration).
- Ensure that the types used in your struct fields (like
-
- Only one field should be marked as
#[model(primary_key)]
. The macro does not currently enforce this and may produce unexpected results if multiple fields are marked.
- Only one field should be marked as
-
- The
primary_key_value
method in the generatedSQLModel
currently returnsOption<i32>
, which is suitable for auto-increment integer primary keys. If you useUuid
or other types for your primary key, you might need to interact with the field directly or potentially extend theSQLModel
trait in the mainrusticx
crate to support different primary key return types. Similarly,set_primary_key
takesi32
.
- The
License
Dependencies
~175–590KB
~14K SLoC