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}