Skip to main content

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}