edgevec/error.rs
1//! Unified error hierarchy for EdgeVec.
2//!
3//! This module defines the error types used throughout EdgeVec:
4//!
5//! - `EdgeVecError` — Top-level error type wrapping all component errors
6//! - [`BatchError`] — Errors specific to batch insertion operations
7//!
8//! # Error Mapping
9//!
10//! All errors automatically convert to JavaScript objects when used in WASM,
11//! with `code` and `message` properties for structured error handling.
12//!
13//! # Batch Error Handling
14//!
15//! [`BatchError`] supports **best-effort semantics**:
16//! - Fatal errors (dimension mismatch on first vector, capacity exceeded) abort immediately
17//! - Non-fatal errors (duplicates, invalid vectors mid-batch) are skipped
18//! - Partial success is returned via `Ok(Vec<u64>)`
19//!
20//! # Example
21//!
22//! ```ignore
23//! use edgevec::error::BatchError;
24//!
25//! fn handle_batch_error(err: BatchError) {
26//! match err {
27//! BatchError::DimensionMismatch { expected, actual, vector_id } => {
28//! eprintln!("Vector {} has {} dims, expected {}", vector_id, actual, expected);
29//! }
30//! BatchError::DuplicateId { vector_id } => {
31//! eprintln!("Duplicate ID: {}", vector_id);
32//! }
33//! BatchError::CapacityExceeded { current, max } => {
34//! eprintln!("Index full: {}/{}", current, max);
35//! }
36//! _ => eprintln!("Other error: {}", err),
37//! }
38//! }
39//! ```
40
41use crate::flat::BinaryFlatIndexError;
42use crate::hnsw::GraphError;
43use crate::persistence::PersistenceError;
44use thiserror::Error;
45
46/// The Unified EdgeVec Error type.
47#[derive(Debug, Error)]
48pub enum EdgeVecError {
49 /// Input/Output errors (filesystem, network, etc).
50 #[error("IO error: {0}")]
51 Io(#[from] std::io::Error),
52
53 /// Persistence and storage errors.
54 #[error(transparent)]
55 Persistence(#[from] PersistenceError),
56
57 /// Graph algorithm and index errors.
58 #[error(transparent)]
59 Graph(#[from] GraphError),
60
61 /// Flat index errors (dimension mismatch, invalid dimensions).
62 #[error(transparent)]
63 FlatIndex(#[from] BinaryFlatIndexError),
64
65 /// Validation errors (invalid arguments, dimensions, etc).
66 #[error("Validation error: {0}")]
67 Validation(String),
68}
69
70/// Errors that can occur during batch insertion operations.
71///
72/// This type represents errors specific to batch insertion workflows.
73/// Unlike `GraphError`, which handles single-vector operations,
74/// `BatchError` provides context about which vector in a batch failed
75/// and supports best-effort semantics (partial success).
76///
77/// # WASM Error Codes
78///
79/// When used via WASM, these errors map to JavaScript error objects with `code` property:
80/// - `EmptyBatch` → `EMPTY_BATCH`
81/// - `DimensionMismatch` → `DIMENSION_MISMATCH`
82/// - `DuplicateId` → `DUPLICATE_ID`
83/// - `InvalidVector` → `INVALID_VECTOR`
84/// - `CapacityExceeded` → `CAPACITY_EXCEEDED`
85/// - `InternalError` → `INTERNAL_ERROR`
86#[derive(Debug, Clone, PartialEq, Error)]
87pub enum BatchError {
88 /// Empty batch provided (no vectors to insert).
89 #[error("Empty batch: cannot insert zero vectors")]
90 EmptyBatch,
91
92 /// Vector dimensionality does not match index configuration.
93 #[error("Dimension mismatch for vector {vector_id}: expected {expected}, got {actual}")]
94 DimensionMismatch {
95 /// Expected dimension from index
96 expected: usize,
97 /// Actual dimension of rejected vector
98 actual: usize,
99 /// ID of the problematic vector
100 vector_id: u64,
101 },
102
103 /// Vector ID already exists in the index.
104 #[error("Duplicate vector ID: {vector_id}")]
105 DuplicateId {
106 /// Duplicate vector ID
107 vector_id: u64,
108 },
109
110 /// Vector contains invalid floating-point values (NaN, Infinity).
111 #[error("Invalid vector {vector_id}: {reason}")]
112 InvalidVector {
113 /// ID of the invalid vector
114 vector_id: u64,
115 /// Description of the invalid value
116 reason: String,
117 },
118
119 /// Index has reached maximum capacity.
120 #[error("Capacity exceeded: current={current}, max={max}")]
121 CapacityExceeded {
122 /// Current number of vectors
123 current: usize,
124 /// Maximum allowed vectors
125 max: usize,
126 },
127
128 /// Internal HNSW invariant violated during insertion.
129 #[error("Internal error: {message}")]
130 InternalError {
131 /// Description of the violated invariant
132 message: String,
133 },
134}
135
136use wasm_bindgen::prelude::*;
137
138impl From<EdgeVecError> for JsValue {
139 fn from(err: EdgeVecError) -> Self {
140 let (code, msg) = match &err {
141 EdgeVecError::Io(e) => ("ERR_IO", e.to_string()),
142
143 EdgeVecError::Persistence(pe) => match pe {
144 PersistenceError::Io(e) => ("ERR_IO", e.to_string()),
145 PersistenceError::ChecksumMismatch { .. }
146 | PersistenceError::Corrupted(_)
147 | PersistenceError::InvalidMagic { .. } => ("ERR_CORRUPTION", pe.to_string()),
148 _ => ("ERR_PERSISTENCE", pe.to_string()),
149 },
150
151 EdgeVecError::Graph(ge) => match ge {
152 GraphError::ConfigMismatch { .. } | GraphError::DimensionMismatch { .. } => {
153 ("ERR_DIMENSION", ge.to_string())
154 }
155 GraphError::CapacityExceeded => ("ERR_CAPACITY", ge.to_string()),
156 _ => ("ERR_GRAPH", ge.to_string()),
157 },
158
159 EdgeVecError::FlatIndex(fe) => match fe {
160 BinaryFlatIndexError::InvalidDimensions(_)
161 | BinaryFlatIndexError::DimensionMismatch { .. } => {
162 ("ERR_DIMENSION", fe.to_string())
163 }
164 BinaryFlatIndexError::CapacityOverflow(_, _) => ("ERR_CAPACITY", fe.to_string()),
165 },
166
167 EdgeVecError::Validation(msg) => ("ERR_VALIDATION", msg.clone()),
168 };
169
170 // Create JS Error object with code property
171 // Note: In a real implementation we might use a helper to create
172 // a custom object with code/message, or just an Error with prefix.
173 // Here we return a plain Error object, but we could attach `code`.
174 // Ideally: new Error(msg); err.code = code;
175
176 // Simple string for now as fallback, but ideally Object:
177 let obj = js_sys::Object::new();
178 let _ = js_sys::Reflect::set(&obj, &"code".into(), &code.into());
179 let _ = js_sys::Reflect::set(&obj, &"message".into(), &msg.into());
180 obj.into()
181 }
182}
183
184impl From<BatchError> for JsValue {
185 fn from(err: BatchError) -> Self {
186 let (code, msg) = match &err {
187 BatchError::EmptyBatch => ("EMPTY_BATCH", err.to_string()),
188 BatchError::DimensionMismatch { .. } => ("DIMENSION_MISMATCH", err.to_string()),
189 BatchError::DuplicateId { .. } => ("DUPLICATE_ID", err.to_string()),
190 BatchError::InvalidVector { .. } => ("INVALID_VECTOR", err.to_string()),
191 BatchError::CapacityExceeded { .. } => ("CAPACITY_EXCEEDED", err.to_string()),
192 BatchError::InternalError { .. } => ("INTERNAL_ERROR", err.to_string()),
193 };
194
195 let obj = js_sys::Object::new();
196 let _ = js_sys::Reflect::set(&obj, &"code".into(), &code.into());
197 let _ = js_sys::Reflect::set(&obj, &"message".into(), &msg.into());
198 obj.into()
199 }
200}