Skip to main content

iqdb_types/
error.rs

1//! The iqdb domain error.
2//!
3//! [`IqdbError`] is the single error type for the iqdb vector-database spine.
4//! Each variant names one specific failure so a caller can react to the exact
5//! cause rather than parsing a message. It implements
6//! [`error_forge::ForgeError`], so it slots into the portfolio error stack
7//! (kinds, captions, the central error hook) the same way every other domain
8//! error does.
9
10use core::fmt;
11
12use error_forge::ForgeError;
13
14/// An error from an iqdb vector-database operation.
15///
16/// Each variant identifies one specific failure. The enum is
17/// `#[non_exhaustive]`: future releases may add variants without it being a
18/// breaking change, so a `match` on it must include a wildcard arm.
19///
20/// # Examples
21///
22/// ```
23/// use iqdb_types::IqdbError;
24///
25/// let err = IqdbError::DimensionMismatch { expected: 3, found: 2 };
26/// assert_eq!(
27///     err.to_string(),
28///     "vector dimension mismatch: expected 3, found 2",
29/// );
30///
31/// let cfg = IqdbError::InvalidConfig { reason: "dim must be greater than zero" };
32/// assert_eq!(
33///     cfg.to_string(),
34///     "invalid configuration: dim must be greater than zero",
35/// );
36/// ```
37#[non_exhaustive]
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum IqdbError {
40    /// A vector did not have the dimensionality the operation required —
41    /// `expected` is the index's dimension, `found` is what was supplied.
42    DimensionMismatch {
43        /// The dimensionality the operation required.
44        expected: usize,
45        /// The dimensionality that was actually supplied.
46        found: usize,
47    },
48    /// A vector was not valid for the operation (for example, empty when a
49    /// non-empty vector was required).
50    InvalidVector,
51    /// A configuration value could not describe a working index or query.
52    /// `reason` is a short static description of which configuration check
53    /// failed, suitable for inclusion in operator-facing logs.
54    InvalidConfig {
55        /// Short static description of which configuration check failed.
56        reason: &'static str,
57    },
58    /// The requested id or record does not exist.
59    NotFound,
60    /// An insert collided with an id that is already present.
61    Duplicate,
62    /// The distance metric was not valid for the operation or the vectors.
63    InvalidMetric,
64    /// A filter expression was malformed or could not be evaluated.
65    InvalidFilter,
66    /// An incoming write exceeded a configured resource limit. `kind` names
67    /// which cap was hit (one of `"id_bytes"`, `"metadata_keys"`,
68    /// `"metadata_key_bytes"`, `"metadata_value_string_bytes"`,
69    /// `"total_vectors"`); `max` is the cap; `found` is what the caller
70    /// supplied. Surfaces from the `Database` write boundary; the
71    /// type-level constructors in this crate never produce it.
72    ResourceLimitExceeded {
73        /// Short, stable identifier for the cap that was exceeded.
74        kind: &'static str,
75        /// The configured maximum the cap allowed.
76        max: usize,
77        /// The value the caller actually supplied.
78        found: usize,
79    },
80}
81
82impl fmt::Display for IqdbError {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        match self {
85            Self::DimensionMismatch { expected, found } => {
86                write!(
87                    f,
88                    "vector dimension mismatch: expected {expected}, found {found}"
89                )
90            }
91            Self::InvalidVector => f.write_str("invalid vector"),
92            Self::InvalidConfig { reason } => write!(f, "invalid configuration: {reason}"),
93            Self::NotFound => f.write_str("not found"),
94            Self::Duplicate => f.write_str("duplicate id"),
95            Self::InvalidMetric => f.write_str("invalid distance metric"),
96            Self::InvalidFilter => f.write_str("invalid filter"),
97            Self::ResourceLimitExceeded { kind, max, found } => {
98                write!(f, "resource limit exceeded: {kind} max={max} found={found}")
99            }
100        }
101    }
102}
103
104impl std::error::Error for IqdbError {}
105
106impl ForgeError for IqdbError {
107    #[inline]
108    fn kind(&self) -> &'static str {
109        match self {
110            Self::DimensionMismatch { .. } => "DimensionMismatch",
111            Self::InvalidVector => "InvalidVector",
112            Self::InvalidConfig { .. } => "InvalidConfig",
113            Self::NotFound => "NotFound",
114            Self::Duplicate => "Duplicate",
115            Self::InvalidMetric => "InvalidMetric",
116            Self::InvalidFilter => "InvalidFilter",
117            Self::ResourceLimitExceeded { .. } => "ResourceLimitExceeded",
118        }
119    }
120
121    #[inline]
122    fn caption(&self) -> &'static str {
123        match self {
124            Self::DimensionMismatch { .. } => "vector dimension does not match the index",
125            Self::InvalidVector => "vector is empty or contains non-finite components",
126            Self::InvalidConfig { .. } => "configuration is not valid for the requested operation",
127            Self::NotFound => "requested id is not present in the index",
128            Self::Duplicate => "id already exists in the index",
129            Self::InvalidMetric => "distance metric does not match the index or vectors",
130            Self::InvalidFilter => "filter expression is malformed or cannot be evaluated",
131            Self::ResourceLimitExceeded { .. } => "an input exceeded a configured resource limit",
132        }
133    }
134}
135
136/// A specialized [`Result`](core::result::Result) whose error is [`IqdbError`].
137///
138/// # Examples
139///
140/// ```
141/// use iqdb_types::{IqdbError, Result};
142///
143/// fn require_non_empty(dim: usize) -> Result<()> {
144///     if dim == 0 {
145///         return Err(IqdbError::InvalidConfig { reason: "dim must be greater than zero" });
146///     }
147///     Ok(())
148/// }
149///
150/// assert!(require_non_empty(0).is_err());
151/// assert!(require_non_empty(3).is_ok());
152/// ```
153pub type Result<T> = core::result::Result<T, IqdbError>;