Skip to content

Commit 4ec8716

Browse files
authored
Merge pull request #124 from dev-five-git/jpa
Implement jpa
2 parents 065a13c + dd27e92 commit 4ec8716

20 files changed

Lines changed: 1756 additions & 13 deletions
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"changes":{"crates/vespertide-macro/Cargo.toml":"Patch","crates/vespertide-config/Cargo.toml":"Patch","crates/vespertide-planner/Cargo.toml":"Patch","crates/vespertide-query/Cargo.toml":"Patch","crates/vespertide-loader/Cargo.toml":"Patch","crates/vespertide/Cargo.toml":"Patch","crates/vespertide-naming/Cargo.toml":"Patch","crates/vespertide-cli/Cargo.toml":"Patch","crates/vespertide-core/Cargo.toml":"Patch","crates/vespertide-exporter/Cargo.toml":"Patch"},"note":"Implement jpa","date":"2026-03-24T10:44:57.164009900Z"}

Cargo.lock

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

crates/vespertide-cli/src/commands/export.rs

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub enum OrmArg {
1515
Seaorm,
1616
Sqlalchemy,
1717
Sqlmodel,
18+
Jpa,
1819
}
1920

2021
impl From<OrmArg> for Orm {
@@ -23,6 +24,7 @@ impl From<OrmArg> for Orm {
2324
OrmArg::Seaorm => Orm::SeaOrm,
2425
OrmArg::Sqlalchemy => Orm::SqlAlchemy,
2526
OrmArg::Sqlmodel => Orm::SqlModel,
27+
OrmArg::Jpa => Orm::Jpa,
2628
}
2729
}
2830
}
@@ -133,6 +135,7 @@ async fn clean_export_dir(root: &Path, orm: Orm) -> Result<()> {
133135
let ext = match orm {
134136
Orm::SeaOrm => "rs",
135137
Orm::SqlAlchemy | Orm::SqlModel => "py",
138+
Orm::Jpa => "java",
136139
};
137140

138141
clean_dir_recursive(root, ext).await?;
@@ -224,13 +227,32 @@ fn build_output_path(root: &Path, rel_path: &Path, orm: Orm) -> PathBuf {
224227
let ext = match orm {
225228
Orm::SeaOrm => "rs",
226229
Orm::SqlAlchemy | Orm::SqlModel => "py",
230+
Orm::Jpa => "java",
227231
};
228-
out.set_file_name(format!("{}.{}", sanitized, ext));
232+
// Java requires filename to match PascalCase class name
233+
let file_stem = if matches!(orm, Orm::Jpa) {
234+
to_pascal_case(&sanitized)
235+
} else {
236+
sanitized
237+
};
238+
out.set_file_name(format!("{}.{}", file_stem, ext));
229239
}
230240

231241
out
232242
}
233243

244+
fn to_pascal_case(s: &str) -> String {
245+
s.split('_')
246+
.map(|word| {
247+
let mut chars = word.chars();
248+
match chars.next() {
249+
None => String::new(),
250+
Some(first) => first.to_uppercase().chain(chars).collect(),
251+
}
252+
})
253+
.collect()
254+
}
255+
234256
fn sanitize_filename(name: &str) -> String {
235257
name.chars()
236258
.map(|ch| {
@@ -581,6 +603,7 @@ mod tests {
581603
#[case(OrmArg::Seaorm, Orm::SeaOrm)]
582604
#[case(OrmArg::Sqlalchemy, Orm::SqlAlchemy)]
583605
#[case(OrmArg::Sqlmodel, Orm::SqlModel)]
606+
#[case(OrmArg::Jpa, Orm::Jpa)]
584607
fn orm_arg_maps_to_enum(#[case] arg: OrmArg, #[case] expected: Orm) {
585608
assert_eq!(Orm::from(arg), expected);
586609
}
@@ -745,6 +768,23 @@ mod tests {
745768
assert!(!root.join("model.py").exists());
746769
}
747770

771+
#[tokio::test]
772+
async fn clean_export_dir_removes_java_files_for_jpa() {
773+
let tmp = tempdir().unwrap();
774+
let root = tmp.path().join("export_dir");
775+
std_fs::create_dir_all(&root).unwrap();
776+
777+
std_fs::write(root.join("User.java"), "// java entity").unwrap();
778+
std_fs::write(root.join("Order.java"), "// java entity").unwrap();
779+
std_fs::write(root.join("keep.rs"), "// keep this").unwrap();
780+
781+
clean_export_dir(&root, Orm::Jpa).await.unwrap();
782+
783+
assert!(!root.join("User.java").exists());
784+
assert!(!root.join("Order.java").exists());
785+
assert!(root.join("keep.rs").exists());
786+
}
787+
748788
#[tokio::test]
749789
async fn clean_export_dir_handles_missing_directory() {
750790
let tmp = tempdir().unwrap();
@@ -806,4 +846,45 @@ mod tests {
806846
let result = clean_dir_recursive(&file_path, "rs").await;
807847
assert!(result.is_ok());
808848
}
849+
850+
#[test]
851+
fn build_output_path_jpa_uses_pascal_case_java_extension() {
852+
use std::path::Path;
853+
let root = Path::new("src/models");
854+
855+
// snake_case model → PascalCase .java
856+
let rel_path = Path::new("order_item.json");
857+
let out = build_output_path(root, rel_path, Orm::Jpa);
858+
assert_eq!(out, Path::new("src/models/OrderItem.java"));
859+
860+
// Single word
861+
let rel_path2 = Path::new("users.json");
862+
let out2 = build_output_path(root, rel_path2, Orm::Jpa);
863+
assert_eq!(out2, Path::new("src/models/Users.java"));
864+
865+
// Nested path
866+
let rel_path3 = Path::new("blog/post_comment.yaml");
867+
let out3 = build_output_path(root, rel_path3, Orm::Jpa);
868+
assert_eq!(out3, Path::new("src/models/blog/PostComment.java"));
869+
}
870+
871+
#[test]
872+
fn build_output_path_jpa_strips_vespertide_suffix() {
873+
use std::path::Path;
874+
let root = Path::new("src/models");
875+
876+
let rel_path = Path::new("user.vespertide.json");
877+
let out = build_output_path(root, rel_path, Orm::Jpa);
878+
assert_eq!(out, Path::new("src/models/User.java"));
879+
}
880+
881+
#[rstest]
882+
#[case("order_item", "OrderItem")]
883+
#[case("users", "Users")]
884+
#[case("a", "A")]
885+
#[case("user_profile_image", "UserProfileImage")]
886+
#[case("a__b", "AB")]
887+
fn test_to_pascal_case(#[case] input: &str, #[case] expected: &str) {
888+
assert_eq!(to_pascal_case(input), expected);
889+
}
809890
}

0 commit comments

Comments
 (0)