use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "snake_case")]
pub enum SanctumAdapterType {
#[default]
InMemory,
Qdrant,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QdrantSanctumConfig {
pub url: String,
pub collection_name: String,
pub vector_dimension: usize,
}
impl Default for QdrantSanctumConfig {
fn default() -> Self {
Self {
url: "http://localhost:6334".to_string(),
collection_name: "paladin_memories".to_string(),
vector_dimension: 1536,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SanctumConfig {
pub enabled: bool,
pub adapter_type: SanctumAdapterType,
pub qdrant: Option<QdrantSanctumConfig>,
}
impl Default for SanctumConfig {
fn default() -> Self {
Self {
enabled: false,
adapter_type: SanctumAdapterType::InMemory,
qdrant: None,
}
}
}
impl SanctumConfig {
pub fn validate(&self) -> Result<(), String> {
if !self.enabled {
return Ok(());
}
if self.adapter_type == SanctumAdapterType::Qdrant {
let qdrant = self.qdrant.as_ref().ok_or_else(|| {
"Qdrant adapter requires 'qdrant' configuration section".to_string()
})?;
if qdrant.url.trim().is_empty() {
return Err("Qdrant URL cannot be empty".to_string());
}
if qdrant.collection_name.trim().is_empty() {
return Err("Qdrant collection_name cannot be empty".to_string());
}
if qdrant.vector_dimension == 0 {
return Err("Qdrant vector_dimension must be greater than 0".to_string());
}
if qdrant.vector_dimension > 10000 {
return Err(format!(
"Qdrant vector_dimension {} seems unusually large (max 10000)",
qdrant.vector_dimension
));
}
}
Ok(())
}
pub fn adapter_type_str(&self) -> &str {
match self.adapter_type {
SanctumAdapterType::InMemory => "in_memory",
SanctumAdapterType::Qdrant => "qdrant",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_sanctum_config() {
let config = SanctumConfig::default();
assert!(!config.enabled);
assert_eq!(config.adapter_type, SanctumAdapterType::InMemory);
assert!(config.qdrant.is_none());
}
#[test]
fn test_sanctum_validation_disabled() {
let config = SanctumConfig {
enabled: false,
adapter_type: SanctumAdapterType::Qdrant,
qdrant: None,
};
assert!(config.validate().is_ok());
}
#[test]
fn test_sanctum_validation_in_memory() {
let config = SanctumConfig {
enabled: true,
adapter_type: SanctumAdapterType::InMemory,
qdrant: None,
};
assert!(config.validate().is_ok());
}
#[test]
fn test_sanctum_validation_qdrant_missing_config() {
let config = SanctumConfig {
enabled: true,
adapter_type: SanctumAdapterType::Qdrant,
qdrant: None,
};
let result = config.validate();
assert!(result.is_err());
assert!(
result
.unwrap_err()
.contains("qdrant' configuration section")
);
}
#[test]
fn test_sanctum_validation_qdrant_valid() {
let config = SanctumConfig {
enabled: true,
adapter_type: SanctumAdapterType::Qdrant,
qdrant: Some(QdrantSanctumConfig {
url: "http://localhost:6334".to_string(),
collection_name: "test".to_string(),
vector_dimension: 1536,
}),
};
assert!(config.validate().is_ok());
}
#[test]
fn test_sanctum_validation_empty_url() {
let config = SanctumConfig {
enabled: true,
adapter_type: SanctumAdapterType::Qdrant,
qdrant: Some(QdrantSanctumConfig {
url: " ".to_string(),
collection_name: "test".to_string(),
vector_dimension: 1536,
}),
};
let result = config.validate();
assert!(result.is_err());
assert!(result.unwrap_err().contains("URL cannot be empty"));
}
#[test]
fn test_sanctum_validation_zero_dimension() {
let config = SanctumConfig {
enabled: true,
adapter_type: SanctumAdapterType::Qdrant,
qdrant: Some(QdrantSanctumConfig {
url: "http://localhost:6334".to_string(),
collection_name: "test".to_string(),
vector_dimension: 0,
}),
};
let result = config.validate();
assert!(result.is_err());
assert!(result.unwrap_err().contains("must be greater than 0"));
}
}