use async_trait::async_trait;
use thiserror::Error;
use super::entities::{
Anomaly, Cluster, ClusterId, EmbeddingId, Motif, Prototype, RecordingId, SequenceAnalysis,
};
#[derive(Debug, Error)]
pub enum RepositoryError {
#[error("Entity not found: {0}")]
NotFound(String),
#[error("Duplicate entity: {0}")]
Duplicate(String),
#[error("Connection error: {0}")]
ConnectionError(String),
#[error("Query error: {0}")]
QueryError(String),
#[error("Serialization error: {0}")]
SerializationError(String),
#[error("Invalid data: {0}")]
InvalidData(String),
#[error("Concurrency conflict: {0}")]
ConcurrencyError(String),
#[error("Internal error: {0}")]
Internal(String),
}
pub type Result<T> = std::result::Result<T, RepositoryError>;
#[async_trait]
pub trait ClusterRepository: Send + Sync {
async fn save_cluster(&self, cluster: &Cluster) -> Result<()>;
async fn save_clusters(&self, clusters: &[Cluster]) -> Result<()>;
async fn find_cluster(&self, id: &ClusterId) -> Result<Option<Cluster>>;
async fn list_clusters(&self) -> Result<Vec<Cluster>>;
async fn list_clusters_paginated(
&self,
offset: usize,
limit: usize,
) -> Result<Vec<Cluster>>;
async fn assign_to_cluster(
&self,
embedding_id: &EmbeddingId,
cluster_id: &ClusterId,
) -> Result<()>;
async fn remove_from_cluster(&self, embedding_id: &EmbeddingId) -> Result<()>;
async fn find_cluster_by_embedding(
&self,
embedding_id: &EmbeddingId,
) -> Result<Option<Cluster>>;
async fn delete_cluster(&self, id: &ClusterId) -> Result<()>;
async fn delete_all_clusters(&self) -> Result<()>;
async fn cluster_count(&self) -> Result<usize>;
async fn find_clusters_by_label(&self, label_pattern: &str) -> Result<Vec<Cluster>>;
async fn update_cluster_label(
&self,
id: &ClusterId,
label: Option<String>,
) -> Result<()>;
}
#[async_trait]
pub trait PrototypeRepository: Send + Sync {
async fn save_prototype(&self, prototype: &Prototype) -> Result<()>;
async fn save_prototypes(&self, prototypes: &[Prototype]) -> Result<()>;
async fn find_prototypes_by_cluster(
&self,
cluster_id: &ClusterId,
) -> Result<Vec<Prototype>>;
async fn find_best_prototype(
&self,
cluster_id: &ClusterId,
) -> Result<Option<Prototype>>;
async fn delete_prototypes_by_cluster(&self, cluster_id: &ClusterId) -> Result<()>;
async fn delete_all_prototypes(&self) -> Result<()>;
}
#[async_trait]
pub trait MotifRepository: Send + Sync {
async fn save_motif(&self, motif: &Motif) -> Result<()>;
async fn save_motifs(&self, motifs: &[Motif]) -> Result<()>;
async fn find_motif(&self, id: &str) -> Result<Option<Motif>>;
async fn find_motifs_by_cluster(&self, cluster_id: &ClusterId) -> Result<Vec<Motif>>;
async fn list_motifs(&self) -> Result<Vec<Motif>>;
async fn find_motifs_by_confidence(&self, min_confidence: f32) -> Result<Vec<Motif>>;
async fn find_motifs_by_occurrences(&self, min_occurrences: usize) -> Result<Vec<Motif>>;
async fn delete_motif(&self, id: &str) -> Result<()>;
async fn delete_all_motifs(&self) -> Result<()>;
async fn motif_count(&self) -> Result<usize>;
async fn find_motifs_by_sequence(&self, sequence: &[ClusterId]) -> Result<Vec<Motif>>;
async fn find_motifs_containing_subsequence(
&self,
subsequence: &[ClusterId],
) -> Result<Vec<Motif>>;
}
#[async_trait]
pub trait SequenceRepository: Send + Sync {
async fn save_sequence_analysis(&self, analysis: &SequenceAnalysis) -> Result<()>;
async fn find_sequence_by_recording(
&self,
recording_id: &RecordingId,
) -> Result<Option<SequenceAnalysis>>;
async fn list_sequence_analyses(&self) -> Result<Vec<SequenceAnalysis>>;
async fn delete_sequence_by_recording(&self, recording_id: &RecordingId) -> Result<()>;
async fn delete_all_sequences(&self) -> Result<()>;
async fn find_sequences_by_entropy(&self, min_entropy: f32) -> Result<Vec<SequenceAnalysis>>;
async fn find_sequences_by_stereotypy(
&self,
min_stereotypy: f32,
) -> Result<Vec<SequenceAnalysis>>;
}
#[async_trait]
pub trait AnomalyRepository: Send + Sync {
async fn save_anomaly(&self, anomaly: &Anomaly) -> Result<()>;
async fn save_anomalies(&self, anomalies: &[Anomaly]) -> Result<()>;
async fn find_anomaly(&self, embedding_id: &EmbeddingId) -> Result<Option<Anomaly>>;
async fn list_anomalies(&self) -> Result<Vec<Anomaly>>;
async fn find_anomalies_by_score(&self, min_score: f32) -> Result<Vec<Anomaly>>;
async fn find_anomalies_by_cluster(&self, cluster_id: &ClusterId) -> Result<Vec<Anomaly>>;
async fn delete_anomaly(&self, embedding_id: &EmbeddingId) -> Result<()>;
async fn delete_all_anomalies(&self) -> Result<()>;
async fn anomaly_count(&self) -> Result<usize>;
}
#[async_trait]
pub trait AnalysisRepository:
ClusterRepository + PrototypeRepository + MotifRepository + SequenceRepository + AnomalyRepository
{
async fn clear_all(&self) -> Result<()> {
self.delete_all_clusters().await?;
self.delete_all_prototypes().await?;
self.delete_all_motifs().await?;
self.delete_all_sequences().await?;
self.delete_all_anomalies().await?;
Ok(())
}
}
#[async_trait]
pub trait UnitOfWork: Send + Sync {
type Repository: AnalysisRepository;
async fn begin(&self) -> Result<Self::Repository>;
async fn commit(&self) -> Result<()>;
async fn rollback(&self) -> Result<()>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_repository_error_display() {
let err = RepositoryError::NotFound("cluster-123".to_string());
assert!(format!("{}", err).contains("cluster-123"));
let err = RepositoryError::QueryError("syntax error".to_string());
assert!(format!("{}", err).contains("syntax error"));
}
}