rusticx_core/model.rs
1use crate::{column::ColumnDef, error::Result, value::{Row, Value}};
2
3/// Core trait that every ORM model must implement.
4///
5/// You should **never implement this by hand**. Apply `#[derive(Model)]` to
6/// your struct and the macro generates all methods automatically from the
7/// field types and `#[rusticx(...)]` attributes.
8///
9/// # Example
10///
11/// ```rust,ignore
12/// #[derive(Debug, Serialize, Deserialize, Model)]
13/// #[rusticx(table = "orders")]
14/// pub struct Order {
15/// #[rusticx(primary_key)]
16/// pub id: uuid::Uuid,
17/// pub user_id: uuid::Uuid,
18/// pub total: f64,
19/// pub placed_at: chrono::DateTime<chrono::Utc>,
20/// }
21/// ```
22pub trait Model: Sized + Send + Sync + 'static {
23 /// Table name (SQL) or collection name (MongoDB).
24 ///
25 /// Defaults to the struct name converted to `snake_case` and pluralised
26 /// (`User` → `users`). Override with `#[rusticx(table = "my_table")]`.
27 fn table_name() -> &'static str;
28
29 /// Full column schema — used by `migrate()` to emit `CREATE TABLE` DDL.
30 fn columns() -> Vec<ColumnDef>;
31
32 /// Primary key column name (default: `"id"`).
33 ///
34 /// Override at struct level: `#[rusticx(primary_key = "uuid")]`.
35 fn primary_key() -> &'static str {
36 "id"
37 }
38
39 /// Serialize all persisted fields into a flat [`Row`] map.
40 ///
41 /// Called by `Repository::insert` and `Repository::save` before
42 /// handing data to the backend adapter.
43 fn to_row(&self) -> Result<Row>;
44
45 /// Deserialize a [`Row`] map (returned by the backend) into `Self`.
46 ///
47 /// Called by every `find*` method after fetching raw rows.
48 fn from_row(row: Row) -> Result<Self>;
49
50 /// Extract the primary key value from this instance.
51 ///
52 /// Used by `Repository::save` to decide insert vs. update and by
53 /// `Repository::delete_by_id`.
54 fn pk_value(&self) -> Result<Value>;
55}
56
57/// Schema descriptor built from `Model::columns()`.
58#[derive(Debug, Clone)]
59pub struct TableSchema {
60 pub table: String,
61 pub columns: Vec<ColumnDef>,
62 pub indexes: Vec<IndexDef>,
63}
64
65#[derive(Debug, Clone)]
66pub struct IndexDef {
67 pub name: String,
68 pub columns: Vec<String>,
69 pub unique: bool,
70}
71
72impl TableSchema {
73 pub fn from_model<M: Model>() -> Self {
74 Self {
75 table: M::table_name().to_owned(),
76 columns: M::columns(),
77 indexes: vec![],
78 }
79 }
80
81 pub fn with_index(mut self, name: impl Into<String>, cols: Vec<impl Into<String>>, unique: bool) -> Self {
82 self.indexes.push(IndexDef {
83 name: name.into(),
84 columns: cols.into_iter().map(|c| c.into()).collect(),
85 unique,
86 });
87 self
88 }
89}