1use std::borrow::Cow;
2use std::path::PathBuf;
3
4use thiserror::Error;
5
6pub type Result<T> = std::result::Result<T, MemvidError>;
8
9#[derive(Debug, Clone)]
11pub struct LockOwnerHint {
12 pub pid: Option<u32>,
13 pub cmd: Option<String>,
14 pub started_at: Option<String>,
15 pub file_path: Option<PathBuf>,
16 pub file_id: Option<String>,
17 pub last_heartbeat: Option<String>,
18 pub heartbeat_ms: Option<u64>,
19}
20
21#[derive(Debug, Error, Clone)]
23#[error("{message}")]
24pub struct LockedError {
25 pub file: PathBuf,
26 pub message: String,
27 pub owner: Option<LockOwnerHint>,
28 pub stale: bool,
29}
30
31impl LockedError {
32 #[must_use]
33 pub fn new(
34 file: PathBuf,
35 message: impl Into<String>,
36 owner: Option<LockOwnerHint>,
37 stale: bool,
38 ) -> Self {
39 Self {
40 file,
41 message: message.into(),
42 owner,
43 stale,
44 }
45 }
46}
47
48#[derive(Debug, Error)]
50pub enum MemvidError {
51 #[error("I/O error: {source}")]
52 Io {
53 source: std::io::Error,
54 path: Option<PathBuf>,
55 },
56
57 #[error("Serialization error: {0}")]
58 Encode(#[from] bincode::error::EncodeError),
59
60 #[error("Deserialization error: {0}")]
61 Decode(#[from] bincode::error::DecodeError),
62
63 #[error("Lock acquisition failed: {0}")]
64 Lock(String),
65
66 #[error(transparent)]
67 Locked(#[from] LockedError),
68
69 #[error("Checksum mismatch while validating {context}")]
70 ChecksumMismatch { context: &'static str },
71
72 #[error("Header validation failed: {reason}")]
73 InvalidHeader { reason: Cow<'static, str> },
74
75 #[error("Table of contents validation failed: {reason}")]
76 InvalidToc { reason: Cow<'static, str> },
77
78 #[error("Time index track is invalid: {reason}")]
79 InvalidTimeIndex { reason: Cow<'static, str> },
80
81 #[cfg(feature = "temporal_track")]
82 #[error("Temporal track is invalid: {reason}")]
83 InvalidTemporalTrack { reason: Cow<'static, str> },
84
85 #[error("Logic-Mesh is invalid: {reason}")]
86 InvalidLogicMesh { reason: Cow<'static, str> },
87
88 #[error("Logic-Mesh is not enabled")]
89 LogicMeshNotEnabled,
90
91 #[error("NER model not available: {reason}")]
92 NerModelNotAvailable { reason: Cow<'static, str> },
93
94 #[error("Unsupported tier requested")]
95 InvalidTier,
96
97 #[error("Lexical index is not enabled")]
98 LexNotEnabled,
99
100 #[error("Vector index is not enabled")]
101 VecNotEnabled,
102
103 #[error("CLIP index is not enabled")]
104 ClipNotEnabled,
105
106 #[error("Vector dimension mismatch (expected {expected}, got {actual})")]
107 VecDimensionMismatch { expected: u32, actual: usize },
108
109 #[error("Auxiliary file detected: {path:?}")]
110 AuxiliaryFileDetected { path: PathBuf },
111
112 #[error("Embedded WAL is corrupted at offset {offset}: {reason}")]
113 WalCorruption {
114 offset: u64,
115 reason: Cow<'static, str>,
116 },
117
118 #[error("Manifest WAL is corrupted at offset {offset}: {reason}")]
119 ManifestWalCorrupted { offset: u64, reason: &'static str },
120
121 #[error("Unable to checkpoint embedded WAL: {reason}")]
122 CheckpointFailed { reason: String },
123
124 #[error("Ticket sequence is out of order (expected > {expected}, got {actual})")]
125 TicketSequence { expected: i64, actual: i64 },
126
127 #[error("Apply a ticket before mutating this memory (tier {tier:?})")]
128 TicketRequired { tier: crate::types::Tier },
129
130 #[error(
131 "Capacity exceeded. Current: {current} bytes, Limit: {limit} bytes, Required: {required} bytes"
132 )]
133 CapacityExceeded {
134 current: u64,
135 limit: u64,
136 required: u64,
137 },
138
139 #[error("API key required for files larger than {limit} bytes. File size: {file_size} bytes")]
140 ApiKeyRequired { file_size: u64, limit: u64 },
141
142 #[error(
143 "Memory already bound to '{existing_memory_name}' ({existing_memory_id}). Bound at: {bound_at}"
144 )]
145 MemoryAlreadyBound {
146 existing_memory_id: uuid::Uuid,
147 existing_memory_name: String,
148 bound_at: String,
149 },
150
151 #[error("Operation requires a sealed memory")]
152 RequiresSealed,
153
154 #[error("Operation requires an open memory")]
155 RequiresOpen,
156
157 #[error("Doctor command requires at least one operation")]
158 DoctorNoOp,
159
160 #[error("Doctor operation failed: {reason}")]
161 Doctor { reason: String },
162
163 #[error("Feature '{feature}' is not available in this build")]
164 FeatureUnavailable { feature: &'static str },
165
166 #[error("Invalid search cursor: {reason}")]
167 InvalidCursor { reason: &'static str },
168
169 #[error("Invalid frame {frame_id}: {reason}")]
170 InvalidFrame {
171 frame_id: crate::types::FrameId,
172 reason: &'static str,
173 },
174
175 #[error("Frame {frame_id} was not found")]
176 FrameNotFound { frame_id: crate::types::FrameId },
177
178 #[error("Frame with uri '{uri}' was not found")]
179 FrameNotFoundByUri { uri: String },
180
181 #[error("Ticket signature verification failed: {reason}")]
182 TicketSignatureInvalid { reason: Box<str> },
183
184 #[error("Model signature verification failed: {reason}")]
185 ModelSignatureInvalid { reason: Box<str> },
186
187 #[error("Model manifest invalid: {reason}")]
188 ModelManifestInvalid { reason: Box<str> },
189
190 #[error("Model integrity check failed: {reason}")]
191 ModelIntegrity { reason: Box<str> },
192
193 #[error("Extraction failed: {reason}")]
194 ExtractionFailed { reason: Box<str> },
195
196 #[error("Embedding failed: {reason}")]
197 EmbeddingFailed { reason: Box<str> },
198
199 #[error("Reranking failed: {reason}")]
200 RerankFailed { reason: Box<str> },
201
202 #[error("Invalid query: {reason}")]
203 InvalidQuery { reason: String },
204
205 #[error("Tantivy error: {reason}")]
206 Tantivy { reason: String },
207
208 #[error("Table extraction failed: {reason}")]
209 TableExtraction { reason: String },
210}
211
212impl From<std::io::Error> for MemvidError {
213 fn from(source: std::io::Error) -> Self {
214 Self::Io { source, path: None }
215 }
216}
217
218impl From<tantivy::TantivyError> for MemvidError {
219 fn from(value: tantivy::TantivyError) -> Self {
220 Self::Tantivy {
221 reason: value.to_string(),
222 }
223 }
224}