#![allow(missing_docs)]
use std::fmt::{Display, Formatter};
use thiserror::Error;
#[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())
}
}
#[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,
}
}
}
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());
}
}