use prax_query::QueryError;
use thiserror::Error;
pub type VectorResult<T> = Result<T, VectorError>;
#[derive(Error, Debug)]
pub enum VectorError {
#[error("pgvector extension not installed: run CREATE EXTENSION vector")]
ExtensionNotInstalled,
#[error("dimension mismatch: expected {expected}, got {actual}")]
DimensionMismatch {
expected: usize,
actual: usize,
},
#[error("empty vector: vectors must have at least one dimension")]
EmptyVector,
#[error("invalid dimensions: {0}")]
InvalidDimensions(String),
#[error("index error: {0}")]
Index(String),
#[error("postgres error: {0}")]
Postgres(#[from] prax_postgres::PgError),
#[error("query error: {0}")]
Query(String),
#[error("type conversion error: {0}")]
TypeConversion(String),
#[error("configuration error: {0}")]
Config(String),
}
impl VectorError {
pub fn dimension_mismatch(expected: usize, actual: usize) -> Self {
Self::DimensionMismatch { expected, actual }
}
pub fn index(message: impl Into<String>) -> Self {
Self::Index(message.into())
}
pub fn query(message: impl Into<String>) -> Self {
Self::Query(message.into())
}
pub fn type_conversion(message: impl Into<String>) -> Self {
Self::TypeConversion(message.into())
}
pub fn config(message: impl Into<String>) -> Self {
Self::Config(message.into())
}
pub fn is_dimension_mismatch(&self) -> bool {
matches!(self, Self::DimensionMismatch { .. })
}
pub fn is_extension_not_installed(&self) -> bool {
matches!(self, Self::ExtensionNotInstalled)
}
}
impl From<VectorError> for QueryError {
fn from(err: VectorError) -> Self {
match err {
VectorError::ExtensionNotInstalled => {
QueryError::database("pgvector extension not installed".to_string())
}
VectorError::DimensionMismatch { expected, actual } => QueryError::invalid_input(
"vector",
format!("dimension mismatch: expected {expected}, got {actual}"),
),
VectorError::EmptyVector => {
QueryError::invalid_input("vector", "empty vector".to_string())
}
VectorError::InvalidDimensions(msg) => QueryError::invalid_input("vector", msg),
VectorError::Index(msg) => QueryError::database(msg),
VectorError::Postgres(e) => QueryError::from(e),
VectorError::Query(msg) => QueryError::database(msg),
VectorError::TypeConversion(msg) => QueryError::serialization(msg),
VectorError::Config(msg) => QueryError::connection(msg),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_creation() {
let err = VectorError::dimension_mismatch(3, 5);
assert!(err.is_dimension_mismatch());
assert!(err.to_string().contains("expected 3"));
assert!(err.to_string().contains("got 5"));
}
#[test]
fn test_extension_not_installed() {
let err = VectorError::ExtensionNotInstalled;
assert!(err.is_extension_not_installed());
assert!(err.to_string().contains("pgvector"));
}
#[test]
fn test_empty_vector() {
let err = VectorError::EmptyVector;
assert!(err.to_string().contains("empty vector"));
}
#[test]
fn test_into_query_error() {
let err = VectorError::dimension_mismatch(3, 5);
let query_err: QueryError = err.into();
assert!(query_err.to_string().contains("dimension mismatch"));
}
#[test]
fn test_index_error() {
let err = VectorError::index("failed to create HNSW index");
assert!(err.to_string().contains("HNSW"));
}
#[test]
fn test_config_error() {
let err = VectorError::config("invalid probes value");
assert!(err.to_string().contains("probes"));
}
}