Skip to main content

prax_pgvector/
error.rs

1//! Error types for pgvector operations.
2
3use prax_query::QueryError;
4use thiserror::Error;
5
6/// Result type for pgvector operations.
7pub type VectorResult<T> = Result<T, VectorError>;
8
9/// Errors that can occur during pgvector operations.
10#[derive(Error, Debug)]
11pub enum VectorError {
12    /// The pgvector extension is not installed.
13    #[error("pgvector extension not installed: run CREATE EXTENSION vector")]
14    ExtensionNotInstalled,
15
16    /// Dimension mismatch between vectors.
17    #[error("dimension mismatch: expected {expected}, got {actual}")]
18    DimensionMismatch {
19        /// Expected number of dimensions.
20        expected: usize,
21        /// Actual number of dimensions.
22        actual: usize,
23    },
24
25    /// Empty vector provided.
26    #[error("empty vector: vectors must have at least one dimension")]
27    EmptyVector,
28
29    /// Invalid dimensions for an operation.
30    #[error("invalid dimensions: {0}")]
31    InvalidDimensions(String),
32
33    /// Index creation error.
34    #[error("index error: {0}")]
35    Index(String),
36
37    /// PostgreSQL error.
38    #[error("postgres error: {0}")]
39    Postgres(#[from] prax_postgres::PgError),
40
41    /// Query execution error.
42    #[error("query error: {0}")]
43    Query(String),
44
45    /// Type conversion error.
46    #[error("type conversion error: {0}")]
47    TypeConversion(String),
48
49    /// Configuration error.
50    #[error("configuration error: {0}")]
51    Config(String),
52}
53
54impl VectorError {
55    /// Create a dimension mismatch error.
56    pub fn dimension_mismatch(expected: usize, actual: usize) -> Self {
57        Self::DimensionMismatch { expected, actual }
58    }
59
60    /// Create an index error.
61    pub fn index(message: impl Into<String>) -> Self {
62        Self::Index(message.into())
63    }
64
65    /// Create a query error.
66    pub fn query(message: impl Into<String>) -> Self {
67        Self::Query(message.into())
68    }
69
70    /// Create a type conversion error.
71    pub fn type_conversion(message: impl Into<String>) -> Self {
72        Self::TypeConversion(message.into())
73    }
74
75    /// Create a configuration error.
76    pub fn config(message: impl Into<String>) -> Self {
77        Self::Config(message.into())
78    }
79
80    /// Check if this is a dimension mismatch error.
81    pub fn is_dimension_mismatch(&self) -> bool {
82        matches!(self, Self::DimensionMismatch { .. })
83    }
84
85    /// Check if this is an extension not installed error.
86    pub fn is_extension_not_installed(&self) -> bool {
87        matches!(self, Self::ExtensionNotInstalled)
88    }
89}
90
91impl From<VectorError> for QueryError {
92    fn from(err: VectorError) -> Self {
93        match err {
94            VectorError::ExtensionNotInstalled => {
95                QueryError::database("pgvector extension not installed".to_string())
96            }
97            VectorError::DimensionMismatch { expected, actual } => QueryError::invalid_input(
98                "vector",
99                format!("dimension mismatch: expected {expected}, got {actual}"),
100            ),
101            VectorError::EmptyVector => {
102                QueryError::invalid_input("vector", "empty vector".to_string())
103            }
104            VectorError::InvalidDimensions(msg) => QueryError::invalid_input("vector", msg),
105            VectorError::Index(msg) => QueryError::database(msg),
106            VectorError::Postgres(e) => QueryError::from(e),
107            VectorError::Query(msg) => QueryError::database(msg),
108            VectorError::TypeConversion(msg) => QueryError::serialization(msg),
109            VectorError::Config(msg) => QueryError::connection(msg),
110        }
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn test_error_creation() {
120        let err = VectorError::dimension_mismatch(3, 5);
121        assert!(err.is_dimension_mismatch());
122        assert!(err.to_string().contains("expected 3"));
123        assert!(err.to_string().contains("got 5"));
124    }
125
126    #[test]
127    fn test_extension_not_installed() {
128        let err = VectorError::ExtensionNotInstalled;
129        assert!(err.is_extension_not_installed());
130        assert!(err.to_string().contains("pgvector"));
131    }
132
133    #[test]
134    fn test_empty_vector() {
135        let err = VectorError::EmptyVector;
136        assert!(err.to_string().contains("empty vector"));
137    }
138
139    #[test]
140    fn test_into_query_error() {
141        let err = VectorError::dimension_mismatch(3, 5);
142        let query_err: QueryError = err.into();
143        assert!(query_err.to_string().contains("dimension mismatch"));
144    }
145
146    #[test]
147    fn test_index_error() {
148        let err = VectorError::index("failed to create HNSW index");
149        assert!(err.to_string().contains("HNSW"));
150    }
151
152    #[test]
153    fn test_config_error() {
154        let err = VectorError::config("invalid probes value");
155        assert!(err.to_string().contains("probes"));
156    }
157}