use crate::application::dto::EventDto;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoreEmbeddingRequest {
pub event_id: Uuid,
pub tenant_id: String,
pub embedding: Vec<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_text: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoreBatchEmbeddingsRequest {
pub embeddings: Vec<StoreEmbeddingRequest>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoreEmbeddingResponse {
pub success: bool,
pub event_id: Uuid,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoreBatchEmbeddingsResponse {
pub indexed: usize,
pub failed: usize,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub errors: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VectorSearchRequest {
pub query_embedding: Vec<f32>,
#[serde(default = "default_k")]
pub k: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub tenant_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub event_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_similarity: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_distance: Option<f32>,
#[serde(default = "default_metric")]
pub metric: String,
#[serde(default)]
pub include_events: bool,
}
fn default_k() -> usize {
10
}
fn default_metric() -> String {
"cosine".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VectorSearchResultItem {
pub event_id: Uuid,
pub score: f32,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub event: Option<EventDto>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VectorSearchResponse {
pub results: Vec<VectorSearchResultItem>,
pub count: usize,
pub metric: String,
pub stats: VectorSearchStats,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VectorSearchStats {
pub total_vectors: usize,
pub vectors_searched: usize,
pub search_time_us: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FindSimilarEventsRequest {
pub event_id: Uuid,
#[serde(default = "default_k")]
pub k: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub tenant_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FindSimilarEventsResponse {
pub source_event_id: Uuid,
pub similar_events: Vec<VectorSearchResultItem>,
pub count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetEmbeddingRequest {
pub event_id: Uuid,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetEmbeddingResponse {
pub event_id: Uuid,
#[serde(skip_serializing_if = "Option::is_none")]
pub embedding: Option<Vec<f32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dimensions: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeleteEmbeddingRequest {
pub event_id: Uuid,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeleteEmbeddingResponse {
pub deleted: bool,
pub event_id: Uuid,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VectorIndexStats {
pub total_vectors: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub dimensions: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tenant_count: Option<usize>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_store_embedding_request_serialization() {
let request = StoreEmbeddingRequest {
event_id: Uuid::new_v4(),
tenant_id: "tenant-1".to_string(),
embedding: vec![0.1, 0.2, 0.3],
source_text: Some("test content".to_string()),
};
let json = serde_json::to_string(&request).unwrap();
let deserialized: StoreEmbeddingRequest = serde_json::from_str(&json).unwrap();
assert_eq!(request.event_id, deserialized.event_id);
assert_eq!(request.tenant_id, deserialized.tenant_id);
assert_eq!(request.embedding, deserialized.embedding);
assert_eq!(request.source_text, deserialized.source_text);
}
#[test]
fn test_vector_search_request_defaults() {
let json = r#"{
"query_embedding": [0.1, 0.2, 0.3]
}"#;
let request: VectorSearchRequest = serde_json::from_str(json).unwrap();
assert_eq!(request.k, 10);
assert_eq!(request.metric, "cosine");
assert!(!request.include_events);
assert!(request.tenant_id.is_none());
}
#[test]
fn test_vector_search_response_serialization() {
let response = VectorSearchResponse {
results: vec![VectorSearchResultItem {
event_id: Uuid::new_v4(),
score: 0.95,
source_text: Some("matched text".to_string()),
event: None,
}],
count: 1,
metric: "cosine".to_string(),
stats: VectorSearchStats {
total_vectors: 1000,
vectors_searched: 1000,
search_time_us: 150,
},
};
let json = serde_json::to_string(&response).unwrap();
let deserialized: VectorSearchResponse = serde_json::from_str(&json).unwrap();
assert_eq!(response.count, deserialized.count);
assert_eq!(response.metric, deserialized.metric);
assert_eq!(response.results.len(), deserialized.results.len());
}
#[test]
fn test_find_similar_request_defaults() {
let json = r#"{
"event_id": "550e8400-e29b-41d4-a716-446655440000"
}"#;
let request: FindSimilarEventsRequest = serde_json::from_str(json).unwrap();
assert_eq!(request.k, 10);
assert!(request.tenant_id.is_none());
}
#[test]
fn test_batch_embeddings_request() {
let request = StoreBatchEmbeddingsRequest {
embeddings: vec![
StoreEmbeddingRequest {
event_id: Uuid::new_v4(),
tenant_id: "tenant-1".to_string(),
embedding: vec![0.1, 0.2, 0.3],
source_text: None,
},
StoreEmbeddingRequest {
event_id: Uuid::new_v4(),
tenant_id: "tenant-1".to_string(),
embedding: vec![0.4, 0.5, 0.6],
source_text: Some("content".to_string()),
},
],
};
let json = serde_json::to_string(&request).unwrap();
let deserialized: StoreBatchEmbeddingsRequest = serde_json::from_str(&json).unwrap();
assert_eq!(request.embeddings.len(), deserialized.embeddings.len());
}
#[test]
fn test_skip_serializing_none_fields() {
let request = StoreEmbeddingRequest {
event_id: Uuid::new_v4(),
tenant_id: "tenant-1".to_string(),
embedding: vec![0.1, 0.2, 0.3],
source_text: None,
};
let json = serde_json::to_string(&request).unwrap();
assert!(!json.contains("source_text"));
}
}