@@ -9,9 +9,12 @@ use tokio::fs;
99use vespertide_config:: VespertideConfig ;
1010use vespertide_core:: TableDef ;
1111use vespertide_exporter:: {
12- Orm , prisma:: PrismaExporterWithConfig , render_entity_with_schema,
12+ Orm ,
13+ prisma:: { PrismaExporterWithConfig , PrismaProvider } ,
14+ render_entity_with_schema,
1315 seaorm:: SeaOrmExporterWithConfig ,
1416} ;
17+ use vespertide_query:: DatabaseBackend ;
1518
1619use crate :: parallel_config:: { EXPORT_RENDER_PAR_MIN_LEN , EXPORT_RENDER_PAR_THRESHOLD } ;
1720use crate :: utils:: load_config;
@@ -37,7 +40,21 @@ impl From<OrmArg> for Orm {
3740 }
3841}
3942
40- pub async fn cmd_export ( orm : OrmArg , export_dir : Option < PathBuf > ) -> Result < ( ) > {
43+ /// A Prisma schema is provider-specific (`datasource` provider + `@db.*`
44+ /// native attrs), so the export backend maps onto the Prisma provider.
45+ fn prisma_provider_for_backend ( backend : DatabaseBackend ) -> PrismaProvider {
46+ match backend {
47+ DatabaseBackend :: Postgres => PrismaProvider :: Postgres ,
48+ DatabaseBackend :: MySql => PrismaProvider :: MySql ,
49+ DatabaseBackend :: Sqlite => PrismaProvider :: Sqlite ,
50+ }
51+ }
52+
53+ pub async fn cmd_export (
54+ orm : OrmArg ,
55+ export_dir : Option < PathBuf > ,
56+ backend : DatabaseBackend ,
57+ ) -> Result < ( ) > {
4158 let config = load_config ( ) ?;
4259 let models = load_models_recursive ( config. models_dir ( ) )
4360 . await
@@ -58,7 +75,8 @@ pub async fn cmd_export(orm: OrmArg, export_dir: Option<PathBuf>) -> Result<()>
5875
5976 // Prisma uses a single-file output strategy
6077 if matches ! ( orm, OrmArg :: Prisma ) {
61- return cmd_export_prisma ( config, normalized_models, target_root) . await ;
78+ let provider = prisma_provider_for_backend ( backend) ;
79+ return cmd_export_prisma ( config, normalized_models, target_root, provider) . await ;
6280 }
6381
6482 // Clean the export directory before regenerating
@@ -183,7 +201,7 @@ fn render_export_entity(
183201
184202/// Derive `crate::` prefix from the export directory path.
185203///
186- /// For example: `src/models` → `crate::models`, `src/db/entities` → `crate::db::entities`.
204+ /// For example: `src/models` ?? `crate::models`, `src/db/entities` ?? `crate::db::entities`.
187205/// If the path doesn't start with `src/`, returns empty string (fallback to `super::` behavior).
188206fn export_dir_to_crate_prefix ( export_dir : & Path ) -> String {
189207 let normalized = export_dir. to_string_lossy ( ) . replace ( '\\' , "/" ) ;
@@ -199,8 +217,8 @@ fn export_dir_to_crate_prefix(export_dir: &Path) -> String {
199217
200218/// Convert a relative model file path to Rust module path segments.
201219///
202- /// For example: `admin/admin.json` → `["admin", "admin"]`
203- /// `estimate/estimate_checker.vespertide.json` → `["estimate", "estimate_checker"]`
220+ /// For example: `admin/admin.json` ?? `["admin", "admin"]`
221+ /// `estimate/estimate_checker.vespertide.json` ?? `["estimate", "estimate_checker"]`
204222fn rel_path_to_module_segments ( rel_path : & Path ) -> Vec < String > {
205223 let mut segments = Vec :: new ( ) ;
206224
@@ -442,9 +460,11 @@ async fn cmd_export_prisma(
442460 config : VespertideConfig ,
443461 normalized_models : Vec < ( TableDef , PathBuf ) > ,
444462 target_root : PathBuf ,
463+ provider : PrismaProvider ,
445464) -> Result < ( ) > {
446465 let all_tables: Vec < TableDef > = normalized_models. iter ( ) . map ( |( t, _) | t. clone ( ) ) . collect ( ) ;
447- let content = PrismaExporterWithConfig :: new ( config. prisma ( ) ) . render_schema ( & all_tables) ;
466+ let content = PrismaExporterWithConfig :: with_provider ( config. prisma ( ) , provider)
467+ . render_schema ( & all_tables) ;
448468
449469 clean_export_dir ( & target_root, Orm :: Prisma ) . await ?;
450470
@@ -560,7 +580,9 @@ mod tests {
560580 let model = sample_table ( "users" ) ;
561581 write_model ( Path :: new ( "models/users.json" ) , & model) ;
562582
563- cmd_export ( OrmArg :: Seaorm , None ) . await . unwrap ( ) ;
583+ cmd_export ( OrmArg :: Seaorm , None , DatabaseBackend :: Postgres )
584+ . await
585+ . unwrap ( ) ;
564586
565587 let out = PathBuf :: from ( "src/models/users.rs" ) ;
566588 assert ! ( out. exists( ) ) ;
@@ -585,9 +607,13 @@ mod tests {
585607 write_model ( Path :: new ( "models/blog/posts.json" ) , & model) ;
586608
587609 let custom = PathBuf :: from ( "out_dir" ) ;
588- cmd_export ( OrmArg :: Seaorm , Some ( custom. clone ( ) ) )
589- . await
590- . unwrap ( ) ;
610+ cmd_export (
611+ OrmArg :: Seaorm ,
612+ Some ( custom. clone ( ) ) ,
613+ DatabaseBackend :: Postgres ,
614+ )
615+ . await
616+ . unwrap ( ) ;
591617
592618 let out = custom. join ( "blog/posts.rs" ) ;
593619 assert ! ( out. exists( ) ) ;
@@ -615,7 +641,9 @@ mod tests {
615641 let model = sample_table ( "items" ) ;
616642 write_model ( Path :: new ( "models/items.json" ) , & model) ;
617643
618- cmd_export ( OrmArg :: Sqlalchemy , None ) . await . unwrap ( ) ;
644+ cmd_export ( OrmArg :: Sqlalchemy , None , DatabaseBackend :: Postgres )
645+ . await
646+ . unwrap ( ) ;
619647
620648 let out = PathBuf :: from ( "src/models/items.py" ) ;
621649 assert ! ( out. exists( ) ) ;
@@ -633,14 +661,73 @@ mod tests {
633661 let model = sample_table ( "orders" ) ;
634662 write_model ( Path :: new ( "models/orders.json" ) , & model) ;
635663
636- cmd_export ( OrmArg :: Sqlmodel , None ) . await . unwrap ( ) ;
664+ cmd_export ( OrmArg :: Sqlmodel , None , DatabaseBackend :: Postgres )
665+ . await
666+ . unwrap ( ) ;
637667
638668 let out = PathBuf :: from ( "src/models/orders.py" ) ;
639669 assert ! ( out. exists( ) ) ;
640670 let content = std_fs:: read_to_string ( out) . unwrap ( ) ;
641671 assert ! ( content. contains( "orders" ) ) ;
642672 }
643673
674+ #[ rstest]
675+ #[ case:: postgres(
676+ DatabaseBackend :: Postgres ,
677+ "provider = \" postgresql\" " ,
678+ "@db.Timestamptz" ,
679+ None
680+ ) ]
681+ #[ case:: mysql(
682+ DatabaseBackend :: MySql ,
683+ "provider = \" mysql\" " ,
684+ "@db.Timestamp" ,
685+ Some ( "@db.Timestamptz" )
686+ ) ]
687+ #[ case:: sqlite(
688+ DatabaseBackend :: Sqlite ,
689+ "provider = \" sqlite\" " ,
690+ "DateTime" ,
691+ Some ( "@db." )
692+ ) ]
693+ #[ tokio:: test]
694+ #[ serial]
695+ async fn export_prisma_writes_provider_specific_schema (
696+ #[ case] backend : DatabaseBackend ,
697+ #[ case] provider_line : & str ,
698+ #[ case] expected : & str ,
699+ #[ case] absent : Option < & str > ,
700+ ) {
701+ let tmp = tempdir ( ) . unwrap ( ) ;
702+ let _guard = CwdGuard :: new ( & tmp. path ( ) . to_path_buf ( ) ) ;
703+ write_config ( ) ;
704+
705+ let mut model = sample_table ( "events" ) ;
706+ model. columns . push ( ColumnDef {
707+ name : "occurred_at" . into ( ) ,
708+ r#type : ColumnType :: Simple ( SimpleColumnType :: Timestamptz ) ,
709+ nullable : false ,
710+ default : None ,
711+ comment : None ,
712+ primary_key : None ,
713+ unique : None ,
714+ index : None ,
715+ foreign_key : None ,
716+ } ) ;
717+ write_model ( Path :: new ( "models/events.json" ) , & model) ;
718+
719+ cmd_export ( OrmArg :: Prisma , None , backend) . await . unwrap ( ) ;
720+
721+ let out = PathBuf :: from ( "src/models/schema.prisma" ) ;
722+ assert ! ( out. exists( ) ) ;
723+ let content = std_fs:: read_to_string ( out) . unwrap ( ) ;
724+ assert ! ( content. contains( provider_line) ) ;
725+ assert ! ( content. contains( expected) ) ;
726+ if let Some ( absent) = absent {
727+ assert ! ( !content. contains( absent) ) ;
728+ }
729+ }
730+
644731 #[ tokio:: test]
645732 #[ serial]
646733 async fn load_models_recursive_returns_empty_when_absent ( ) {
@@ -730,6 +817,7 @@ mod tests {
730817 #[ case( OrmArg :: Sqlalchemy , Orm :: SqlAlchemy ) ]
731818 #[ case( OrmArg :: Sqlmodel , Orm :: SqlModel ) ]
732819 #[ case( OrmArg :: Jpa , Orm :: Jpa ) ]
820+ #[ case( OrmArg :: Prisma , Orm :: Prisma ) ]
733821 fn orm_arg_maps_to_enum ( #[ case] arg : OrmArg , #[ case] expected : Orm ) {
734822 assert_eq ! ( Orm :: from( arg) , expected) ;
735823 }
@@ -979,7 +1067,7 @@ mod tests {
9791067 use std:: path:: Path ;
9801068 let root = Path :: new ( "src/models" ) ;
9811069
982- // snake_case model → PascalCase .java
1070+ // snake_case model ?? PascalCase .java
9831071 let rel_path = Path :: new ( "order_item.json" ) ;
9841072 let out = build_output_path ( root, rel_path, Orm :: Jpa ) ;
9851073 assert_eq ! ( out, Path :: new( "src/models/OrderItem.java" ) ) ;
0 commit comments