neomemx 0.1.2

A high-performance memory library for AI agents with semantic search
Documentation
//! Error types for neomemx.

#![allow(missing_docs)]

use std::fmt::{Display, Formatter};
use thiserror::Error;

/// Structured error codes.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ErrorCode {
    ConfigMissing = 1001,
    ConfigInvalid = 1002,
    ValidationRequired = 2001,
    ValidationFormat = 2002,
    ProviderConnection = 3001,
    ProviderAuth = 3002,
    ProviderRateLimit = 3003,
    ProviderTimeout = 3004,
    ProviderError = 3005,
    StorageConnection = 4001,
    StorageNotFound = 4002,
    StorageError = 4003,
    Internal = 9001,
}

impl ErrorCode {
    pub fn is_retryable(&self) -> bool {
        matches!(
            self,
            Self::ProviderRateLimit | Self::ProviderTimeout | Self::ProviderConnection
        )
    }

    pub fn code(&self) -> u32 {
        *self as u32
    }
}

impl Display for ErrorCode {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "E{:04}", self.code())
    }
}

/// Main error type for neomemx.
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum NeomemxError {
    #[error("Config error [{code}]: {message}")]
    ConfigError { message: String, code: ErrorCode },

    #[error("Validation error [{code}]: {message}")]
    ValidationError {
        message: String,
        code: ErrorCode,
        details: Option<String>,
        suggestion: Option<String>,
    },

    #[error("LLM error: {0}")]
    LlmError(String),

    #[error("Embedding error: {0}")]
    EmbeddingError(String),

    #[error("Vector store error: {0}")]
    VectorStoreError(String),

    #[error("Reranker error: {0}")]
    RerankerError(String),

    #[error("Database error: {0}")]
    DatabaseError(String),

    #[error("HTTP error: {0}")]
    HttpError(#[from] reqwest::Error),

    #[error("JSON error: {0}")]
    JsonError(#[from] serde_json::Error),

    #[error("SQLite error: {0}")]
    SqliteError(#[from] rusqlite::Error),

    #[error("IO error: {0}")]
    IoError(#[from] std::io::Error),

    #[error("Memory not found: {0}")]
    MemoryNotFound(String),

    #[error("Internal error: {0}")]
    InternalError(String),
}

impl NeomemxError {
    pub fn validation(
        message: impl Into<String>,
        code: ErrorCode,
        details: Option<String>,
        suggestion: Option<String>,
    ) -> Self {
        Self::ValidationError {
            message: message.into(),
            code,
            details,
            suggestion,
        }
    }

    pub fn required(field: &str) -> Self {
        Self::validation(
            format!("Required field '{}' is missing", field),
            ErrorCode::ValidationRequired,
            None,
            Some(format!("Please provide a value for '{}'", field)),
        )
    }

    pub fn invalid_format(message: impl Into<String>) -> Self {
        Self::validation(message, ErrorCode::ValidationFormat, None, None)
    }

    pub fn config(message: impl Into<String>, code: ErrorCode) -> Self {
        Self::ConfigError {
            message: message.into(),
            code,
        }
    }

    pub fn config_missing(name: &str) -> Self {
        Self::config(
            format!("Missing configuration: {}", name),
            ErrorCode::ConfigMissing,
        )
    }

    pub fn config_invalid(message: impl Into<String>) -> Self {
        Self::config(message, ErrorCode::ConfigInvalid)
    }

    pub fn is_retryable(&self) -> bool {
        match self {
            Self::HttpError(e) => e.is_timeout() || e.is_connect(),
            Self::ValidationError { code, .. } | Self::ConfigError { code, .. } => {
                code.is_retryable()
            }
            _ => false,
        }
    }

    pub fn error_code(&self) -> Option<ErrorCode> {
        match self {
            Self::ValidationError { code, .. } | Self::ConfigError { code, .. } => Some(*code),
            _ => None,
        }
    }
}

/// Result type alias for neomemx operations.
pub type Result<T> = std::result::Result<T, NeomemxError>;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_error_code_display() {
        assert_eq!(ErrorCode::ConfigMissing.to_string(), "E1001");
        assert_eq!(ErrorCode::ProviderRateLimit.to_string(), "E3003");
    }

    #[test]
    fn test_error_retryable() {
        assert!(ErrorCode::ProviderRateLimit.is_retryable());
        assert!(ErrorCode::ProviderTimeout.is_retryable());
        assert!(!ErrorCode::ConfigMissing.is_retryable());
    }
}