use std::collections::HashMap;
#[derive(Debug, Clone, Default)]
pub struct PutOptions {
pub content_type: Option<String>,
pub created_by: Option<String>,
pub linked_to: Vec<String>,
pub tags: Vec<String>,
pub metadata: HashMap<String, String>,
pub embedding: Option<(Vec<f32>, String)>,
}
impl PutOptions {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_content_type(mut self, content_type: impl Into<String>) -> Self {
self.content_type = Some(content_type.into());
self
}
#[must_use]
pub fn with_created_by(mut self, creator: impl Into<String>) -> Self {
self.created_by = Some(creator.into());
self
}
#[must_use]
pub fn with_link(mut self, entity: impl Into<String>) -> Self {
self.linked_to.push(entity.into());
self
}
#[must_use]
pub fn with_links(mut self, entities: Vec<String>) -> Self {
self.linked_to.extend(entities);
self
}
#[must_use]
pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
self.tags.push(tag.into());
self
}
#[must_use]
pub fn with_tags(mut self, tags: Vec<String>) -> Self {
self.tags.extend(tags);
self
}
#[must_use]
pub fn with_meta(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
#[must_use]
pub fn with_embedding(mut self, embedding: Vec<f32>, model: impl Into<String>) -> Self {
self.embedding = Some((embedding, model.into()));
self
}
}
#[derive(Debug, Clone)]
pub struct ArtifactMetadata {
pub id: String,
pub filename: String,
pub content_type: String,
pub size: usize,
pub checksum: String,
pub chunk_count: usize,
pub chunk_size: usize,
pub created_by: String,
pub created: u64,
pub modified: u64,
pub linked_to: Vec<String>,
pub tags: Vec<String>,
pub custom: HashMap<String, String>,
pub has_embedding: bool,
pub embedding_model: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct MetadataUpdates {
pub filename: Option<String>,
pub content_type: Option<String>,
pub custom: HashMap<String, Option<String>>,
}
impl MetadataUpdates {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_filename(mut self, filename: impl Into<String>) -> Self {
self.filename = Some(filename.into());
self
}
#[must_use]
pub fn with_content_type(mut self, content_type: impl Into<String>) -> Self {
self.content_type = Some(content_type.into());
self
}
#[must_use]
pub fn set_meta(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.custom.insert(key.into(), Some(value.into()));
self
}
#[must_use]
pub fn delete_meta(mut self, key: impl Into<String>) -> Self {
self.custom.insert(key.into(), None);
self
}
}
#[derive(Debug, Clone)]
pub struct SimilarArtifact {
pub id: String,
pub filename: String,
pub similarity: f32,
}
#[derive(Debug, Clone, Default)]
pub struct BlobStats {
pub artifact_count: usize,
pub chunk_count: usize,
pub total_bytes: usize,
pub unique_bytes: usize,
pub dedup_ratio: f64,
pub orphaned_chunks: usize,
}
#[derive(Debug, Clone, Default)]
pub struct GcStats {
pub deleted: usize,
pub freed_bytes: usize,
}
impl GcStats {
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
#[derive(Debug, Clone, Default)]
pub struct RepairStats {
pub artifacts_checked: usize,
pub chunks_verified: usize,
pub refs_fixed: usize,
pub orphans_deleted: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_put_options_builder() {
let options = PutOptions::new()
.with_content_type("application/pdf")
.with_created_by("user:alice")
.with_link("task:123")
.with_links(vec!["project:neumann".to_string()])
.with_tag("quarterly")
.with_tags(vec!["finance".to_string(), "report".to_string()])
.with_meta("author", "Alice")
.with_embedding(vec![0.1, 0.2, 0.3], "text-embedding-3-small");
assert_eq!(options.content_type, Some("application/pdf".to_string()));
assert_eq!(options.created_by, Some("user:alice".to_string()));
assert_eq!(
options.linked_to,
vec!["task:123".to_string(), "project:neumann".to_string()]
);
assert_eq!(
options.tags,
vec![
"quarterly".to_string(),
"finance".to_string(),
"report".to_string()
]
);
assert_eq!(options.metadata.get("author"), Some(&"Alice".to_string()));
assert!(options.embedding.is_some());
let (emb, model) = options.embedding.unwrap();
assert_eq!(emb, vec![0.1, 0.2, 0.3]);
assert_eq!(model, "text-embedding-3-small");
}
#[test]
fn test_metadata_updates_builder() {
let updates = MetadataUpdates::new()
.with_filename("new_name.pdf")
.with_content_type("application/pdf")
.set_meta("author", "Bob")
.delete_meta("old_field");
assert_eq!(updates.filename, Some("new_name.pdf".to_string()));
assert_eq!(updates.content_type, Some("application/pdf".to_string()));
assert_eq!(updates.custom.get("author"), Some(&Some("Bob".to_string())));
assert_eq!(updates.custom.get("old_field"), Some(&None));
}
#[test]
fn test_gc_stats_default() {
let stats = GcStats::default();
assert_eq!(stats.deleted, 0);
assert_eq!(stats.freed_bytes, 0);
}
#[test]
fn test_blob_stats_default() {
let stats = BlobStats::default();
assert_eq!(stats.artifact_count, 0);
assert_eq!(stats.chunk_count, 0);
assert_eq!(stats.total_bytes, 0);
assert_eq!(stats.unique_bytes, 0);
assert_eq!(stats.dedup_ratio, 0.0);
assert_eq!(stats.orphaned_chunks, 0);
}
#[test]
fn test_similar_artifact() {
let similar = SimilarArtifact {
id: "artifact:report.pdf".to_string(),
filename: "report.pdf".to_string(),
similarity: 0.92,
};
assert_eq!(similar.id, "artifact:report.pdf");
assert_eq!(similar.filename, "report.pdf");
assert!((similar.similarity - 0.92).abs() < f32::EPSILON);
}
}