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

Download history 81/week @ 2025-05-07

81 downloads per month

MIT license

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.
    • Logic to extract field names and values for SQL queries (like INSERT or UPDATE).
    • 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 SQL DEFAULT constraint for the column. The value provided inside the quotes is inserted directly into the CREATE 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 allowing NULL values (NULL in SQL). Fields with Option<T> type are automatically treated as nullable, but this attribute can be used on non-Option types that should still allow NULL.
    • #[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 the CREATE TABLE statement.
    • #[model(skip)]: Excludes this field entirely from the generated SQLModel implementation. It will not be included in CREATE TABLE statements, INSERT/UPDATE queries, or deserialized by from_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 (like i32, i64, etc., often Option<i32>). Adds the database-specific syntax for auto-incrementing integer primary keys (SERIAL or GENERATED 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 a uuid::Uuid or Option<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 struct needs to derive serde::Deserialize for the from_row method to function correctly, as row data is currently processed by converting it to a serde_json::Value object before deserialization.
    • Ensure that the types used in your struct fields (like Uuid, NaiveDateTime, serde_json::Value) are included as dependencies in your Cargo.toml with necessary features (e.g., serde feature for integration).
    • 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.
    • The primary_key_value method in the generated SQLModel currently returns Option<i32>, which is suitable for auto-increment integer primary keys. If you use Uuid or other types for your primary key, you might need to interact with the field directly or potentially extend the SQLModel trait in the main rusticx crate to support different primary key return types. Similarly, set_primary_key takes i32.

License

Dependencies

~175–590KB
~14K SLoC