memvid_core/
error.rs

1use std::borrow::Cow;
2use std::path::PathBuf;
3
4use thiserror::Error;
5
6/// Result alias used throughout the core crate.
7pub type Result<T> = std::result::Result<T, MemvidError>;
8
9/// Process metadata for a lock holder used to produce human readable diagnostics.
10#[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/// Structured error returned when a `.mv2` is locked by another writer.
22#[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/// Canonical error surface for memvid-core.
49#[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("This file is encrypted: {path}\n{hint}")]
76    EncryptedFile { path: PathBuf, hint: String },
77
78    #[error("Table of contents validation failed: {reason}")]
79    InvalidToc { reason: Cow<'static, str> },
80
81    #[error("Time index track is invalid: {reason}")]
82    InvalidTimeIndex { reason: Cow<'static, str> },
83
84    #[error("Sketch track is invalid: {reason}")]
85    InvalidSketchTrack { reason: Cow<'static, str> },
86
87    #[cfg(feature = "temporal_track")]
88    #[error("Temporal track is invalid: {reason}")]
89    InvalidTemporalTrack { reason: Cow<'static, str> },
90
91    #[error("Logic-Mesh is invalid: {reason}")]
92    InvalidLogicMesh { reason: Cow<'static, str> },
93
94    #[error("Logic-Mesh is not enabled")]
95    LogicMeshNotEnabled,
96
97    #[error("NER model not available: {reason}")]
98    NerModelNotAvailable { reason: Cow<'static, str> },
99
100    #[error("Unsupported tier requested")]
101    InvalidTier,
102
103    #[error("Lexical index is not enabled")]
104    LexNotEnabled,
105
106    #[error("Vector index is not enabled")]
107    VecNotEnabled,
108
109    #[error("CLIP index is not enabled")]
110    ClipNotEnabled,
111
112    #[error("Vector dimension mismatch (expected {expected}, got {actual})")]
113    VecDimensionMismatch { expected: u32, actual: usize },
114
115    #[error("Auxiliary file detected: {path:?}")]
116    AuxiliaryFileDetected { path: PathBuf },
117
118    #[error("Embedded WAL is corrupted at offset {offset}: {reason}")]
119    WalCorruption {
120        offset: u64,
121        reason: Cow<'static, str>,
122    },
123
124    #[error("Manifest WAL is corrupted at offset {offset}: {reason}")]
125    ManifestWalCorrupted { offset: u64, reason: &'static str },
126
127    #[error("Unable to checkpoint embedded WAL: {reason}")]
128    CheckpointFailed { reason: String },
129
130    #[error("Ticket sequence is out of order (expected > {expected}, got {actual})")]
131    TicketSequence { expected: i64, actual: i64 },
132
133    #[error("Apply a ticket before mutating this memory (tier {tier:?})")]
134    TicketRequired { tier: crate::types::Tier },
135
136    #[error(
137        "Capacity exceeded. Current: {current} bytes, Limit: {limit} bytes, Required: {required} bytes"
138    )]
139    CapacityExceeded {
140        current: u64,
141        limit: u64,
142        required: u64,
143    },
144
145    #[error("API key required for files larger than {limit} bytes. File size: {file_size} bytes")]
146    ApiKeyRequired { file_size: u64, limit: u64 },
147
148    #[error(
149        "Memory already bound to '{existing_memory_name}' ({existing_memory_id}). Bound at: {bound_at}"
150    )]
151    MemoryAlreadyBound {
152        existing_memory_id: uuid::Uuid,
153        existing_memory_name: String,
154        bound_at: String,
155    },
156
157    #[error("Operation requires a sealed memory")]
158    RequiresSealed,
159
160    #[error("Operation requires an open memory")]
161    RequiresOpen,
162
163    #[error("Doctor command requires at least one operation")]
164    DoctorNoOp,
165
166    #[error("Doctor operation failed: {reason}")]
167    Doctor { reason: String },
168
169    #[error("Feature '{feature}' is not available in this build")]
170    FeatureUnavailable { feature: &'static str },
171
172    #[error("Invalid search cursor: {reason}")]
173    InvalidCursor { reason: &'static str },
174
175    #[error("Invalid frame {frame_id}: {reason}")]
176    InvalidFrame {
177        frame_id: crate::types::FrameId,
178        reason: &'static str,
179    },
180
181    #[error("Frame {frame_id} was not found")]
182    FrameNotFound { frame_id: crate::types::FrameId },
183
184    #[error("Frame with uri '{uri}' was not found")]
185    FrameNotFoundByUri { uri: String },
186
187    #[error("Ticket signature verification failed: {reason}")]
188    TicketSignatureInvalid { reason: Box<str> },
189
190    #[error("Model signature verification failed: {reason}")]
191    ModelSignatureInvalid { reason: Box<str> },
192
193    #[error("Model manifest invalid: {reason}")]
194    ModelManifestInvalid { reason: Box<str> },
195
196    #[error("Model integrity check failed: {reason}")]
197    ModelIntegrity { reason: Box<str> },
198
199    #[error("Extraction failed: {reason}")]
200    ExtractionFailed { reason: Box<str> },
201
202    #[error("Embedding failed: {reason}")]
203    EmbeddingFailed { reason: Box<str> },
204
205    #[error("Reranking failed: {reason}")]
206    RerankFailed { reason: Box<str> },
207
208    #[error("Invalid query: {reason}")]
209    InvalidQuery { reason: String },
210
211    #[error("Tantivy error: {reason}")]
212    Tantivy { reason: String },
213
214    #[error("Table extraction failed: {reason}")]
215    TableExtraction { reason: String },
216
217    #[error("Schema validation failed: {reason}")]
218    SchemaValidation { reason: String },
219}
220
221impl From<std::io::Error> for MemvidError {
222    fn from(source: std::io::Error) -> Self {
223        Self::Io { source, path: None }
224    }
225}
226
227impl From<tantivy::TantivyError> for MemvidError {
228    fn from(value: tantivy::TantivyError) -> Self {
229        Self::Tantivy {
230            reason: value.to_string(),
231        }
232    }
233}