Replace InvalidRepoName(String) with RepoNameError enum; fix rebase issues
- Add RepoNameError enum with specific variants (Empty, MissingGitSuffix,
  TooManySegments, PathOutsideBase, Invalid) instead of a plain String payload
- Error::Repo wraps RepoNameError via #[from]; validate_name now returns
  Result<(), RepoNameError> so each failure site names the exact variant
- Fix AppResult->Result regression introduced by the rebase in repo_config
- Fix mirror.rs tracing macro: shorthand `mirror_url` (owned String) caused
  str type inference to backflow into the let-Some binding; use %mirror_url
change
commit 900ba35398360fe90adfa8b1eb2d75fcde6faec6
author Claude <noreply@anthropic.com>
date
parent 2ac517ae
diff --git a/quire-server/src/error.rs b/quire-server/src/error.rs
index 87181a7..bc742cb 100644
--- a/quire-server/src/error.rs
+++ b/quire-server/src/error.rs
@@ -12,8 +12,9 @@ pub enum Error {
     #[error("config not found: {0}")]
     ConfigNotFound(String),
 
-    #[error("{0}")]
-    InvalidRepoName(String),
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    Repo(#[from] RepoNameError),
 
     #[error(transparent)]
     #[diagnostic(transparent)]
@@ -38,6 +39,20 @@ pub enum Error {
 
 pub type Result<T> = std::result::Result<T, Error>;
 
+#[derive(Debug, thiserror::Error, Diagnostic)]
+pub enum RepoNameError {
+    #[error("repository name cannot be empty")]
+    Empty,
+    #[error("repository name must end in .git: {0}")]
+    MissingGitSuffix(String),
+    #[error("repository name allows at most one level of grouping: {0}")]
+    TooManySegments(String),
+    #[error("path is not under repos directory: {0}")]
+    PathOutsideBase(String),
+    #[error("invalid repository name: {0}")]
+    Invalid(String),
+}
+
 impl From<FennelError> for Error {
     fn from(err: FennelError) -> Self {
         Error::Fennel(Box::new(err))
diff --git a/quire-server/src/lib.rs b/quire-server/src/lib.rs
index 532b1ac..b3d9e86 100644
--- a/quire-server/src/lib.rs
+++ b/quire-server/src/lib.rs
@@ -8,5 +8,6 @@ pub mod quire;
 pub use quire_core::telemetry::SentryConfig;
 
 pub use error::Error;
+pub use error::RepoNameError;
 pub use error::Result;
 pub use quire::Quire;
diff --git a/quire-server/src/mirror.rs b/quire-server/src/mirror.rs
index c8cbf49..863292b 100644
--- a/quire-server/src/mirror.rs
+++ b/quire-server/src/mirror.rs
@@ -100,8 +100,8 @@ fn mirror_ref(
     }
 
     tracing::info!(
-        ref_name = push_ref.ref_name,
-        mirror_url,
+        ref_name = %push_ref.ref_name,
+        mirror_url = %mirror_url,
         "mirror: push succeeded"
     );
     Ok(())
diff --git a/quire-server/src/quire/mod.rs b/quire-server/src/quire/mod.rs
index 04f688e..9d15310 100644
--- a/quire-server/src/quire/mod.rs
+++ b/quire-server/src/quire/mod.rs
@@ -5,7 +5,7 @@ use std::sync::{Arc, Mutex, OnceLock};
 pub mod web;
 
 use crate::ci::{Ci, Executor, Runs};
-use crate::{Error, Result};
+use crate::{Error, RepoNameError, Result};
 pub use quire_core::telemetry::SentryConfig;
 
 use quire_core::fennel::Fennel;
@@ -86,10 +86,7 @@ impl Repo {
     /// Used by hooks that receive `GIT_DIR` from git.
     pub fn from_path(repos_base: &Path, path: &Path) -> Result<Self> {
         let Ok(relative) = path.strip_prefix(repos_base) else {
-            return Err(Error::InvalidRepoName(format!(
-                "path is not under repos directory: {}",
-                path.display()
-            )));
+            return Err(RepoNameError::PathOutsideBase(path.display().to_string()).into());
         };
         let name = relative.to_string_lossy();
         Self::validate_name(&name)?;
@@ -99,40 +96,28 @@ impl Repo {
         })
     }
 
-    fn validate_name(name: &str) -> Result<()> {
+    fn validate_name(name: &str) -> std::result::Result<(), RepoNameError> {
         if name.is_empty() {
-            return Err(Error::InvalidRepoName(
-                "repository name cannot be empty".to_string(),
-            ));
+            return Err(RepoNameError::Empty);
         }
         if name.contains("..") {
-            return Err(Error::InvalidRepoName(format!(
-                "invalid repository name: {name}"
-            )));
+            return Err(RepoNameError::Invalid(name.to_string()));
         }
         if !name.ends_with(".git") {
-            return Err(Error::InvalidRepoName(format!(
-                "repository name must end in .git: {name}"
-            )));
+            return Err(RepoNameError::MissingGitSuffix(name.to_string()));
         }
         if name.contains("//") {
-            return Err(Error::InvalidRepoName(format!(
-                "invalid repository name: {name}"
-            )));
+            return Err(RepoNameError::Invalid(name.to_string()));
         }
 
         let segments = name.split('/').collect::<Vec<_>>();
         if segments.len() > 2 {
-            return Err(Error::InvalidRepoName(format!(
-                "repository name allows at most one level of grouping: {name}"
-            )));
+            return Err(RepoNameError::TooManySegments(name.to_string()));
         }
 
         for seg in &segments {
             if seg.is_empty() || *seg == "." || *seg == ".." || *seg == ".git" {
-                return Err(Error::InvalidRepoName(format!(
-                    "invalid repository name: {name}"
-                )));
+                return Err(RepoNameError::Invalid(name.to_string()));
             }
         }
 
@@ -170,7 +155,7 @@ impl Repo {
     /// Read and parse `.quire/config.fnl` at the given commit SHA.
     ///
     /// Returns defaults if the file does not exist at that commit.
-    pub fn repo_config(&self, sha: &str) -> AppResult<RepoConfig> {
+    pub fn repo_config(&self, sha: &str) -> Result<RepoConfig> {
         let path = format!("{sha}:.quire/config.fnl");
 
         // cat-file -e: exit 0 if the object exists, non-zero if absent.