use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ErrorKind {
AmbiguousSource,
ArchiveAnalyticsRebuild,
ArchiveCount,
ArchiveDailyStatsRebuild,
ArchiveFtsRebuild,
ArchivePurge,
ArchiveTokenDailyStatsRebuild,
Config,
CursorDecode,
CursorParse,
Daemon,
DbError,
DbOpen,
DbQuery,
Doctor,
Download,
EmbedderUnavailable,
EmptyFile,
EmptySession,
EncodeJson,
ExportFailed,
FailedSeedBundleFile,
FileCreate,
FileNotFound,
FileOpen,
FileRead,
FileWrite,
Health,
IdempotencyMismatch,
Index,
IndexBusy,
IndexMissing,
IndexedSessionRequired,
InvalidAgent,
InvalidFilename,
InvalidLine,
Io,
IoError,
LexicalRebuild,
LexicalGeneration,
LexicalShard,
LineNotFound,
LineOutOfRange,
Local,
Mapping,
MissingDb,
MissingIndex,
Model,
NotFound,
OpenIndex,
OpencodeParse,
OpencodeSqliteParse,
OutputNotWritable,
PackEmptyQuery,
PackInvalidField,
PackInvalidLimit,
PackNoEvidence,
PackUnsupportedFormat,
Pages,
ParseError,
PasswordReadError,
PasswordRequired,
RebuildError,
RepairError,
ResumeEmptyCommand,
ResumeExecFailed,
RetainedPublishBackup,
Search,
SemanticBackfill,
SemanticManifest,
SemanticUnavailable,
SerializeMessage,
SessionFileUnreadable,
SessionIdNotFound,
SessionNotFound,
SessionParse,
SessionsFrom,
Setup,
Source,
Ssh,
Storage,
StorageFingerprint,
Timeout,
Tui,
TuiHeadlessOnce,
TuiResetState,
Unknown,
UnknownAgent,
UpdateCheck,
Usage,
WriteFailed,
}
impl ErrorKind {
pub fn kind_str(self) -> &'static str {
match self {
Self::AmbiguousSource => "ambiguous-source",
Self::ArchiveAnalyticsRebuild => "archive-analytics-rebuild",
Self::ArchiveCount => "archive-count",
Self::ArchiveDailyStatsRebuild => "archive-daily-stats-rebuild",
Self::ArchiveFtsRebuild => "archive-fts-rebuild",
Self::ArchivePurge => "archive-purge",
Self::ArchiveTokenDailyStatsRebuild => "archive-token-daily-stats-rebuild",
Self::Config => "config",
Self::CursorDecode => "cursor-decode",
Self::CursorParse => "cursor-parse",
Self::Daemon => "daemon",
Self::DbError => "db-error",
Self::DbOpen => "db-open",
Self::DbQuery => "db-query",
Self::Doctor => "doctor",
Self::Download => "download",
Self::EmbedderUnavailable => "embedder-unavailable",
Self::EmptyFile => "empty-file",
Self::EmptySession => "empty-session",
Self::EncodeJson => "encode-json",
Self::ExportFailed => "export-failed",
Self::FailedSeedBundleFile => "failed_seed_bundle_file",
Self::FileCreate => "file-create",
Self::FileNotFound => "file-not-found",
Self::FileOpen => "file-open",
Self::FileRead => "file-read",
Self::FileWrite => "file-write",
Self::Health => "health",
Self::IdempotencyMismatch => "idempotency-mismatch",
Self::Index => "index",
Self::IndexBusy => "index-busy",
Self::IndexMissing => "index-missing",
Self::IndexedSessionRequired => "indexed-session-required",
Self::InvalidAgent => "invalid-agent",
Self::InvalidFilename => "invalid-filename",
Self::InvalidLine => "invalid-line",
Self::Io => "io",
Self::IoError => "io-error",
Self::LexicalRebuild => "lexical-rebuild",
Self::LexicalGeneration => "lexical_generation",
Self::LexicalShard => "lexical_shard",
Self::LineNotFound => "line-not-found",
Self::LineOutOfRange => "line-out-of-range",
Self::Local => "local",
Self::Mapping => "mapping",
Self::MissingDb => "missing-db",
Self::MissingIndex => "missing-index",
Self::Model => "model",
Self::NotFound => "not-found",
Self::OpenIndex => "open-index",
Self::OpencodeParse => "opencode-parse",
Self::OpencodeSqliteParse => "opencode-sqlite-parse",
Self::OutputNotWritable => "output-not-writable",
Self::PackEmptyQuery => "pack-empty-query",
Self::PackInvalidField => "pack-invalid-field",
Self::PackInvalidLimit => "pack-invalid-limit",
Self::PackNoEvidence => "pack-no-evidence",
Self::PackUnsupportedFormat => "pack-unsupported-format",
Self::Pages => "pages",
Self::ParseError => "parse-error",
Self::PasswordReadError => "password-read-error",
Self::PasswordRequired => "password-required",
Self::RebuildError => "rebuild-error",
Self::RepairError => "repair-error",
Self::ResumeEmptyCommand => "resume-empty-command",
Self::ResumeExecFailed => "resume-exec-failed",
Self::RetainedPublishBackup => "retained_publish_backup",
Self::Search => "search",
Self::SemanticBackfill => "semantic-backfill",
Self::SemanticManifest => "semantic-manifest",
Self::SemanticUnavailable => "semantic-unavailable",
Self::SerializeMessage => "serialize-message",
Self::SessionFileUnreadable => "session-file-unreadable",
Self::SessionIdNotFound => "session-id-not-found",
Self::SessionNotFound => "session-not-found",
Self::SessionParse => "session-parse",
Self::SessionsFrom => "sessions-from",
Self::Setup => "setup",
Self::Source => "source",
Self::Ssh => "ssh",
Self::Storage => "storage",
Self::StorageFingerprint => "storage-fingerprint",
Self::Timeout => "timeout",
Self::Tui => "tui",
Self::TuiHeadlessOnce => "tui-headless-once",
Self::TuiResetState => "tui-reset-state",
Self::Unknown => "unknown",
Self::UnknownAgent => "unknown-agent",
Self::UpdateCheck => "update-check",
Self::Usage => "usage",
Self::WriteFailed => "write-failed",
}
}
pub fn from_kind_str(kind: &str) -> Option<Self> {
Some(match kind {
"ambiguous-source" => Self::AmbiguousSource,
"archive-analytics-rebuild" => Self::ArchiveAnalyticsRebuild,
"archive-count" => Self::ArchiveCount,
"archive-daily-stats-rebuild" => Self::ArchiveDailyStatsRebuild,
"archive-fts-rebuild" => Self::ArchiveFtsRebuild,
"archive-purge" => Self::ArchivePurge,
"archive-token-daily-stats-rebuild" => Self::ArchiveTokenDailyStatsRebuild,
"config" => Self::Config,
"cursor-decode" => Self::CursorDecode,
"cursor-parse" => Self::CursorParse,
"daemon" => Self::Daemon,
"db-error" => Self::DbError,
"db-open" => Self::DbOpen,
"db-query" => Self::DbQuery,
"doctor" => Self::Doctor,
"download" => Self::Download,
"embedder-unavailable" => Self::EmbedderUnavailable,
"empty-file" => Self::EmptyFile,
"empty-session" => Self::EmptySession,
"encode-json" => Self::EncodeJson,
"export-failed" => Self::ExportFailed,
"failed_seed_bundle_file" => Self::FailedSeedBundleFile,
"file-create" => Self::FileCreate,
"file-not-found" => Self::FileNotFound,
"file-open" => Self::FileOpen,
"file-read" => Self::FileRead,
"file-write" => Self::FileWrite,
"health" => Self::Health,
"idempotency-mismatch" => Self::IdempotencyMismatch,
"index" => Self::Index,
"index-busy" => Self::IndexBusy,
"index-missing" => Self::IndexMissing,
"indexed-session-required" => Self::IndexedSessionRequired,
"invalid-agent" => Self::InvalidAgent,
"invalid-filename" => Self::InvalidFilename,
"invalid-line" => Self::InvalidLine,
"io" => Self::Io,
"io-error" => Self::IoError,
"lexical-rebuild" => Self::LexicalRebuild,
"lexical_generation" => Self::LexicalGeneration,
"lexical_shard" => Self::LexicalShard,
"line-not-found" => Self::LineNotFound,
"line-out-of-range" => Self::LineOutOfRange,
"local" => Self::Local,
"mapping" => Self::Mapping,
"missing-db" => Self::MissingDb,
"missing-index" => Self::MissingIndex,
"model" => Self::Model,
"not-found" => Self::NotFound,
"open-index" => Self::OpenIndex,
"opencode-parse" => Self::OpencodeParse,
"opencode-sqlite-parse" => Self::OpencodeSqliteParse,
"output-not-writable" => Self::OutputNotWritable,
"pack-empty-query" => Self::PackEmptyQuery,
"pack-invalid-field" => Self::PackInvalidField,
"pack-invalid-limit" => Self::PackInvalidLimit,
"pack-no-evidence" => Self::PackNoEvidence,
"pack-unsupported-format" => Self::PackUnsupportedFormat,
"pages" => Self::Pages,
"parse-error" => Self::ParseError,
"password-read-error" => Self::PasswordReadError,
"password-required" => Self::PasswordRequired,
"rebuild-error" => Self::RebuildError,
"repair-error" => Self::RepairError,
"resume-empty-command" => Self::ResumeEmptyCommand,
"resume-exec-failed" => Self::ResumeExecFailed,
"retained_publish_backup" => Self::RetainedPublishBackup,
"search" => Self::Search,
"semantic-backfill" => Self::SemanticBackfill,
"semantic-manifest" => Self::SemanticManifest,
"semantic-unavailable" => Self::SemanticUnavailable,
"serialize-message" => Self::SerializeMessage,
"session-file-unreadable" => Self::SessionFileUnreadable,
"session-id-not-found" => Self::SessionIdNotFound,
"session-not-found" => Self::SessionNotFound,
"session-parse" => Self::SessionParse,
"sessions-from" => Self::SessionsFrom,
"setup" => Self::Setup,
"source" => Self::Source,
"ssh" => Self::Ssh,
"storage" => Self::Storage,
"storage-fingerprint" => Self::StorageFingerprint,
"timeout" => Self::Timeout,
"tui" => Self::Tui,
"tui-headless-once" => Self::TuiHeadlessOnce,
"tui-reset-state" => Self::TuiResetState,
"unknown" => Self::Unknown,
"unknown-agent" => Self::UnknownAgent,
"update-check" => Self::UpdateCheck,
"usage" => Self::Usage,
"write-failed" => Self::WriteFailed,
_ => return None,
})
}
pub fn all_variants() -> &'static [Self] {
&[
Self::AmbiguousSource,
Self::ArchiveAnalyticsRebuild,
Self::ArchiveCount,
Self::ArchiveDailyStatsRebuild,
Self::ArchiveFtsRebuild,
Self::ArchivePurge,
Self::ArchiveTokenDailyStatsRebuild,
Self::Config,
Self::CursorDecode,
Self::CursorParse,
Self::Daemon,
Self::DbError,
Self::DbOpen,
Self::DbQuery,
Self::Doctor,
Self::Download,
Self::EmbedderUnavailable,
Self::EmptyFile,
Self::EmptySession,
Self::EncodeJson,
Self::ExportFailed,
Self::FailedSeedBundleFile,
Self::FileCreate,
Self::FileNotFound,
Self::FileOpen,
Self::FileRead,
Self::FileWrite,
Self::Health,
Self::IdempotencyMismatch,
Self::Index,
Self::IndexBusy,
Self::IndexMissing,
Self::IndexedSessionRequired,
Self::InvalidAgent,
Self::InvalidFilename,
Self::InvalidLine,
Self::Io,
Self::IoError,
Self::LexicalRebuild,
Self::LexicalGeneration,
Self::LexicalShard,
Self::LineNotFound,
Self::LineOutOfRange,
Self::Local,
Self::Mapping,
Self::MissingDb,
Self::MissingIndex,
Self::Model,
Self::NotFound,
Self::OpenIndex,
Self::OpencodeParse,
Self::OpencodeSqliteParse,
Self::OutputNotWritable,
Self::PackEmptyQuery,
Self::PackInvalidField,
Self::PackInvalidLimit,
Self::PackNoEvidence,
Self::PackUnsupportedFormat,
Self::Pages,
Self::ParseError,
Self::PasswordReadError,
Self::PasswordRequired,
Self::RebuildError,
Self::RepairError,
Self::ResumeEmptyCommand,
Self::ResumeExecFailed,
Self::RetainedPublishBackup,
Self::Search,
Self::SemanticBackfill,
Self::SemanticManifest,
Self::SemanticUnavailable,
Self::SerializeMessage,
Self::SessionFileUnreadable,
Self::SessionIdNotFound,
Self::SessionNotFound,
Self::SessionParse,
Self::SessionsFrom,
Self::Setup,
Self::Source,
Self::Ssh,
Self::Storage,
Self::StorageFingerprint,
Self::Timeout,
Self::Tui,
Self::TuiHeadlessOnce,
Self::TuiResetState,
Self::Unknown,
Self::UnknownAgent,
Self::UpdateCheck,
Self::Usage,
Self::WriteFailed,
]
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn every_error_kind_round_trips_through_kind_str() {
for variant in ErrorKind::all_variants() {
let kind = variant.kind_str();
let parsed = ErrorKind::from_kind_str(kind).unwrap_or_else(|| {
panic!(
"ErrorKind::{:?}.kind_str() = {:?} but from_kind_str returned None — \
missing from_kind_str arm",
variant, kind
)
});
assert_eq!(
parsed, *variant,
"round-trip mismatch: {:?}.kind_str() → {:?} → {:?}",
variant, kind, parsed
);
}
}
#[test]
fn every_kind_str_is_unique() {
let mut seen: HashSet<&'static str> = HashSet::new();
for variant in ErrorKind::all_variants() {
let kind = variant.kind_str();
assert!(
seen.insert(kind),
"duplicate kind_str detected: {:?} maps to {:?} which was already \
registered by an earlier variant",
variant,
kind
);
}
}
#[test]
fn variant_count_matches_audited_lib_rs_kind_literals() {
const AUDITED_KIND_COUNT: usize = 91;
assert_eq!(
ErrorKind::all_variants().len(),
AUDITED_KIND_COUNT,
"ErrorKind variant count drifted from the audited lib.rs literal set; \
re-run `grep -oE 'kind: \"[a-z_-]+\"' src/lib.rs | sort -u | wc -l` and \
update the constant + add the missing variant"
);
}
#[test]
fn snake_case_stragglers_preserve_legacy_wire_format() {
assert_eq!(
ErrorKind::FailedSeedBundleFile.kind_str(),
"failed_seed_bundle_file"
);
assert_eq!(
ErrorKind::LexicalGeneration.kind_str(),
"lexical_generation"
);
assert_eq!(ErrorKind::LexicalShard.kind_str(), "lexical_shard");
assert_eq!(
ErrorKind::RetainedPublishBackup.kind_str(),
"retained_publish_backup"
);
}
#[test]
fn from_kind_str_returns_none_for_unknown_inputs() {
assert_eq!(ErrorKind::from_kind_str(""), None);
assert_eq!(ErrorKind::from_kind_str("not-a-real-kind"), None);
assert_eq!(ErrorKind::from_kind_str("DB-ERROR"), None);
assert_eq!(ErrorKind::from_kind_str("Db-Error"), None);
assert_eq!(
ErrorKind::from_kind_str("unknown"),
Some(ErrorKind::Unknown)
);
}
#[test]
fn error_kind_is_serde_compatible() {
let json = serde_json::to_string(&ErrorKind::DbError).expect("serialize");
let parsed: ErrorKind = serde_json::from_str(&json).expect("deserialize");
assert_eq!(parsed, ErrorKind::DbError);
}
}