Extract Repo struct wrapping a resolved path
Gives commands a typed handle instead of a bare PathBuf, with room to
add methods later.

Assisted-by: GLM-5.1 via pi
change mtzupqxrzyzlovsvrkmtvnwuurymuymk
commit 67de6284c00b7b84829d04c9a9a645c57eac9f20
author Alpha Chen <alpha@kejadlen.dev>
date
parent nlpwvrnv
diff --git a/src/bin/quire/commands/exec.rs b/src/bin/quire/commands/exec.rs
index f66d2f3..f408924 100644
--- a/src/bin/quire/commands/exec.rs
+++ b/src/bin/quire/commands/exec.rs
@@ -44,11 +44,14 @@ fn dispatch_git(quire: &Quire, git_cmd: &str, args: &[String]) -> Result<()> {
     let path = args[0].trim_start_matches('/');
     ensure!(!path.is_empty(), "empty repository path");
 
-    let repo_dir = quire.repo(path)?;
-    ensure!(repo_dir.is_dir(), "repository not found: {path}");
+    let repo = quire.repo(path)?;
+    ensure!(repo.exists(), "repository not found: {path}");
 
     tracing::info!(%git_cmd, %path, "dispatching git command");
-    let err = Command::new(git_cmd).arg(".").current_dir(&repo_dir).exec();
+    let err = Command::new(git_cmd)
+        .arg(".")
+        .current_dir(repo.path())
+        .exec();
 
     bail!("exec failed: {err}")
 }
diff --git a/src/bin/quire/commands/repo.rs b/src/bin/quire/commands/repo.rs
index bff0b27..557c59c 100644
--- a/src/bin/quire/commands/repo.rs
+++ b/src/bin/quire/commands/repo.rs
@@ -5,11 +5,11 @@ use miette::{IntoDiagnostic, Result, ensure};
 use quire::Quire;
 
 pub async fn new(quire: &Quire, name: &str) -> Result<()> {
-    let repo_dir = quire.repo(name)?;
-    ensure!(!repo_dir.is_dir(), "repository already exists: {name}");
+    let repo = quire.repo(name)?;
+    ensure!(!repo.exists(), "repository already exists: {name}");
 
     // Create parent directory for grouped repos (e.g. work/foo.git).
-    if let Some(parent) = repo_dir.parent() {
+    if let Some(parent) = repo.path().parent() {
         fs_err::create_dir_all(parent).into_diagnostic()?;
     }
 
@@ -35,13 +35,13 @@ pub async fn list(quire: &Quire) -> Result<()> {
 }
 
 pub async fn rm(quire: &Quire, name: &str) -> Result<()> {
-    let repo_dir = quire.repo(name)?;
-    ensure!(repo_dir.is_dir(), "repository not found: {name}");
+    let repo = quire.repo(name)?;
+    ensure!(repo.exists(), "repository not found: {name}");
 
-    fs_err::remove_dir_all(&repo_dir).into_diagnostic()?;
+    fs_err::remove_dir_all(repo.path()).into_diagnostic()?;
 
     // Clean up empty parent directory for grouped repos.
-    if let Some(parent) = repo_dir.parent()
+    if let Some(parent) = repo.path().parent()
         && parent != quire.repos_dir()
     {
         let _ = fs_err::remove_dir(parent);
diff --git a/src/quire.rs b/src/quire.rs
index 66fef54..6227fb2 100644
--- a/src/quire.rs
+++ b/src/quire.rs
@@ -4,6 +4,23 @@ use miette::{IntoDiagnostic, Result, ensure};
 
 use crate::config::Config;
 
+/// A resolved repository path.
+///
+/// Created by `Quire::repo` after validating the name.
+pub struct Repo {
+    path: PathBuf,
+}
+
+impl Repo {
+    pub fn path(&self) -> &Path {
+        &self.path
+    }
+
+    pub fn exists(&self) -> bool {
+        self.path.is_dir()
+    }
+}
+
 /// Application runtime context.
 ///
 /// Carries configuration and provides resolved paths to repositories.
@@ -25,9 +42,11 @@ impl Quire {
     ///
     /// Rejects path traversal, missing `.git` suffix, empty segments,
     /// reserved path components, and more than one level of grouping.
-    pub fn repo(&self, name: &str) -> Result<PathBuf> {
+    pub fn repo(&self, name: &str) -> Result<Repo> {
         validate_repo_name(name)?;
-        Ok(self.config.repos_dir.join(name))
+        Ok(Repo {
+            path: self.config.repos_dir.join(name),
+        })
     }
 
     /// List all repository names under the repos directory.
@@ -124,8 +143,8 @@ mod tests {
     fn repo_resolves_path() {
         let q = quire();
         assert_eq!(
-            q.repo("foo.git").unwrap(),
-            PathBuf::from("/var/quire/repos/foo.git")
+            q.repo("foo.git").unwrap().path(),
+            Path::new("/var/quire/repos/foo.git")
         );
     }