//! Error types for RETRIEVE query construction, validation, and execution. /// Errors arising from query construction, validation, or execution. /// /// Replaces the Milestone 1 stub `QueryError { message }`. Each variant /// carries structured context so callers can programmatically handle /// specific failure modes. #[derive(Debug, thiserror::Error)] pub enum QueryError { /// The named ranking profile does not exist in the registry. #[error("profile '{0}' not found")] ProfileNotFound(String), /// A filter references an invalid field or has an unsupported value. #[error("invalid filter on '{field}': {reason}")] InvalidFilter { field: String, reason: String }, /// The requested limit is outside the allowed range. #[error("limit {requested} out of range [{min}, {max}]")] InvalidLimit { requested: usize, min: usize, max: usize, }, /// A required index (vector, text, bitmap) is not available. #[error("index '{0}' not available")] IndexNotAvailable(String), /// The underlying storage engine returned an error. #[error("storage error: {0}")] StorageError(String), /// A pagination cursor could not be decoded. #[error("invalid cursor: {0}")] InvalidCursor(String), /// The query requires a candidate strategy not yet implemented. #[error("unsupported strategy: {0}")] UnsupportedStrategy(String), /// The FOR SESSION clause references a session that does not exist. #[error("session not found: {0}")] SessionNotFound(String), } // Manual `From` because this is a lossy conversion: `StorageError` is // flattened to its `Display` string. The variant holds `String`, not // `StorageError`, so `#[from]` cannot be used. impl From for QueryError { fn from(e: crate::storage::StorageError) -> Self { Self::StorageError(e.to_string()) } } // ── Tests ─────────────────────────────────────────────────────────────────── #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { use super::*; use crate::storage::StorageError; #[test] fn query_error_display_profile_not_found() { let e = QueryError::ProfileNotFound("trending".into()); assert!(e.to_string().contains("trending")); assert!(e.to_string().contains("not found")); } #[test] fn query_error_display_invalid_filter() { let e = QueryError::InvalidFilter { field: "category".into(), reason: "unknown value".into(), }; assert!(e.to_string().contains("category")); } #[test] fn query_error_display_invalid_limit() { let e = QueryError::InvalidLimit { requested: 0, min: 1, max: 500, }; assert!(e.to_string().contains('0')); } #[test] fn query_error_display_invalid_cursor() { let e = QueryError::InvalidCursor("bad data".into()); assert!(e.to_string().contains("bad data")); } #[test] fn query_error_display_unsupported_strategy() { let e = QueryError::UnsupportedStrategy("Hybrid requires M3".into()); assert!(e.to_string().contains("Hybrid")); } #[test] fn query_error_from_storage_error() { let storage_err = StorageError::Closed; let query_err: QueryError = storage_err.into(); assert!(matches!(query_err, QueryError::StorageError(_))); assert!(query_err.to_string().contains("storage")); } }