Skip to content

Commit 9ac227c

Browse files
committed
Add model
1 parent c80870f commit 9ac227c

9 files changed

Lines changed: 195 additions & 119 deletions

Cargo.lock

Lines changed: 43 additions & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/vespertide-exporter/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,8 @@
1010

1111
[dependencies]
1212
vespertide-core = { workspace = true }
13-
thiserror = "1"
13+
thiserror = "2"
14+
15+
[dev-dependencies]
16+
rstest = "0.26"
17+
insta = { version = "1.44", features = ["yaml"] }

crates/vespertide-exporter/src/seaorm/mod.rs

Lines changed: 70 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub fn render_entity(table: &TableDef) -> String {
2424
let mut lines: Vec<String> = Vec::new();
2525
lines.push("use sea_orm::entity::prelude::*;".into());
2626
lines.push(String::new());
27+
lines.push("#[sea_orm::model]".into());
2728
lines.push("#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]".into());
2829
lines.push(format!("#[sea_orm(table_name = \"{}\")]", table.name));
2930
lines.push("pub struct Model {".into());
@@ -41,6 +42,10 @@ pub fn render_entity(table: &TableDef) -> String {
4142
lines.push(String::new());
4243
render_indexes(&mut lines, indexes);
4344

45+
lines.push("impl ActiveModelBehavior for ActiveModel {}".into());
46+
47+
lines.push(String::new());
48+
4449
lines.join("\n")
4550
}
4651

@@ -176,100 +181,72 @@ fn unique_name(base: &str, used: &mut HashSet<String>) -> String {
176181
#[cfg(test)]
177182
mod tests {
178183
use super::*;
179-
180-
fn base_table() -> TableDef {
181-
TableDef {
182-
name: "users".into(),
183-
columns: vec![
184-
ColumnDef {
185-
name: "id".into(),
186-
r#type: ColumnType::Integer,
187-
nullable: false,
188-
default: None,
189-
},
190-
ColumnDef {
191-
name: "display_name".into(),
192-
r#type: ColumnType::Text,
193-
nullable: true,
194-
default: None,
195-
},
196-
],
197-
constraints: vec![TableConstraint::PrimaryKey {
198-
columns: vec!["id".into()],
199-
}],
200-
indexes: vec![],
201-
}
202-
}
203-
204-
#[test]
205-
fn render_entity_outputs_basic_model() {
206-
let table = base_table();
184+
use insta::{assert_snapshot, with_settings};
185+
use rstest::rstest;
186+
187+
#[rstest]
188+
#[case("basic_single_pk", TableDef {
189+
name: "users".into(),
190+
columns: vec![
191+
ColumnDef { name: "id".into(), r#type: ColumnType::Integer, nullable: false, default: None },
192+
ColumnDef { name: "display_name".into(), r#type: ColumnType::Text, nullable: true, default: None },
193+
],
194+
constraints: vec![TableConstraint::PrimaryKey { columns: vec!["id".into()] }],
195+
indexes: vec![],
196+
})]
197+
#[case("composite_pk", TableDef {
198+
name: "accounts".into(),
199+
columns: vec![
200+
ColumnDef { name: "id".into(), r#type: ColumnType::Integer, nullable: false, default: None },
201+
ColumnDef { name: "tenant_id".into(), r#type: ColumnType::BigInt, nullable: false, default: None },
202+
],
203+
constraints: vec![TableConstraint::PrimaryKey { columns: vec!["id".into(), "tenant_id".into()] }],
204+
indexes: vec![],
205+
})]
206+
#[case("fk_single", TableDef {
207+
name: "posts".into(),
208+
columns: vec![
209+
ColumnDef { name: "id".into(), r#type: ColumnType::Integer, nullable: false, default: None },
210+
ColumnDef { name: "user_id".into(), r#type: ColumnType::Integer, nullable: false, default: None },
211+
ColumnDef { name: "title".into(), r#type: ColumnType::Text, nullable: true, default: None },
212+
],
213+
constraints: vec![
214+
TableConstraint::PrimaryKey { columns: vec!["id".into()] },
215+
TableConstraint::ForeignKey {
216+
name: None,
217+
columns: vec!["user_id".into()],
218+
ref_table: "users".into(),
219+
ref_columns: vec!["id".into()],
220+
on_delete: None,
221+
on_update: None,
222+
},
223+
],
224+
indexes: vec![],
225+
})]
226+
#[case("fk_composite", TableDef {
227+
name: "invoices".into(),
228+
columns: vec![
229+
ColumnDef { name: "id".into(), r#type: ColumnType::Integer, nullable: false, default: None },
230+
ColumnDef { name: "customer_id".into(), r#type: ColumnType::Integer, nullable: false, default: None },
231+
ColumnDef { name: "customer_tenant_id".into(), r#type: ColumnType::Integer, nullable: false, default: None },
232+
],
233+
constraints: vec![
234+
TableConstraint::PrimaryKey { columns: vec!["id".into()] },
235+
TableConstraint::ForeignKey {
236+
name: None,
237+
columns: vec!["customer_id".into(), "customer_tenant_id".into()],
238+
ref_table: "customers".into(),
239+
ref_columns: vec!["id".into(), "tenant_id".into()],
240+
on_delete: None,
241+
on_update: None,
242+
},
243+
],
244+
indexes: vec![],
245+
})]
246+
fn render_entity_snapshots(#[case] name: &str, #[case] table: TableDef) {
207247
let rendered = render_entity(&table);
208-
209-
assert!(rendered.contains("#[sea_orm(table_name = \"users\")]"));
210-
assert!(rendered.contains("#[sea_orm(primary_key)]"));
211-
assert!(rendered.contains("pub id: i32,"));
212-
assert!(rendered.contains("pub display_name: Option<String>,"));
213-
}
214-
215-
#[test]
216-
fn render_entity_marks_composite_primary_keys() {
217-
let mut table = base_table();
218-
table.columns.push(ColumnDef {
219-
name: "tenant_id".into(),
220-
r#type: ColumnType::BigInt,
221-
nullable: false,
222-
default: None,
248+
with_settings!({ snapshot_suffix => format!("params_{}", name) }, {
249+
assert_snapshot!(rendered);
223250
});
224-
table.constraints = vec![TableConstraint::PrimaryKey {
225-
columns: vec!["id".into(), "tenant_id".into()],
226-
}];
227-
228-
let rendered = render_entity(&table);
229-
let pk_lines: Vec<_> = rendered
230-
.lines()
231-
.filter(|line| line.contains("primary_key"))
232-
.collect();
233-
234-
// composite PK should disable auto increment
235-
assert!(
236-
pk_lines
237-
.iter()
238-
.all(|line| line.contains("auto_increment = false"))
239-
);
240-
assert!(rendered.contains("pub tenant_id: i64,"));
241-
}
242-
243-
#[test]
244-
fn render_entity_handles_multiple_tables_individually() {
245-
let tables = [
246-
base_table(),
247-
TableDef {
248-
name: "posts".into(),
249-
columns: vec![ColumnDef {
250-
name: "id".into(),
251-
r#type: ColumnType::Integer,
252-
nullable: false,
253-
default: None,
254-
}],
255-
constraints: vec![TableConstraint::PrimaryKey {
256-
columns: vec!["id".into()],
257-
}],
258-
indexes: vec![],
259-
},
260-
];
261-
262-
let rendered: Vec<_> = tables.iter().map(render_entity).collect();
263-
assert_eq!(rendered.len(), 2);
264-
assert!(
265-
rendered
266-
.iter()
267-
.any(|code| code.contains("#[sea_orm(table_name = \"users\")]"))
268-
);
269-
assert!(
270-
rendered
271-
.iter()
272-
.any(|code| code.contains("#[sea_orm(table_name = \"posts\")]"))
273-
);
274251
}
275252
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
source: crates/vespertide-exporter/src/seaorm/mod.rs
3+
expression: rendered
4+
---
5+
use sea_orm::entity::prelude::*;
6+
7+
#[sea_orm::model]
8+
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
9+
#[sea_orm(table_name = "users")]
10+
pub struct Model {
11+
#[sea_orm(primary_key)]
12+
pub id: i32,
13+
pub display_name: Option<String>,
14+
}
15+
16+
impl ActiveModelBehavior for ActiveModel {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
source: crates/vespertide-exporter/src/seaorm/mod.rs
3+
expression: rendered
4+
---
5+
use sea_orm::entity::prelude::*;
6+
7+
#[sea_orm::model]
8+
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
9+
#[sea_orm(table_name = "accounts")]
10+
pub struct Model {
11+
#[sea_orm(primary_key, auto_increment = false)]
12+
pub id: i32,
13+
#[sea_orm(primary_key, auto_increment = false)]
14+
pub tenant_id: i64,
15+
}
16+
17+
impl ActiveModelBehavior for ActiveModel {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
source: crates/vespertide-exporter/src/seaorm/mod.rs
3+
expression: rendered
4+
---
5+
use sea_orm::entity::prelude::*;
6+
7+
#[sea_orm::model]
8+
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
9+
#[sea_orm(table_name = "invoices")]
10+
pub struct Model {
11+
#[sea_orm(primary_key)]
12+
pub id: i32,
13+
pub customer_id: i32,
14+
pub customer_tenant_id: i32,
15+
#[sea_orm(belongs_to, from = "(customer_id, customer_tenant_id)", to = "(id, tenant_id)")]
16+
pub customers: HasOne<super::customers::Entity>,
17+
}
18+
19+
impl ActiveModelBehavior for ActiveModel {}

0 commit comments

Comments
 (0)