1use serde::{Deserialize, Serialize};
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
59pub enum ErrorKind {
60 AmbiguousSource,
61 ArchiveAnalyticsRebuild,
62 ArchiveCount,
63 ArchiveDailyStatsRebuild,
64 ArchiveFtsRebuild,
65 ArchivePurge,
66 ArchiveTokenDailyStatsRebuild,
67 Config,
68 CursorDecode,
69 CursorParse,
70 Daemon,
71 DbError,
72 DbOpen,
73 DbQuery,
74 Doctor,
75 Download,
76 EmbedderUnavailable,
77 EmptyFile,
78 EmptySession,
79 EncodeJson,
80 ExportFailed,
81 FailedSeedBundleFile,
84 FileCreate,
85 FileNotFound,
86 FileOpen,
87 FileRead,
88 FileWrite,
89 Health,
90 IdempotencyMismatch,
91 Index,
92 IndexBusy,
93 IndexMissing,
94 IndexedSessionRequired,
95 InvalidAgent,
96 InvalidFilename,
97 InvalidLine,
98 Io,
99 IoError,
100 LexicalRebuild,
101 LexicalGeneration,
103 LexicalShard,
105 LineNotFound,
106 LineOutOfRange,
107 Local,
108 Mapping,
109 MissingDb,
110 MissingIndex,
111 Model,
112 NotFound,
113 OpenIndex,
114 OpencodeParse,
115 OpencodeSqliteParse,
116 OutputNotWritable,
117 PackEmptyQuery,
118 PackInvalidField,
119 PackInvalidLimit,
120 PackNoEvidence,
121 PackUnsupportedFormat,
122 Pages,
123 ParseError,
124 PasswordReadError,
125 PasswordRequired,
126 RebuildError,
127 RepairError,
128 ResumeEmptyCommand,
129 ResumeExecFailed,
130 RetainedPublishBackup,
132 Search,
133 SemanticBackfill,
134 SemanticManifest,
135 SemanticUnavailable,
136 SerializeMessage,
137 SessionFileUnreadable,
138 SessionIdNotFound,
139 SessionNotFound,
140 SessionParse,
141 SessionsFrom,
142 Setup,
143 Source,
144 Ssh,
145 Storage,
146 StorageFingerprint,
147 Timeout,
148 Tui,
149 TuiHeadlessOnce,
150 TuiResetState,
151 Unknown,
152 UnknownAgent,
153 UpdateCheck,
154 Usage,
155 WriteFailed,
156}
157
158impl ErrorKind {
159 pub fn kind_str(self) -> &'static str {
165 match self {
166 Self::AmbiguousSource => "ambiguous-source",
167 Self::ArchiveAnalyticsRebuild => "archive-analytics-rebuild",
168 Self::ArchiveCount => "archive-count",
169 Self::ArchiveDailyStatsRebuild => "archive-daily-stats-rebuild",
170 Self::ArchiveFtsRebuild => "archive-fts-rebuild",
171 Self::ArchivePurge => "archive-purge",
172 Self::ArchiveTokenDailyStatsRebuild => "archive-token-daily-stats-rebuild",
173 Self::Config => "config",
174 Self::CursorDecode => "cursor-decode",
175 Self::CursorParse => "cursor-parse",
176 Self::Daemon => "daemon",
177 Self::DbError => "db-error",
178 Self::DbOpen => "db-open",
179 Self::DbQuery => "db-query",
180 Self::Doctor => "doctor",
181 Self::Download => "download",
182 Self::EmbedderUnavailable => "embedder-unavailable",
183 Self::EmptyFile => "empty-file",
184 Self::EmptySession => "empty-session",
185 Self::EncodeJson => "encode-json",
186 Self::ExportFailed => "export-failed",
187 Self::FailedSeedBundleFile => "failed_seed_bundle_file",
188 Self::FileCreate => "file-create",
189 Self::FileNotFound => "file-not-found",
190 Self::FileOpen => "file-open",
191 Self::FileRead => "file-read",
192 Self::FileWrite => "file-write",
193 Self::Health => "health",
194 Self::IdempotencyMismatch => "idempotency-mismatch",
195 Self::Index => "index",
196 Self::IndexBusy => "index-busy",
197 Self::IndexMissing => "index-missing",
198 Self::IndexedSessionRequired => "indexed-session-required",
199 Self::InvalidAgent => "invalid-agent",
200 Self::InvalidFilename => "invalid-filename",
201 Self::InvalidLine => "invalid-line",
202 Self::Io => "io",
203 Self::IoError => "io-error",
204 Self::LexicalRebuild => "lexical-rebuild",
205 Self::LexicalGeneration => "lexical_generation",
206 Self::LexicalShard => "lexical_shard",
207 Self::LineNotFound => "line-not-found",
208 Self::LineOutOfRange => "line-out-of-range",
209 Self::Local => "local",
210 Self::Mapping => "mapping",
211 Self::MissingDb => "missing-db",
212 Self::MissingIndex => "missing-index",
213 Self::Model => "model",
214 Self::NotFound => "not-found",
215 Self::OpenIndex => "open-index",
216 Self::OpencodeParse => "opencode-parse",
217 Self::OpencodeSqliteParse => "opencode-sqlite-parse",
218 Self::OutputNotWritable => "output-not-writable",
219 Self::PackEmptyQuery => "pack-empty-query",
220 Self::PackInvalidField => "pack-invalid-field",
221 Self::PackInvalidLimit => "pack-invalid-limit",
222 Self::PackNoEvidence => "pack-no-evidence",
223 Self::PackUnsupportedFormat => "pack-unsupported-format",
224 Self::Pages => "pages",
225 Self::ParseError => "parse-error",
226 Self::PasswordReadError => "password-read-error",
227 Self::PasswordRequired => "password-required",
228 Self::RebuildError => "rebuild-error",
229 Self::RepairError => "repair-error",
230 Self::ResumeEmptyCommand => "resume-empty-command",
231 Self::ResumeExecFailed => "resume-exec-failed",
232 Self::RetainedPublishBackup => "retained_publish_backup",
233 Self::Search => "search",
234 Self::SemanticBackfill => "semantic-backfill",
235 Self::SemanticManifest => "semantic-manifest",
236 Self::SemanticUnavailable => "semantic-unavailable",
237 Self::SerializeMessage => "serialize-message",
238 Self::SessionFileUnreadable => "session-file-unreadable",
239 Self::SessionIdNotFound => "session-id-not-found",
240 Self::SessionNotFound => "session-not-found",
241 Self::SessionParse => "session-parse",
242 Self::SessionsFrom => "sessions-from",
243 Self::Setup => "setup",
244 Self::Source => "source",
245 Self::Ssh => "ssh",
246 Self::Storage => "storage",
247 Self::StorageFingerprint => "storage-fingerprint",
248 Self::Timeout => "timeout",
249 Self::Tui => "tui",
250 Self::TuiHeadlessOnce => "tui-headless-once",
251 Self::TuiResetState => "tui-reset-state",
252 Self::Unknown => "unknown",
253 Self::UnknownAgent => "unknown-agent",
254 Self::UpdateCheck => "update-check",
255 Self::Usage => "usage",
256 Self::WriteFailed => "write-failed",
257 }
258 }
259
260 pub fn from_kind_str(kind: &str) -> Option<Self> {
265 Some(match kind {
266 "ambiguous-source" => Self::AmbiguousSource,
267 "archive-analytics-rebuild" => Self::ArchiveAnalyticsRebuild,
268 "archive-count" => Self::ArchiveCount,
269 "archive-daily-stats-rebuild" => Self::ArchiveDailyStatsRebuild,
270 "archive-fts-rebuild" => Self::ArchiveFtsRebuild,
271 "archive-purge" => Self::ArchivePurge,
272 "archive-token-daily-stats-rebuild" => Self::ArchiveTokenDailyStatsRebuild,
273 "config" => Self::Config,
274 "cursor-decode" => Self::CursorDecode,
275 "cursor-parse" => Self::CursorParse,
276 "daemon" => Self::Daemon,
277 "db-error" => Self::DbError,
278 "db-open" => Self::DbOpen,
279 "db-query" => Self::DbQuery,
280 "doctor" => Self::Doctor,
281 "download" => Self::Download,
282 "embedder-unavailable" => Self::EmbedderUnavailable,
283 "empty-file" => Self::EmptyFile,
284 "empty-session" => Self::EmptySession,
285 "encode-json" => Self::EncodeJson,
286 "export-failed" => Self::ExportFailed,
287 "failed_seed_bundle_file" => Self::FailedSeedBundleFile,
288 "file-create" => Self::FileCreate,
289 "file-not-found" => Self::FileNotFound,
290 "file-open" => Self::FileOpen,
291 "file-read" => Self::FileRead,
292 "file-write" => Self::FileWrite,
293 "health" => Self::Health,
294 "idempotency-mismatch" => Self::IdempotencyMismatch,
295 "index" => Self::Index,
296 "index-busy" => Self::IndexBusy,
297 "index-missing" => Self::IndexMissing,
298 "indexed-session-required" => Self::IndexedSessionRequired,
299 "invalid-agent" => Self::InvalidAgent,
300 "invalid-filename" => Self::InvalidFilename,
301 "invalid-line" => Self::InvalidLine,
302 "io" => Self::Io,
303 "io-error" => Self::IoError,
304 "lexical-rebuild" => Self::LexicalRebuild,
305 "lexical_generation" => Self::LexicalGeneration,
306 "lexical_shard" => Self::LexicalShard,
307 "line-not-found" => Self::LineNotFound,
308 "line-out-of-range" => Self::LineOutOfRange,
309 "local" => Self::Local,
310 "mapping" => Self::Mapping,
311 "missing-db" => Self::MissingDb,
312 "missing-index" => Self::MissingIndex,
313 "model" => Self::Model,
314 "not-found" => Self::NotFound,
315 "open-index" => Self::OpenIndex,
316 "opencode-parse" => Self::OpencodeParse,
317 "opencode-sqlite-parse" => Self::OpencodeSqliteParse,
318 "output-not-writable" => Self::OutputNotWritable,
319 "pack-empty-query" => Self::PackEmptyQuery,
320 "pack-invalid-field" => Self::PackInvalidField,
321 "pack-invalid-limit" => Self::PackInvalidLimit,
322 "pack-no-evidence" => Self::PackNoEvidence,
323 "pack-unsupported-format" => Self::PackUnsupportedFormat,
324 "pages" => Self::Pages,
325 "parse-error" => Self::ParseError,
326 "password-read-error" => Self::PasswordReadError,
327 "password-required" => Self::PasswordRequired,
328 "rebuild-error" => Self::RebuildError,
329 "repair-error" => Self::RepairError,
330 "resume-empty-command" => Self::ResumeEmptyCommand,
331 "resume-exec-failed" => Self::ResumeExecFailed,
332 "retained_publish_backup" => Self::RetainedPublishBackup,
333 "search" => Self::Search,
334 "semantic-backfill" => Self::SemanticBackfill,
335 "semantic-manifest" => Self::SemanticManifest,
336 "semantic-unavailable" => Self::SemanticUnavailable,
337 "serialize-message" => Self::SerializeMessage,
338 "session-file-unreadable" => Self::SessionFileUnreadable,
339 "session-id-not-found" => Self::SessionIdNotFound,
340 "session-not-found" => Self::SessionNotFound,
341 "session-parse" => Self::SessionParse,
342 "sessions-from" => Self::SessionsFrom,
343 "setup" => Self::Setup,
344 "source" => Self::Source,
345 "ssh" => Self::Ssh,
346 "storage" => Self::Storage,
347 "storage-fingerprint" => Self::StorageFingerprint,
348 "timeout" => Self::Timeout,
349 "tui" => Self::Tui,
350 "tui-headless-once" => Self::TuiHeadlessOnce,
351 "tui-reset-state" => Self::TuiResetState,
352 "unknown" => Self::Unknown,
353 "unknown-agent" => Self::UnknownAgent,
354 "update-check" => Self::UpdateCheck,
355 "usage" => Self::Usage,
356 "write-failed" => Self::WriteFailed,
357 _ => return None,
358 })
359 }
360
361 pub fn all_variants() -> &'static [Self] {
365 &[
366 Self::AmbiguousSource,
367 Self::ArchiveAnalyticsRebuild,
368 Self::ArchiveCount,
369 Self::ArchiveDailyStatsRebuild,
370 Self::ArchiveFtsRebuild,
371 Self::ArchivePurge,
372 Self::ArchiveTokenDailyStatsRebuild,
373 Self::Config,
374 Self::CursorDecode,
375 Self::CursorParse,
376 Self::Daemon,
377 Self::DbError,
378 Self::DbOpen,
379 Self::DbQuery,
380 Self::Doctor,
381 Self::Download,
382 Self::EmbedderUnavailable,
383 Self::EmptyFile,
384 Self::EmptySession,
385 Self::EncodeJson,
386 Self::ExportFailed,
387 Self::FailedSeedBundleFile,
388 Self::FileCreate,
389 Self::FileNotFound,
390 Self::FileOpen,
391 Self::FileRead,
392 Self::FileWrite,
393 Self::Health,
394 Self::IdempotencyMismatch,
395 Self::Index,
396 Self::IndexBusy,
397 Self::IndexMissing,
398 Self::IndexedSessionRequired,
399 Self::InvalidAgent,
400 Self::InvalidFilename,
401 Self::InvalidLine,
402 Self::Io,
403 Self::IoError,
404 Self::LexicalRebuild,
405 Self::LexicalGeneration,
406 Self::LexicalShard,
407 Self::LineNotFound,
408 Self::LineOutOfRange,
409 Self::Local,
410 Self::Mapping,
411 Self::MissingDb,
412 Self::MissingIndex,
413 Self::Model,
414 Self::NotFound,
415 Self::OpenIndex,
416 Self::OpencodeParse,
417 Self::OpencodeSqliteParse,
418 Self::OutputNotWritable,
419 Self::PackEmptyQuery,
420 Self::PackInvalidField,
421 Self::PackInvalidLimit,
422 Self::PackNoEvidence,
423 Self::PackUnsupportedFormat,
424 Self::Pages,
425 Self::ParseError,
426 Self::PasswordReadError,
427 Self::PasswordRequired,
428 Self::RebuildError,
429 Self::RepairError,
430 Self::ResumeEmptyCommand,
431 Self::ResumeExecFailed,
432 Self::RetainedPublishBackup,
433 Self::Search,
434 Self::SemanticBackfill,
435 Self::SemanticManifest,
436 Self::SemanticUnavailable,
437 Self::SerializeMessage,
438 Self::SessionFileUnreadable,
439 Self::SessionIdNotFound,
440 Self::SessionNotFound,
441 Self::SessionParse,
442 Self::SessionsFrom,
443 Self::Setup,
444 Self::Source,
445 Self::Ssh,
446 Self::Storage,
447 Self::StorageFingerprint,
448 Self::Timeout,
449 Self::Tui,
450 Self::TuiHeadlessOnce,
451 Self::TuiResetState,
452 Self::Unknown,
453 Self::UnknownAgent,
454 Self::UpdateCheck,
455 Self::Usage,
456 Self::WriteFailed,
457 ]
458 }
459}
460
461#[cfg(test)]
462mod tests {
463 use super::*;
464 use std::collections::HashSet;
465
466 #[test]
471 fn every_error_kind_round_trips_through_kind_str() {
472 for variant in ErrorKind::all_variants() {
473 let kind = variant.kind_str();
474 let parsed = ErrorKind::from_kind_str(kind).unwrap_or_else(|| {
475 panic!(
476 "ErrorKind::{:?}.kind_str() = {:?} but from_kind_str returned None — \
477 missing from_kind_str arm",
478 variant, kind
479 )
480 });
481 assert_eq!(
482 parsed, *variant,
483 "round-trip mismatch: {:?}.kind_str() → {:?} → {:?}",
484 variant, kind, parsed
485 );
486 }
487 }
488
489 #[test]
493 fn every_kind_str_is_unique() {
494 let mut seen: HashSet<&'static str> = HashSet::new();
495 for variant in ErrorKind::all_variants() {
496 let kind = variant.kind_str();
497 assert!(
498 seen.insert(kind),
499 "duplicate kind_str detected: {:?} maps to {:?} which was already \
500 registered by an earlier variant",
501 variant,
502 kind
503 );
504 }
505 }
506
507 #[test]
515 fn variant_count_matches_audited_lib_rs_kind_literals() {
516 const AUDITED_KIND_COUNT: usize = 91;
520 assert_eq!(
521 ErrorKind::all_variants().len(),
522 AUDITED_KIND_COUNT,
523 "ErrorKind variant count drifted from the audited lib.rs literal set; \
524 re-run `grep -oE 'kind: \"[a-z_-]+\"' src/lib.rs | sort -u | wc -l` and \
525 update the constant + add the missing variant"
526 );
527 }
528
529 #[test]
534 fn snake_case_stragglers_preserve_legacy_wire_format() {
535 assert_eq!(
536 ErrorKind::FailedSeedBundleFile.kind_str(),
537 "failed_seed_bundle_file"
538 );
539 assert_eq!(
540 ErrorKind::LexicalGeneration.kind_str(),
541 "lexical_generation"
542 );
543 assert_eq!(ErrorKind::LexicalShard.kind_str(), "lexical_shard");
544 assert_eq!(
545 ErrorKind::RetainedPublishBackup.kind_str(),
546 "retained_publish_backup"
547 );
548 }
549
550 #[test]
553 fn from_kind_str_returns_none_for_unknown_inputs() {
554 assert_eq!(ErrorKind::from_kind_str(""), None);
555 assert_eq!(ErrorKind::from_kind_str("not-a-real-kind"), None);
556 assert_eq!(ErrorKind::from_kind_str("DB-ERROR"), None);
558 assert_eq!(ErrorKind::from_kind_str("Db-Error"), None);
559 assert_eq!(
562 ErrorKind::from_kind_str("unknown"),
563 Some(ErrorKind::Unknown)
564 );
565 }
566
567 #[test]
575 fn error_kind_is_serde_compatible() {
576 let json = serde_json::to_string(&ErrorKind::DbError).expect("serialize");
577 let parsed: ErrorKind = serde_json::from_str(&json).expect("deserialize");
578 assert_eq!(parsed, ErrorKind::DbError);
579 }
580}