use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PointStruct {
pub id: String,
pub vector: Vec<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub payload: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpsertRequest {
pub points: Vec<PointStruct>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpsertResponse {
pub status: String,
pub upserted: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetRequest {
pub ids: Vec<String>,
#[serde(default)]
pub with_payload: bool,
#[serde(default = "default_true")]
pub with_vector: bool,
}
const fn default_true() -> bool {
true
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetResponse {
pub points: Vec<PointStruct>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeleteRequest {
pub ids: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeleteResponse {
pub status: String,
pub deleted: usize,
}
const fn default_limit() -> usize {
10
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QueryRequest {
pub vector: Vec<f32>,
#[serde(default = "default_limit")]
pub limit: usize,
#[serde(default)]
pub offset: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub score_threshold: Option<f32>,
#[serde(default)]
pub with_payload: bool,
#[serde(default)]
pub with_vector: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScoredPoint {
pub id: String,
pub score: f32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub payload: Option<HashMap<String, serde_json::Value>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub vector: Option<Vec<f32>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QueryResponse {
pub result: Vec<ScoredPoint>,
pub time: f64,
}
const fn default_scroll_limit() -> usize {
100
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScrollRequest {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub offset_id: Option<String>,
#[serde(default = "default_scroll_limit")]
pub limit: usize,
#[serde(default)]
pub with_payload: bool,
#[serde(default = "default_true")]
pub with_vector: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScrollResponse {
pub points: Vec<PointStruct>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub next_offset: Option<String>,
}
fn default_distance() -> String {
"cosine".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateCollectionRequest {
pub dimension: usize,
#[serde(default = "default_distance")]
pub distance: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateCollectionResponse {
pub created: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CollectionInfo {
pub name: String,
pub points_count: usize,
pub dimension: usize,
pub distance: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeleteCollectionResponse {
pub deleted: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListCollectionsResponse {
pub collections: Vec<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_point_struct_serde_roundtrip() {
let point = PointStruct {
id: "test-id".to_string(),
vector: vec![0.1, 0.2, 0.3],
payload: Some(HashMap::from([(
"key".to_string(),
serde_json::json!("value"),
)])),
};
let json = serde_json::to_string(&point).unwrap();
let parsed: PointStruct = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.id, "test-id");
assert_eq!(parsed.vector, vec![0.1, 0.2, 0.3]);
assert!(parsed.payload.is_some());
}
#[test]
fn test_point_struct_optional_payload() {
let point = PointStruct {
id: "test-id".to_string(),
vector: vec![0.1, 0.2, 0.3],
payload: None,
};
let json = serde_json::to_string(&point).unwrap();
assert!(!json.contains("payload"));
let parsed: PointStruct = serde_json::from_str(&json).unwrap();
assert!(parsed.payload.is_none());
}
#[test]
fn test_query_request_defaults() {
let json = r#"{"vector": [0.1, 0.2, 0.3]}"#;
let request: QueryRequest = serde_json::from_str(json).unwrap();
assert_eq!(request.limit, 10);
assert_eq!(request.offset, 0);
assert!(request.score_threshold.is_none());
assert!(!request.with_payload);
assert!(!request.with_vector);
}
#[test]
fn test_scored_point_skip_none() {
let point = ScoredPoint {
id: "test-id".to_string(),
score: 0.95,
payload: None,
vector: None,
};
let json = serde_json::to_string(&point).unwrap();
assert!(!json.contains("payload"));
assert!(!json.contains("vector"));
}
#[test]
fn test_collection_info_serde() {
let info = CollectionInfo {
name: "test-collection".to_string(),
points_count: 1000,
dimension: 128,
distance: "cosine".to_string(),
};
let json = serde_json::to_string(&info).unwrap();
let parsed: CollectionInfo = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.name, "test-collection");
assert_eq!(parsed.points_count, 1000);
assert_eq!(parsed.dimension, 128);
assert_eq!(parsed.distance, "cosine");
}
#[test]
fn test_scroll_request_defaults() {
let json = r"{}";
let request: ScrollRequest = serde_json::from_str(json).unwrap();
assert!(request.offset_id.is_none());
assert_eq!(request.limit, 100);
assert!(!request.with_payload);
assert!(request.with_vector);
}
#[test]
fn test_create_collection_request_defaults() {
let json = r#"{"dimension": 128}"#;
let request: CreateCollectionRequest = serde_json::from_str(json).unwrap();
assert_eq!(request.dimension, 128);
assert_eq!(request.distance, "cosine");
}
#[test]
fn test_get_request_defaults() {
let json = r#"{"ids": ["id1", "id2"]}"#;
let request: GetRequest = serde_json::from_str(json).unwrap();
assert_eq!(request.ids, vec!["id1", "id2"]);
assert!(!request.with_payload);
assert!(request.with_vector);
}
}