1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Deserialize)]
7pub struct Collection {
8 pub id: String,
10 pub name: String,
12 #[serde(default)]
14 pub description: Option<String>,
15 #[serde(default)]
17 pub document_count: u64,
18 #[serde(default)]
20 pub created_at: Option<i64>,
21 #[serde(default)]
23 pub updated_at: Option<i64>,
24}
25
26#[derive(Debug, Clone, Deserialize)]
28pub struct CollectionListResponse {
29 pub data: Vec<Collection>,
31 #[serde(default)]
33 pub next_token: Option<String>,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct Document {
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub id: Option<String>,
42 pub content: String,
44 #[serde(default, skip_serializing_if = "Option::is_none")]
46 pub metadata: Option<serde_json::Value>,
47}
48
49#[derive(Debug, Clone, Serialize)]
51pub struct UpdateCollectionRequest {
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub name: Option<String>,
55 #[serde(skip_serializing_if = "Option::is_none")]
57 pub description: Option<String>,
58}
59
60impl UpdateCollectionRequest {
61 pub fn new() -> Self {
63 Self {
64 name: None,
65 description: None,
66 }
67 }
68
69 pub fn name(mut self, name: impl Into<String>) -> Self {
71 self.name = Some(name.into());
72 self
73 }
74
75 pub fn description(mut self, description: impl Into<String>) -> Self {
77 self.description = Some(description.into());
78 self
79 }
80}
81
82impl Default for UpdateCollectionRequest {
83 fn default() -> Self {
84 Self::new()
85 }
86}
87
88#[derive(Debug, Clone, Serialize)]
90pub struct BatchGetDocumentsRequest {
91 pub ids: Vec<String>,
93}
94
95impl BatchGetDocumentsRequest {
96 pub fn new(ids: Vec<String>) -> Self {
98 Self { ids }
99 }
100
101 pub fn from_ids(ids: &[&str]) -> Self {
103 Self {
104 ids: ids.iter().map(|id| (*id).to_string()).collect(),
105 }
106 }
107}
108
109impl Document {
110 pub fn new(content: impl Into<String>) -> Self {
112 Self {
113 id: None,
114 content: content.into(),
115 metadata: None,
116 }
117 }
118
119 pub fn with_id(id: impl Into<String>, content: impl Into<String>) -> Self {
121 Self {
122 id: Some(id.into()),
123 content: content.into(),
124 metadata: None,
125 }
126 }
127
128 pub fn metadata(mut self, metadata: serde_json::Value) -> Self {
130 self.metadata = Some(metadata);
131 self
132 }
133}
134
135#[derive(Debug, Clone, Deserialize)]
137pub struct DocumentListResponse {
138 pub data: Vec<Document>,
140 #[serde(default)]
142 pub next_token: Option<String>,
143}
144
145#[derive(Debug, Clone, Serialize)]
147pub struct CreateCollectionRequest {
148 pub name: String,
150 #[serde(skip_serializing_if = "Option::is_none")]
152 pub description: Option<String>,
153}
154
155impl CreateCollectionRequest {
156 pub fn new(name: impl Into<String>) -> Self {
158 Self {
159 name: name.into(),
160 description: None,
161 }
162 }
163
164 pub fn description(mut self, description: impl Into<String>) -> Self {
166 self.description = Some(description.into());
167 self
168 }
169}
170
171#[derive(Debug, Clone, Serialize)]
173pub struct AddDocumentsRequest {
174 pub documents: Vec<Document>,
176}
177
178#[derive(Debug, Clone, Deserialize)]
180pub struct AddDocumentsResponse {
181 pub ids: Vec<String>,
183}
184
185#[derive(Debug, Clone, Serialize)]
187pub struct SearchRequest {
188 pub query: String,
190 #[serde(skip_serializing_if = "Option::is_none")]
192 pub limit: Option<u32>,
193 #[serde(skip_serializing_if = "Option::is_none")]
195 pub score_threshold: Option<f32>,
196}
197
198impl SearchRequest {
199 pub fn new(query: impl Into<String>) -> Self {
201 Self {
202 query: query.into(),
203 limit: None,
204 score_threshold: None,
205 }
206 }
207
208 pub fn limit(mut self, limit: u32) -> Self {
210 self.limit = Some(limit);
211 self
212 }
213
214 pub fn score_threshold(mut self, threshold: f32) -> Self {
216 self.score_threshold = Some(threshold);
217 self
218 }
219}
220
221#[derive(Debug, Clone, Deserialize)]
223pub struct SearchResult {
224 pub document: Document,
226 pub score: f32,
228}
229
230#[derive(Debug, Clone, Deserialize)]
232pub struct SearchResponse {
233 pub results: Vec<SearchResult>,
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240 use serde_json::json;
241
242 #[test]
243 fn document_builder_sets_optional_fields() {
244 let document = Document::new("simple content");
245 assert_eq!(document.id, None);
246 assert_eq!(document.content, "simple content");
247 assert!(document.metadata.is_none());
248
249 let with_metadata = Document::with_id("doc-1", "metadata content").metadata(json!({
250 "author": "tests",
251 "version": 1
252 }));
253 assert_eq!(with_metadata.id.as_deref(), Some("doc-1"));
254 assert_eq!(with_metadata.content, "metadata content");
255 assert_eq!(
256 with_metadata.metadata.as_ref().unwrap()["author"].as_str(),
257 Some("tests")
258 );
259 }
260
261 #[test]
262 fn collection_request_builder_populates_description() {
263 let request = CreateCollectionRequest::new("research").description("notes corpus");
264 assert_eq!(request.name, "research");
265 assert_eq!(request.description.as_deref(), Some("notes corpus"));
266 }
267
268 #[test]
269 fn search_request_builder_sets_options() {
270 let request = SearchRequest::new("query term")
271 .limit(10)
272 .score_threshold(0.85);
273 assert_eq!(request.query, "query term");
274 assert_eq!(request.limit, Some(10));
275 assert_eq!(request.score_threshold, Some(0.85));
276 }
277
278 #[test]
279 fn collection_update_request_builder() {
280 let request = UpdateCollectionRequest::new()
281 .name("research")
282 .description("notes");
283 assert_eq!(request.name.as_deref(), Some("research"));
284 assert_eq!(request.description.as_deref(), Some("notes"));
285 }
286
287 #[test]
288 fn batch_get_documents_request_from_ids() {
289 let request = BatchGetDocumentsRequest::from_ids(&["d1", "d2"]);
290 assert_eq!(request.ids, vec!["d1".to_string(), "d2".to_string()]);
291 }
292}