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::hnsw::GraphError;
42use crate::persistence::PersistenceError;
43use thiserror::Error;
44
45/// The Unified EdgeVec Error type.
46#[derive(Debug, Error)]
47pub enum EdgeVecError {
48 /// Input/Output errors (filesystem, network, etc).
49 #[error("IO error: {0}")]
50 Io(#[from] std::io::Error),
51
52 /// Persistence and storage errors.
53 #[error(transparent)]
54 Persistence(#[from] PersistenceError),
55
56 /// Graph algorithm and index errors.
57 #[error(transparent)]
58 Graph(#[from] GraphError),
59
60 /// Validation errors (invalid arguments, dimensions, etc).
61 #[error("Validation error: {0}")]
62 Validation(String),
63}
64
65/// Errors that can occur during batch insertion operations.
66///
67/// This type represents errors specific to batch insertion workflows.
68/// Unlike `GraphError`, which handles single-vector operations,
69/// `BatchError` provides context about which vector in a batch failed
70/// and supports best-effort semantics (partial success).
71///
72/// # WASM Error Codes
73///
74/// When used via WASM, these errors map to JavaScript error objects with `code` property:
75/// - `EmptyBatch` → `EMPTY_BATCH`
76/// - `DimensionMismatch` → `DIMENSION_MISMATCH`
77/// - `DuplicateId` → `DUPLICATE_ID`
78/// - `InvalidVector` → `INVALID_VECTOR`
79/// - `CapacityExceeded` → `CAPACITY_EXCEEDED`
80/// - `InternalError` → `INTERNAL_ERROR`
81#[derive(Debug, Clone, PartialEq, Error)]
82pub enum BatchError {
83 /// Empty batch provided (no vectors to insert).
84 #[error("Empty batch: cannot insert zero vectors")]
85 EmptyBatch,
86
87 /// Vector dimensionality does not match index configuration.
88 #[error("Dimension mismatch for vector {vector_id}: expected {expected}, got {actual}")]
89 DimensionMismatch {
90 /// Expected dimension from index
91 expected: usize,
92 /// Actual dimension of rejected vector
93 actual: usize,
94 /// ID of the problematic vector
95 vector_id: u64,
96 },
97
98 /// Vector ID already exists in the index.
99 #[error("Duplicate vector ID: {vector_id}")]
100 DuplicateId {
101 /// Duplicate vector ID
102 vector_id: u64,
103 },
104
105 /// Vector contains invalid floating-point values (NaN, Infinity).
106 #[error("Invalid vector {vector_id}: {reason}")]
107 InvalidVector {
108 /// ID of the invalid vector
109 vector_id: u64,
110 /// Description of the invalid value
111 reason: String,
112 },
113
114 /// Index has reached maximum capacity.
115 #[error("Capacity exceeded: current={current}, max={max}")]
116 CapacityExceeded {
117 /// Current number of vectors
118 current: usize,
119 /// Maximum allowed vectors
120 max: usize,
121 },
122
123 /// Internal HNSW invariant violated during insertion.
124 #[error("Internal error: {message}")]
125 InternalError {
126 /// Description of the violated invariant
127 message: String,
128 },
129}
130
131use wasm_bindgen::prelude::*;
132
133impl From<EdgeVecError> for JsValue {
134 fn from(err: EdgeVecError) -> Self {
135 let (code, msg) = match &err {
136 EdgeVecError::Io(e) => ("ERR_IO", e.to_string()),
137
138 EdgeVecError::Persistence(pe) => match pe {
139 PersistenceError::Io(e) => ("ERR_IO", e.to_string()),
140 PersistenceError::ChecksumMismatch { .. }
141 | PersistenceError::Corrupted(_)
142 | PersistenceError::InvalidMagic { .. } => ("ERR_CORRUPTION", pe.to_string()),
143 _ => ("ERR_PERSISTENCE", pe.to_string()),
144 },
145
146 EdgeVecError::Graph(ge) => match ge {
147 GraphError::ConfigMismatch { .. } | GraphError::DimensionMismatch { .. } => {
148 ("ERR_DIMENSION", ge.to_string())
149 }
150 GraphError::CapacityExceeded => ("ERR_CAPACITY", ge.to_string()),
151 _ => ("ERR_GRAPH", ge.to_string()),
152 },
153
154 EdgeVecError::Validation(msg) => ("ERR_VALIDATION", msg.clone()),
155 };
156
157 // Create JS Error object with code property
158 // Note: In a real implementation we might use a helper to create
159 // a custom object with code/message, or just an Error with prefix.
160 // Here we return a plain Error object, but we could attach `code`.
161 // Ideally: new Error(msg); err.code = code;
162
163 // Simple string for now as fallback, but ideally Object:
164 let obj = js_sys::Object::new();
165 let _ = js_sys::Reflect::set(&obj, &"code".into(), &code.into());
166 let _ = js_sys::Reflect::set(&obj, &"message".into(), &msg.into());
167 obj.into()
168 }
169}
170
171impl From<BatchError> for JsValue {
172 fn from(err: BatchError) -> Self {
173 let (code, msg) = match &err {
174 BatchError::EmptyBatch => ("EMPTY_BATCH", err.to_string()),
175 BatchError::DimensionMismatch { .. } => ("DIMENSION_MISMATCH", err.to_string()),
176 BatchError::DuplicateId { .. } => ("DUPLICATE_ID", err.to_string()),
177 BatchError::InvalidVector { .. } => ("INVALID_VECTOR", err.to_string()),
178 BatchError::CapacityExceeded { .. } => ("CAPACITY_EXCEEDED", err.to_string()),
179 BatchError::InternalError { .. } => ("INTERNAL_ERROR", err.to_string()),
180 };
181
182 let obj = js_sys::Object::new();
183 let _ = js_sys::Reflect::set(&obj, &"code".into(), &code.into());
184 let _ = js_sys::Reflect::set(&obj, &"message".into(), &msg.into());
185 obj.into()
186 }
187}