use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ReindexOutcome {
Ready,
Failed { reason: String },
}
impl ReindexOutcome {
pub(crate) fn is_ready(&self) -> bool {
matches!(self, ReindexOutcome::Ready)
}
pub(crate) fn failure_reason(&self) -> Option<&str> {
match self {
ReindexOutcome::Failed { reason } => Some(reason.as_str()),
ReindexOutcome::Ready => None,
}
}
}
pub(crate) fn reindex_outcome(
lexical_only: bool,
embedder_present: bool,
walked_files: usize,
total_vector_count: usize,
) -> ReindexOutcome {
if lexical_only {
return ReindexOutcome::Ready;
}
if !embedder_present {
return ReindexOutcome::Ready;
}
if walked_files == 0 {
return ReindexOutcome::Ready;
}
if total_vector_count == 0 {
return ReindexOutcome::Failed {
reason: format!(
"embedding produced zero vectors for {walked_files} walked file(s) — \
the embedder backend likely failed for every batch (sidecar crash, \
OOM, or model-load stall). The previous index was preserved; \
check the embedderd logs and retry."
),
};
}
ReindexOutcome::Ready
}
pub(crate) fn canonical_walk_root(root: &Path) -> PathBuf {
std::fs::canonicalize(root).unwrap_or_else(|_| root.to_path_buf())
}
pub(crate) fn needs_path_relativization(previous_root: Option<&Path>, current_root: &Path) -> bool {
let Some(prev) = previous_root else {
return false;
};
canonical_walk_root(prev) != canonical_walk_root(current_root)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reindex_outcome_lexical_only_is_ready_with_zero_vectors() {
let outcome = reindex_outcome(true, true, 100, 0);
assert!(outcome.is_ready());
assert_eq!(outcome.failure_reason(), None);
}
#[test]
fn reindex_outcome_no_embedder_is_ready_with_zero_vectors() {
let outcome = reindex_outcome(false, false, 100, 0);
assert!(outcome.is_ready());
assert_eq!(outcome.failure_reason(), None);
}
#[test]
fn reindex_outcome_full_pipeline_zero_vectors_fails() {
let outcome = reindex_outcome(false, true, 42, 0);
assert!(!outcome.is_ready());
let reason = outcome.failure_reason().expect("must carry a reason");
assert!(reason.contains("zero vectors"), "reason: {reason}");
assert!(
reason.contains("42"),
"reason should cite file count: {reason}"
);
}
#[test]
fn reindex_outcome_zero_files_is_ready() {
assert!(reindex_outcome(false, true, 0, 0).is_ready());
assert!(reindex_outcome(true, true, 0, 0).is_ready());
}
#[test]
fn reindex_outcome_healthy_is_ready() {
assert!(reindex_outcome(false, true, 42, 1337).is_ready());
}
#[test]
fn reindex_outcome_single_vector_is_ready() {
assert!(reindex_outcome(false, true, 1000, 1).is_ready());
}
#[test]
fn canonical_walk_root_resolves_symlink() {
let tmp = tempfile::tempdir().expect("tempdir");
let real = tmp.path().join("real");
std::fs::create_dir(&real).unwrap();
let link = tmp.path().join("link");
#[cfg(unix)]
std::os::unix::fs::symlink(&real, &link).unwrap();
#[cfg(not(unix))]
std::fs::create_dir(&link).unwrap();
let canonical = canonical_walk_root(&link);
let real_canonical = std::fs::canonicalize(&real).unwrap();
#[cfg(unix)]
assert_eq!(
canonical, real_canonical,
"symlinked root must canonicalize to the real target"
);
#[cfg(not(unix))]
let _ = real_canonical;
}
#[test]
fn canonical_walk_root_falls_back_on_missing_path() {
let missing = PathBuf::from("/this/path/does/not/exist/anywhere/xyz");
assert_eq!(canonical_walk_root(&missing), missing);
}
#[test]
fn needs_path_relativization_first_reindex_is_false() {
let tmp = tempfile::tempdir().expect("tempdir");
assert!(!needs_path_relativization(None, tmp.path()));
}
#[test]
fn needs_path_relativization_root_moved_is_true() {
let a = tempfile::tempdir().expect("tempdir a");
let b = tempfile::tempdir().expect("tempdir b");
assert!(needs_path_relativization(Some(a.path()), b.path()));
}
#[test]
fn needs_path_relativization_same_root_is_false() {
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path();
assert!(!needs_path_relativization(Some(root), root));
}
#[cfg(unix)]
#[test]
fn needs_path_relativization_symlink_alias_is_false() {
let tmp = tempfile::tempdir().expect("tempdir");
let real = tmp.path().join("real");
std::fs::create_dir(&real).unwrap();
let link = tmp.path().join("link");
std::os::unix::fs::symlink(&real, &link).unwrap();
assert!(!needs_path_relativization(Some(&link), &real));
}
}