rusticx-core 1.0.0

Core traits, types, and abstractions for the Rusticx multi-database ORM
Documentation
use crate::{column::ColumnDef, error::Result, value::{Row, Value}};

/// Core trait that every ORM model must implement.
///
/// You should **never implement this by hand**. Apply `#[derive(Model)]` to
/// your struct and the macro generates all methods automatically from the
/// field types and `#[rusticx(...)]` attributes.
///
/// # Example
///
/// ```rust,ignore
/// #[derive(Debug, Serialize, Deserialize, Model)]
/// #[rusticx(table = "orders")]
/// pub struct Order {
///     #[rusticx(primary_key)]
///     pub id: uuid::Uuid,
///     pub user_id: uuid::Uuid,
///     pub total: f64,
///     pub placed_at: chrono::DateTime<chrono::Utc>,
/// }
/// ```
pub trait Model: Sized + Send + Sync + 'static {
    /// Table name (SQL) or collection name (MongoDB).
    ///
    /// Defaults to the struct name converted to `snake_case` and pluralised
    /// (`User` → `users`). Override with `#[rusticx(table = "my_table")]`.
    fn table_name() -> &'static str;

    /// Full column schema — used by `migrate()` to emit `CREATE TABLE` DDL.
    fn columns() -> Vec<ColumnDef>;

    /// Primary key column name (default: `"id"`).
    ///
    /// Override at struct level: `#[rusticx(primary_key = "uuid")]`.
    fn primary_key() -> &'static str {
        "id"
    }

    /// Serialize all persisted fields into a flat [`Row`] map.
    ///
    /// Called by `Repository::insert` and `Repository::save` before
    /// handing data to the backend adapter.
    fn to_row(&self) -> Result<Row>;

    /// Deserialize a [`Row`] map (returned by the backend) into `Self`.
    ///
    /// Called by every `find*` method after fetching raw rows.
    fn from_row(row: Row) -> Result<Self>;

    /// Extract the primary key value from this instance.
    ///
    /// Used by `Repository::save` to decide insert vs. update and by
    /// `Repository::delete_by_id`.
    fn pk_value(&self) -> Result<Value>;
}

/// Schema descriptor built from `Model::columns()`.
#[derive(Debug, Clone)]
pub struct TableSchema {
    pub table: String,
    pub columns: Vec<ColumnDef>,
    pub indexes: Vec<IndexDef>,
}

#[derive(Debug, Clone)]
pub struct IndexDef {
    pub name: String,
    pub columns: Vec<String>,
    pub unique: bool,
}

impl TableSchema {
    pub fn from_model<M: Model>() -> Self {
        Self {
            table: M::table_name().to_owned(),
            columns: M::columns(),
            indexes: vec![],
        }
    }

    pub fn with_index(mut self, name: impl Into<String>, cols: Vec<impl Into<String>>, unique: bool) -> Self {
        self.indexes.push(IndexDef {
            name: name.into(),
            columns: cols.into_iter().map(|c| c.into()).collect(),
            unique,
        });
        self
    }
}