use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize)]
pub struct Collection {
pub id: String,
pub name: String,
pub description: String,
pub embedding_provider: String,
pub embedding_model: String,
pub dimension: u32,
pub chunk_size: u32,
pub chunk_overlap: u32,
pub document_count: u32,
pub has_api_key: bool,
pub reranking_enabled: bool,
pub reranking_model: String,
pub has_reranking_api_key: bool,
pub default_top_k: u32,
pub default_min_score: Option<f64>,
pub default_search_mode: String,
pub metadata: serde_json::Value,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CollectionListResponse {
pub collections: Vec<Collection>,
pub total: u32,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct CollectionListOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub offset: Option<u32>,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct CreateCollectionBody {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub embedding_provider: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub embedding_model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub embedding_api_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dimension: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub chunk_size: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub chunk_overlap: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reranking_enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reranking_model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reranking_api_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_top_k: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_min_score: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_search_mode: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct UpdateCollectionBody {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reranking_enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reranking_model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reranking_api_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_top_k: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_min_score: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_search_mode: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CollectionStatsResponse {
pub collection: String,
pub document_count: u32,
pub total_chunks: u32,
pub total_tokens: u64,
pub total_size_bytes: u64,
pub status_counts: HashMap<String, u32>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_deserialize_collection() {
let json = r#"{
"id": "col-1", "name": "docs", "description": "My docs",
"embedding_provider": "openai", "embedding_model": "text-embedding-3-small",
"dimension": 1536, "chunk_size": 512, "chunk_overlap": 50,
"document_count": 42, "has_api_key": true,
"reranking_enabled": false, "reranking_model": "rerank-v3.5",
"has_reranking_api_key": false, "default_top_k": 10,
"default_min_score": null, "default_search_mode": "semantic",
"metadata": {}, "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z"
}"#;
let col: Collection = serde_json::from_str(json).unwrap();
assert_eq!(col.name, "docs");
assert_eq!(col.dimension, 1536);
assert_eq!(col.document_count, 42);
assert_eq!(col.default_min_score, None);
}
#[test]
fn test_serialize_create_collection_body_skips_none() {
let body = CreateCollectionBody {
name: "test".into(),
..Default::default()
};
let json = serde_json::to_value(&body).unwrap();
assert_eq!(json["name"], "test");
assert!(json.get("description").is_none());
assert!(json.get("embedding_provider").is_none());
}
#[test]
fn test_deserialize_collection_list_response() {
let json = r#"{"collections":[{"id":"1","name":"a","description":"","embedding_provider":"openai","embedding_model":"m","dimension":768,"chunk_size":512,"chunk_overlap":50,"document_count":0,"has_api_key":false,"reranking_enabled":false,"reranking_model":"","has_reranking_api_key":false,"default_top_k":10,"default_min_score":null,"default_search_mode":"semantic","metadata":{},"created_at":"","updated_at":""}],"total":1}"#;
let resp: CollectionListResponse = serde_json::from_str(json).unwrap();
assert_eq!(resp.total, 1);
assert_eq!(resp.collections[0].name, "a");
}
#[test]
fn test_deserialize_collection_stats() {
let json = r#"{"collection":"docs","document_count":10,"total_chunks":500,"total_tokens":10000,"total_size_bytes":5000000,"status_counts":{"ready":8,"failed":2}}"#;
let resp: CollectionStatsResponse = serde_json::from_str(json).unwrap();
assert_eq!(resp.total_chunks, 500);
assert_eq!(resp.status_counts["ready"], 8);
}
}