Skip to main content

dakera_client/
types.rs

1//! Types for the Dakera client SDK
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6// ============================================================================
7// Health & Status Types
8// ============================================================================
9
10/// Health check response
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct HealthResponse {
13    /// Overall health status
14    pub healthy: bool,
15    /// Service version
16    pub version: Option<String>,
17    /// Uptime in seconds
18    pub uptime_seconds: Option<u64>,
19}
20
21/// Readiness check response
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct ReadinessResponse {
24    /// Is the service ready to accept requests
25    pub ready: bool,
26    /// Component status details
27    pub components: Option<HashMap<String, bool>>,
28}
29
30// ============================================================================
31// Namespace Types
32// ============================================================================
33
34/// Namespace information
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct NamespaceInfo {
37    /// Namespace name
38    #[serde(alias = "namespace")]
39    pub name: String,
40    /// Number of vectors in the namespace
41    pub vector_count: u64,
42    /// Vector dimensions
43    #[serde(alias = "dimension")]
44    pub dimensions: Option<u32>,
45    /// Index type used
46    pub index_type: Option<String>,
47}
48
49/// List namespaces response
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct ListNamespacesResponse {
52    /// List of namespace names
53    pub namespaces: Vec<String>,
54}
55
56// ============================================================================
57// Vector Types
58// ============================================================================
59
60/// A vector with ID and optional metadata
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct Vector {
63    /// Unique vector identifier
64    pub id: String,
65    /// Vector values (embeddings)
66    pub values: Vec<f32>,
67    /// Optional metadata
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub metadata: Option<HashMap<String, serde_json::Value>>,
70}
71
72impl Vector {
73    /// Create a new vector with just ID and values
74    pub fn new(id: impl Into<String>, values: Vec<f32>) -> Self {
75        Self {
76            id: id.into(),
77            values,
78            metadata: None,
79        }
80    }
81
82    /// Create a new vector with metadata
83    pub fn with_metadata(
84        id: impl Into<String>,
85        values: Vec<f32>,
86        metadata: HashMap<String, serde_json::Value>,
87    ) -> Self {
88        Self {
89            id: id.into(),
90            values,
91            metadata: Some(metadata),
92        }
93    }
94}
95
96/// Upsert vectors request
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct UpsertRequest {
99    /// Vectors to upsert
100    pub vectors: Vec<Vector>,
101}
102
103impl UpsertRequest {
104    /// Create a new upsert request with a single vector
105    pub fn single(vector: Vector) -> Self {
106        Self {
107            vectors: vec![vector],
108        }
109    }
110
111    /// Create a new upsert request with multiple vectors
112    pub fn batch(vectors: Vec<Vector>) -> Self {
113        Self { vectors }
114    }
115}
116
117/// Upsert response
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct UpsertResponse {
120    /// Number of vectors upserted
121    pub upserted_count: u64,
122}
123
124/// Column-based upsert request (Turbopuffer-inspired)
125///
126/// This format is more efficient for bulk upserts as it avoids repeating
127/// field names for each vector. All arrays must have equal length.
128///
129/// # Example
130///
131/// ```rust
132/// use dakera_client::ColumnUpsertRequest;
133/// use std::collections::HashMap;
134///
135/// let request = ColumnUpsertRequest::new(
136///     vec!["id1".to_string(), "id2".to_string()],
137///     vec![vec![0.1, 0.2, 0.3], vec![0.4, 0.5, 0.6]],
138/// );
139/// ```
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct ColumnUpsertRequest {
142    /// Array of vector IDs (required)
143    pub ids: Vec<String>,
144    /// Array of vectors (required for vector namespaces)
145    pub vectors: Vec<Vec<f32>>,
146    /// Additional attributes as columns (optional)
147    /// Each key is an attribute name, value is array of attribute values
148    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
149    pub attributes: HashMap<String, Vec<serde_json::Value>>,
150    /// TTL in seconds for all vectors (optional)
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub ttl_seconds: Option<u64>,
153    /// Expected dimension (optional, for validation)
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub dimension: Option<usize>,
156}
157
158impl ColumnUpsertRequest {
159    /// Create a new column upsert request
160    pub fn new(ids: Vec<String>, vectors: Vec<Vec<f32>>) -> Self {
161        Self {
162            ids,
163            vectors,
164            attributes: HashMap::new(),
165            ttl_seconds: None,
166            dimension: None,
167        }
168    }
169
170    /// Add an attribute column
171    pub fn with_attribute(
172        mut self,
173        name: impl Into<String>,
174        values: Vec<serde_json::Value>,
175    ) -> Self {
176        self.attributes.insert(name.into(), values);
177        self
178    }
179
180    /// Set TTL for all vectors
181    pub fn with_ttl(mut self, seconds: u64) -> Self {
182        self.ttl_seconds = Some(seconds);
183        self
184    }
185
186    /// Set expected dimension for validation
187    pub fn with_dimension(mut self, dim: usize) -> Self {
188        self.dimension = Some(dim);
189        self
190    }
191
192    /// Get the number of vectors in this request
193    pub fn len(&self) -> usize {
194        self.ids.len()
195    }
196
197    /// Check if the request is empty
198    pub fn is_empty(&self) -> bool {
199        self.ids.is_empty()
200    }
201}
202
203/// Delete vectors request
204#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct DeleteRequest {
206    /// Vector IDs to delete
207    pub ids: Vec<String>,
208}
209
210impl DeleteRequest {
211    /// Create a delete request for a single ID
212    pub fn single(id: impl Into<String>) -> Self {
213        Self {
214            ids: vec![id.into()],
215        }
216    }
217
218    /// Create a delete request for multiple IDs
219    pub fn batch(ids: Vec<String>) -> Self {
220        Self { ids }
221    }
222}
223
224/// Delete response
225#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct DeleteResponse {
227    /// Number of vectors deleted
228    pub deleted_count: u64,
229}
230
231// ============================================================================
232// Query Types
233// ============================================================================
234
235/// Read consistency level for queries (Turbopuffer-inspired)
236#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
237#[serde(rename_all = "snake_case")]
238pub enum ReadConsistency {
239    /// Always read from primary/leader node - guarantees latest data
240    Strong,
241    /// Read from any replica - may return slightly stale data but faster
242    #[default]
243    Eventual,
244    /// Read from replicas within staleness bounds
245    #[serde(rename = "bounded_staleness")]
246    BoundedStaleness,
247}
248
249/// Configuration for bounded staleness reads
250#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
251pub struct StalenessConfig {
252    /// Maximum acceptable staleness in milliseconds
253    #[serde(default = "default_max_staleness_ms")]
254    pub max_staleness_ms: u64,
255}
256
257fn default_max_staleness_ms() -> u64 {
258    5000 // 5 seconds default
259}
260
261impl StalenessConfig {
262    /// Create a new staleness config with specified max staleness
263    pub fn new(max_staleness_ms: u64) -> Self {
264        Self { max_staleness_ms }
265    }
266}
267
268/// Distance metric for similarity search
269#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
270#[serde(rename_all = "snake_case")]
271pub enum DistanceMetric {
272    /// Cosine similarity (default)
273    #[default]
274    Cosine,
275    /// Euclidean distance
276    Euclidean,
277    /// Dot product
278    DotProduct,
279}
280
281/// Query request for vector similarity search
282#[derive(Debug, Clone, Serialize, Deserialize)]
283pub struct QueryRequest {
284    /// Query vector
285    pub vector: Vec<f32>,
286    /// Number of results to return
287    pub top_k: u32,
288    /// Distance metric to use
289    #[serde(default)]
290    pub distance_metric: DistanceMetric,
291    /// Optional filter expression
292    #[serde(skip_serializing_if = "Option::is_none")]
293    pub filter: Option<serde_json::Value>,
294    /// Whether to include metadata in results
295    #[serde(default = "default_true")]
296    pub include_metadata: bool,
297    /// Whether to include vector values in results
298    #[serde(default)]
299    pub include_vectors: bool,
300    /// Read consistency level
301    #[serde(default)]
302    pub consistency: ReadConsistency,
303    /// Staleness configuration for bounded staleness reads
304    #[serde(skip_serializing_if = "Option::is_none")]
305    pub staleness_config: Option<StalenessConfig>,
306}
307
308fn default_true() -> bool {
309    true
310}
311
312impl QueryRequest {
313    /// Create a new query request
314    pub fn new(vector: Vec<f32>, top_k: u32) -> Self {
315        Self {
316            vector,
317            top_k,
318            distance_metric: DistanceMetric::default(),
319            filter: None,
320            include_metadata: true,
321            include_vectors: false,
322            consistency: ReadConsistency::default(),
323            staleness_config: None,
324        }
325    }
326
327    /// Add a filter to the query
328    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
329        self.filter = Some(filter);
330        self
331    }
332
333    /// Set whether to include metadata
334    pub fn include_metadata(mut self, include: bool) -> Self {
335        self.include_metadata = include;
336        self
337    }
338
339    /// Set whether to include vector values
340    pub fn include_vectors(mut self, include: bool) -> Self {
341        self.include_vectors = include;
342        self
343    }
344
345    /// Set distance metric
346    pub fn with_distance_metric(mut self, metric: DistanceMetric) -> Self {
347        self.distance_metric = metric;
348        self
349    }
350
351    /// Set read consistency level
352    pub fn with_consistency(mut self, consistency: ReadConsistency) -> Self {
353        self.consistency = consistency;
354        self
355    }
356
357    /// Set bounded staleness with max staleness in ms
358    pub fn with_bounded_staleness(mut self, max_staleness_ms: u64) -> Self {
359        self.consistency = ReadConsistency::BoundedStaleness;
360        self.staleness_config = Some(StalenessConfig::new(max_staleness_ms));
361        self
362    }
363
364    /// Use strong consistency (always read from primary)
365    pub fn with_strong_consistency(mut self) -> Self {
366        self.consistency = ReadConsistency::Strong;
367        self
368    }
369}
370
371/// A match result from a query
372#[derive(Debug, Clone, Serialize, Deserialize)]
373pub struct Match {
374    /// Vector ID
375    pub id: String,
376    /// Similarity score
377    pub score: f32,
378    /// Optional metadata
379    #[serde(skip_serializing_if = "Option::is_none")]
380    pub metadata: Option<HashMap<String, serde_json::Value>>,
381}
382
383/// Query response
384#[derive(Debug, Clone, Serialize, Deserialize)]
385pub struct QueryResponse {
386    /// Matched vectors
387    pub matches: Vec<Match>,
388}
389
390// ============================================================================
391// Full-Text Search Types
392// ============================================================================
393
394/// A document for full-text indexing
395#[derive(Debug, Clone, Serialize, Deserialize)]
396pub struct Document {
397    /// Document ID
398    pub id: String,
399    /// Document text content
400    pub text: String,
401    /// Optional metadata
402    #[serde(skip_serializing_if = "Option::is_none")]
403    pub metadata: Option<HashMap<String, serde_json::Value>>,
404}
405
406impl Document {
407    /// Create a new document
408    pub fn new(id: impl Into<String>, text: impl Into<String>) -> Self {
409        Self {
410            id: id.into(),
411            text: text.into(),
412            metadata: None,
413        }
414    }
415
416    /// Create a new document with metadata
417    pub fn with_metadata(
418        id: impl Into<String>,
419        text: impl Into<String>,
420        metadata: HashMap<String, serde_json::Value>,
421    ) -> Self {
422        Self {
423            id: id.into(),
424            text: text.into(),
425            metadata: Some(metadata),
426        }
427    }
428}
429
430/// Index documents request
431#[derive(Debug, Clone, Serialize, Deserialize)]
432pub struct IndexDocumentsRequest {
433    /// Documents to index
434    pub documents: Vec<Document>,
435}
436
437/// Index documents response
438#[derive(Debug, Clone, Serialize, Deserialize)]
439pub struct IndexDocumentsResponse {
440    /// Number of documents indexed
441    pub indexed_count: u64,
442}
443
444/// Full-text search request
445#[derive(Debug, Clone, Serialize, Deserialize)]
446pub struct FullTextSearchRequest {
447    /// Search query
448    pub query: String,
449    /// Maximum number of results
450    pub top_k: u32,
451    /// Optional filter
452    #[serde(skip_serializing_if = "Option::is_none")]
453    pub filter: Option<serde_json::Value>,
454}
455
456impl FullTextSearchRequest {
457    /// Create a new full-text search request
458    pub fn new(query: impl Into<String>, top_k: u32) -> Self {
459        Self {
460            query: query.into(),
461            top_k,
462            filter: None,
463        }
464    }
465
466    /// Add a filter to the search
467    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
468        self.filter = Some(filter);
469        self
470    }
471}
472
473/// Full-text search result
474#[derive(Debug, Clone, Serialize, Deserialize)]
475pub struct FullTextMatch {
476    /// Document ID
477    pub id: String,
478    /// BM25 score
479    pub score: f32,
480    /// Document text
481    #[serde(skip_serializing_if = "Option::is_none")]
482    pub text: Option<String>,
483    /// Optional metadata
484    #[serde(skip_serializing_if = "Option::is_none")]
485    pub metadata: Option<HashMap<String, serde_json::Value>>,
486}
487
488/// Full-text search response
489#[derive(Debug, Clone, Serialize, Deserialize)]
490pub struct FullTextSearchResponse {
491    /// Matched documents
492    pub matches: Vec<FullTextMatch>,
493}
494
495/// Full-text index statistics
496#[derive(Debug, Clone, Serialize, Deserialize)]
497pub struct FullTextStats {
498    /// Number of documents indexed
499    pub document_count: u64,
500    /// Number of unique terms
501    pub term_count: u64,
502}
503
504// ============================================================================
505// Hybrid Search Types
506// ============================================================================
507
508/// Hybrid search request combining vector and full-text search
509#[derive(Debug, Clone, Serialize, Deserialize)]
510pub struct HybridSearchRequest {
511    /// Query vector
512    pub vector: Vec<f32>,
513    /// Text query
514    pub text: String,
515    /// Number of results to return
516    pub top_k: u32,
517    /// Weight for vector search (0.0-1.0)
518    #[serde(default = "default_vector_weight")]
519    pub vector_weight: f32,
520    /// Optional filter
521    #[serde(skip_serializing_if = "Option::is_none")]
522    pub filter: Option<serde_json::Value>,
523}
524
525fn default_vector_weight() -> f32 {
526    0.5
527}
528
529impl HybridSearchRequest {
530    /// Create a new hybrid search request
531    pub fn new(vector: Vec<f32>, text: impl Into<String>, top_k: u32) -> Self {
532        Self {
533            vector,
534            text: text.into(),
535            top_k,
536            vector_weight: 0.5,
537            filter: None,
538        }
539    }
540
541    /// Set the vector weight (text weight is 1.0 - vector_weight)
542    pub fn with_vector_weight(mut self, weight: f32) -> Self {
543        self.vector_weight = weight.clamp(0.0, 1.0);
544        self
545    }
546
547    /// Add a filter to the search
548    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
549        self.filter = Some(filter);
550        self
551    }
552}
553
554/// Hybrid search response
555#[derive(Debug, Clone, Serialize, Deserialize)]
556pub struct HybridSearchResponse {
557    /// Matched results
558    pub matches: Vec<Match>,
559}
560
561// ============================================================================
562// Operations Types
563// ============================================================================
564
565/// System diagnostics
566#[derive(Debug, Clone, Serialize, Deserialize)]
567pub struct SystemDiagnostics {
568    /// System information
569    pub system: SystemInfo,
570    /// Resource usage
571    pub resources: ResourceUsage,
572    /// Component health
573    pub components: ComponentHealth,
574    /// Number of active jobs
575    pub active_jobs: u64,
576}
577
578/// System information
579#[derive(Debug, Clone, Serialize, Deserialize)]
580pub struct SystemInfo {
581    /// Dakera version
582    pub version: String,
583    /// Rust version
584    pub rust_version: String,
585    /// Uptime in seconds
586    pub uptime_seconds: u64,
587    /// Process ID
588    pub pid: u32,
589}
590
591/// Resource usage metrics
592#[derive(Debug, Clone, Serialize, Deserialize)]
593pub struct ResourceUsage {
594    /// Memory usage in bytes
595    pub memory_bytes: u64,
596    /// Thread count
597    pub thread_count: u64,
598    /// Open file descriptors
599    pub open_fds: u64,
600    /// CPU usage percentage
601    pub cpu_percent: Option<f64>,
602}
603
604/// Component health status
605#[derive(Debug, Clone, Serialize, Deserialize)]
606pub struct ComponentHealth {
607    /// Storage health
608    pub storage: HealthStatus,
609    /// Search engine health
610    pub search_engine: HealthStatus,
611    /// Cache health
612    pub cache: HealthStatus,
613    /// gRPC health
614    pub grpc: HealthStatus,
615}
616
617/// Health status for a component
618#[derive(Debug, Clone, Serialize, Deserialize)]
619pub struct HealthStatus {
620    /// Is the component healthy
621    pub healthy: bool,
622    /// Status message
623    pub message: String,
624    /// Last check timestamp
625    pub last_check: u64,
626}
627
628/// Background job information
629#[derive(Debug, Clone, Serialize, Deserialize)]
630pub struct JobInfo {
631    /// Job ID
632    pub id: String,
633    /// Job type
634    pub job_type: String,
635    /// Current status
636    pub status: String,
637    /// Creation timestamp
638    pub created_at: u64,
639    /// Start timestamp
640    pub started_at: Option<u64>,
641    /// Completion timestamp
642    pub completed_at: Option<u64>,
643    /// Progress percentage
644    pub progress: u8,
645    /// Status message
646    pub message: Option<String>,
647}
648
649/// Compaction request
650#[derive(Debug, Clone, Serialize, Deserialize)]
651pub struct CompactionRequest {
652    /// Namespace to compact (None = all)
653    #[serde(skip_serializing_if = "Option::is_none")]
654    pub namespace: Option<String>,
655    /// Force compaction
656    #[serde(default)]
657    pub force: bool,
658}
659
660/// Compaction response
661#[derive(Debug, Clone, Serialize, Deserialize)]
662pub struct CompactionResponse {
663    /// Job ID for tracking
664    pub job_id: String,
665    /// Status message
666    pub message: String,
667}
668
669// ============================================================================
670// Cache Warming Types (Turbopuffer-inspired)
671// ============================================================================
672
673/// Priority level for cache warming operations
674#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
675#[serde(rename_all = "snake_case")]
676pub enum WarmingPriority {
677    /// Highest priority - warm immediately, preempt other operations
678    Critical,
679    /// High priority - warm soon
680    High,
681    /// Normal priority (default)
682    #[default]
683    Normal,
684    /// Low priority - warm when resources available
685    Low,
686    /// Background priority - warm during idle time only
687    Background,
688}
689
690/// Target cache tier for warming
691#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
692#[serde(rename_all = "snake_case")]
693pub enum WarmingTargetTier {
694    /// L1 in-memory cache (Moka) - fastest, limited size
695    L1,
696    /// L2 local disk cache (RocksDB) - larger, persistent
697    #[default]
698    L2,
699    /// Both L1 and L2 caches
700    Both,
701}
702
703/// Access pattern hint for cache optimization
704#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
705#[serde(rename_all = "snake_case")]
706pub enum AccessPatternHint {
707    /// Random access pattern
708    #[default]
709    Random,
710    /// Sequential access pattern
711    Sequential,
712    /// Temporal locality (recently accessed items accessed again)
713    Temporal,
714    /// Spatial locality (nearby items accessed together)
715    Spatial,
716}
717
718/// Cache warming request with priority hints
719#[derive(Debug, Clone, Serialize, Deserialize)]
720pub struct WarmCacheRequest {
721    /// Namespace to warm
722    pub namespace: String,
723    /// Specific vector IDs to warm (None = all)
724    #[serde(skip_serializing_if = "Option::is_none")]
725    pub vector_ids: Option<Vec<String>>,
726    /// Warming priority level
727    #[serde(default)]
728    pub priority: WarmingPriority,
729    /// Target cache tier
730    #[serde(default)]
731    pub target_tier: WarmingTargetTier,
732    /// Run warming in background (non-blocking)
733    #[serde(default)]
734    pub background: bool,
735    /// TTL hint in seconds
736    #[serde(skip_serializing_if = "Option::is_none")]
737    pub ttl_hint_seconds: Option<u64>,
738    /// Access pattern hint for optimization
739    #[serde(default)]
740    pub access_pattern: AccessPatternHint,
741    /// Maximum vectors to warm
742    #[serde(skip_serializing_if = "Option::is_none")]
743    pub max_vectors: Option<usize>,
744}
745
746impl WarmCacheRequest {
747    /// Create a new cache warming request for a namespace
748    pub fn new(namespace: impl Into<String>) -> Self {
749        Self {
750            namespace: namespace.into(),
751            vector_ids: None,
752            priority: WarmingPriority::default(),
753            target_tier: WarmingTargetTier::default(),
754            background: false,
755            ttl_hint_seconds: None,
756            access_pattern: AccessPatternHint::default(),
757            max_vectors: None,
758        }
759    }
760
761    /// Warm specific vector IDs
762    pub fn with_vector_ids(mut self, ids: Vec<String>) -> Self {
763        self.vector_ids = Some(ids);
764        self
765    }
766
767    /// Set warming priority
768    pub fn with_priority(mut self, priority: WarmingPriority) -> Self {
769        self.priority = priority;
770        self
771    }
772
773    /// Set target cache tier
774    pub fn with_target_tier(mut self, tier: WarmingTargetTier) -> Self {
775        self.target_tier = tier;
776        self
777    }
778
779    /// Run warming in background
780    pub fn in_background(mut self) -> Self {
781        self.background = true;
782        self
783    }
784
785    /// Set TTL hint
786    pub fn with_ttl(mut self, seconds: u64) -> Self {
787        self.ttl_hint_seconds = Some(seconds);
788        self
789    }
790
791    /// Set access pattern hint
792    pub fn with_access_pattern(mut self, pattern: AccessPatternHint) -> Self {
793        self.access_pattern = pattern;
794        self
795    }
796
797    /// Limit number of vectors to warm
798    pub fn with_max_vectors(mut self, max: usize) -> Self {
799        self.max_vectors = Some(max);
800        self
801    }
802}
803
804/// Cache warming response
805#[derive(Debug, Clone, Serialize, Deserialize)]
806pub struct WarmCacheResponse {
807    /// Operation success
808    pub success: bool,
809    /// Number of entries warmed
810    pub entries_warmed: u64,
811    /// Number of entries already warm (skipped)
812    pub entries_skipped: u64,
813    /// Job ID for tracking background operations
814    #[serde(skip_serializing_if = "Option::is_none")]
815    pub job_id: Option<String>,
816    /// Status message
817    pub message: String,
818    /// Estimated completion time for background jobs (ISO 8601)
819    #[serde(skip_serializing_if = "Option::is_none")]
820    pub estimated_completion: Option<String>,
821    /// Target tier that was warmed
822    pub target_tier: WarmingTargetTier,
823    /// Priority that was used
824    pub priority: WarmingPriority,
825    /// Bytes warmed (approximate)
826    #[serde(skip_serializing_if = "Option::is_none")]
827    pub bytes_warmed: Option<u64>,
828}
829
830// ============================================================================
831// Export Types (Turbopuffer-inspired)
832// ============================================================================
833
834/// Request to export vectors from a namespace with pagination
835#[derive(Debug, Clone, Serialize, Deserialize)]
836pub struct ExportRequest {
837    /// Maximum number of vectors to return per page (default: 1000, max: 10000)
838    #[serde(default = "default_export_top_k")]
839    pub top_k: usize,
840    /// Cursor for pagination - the last vector ID from previous page
841    #[serde(skip_serializing_if = "Option::is_none")]
842    pub cursor: Option<String>,
843    /// Whether to include vector values in the response (default: true)
844    #[serde(default = "default_true")]
845    pub include_vectors: bool,
846    /// Whether to include metadata in the response (default: true)
847    #[serde(default = "default_true")]
848    pub include_metadata: bool,
849}
850
851fn default_export_top_k() -> usize {
852    1000
853}
854
855impl Default for ExportRequest {
856    fn default() -> Self {
857        Self {
858            top_k: 1000,
859            cursor: None,
860            include_vectors: true,
861            include_metadata: true,
862        }
863    }
864}
865
866impl ExportRequest {
867    /// Create a new export request with default settings
868    pub fn new() -> Self {
869        Self::default()
870    }
871
872    /// Set the maximum number of vectors to return per page
873    pub fn with_top_k(mut self, top_k: usize) -> Self {
874        self.top_k = top_k;
875        self
876    }
877
878    /// Set the pagination cursor
879    pub fn with_cursor(mut self, cursor: impl Into<String>) -> Self {
880        self.cursor = Some(cursor.into());
881        self
882    }
883
884    /// Set whether to include vector values
885    pub fn include_vectors(mut self, include: bool) -> Self {
886        self.include_vectors = include;
887        self
888    }
889
890    /// Set whether to include metadata
891    pub fn include_metadata(mut self, include: bool) -> Self {
892        self.include_metadata = include;
893        self
894    }
895}
896
897/// A single exported vector record
898#[derive(Debug, Clone, Serialize, Deserialize)]
899pub struct ExportedVector {
900    /// Vector ID
901    pub id: String,
902    /// Vector values (optional based on include_vectors)
903    #[serde(skip_serializing_if = "Option::is_none")]
904    pub values: Option<Vec<f32>>,
905    /// Metadata (optional based on include_metadata)
906    #[serde(skip_serializing_if = "Option::is_none")]
907    pub metadata: Option<serde_json::Value>,
908    /// TTL in seconds if set
909    #[serde(skip_serializing_if = "Option::is_none")]
910    pub ttl_seconds: Option<u64>,
911}
912
913/// Response from export operation
914#[derive(Debug, Clone, Serialize, Deserialize)]
915pub struct ExportResponse {
916    /// Exported vectors for this page
917    pub vectors: Vec<ExportedVector>,
918    /// Cursor for next page (None if this is the last page)
919    #[serde(skip_serializing_if = "Option::is_none")]
920    pub next_cursor: Option<String>,
921    /// Total vectors in namespace (for progress tracking)
922    pub total_count: usize,
923    /// Number of vectors returned in this page
924    pub returned_count: usize,
925}
926
927// ============================================================================
928// Batch Query Types
929// ============================================================================
930
931/// A single query within a batch request
932#[derive(Debug, Clone, Serialize, Deserialize)]
933pub struct BatchQueryItem {
934    /// Unique identifier for this query within the batch
935    #[serde(skip_serializing_if = "Option::is_none")]
936    pub id: Option<String>,
937    /// The query vector
938    pub vector: Vec<f32>,
939    /// Number of results to return
940    #[serde(default = "default_batch_top_k")]
941    pub top_k: u32,
942    /// Optional filter expression
943    #[serde(skip_serializing_if = "Option::is_none")]
944    pub filter: Option<serde_json::Value>,
945    /// Whether to include metadata in results
946    #[serde(default)]
947    pub include_metadata: bool,
948    /// Read consistency level
949    #[serde(default)]
950    pub consistency: ReadConsistency,
951    /// Staleness configuration for bounded staleness reads
952    #[serde(skip_serializing_if = "Option::is_none")]
953    pub staleness_config: Option<StalenessConfig>,
954}
955
956fn default_batch_top_k() -> u32 {
957    10
958}
959
960impl BatchQueryItem {
961    /// Create a new batch query item
962    pub fn new(vector: Vec<f32>, top_k: u32) -> Self {
963        Self {
964            id: None,
965            vector,
966            top_k,
967            filter: None,
968            include_metadata: true,
969            consistency: ReadConsistency::default(),
970            staleness_config: None,
971        }
972    }
973
974    /// Set a unique identifier for this query
975    pub fn with_id(mut self, id: impl Into<String>) -> Self {
976        self.id = Some(id.into());
977        self
978    }
979
980    /// Add a filter to the query
981    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
982        self.filter = Some(filter);
983        self
984    }
985
986    /// Set whether to include metadata
987    pub fn include_metadata(mut self, include: bool) -> Self {
988        self.include_metadata = include;
989        self
990    }
991
992    /// Set read consistency level
993    pub fn with_consistency(mut self, consistency: ReadConsistency) -> Self {
994        self.consistency = consistency;
995        self
996    }
997
998    /// Set bounded staleness with max staleness in ms
999    pub fn with_bounded_staleness(mut self, max_staleness_ms: u64) -> Self {
1000        self.consistency = ReadConsistency::BoundedStaleness;
1001        self.staleness_config = Some(StalenessConfig::new(max_staleness_ms));
1002        self
1003    }
1004}
1005
1006/// Batch query request - execute multiple queries in parallel
1007#[derive(Debug, Clone, Serialize, Deserialize)]
1008pub struct BatchQueryRequest {
1009    /// List of queries to execute
1010    pub queries: Vec<BatchQueryItem>,
1011}
1012
1013impl BatchQueryRequest {
1014    /// Create a new batch query request
1015    pub fn new(queries: Vec<BatchQueryItem>) -> Self {
1016        Self { queries }
1017    }
1018
1019    /// Create a batch query request from a single query
1020    pub fn single(query: BatchQueryItem) -> Self {
1021        Self {
1022            queries: vec![query],
1023        }
1024    }
1025}
1026
1027/// Results for a single query within a batch
1028#[derive(Debug, Clone, Serialize, Deserialize)]
1029pub struct BatchQueryResult {
1030    /// The query identifier (if provided in request)
1031    #[serde(skip_serializing_if = "Option::is_none")]
1032    pub id: Option<String>,
1033    /// Query results (empty if an error occurred)
1034    pub results: Vec<Match>,
1035    /// Query execution time in milliseconds
1036    pub latency_ms: f64,
1037    /// Error message if this individual query failed
1038    #[serde(skip_serializing_if = "Option::is_none")]
1039    pub error: Option<String>,
1040}
1041
1042/// Batch query response
1043#[derive(Debug, Clone, Serialize, Deserialize)]
1044pub struct BatchQueryResponse {
1045    /// Results for each query in the batch
1046    pub results: Vec<BatchQueryResult>,
1047    /// Total execution time in milliseconds
1048    pub total_latency_ms: f64,
1049    /// Number of queries executed
1050    pub query_count: usize,
1051}
1052
1053// ============================================================================
1054// Multi-Vector Search Types
1055// ============================================================================
1056
1057/// Request for multi-vector search with positive and negative vectors
1058#[derive(Debug, Clone, Serialize, Deserialize)]
1059pub struct MultiVectorSearchRequest {
1060    /// Positive vectors to search towards (required, at least one)
1061    pub positive_vectors: Vec<Vec<f32>>,
1062    /// Weights for positive vectors (optional, defaults to equal weights)
1063    #[serde(skip_serializing_if = "Option::is_none")]
1064    pub positive_weights: Option<Vec<f32>>,
1065    /// Negative vectors to search away from (optional)
1066    #[serde(skip_serializing_if = "Option::is_none")]
1067    pub negative_vectors: Option<Vec<Vec<f32>>>,
1068    /// Weights for negative vectors (optional, defaults to equal weights)
1069    #[serde(skip_serializing_if = "Option::is_none")]
1070    pub negative_weights: Option<Vec<f32>>,
1071    /// Number of results to return
1072    #[serde(default = "default_multi_vector_top_k")]
1073    pub top_k: u32,
1074    /// Distance metric to use
1075    #[serde(default)]
1076    pub distance_metric: DistanceMetric,
1077    /// Minimum score threshold
1078    #[serde(skip_serializing_if = "Option::is_none")]
1079    pub score_threshold: Option<f32>,
1080    /// Enable MMR (Maximal Marginal Relevance) for diversity
1081    #[serde(default)]
1082    pub enable_mmr: bool,
1083    /// Lambda parameter for MMR (0 = max diversity, 1 = max relevance)
1084    #[serde(default = "default_mmr_lambda")]
1085    pub mmr_lambda: f32,
1086    /// Include metadata in results
1087    #[serde(default = "default_true")]
1088    pub include_metadata: bool,
1089    /// Include vectors in results
1090    #[serde(default)]
1091    pub include_vectors: bool,
1092    /// Optional metadata filter
1093    #[serde(skip_serializing_if = "Option::is_none")]
1094    pub filter: Option<serde_json::Value>,
1095    /// Read consistency level
1096    #[serde(default)]
1097    pub consistency: ReadConsistency,
1098    /// Staleness configuration for bounded staleness reads
1099    #[serde(skip_serializing_if = "Option::is_none")]
1100    pub staleness_config: Option<StalenessConfig>,
1101}
1102
1103fn default_multi_vector_top_k() -> u32 {
1104    10
1105}
1106
1107fn default_mmr_lambda() -> f32 {
1108    0.5
1109}
1110
1111impl MultiVectorSearchRequest {
1112    /// Create a new multi-vector search request with positive vectors
1113    pub fn new(positive_vectors: Vec<Vec<f32>>) -> Self {
1114        Self {
1115            positive_vectors,
1116            positive_weights: None,
1117            negative_vectors: None,
1118            negative_weights: None,
1119            top_k: 10,
1120            distance_metric: DistanceMetric::default(),
1121            score_threshold: None,
1122            enable_mmr: false,
1123            mmr_lambda: 0.5,
1124            include_metadata: true,
1125            include_vectors: false,
1126            filter: None,
1127            consistency: ReadConsistency::default(),
1128            staleness_config: None,
1129        }
1130    }
1131
1132    /// Set the number of results to return
1133    pub fn with_top_k(mut self, top_k: u32) -> Self {
1134        self.top_k = top_k;
1135        self
1136    }
1137
1138    /// Add weights for positive vectors
1139    pub fn with_positive_weights(mut self, weights: Vec<f32>) -> Self {
1140        self.positive_weights = Some(weights);
1141        self
1142    }
1143
1144    /// Add negative vectors to search away from
1145    pub fn with_negative_vectors(mut self, vectors: Vec<Vec<f32>>) -> Self {
1146        self.negative_vectors = Some(vectors);
1147        self
1148    }
1149
1150    /// Add weights for negative vectors
1151    pub fn with_negative_weights(mut self, weights: Vec<f32>) -> Self {
1152        self.negative_weights = Some(weights);
1153        self
1154    }
1155
1156    /// Set distance metric
1157    pub fn with_distance_metric(mut self, metric: DistanceMetric) -> Self {
1158        self.distance_metric = metric;
1159        self
1160    }
1161
1162    /// Set minimum score threshold
1163    pub fn with_score_threshold(mut self, threshold: f32) -> Self {
1164        self.score_threshold = Some(threshold);
1165        self
1166    }
1167
1168    /// Enable MMR for diversity
1169    pub fn with_mmr(mut self, lambda: f32) -> Self {
1170        self.enable_mmr = true;
1171        self.mmr_lambda = lambda.clamp(0.0, 1.0);
1172        self
1173    }
1174
1175    /// Set whether to include metadata
1176    pub fn include_metadata(mut self, include: bool) -> Self {
1177        self.include_metadata = include;
1178        self
1179    }
1180
1181    /// Set whether to include vectors
1182    pub fn include_vectors(mut self, include: bool) -> Self {
1183        self.include_vectors = include;
1184        self
1185    }
1186
1187    /// Add a filter
1188    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
1189        self.filter = Some(filter);
1190        self
1191    }
1192
1193    /// Set read consistency level
1194    pub fn with_consistency(mut self, consistency: ReadConsistency) -> Self {
1195        self.consistency = consistency;
1196        self
1197    }
1198}
1199
1200/// Single result from multi-vector search
1201#[derive(Debug, Clone, Serialize, Deserialize)]
1202pub struct MultiVectorSearchResult {
1203    /// Vector ID
1204    pub id: String,
1205    /// Similarity score
1206    pub score: f32,
1207    /// MMR score (if MMR enabled)
1208    #[serde(skip_serializing_if = "Option::is_none")]
1209    pub mmr_score: Option<f32>,
1210    /// Original rank before reranking
1211    #[serde(skip_serializing_if = "Option::is_none")]
1212    pub original_rank: Option<usize>,
1213    /// Optional metadata
1214    #[serde(skip_serializing_if = "Option::is_none")]
1215    pub metadata: Option<HashMap<String, serde_json::Value>>,
1216    /// Optional vector values
1217    #[serde(skip_serializing_if = "Option::is_none")]
1218    pub vector: Option<Vec<f32>>,
1219}
1220
1221/// Response from multi-vector search
1222#[derive(Debug, Clone, Serialize, Deserialize)]
1223pub struct MultiVectorSearchResponse {
1224    /// Search results
1225    pub results: Vec<MultiVectorSearchResult>,
1226    /// The computed query vector (weighted combination of positive - negative)
1227    #[serde(skip_serializing_if = "Option::is_none")]
1228    pub computed_query_vector: Option<Vec<f32>>,
1229}
1230
1231// ============================================================================
1232// Aggregation Types (Turbopuffer-inspired)
1233// ============================================================================
1234
1235/// Aggregate function for computing values across documents
1236#[derive(Debug, Clone, Serialize, Deserialize)]
1237#[serde(untagged)]
1238pub enum AggregateFunction {
1239    /// Count matching documents
1240    Count,
1241    /// Sum numeric attribute values
1242    Sum { field: String },
1243    /// Average numeric attribute values
1244    Avg { field: String },
1245    /// Minimum numeric attribute value
1246    Min { field: String },
1247    /// Maximum numeric attribute value
1248    Max { field: String },
1249}
1250
1251/// Request for aggregation query (Turbopuffer-inspired)
1252#[derive(Debug, Clone, Serialize, Deserialize)]
1253pub struct AggregationRequest {
1254    /// Named aggregations to compute
1255    /// Example: {"my_count": ["Count"], "total_score": ["Sum", "score"]}
1256    pub aggregate_by: HashMap<String, serde_json::Value>,
1257    /// Fields to group results by (optional)
1258    /// Example: ["category", "status"]
1259    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1260    pub group_by: Vec<String>,
1261    /// Filter to apply before aggregation
1262    #[serde(skip_serializing_if = "Option::is_none")]
1263    pub filter: Option<serde_json::Value>,
1264    /// Maximum number of groups to return (default: 100)
1265    #[serde(default = "default_agg_limit")]
1266    pub limit: usize,
1267}
1268
1269fn default_agg_limit() -> usize {
1270    100
1271}
1272
1273impl AggregationRequest {
1274    /// Create a new aggregation request with a single aggregation
1275    pub fn new() -> Self {
1276        Self {
1277            aggregate_by: HashMap::new(),
1278            group_by: Vec::new(),
1279            filter: None,
1280            limit: 100,
1281        }
1282    }
1283
1284    /// Add a count aggregation
1285    pub fn with_count(mut self, name: impl Into<String>) -> Self {
1286        self.aggregate_by
1287            .insert(name.into(), serde_json::json!(["Count"]));
1288        self
1289    }
1290
1291    /// Add a sum aggregation
1292    pub fn with_sum(mut self, name: impl Into<String>, field: impl Into<String>) -> Self {
1293        self.aggregate_by
1294            .insert(name.into(), serde_json::json!(["Sum", field.into()]));
1295        self
1296    }
1297
1298    /// Add an average aggregation
1299    pub fn with_avg(mut self, name: impl Into<String>, field: impl Into<String>) -> Self {
1300        self.aggregate_by
1301            .insert(name.into(), serde_json::json!(["Avg", field.into()]));
1302        self
1303    }
1304
1305    /// Add a min aggregation
1306    pub fn with_min(mut self, name: impl Into<String>, field: impl Into<String>) -> Self {
1307        self.aggregate_by
1308            .insert(name.into(), serde_json::json!(["Min", field.into()]));
1309        self
1310    }
1311
1312    /// Add a max aggregation
1313    pub fn with_max(mut self, name: impl Into<String>, field: impl Into<String>) -> Self {
1314        self.aggregate_by
1315            .insert(name.into(), serde_json::json!(["Max", field.into()]));
1316        self
1317    }
1318
1319    /// Set group by fields
1320    pub fn group_by(mut self, fields: Vec<String>) -> Self {
1321        self.group_by = fields;
1322        self
1323    }
1324
1325    /// Add a single group by field
1326    pub fn with_group_by(mut self, field: impl Into<String>) -> Self {
1327        self.group_by.push(field.into());
1328        self
1329    }
1330
1331    /// Set filter for aggregation
1332    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
1333        self.filter = Some(filter);
1334        self
1335    }
1336
1337    /// Set maximum number of groups to return
1338    pub fn with_limit(mut self, limit: usize) -> Self {
1339        self.limit = limit;
1340        self
1341    }
1342}
1343
1344impl Default for AggregationRequest {
1345    fn default() -> Self {
1346        Self::new()
1347    }
1348}
1349
1350/// Response for aggregation query
1351#[derive(Debug, Clone, Serialize, Deserialize)]
1352pub struct AggregationResponse {
1353    /// Aggregation results (without grouping)
1354    #[serde(skip_serializing_if = "Option::is_none")]
1355    pub aggregations: Option<HashMap<String, serde_json::Value>>,
1356    /// Grouped aggregation results (with group_by)
1357    #[serde(skip_serializing_if = "Option::is_none")]
1358    pub aggregation_groups: Option<Vec<AggregationGroup>>,
1359}
1360
1361/// Single group in aggregation results
1362#[derive(Debug, Clone, Serialize, Deserialize)]
1363pub struct AggregationGroup {
1364    /// Group key values (flattened into object)
1365    #[serde(flatten)]
1366    pub group_key: HashMap<String, serde_json::Value>,
1367    /// Aggregation results for this group
1368    #[serde(flatten)]
1369    pub aggregations: HashMap<String, serde_json::Value>,
1370}
1371
1372// ============================================================================
1373// Unified Query Types (Turbopuffer-inspired)
1374// ============================================================================
1375
1376/// Vector search method for unified query
1377#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
1378pub enum VectorSearchMethod {
1379    /// Approximate Nearest Neighbor (fast, default)
1380    #[default]
1381    ANN,
1382    /// Exact k-Nearest Neighbor (exhaustive, requires filters)
1383    #[serde(rename = "kNN")]
1384    KNN,
1385}
1386
1387/// Sort direction for attribute ordering
1388#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
1389#[serde(rename_all = "lowercase")]
1390pub enum SortDirection {
1391    /// Ascending order
1392    Asc,
1393    /// Descending order
1394    #[default]
1395    Desc,
1396}
1397
1398/// Ranking function for unified query API
1399/// Supports vector search (ANN/kNN), full-text BM25, and attribute ordering
1400#[derive(Debug, Clone, Serialize, Deserialize)]
1401#[serde(untagged)]
1402pub enum RankBy {
1403    /// Vector search: uses field, method, and query_vector
1404    VectorSearch {
1405        field: String,
1406        method: VectorSearchMethod,
1407        query_vector: Vec<f32>,
1408    },
1409    /// Full-text BM25 search
1410    FullTextSearch {
1411        field: String,
1412        method: String, // Always "BM25"
1413        query: String,
1414    },
1415    /// Attribute ordering
1416    AttributeOrder {
1417        field: String,
1418        direction: SortDirection,
1419    },
1420    /// Sum of multiple ranking functions
1421    Sum(Vec<RankBy>),
1422    /// Max of multiple ranking functions
1423    Max(Vec<RankBy>),
1424    /// Product with weight
1425    Product { weight: f32, ranking: Box<RankBy> },
1426}
1427
1428impl RankBy {
1429    /// Create a vector search ranking using ANN
1430    pub fn vector_ann(field: impl Into<String>, query_vector: Vec<f32>) -> Self {
1431        RankBy::VectorSearch {
1432            field: field.into(),
1433            method: VectorSearchMethod::ANN,
1434            query_vector,
1435        }
1436    }
1437
1438    /// Create a vector search ranking using ANN on the default "vector" field
1439    pub fn ann(query_vector: Vec<f32>) -> Self {
1440        Self::vector_ann("vector", query_vector)
1441    }
1442
1443    /// Create a vector search ranking using exact kNN
1444    pub fn vector_knn(field: impl Into<String>, query_vector: Vec<f32>) -> Self {
1445        RankBy::VectorSearch {
1446            field: field.into(),
1447            method: VectorSearchMethod::KNN,
1448            query_vector,
1449        }
1450    }
1451
1452    /// Create a vector search ranking using kNN on the default "vector" field
1453    pub fn knn(query_vector: Vec<f32>) -> Self {
1454        Self::vector_knn("vector", query_vector)
1455    }
1456
1457    /// Create a BM25 full-text search ranking
1458    pub fn bm25(field: impl Into<String>, query: impl Into<String>) -> Self {
1459        RankBy::FullTextSearch {
1460            field: field.into(),
1461            method: "BM25".to_string(),
1462            query: query.into(),
1463        }
1464    }
1465
1466    /// Create an attribute ordering ranking (ascending)
1467    pub fn asc(field: impl Into<String>) -> Self {
1468        RankBy::AttributeOrder {
1469            field: field.into(),
1470            direction: SortDirection::Asc,
1471        }
1472    }
1473
1474    /// Create an attribute ordering ranking (descending)
1475    pub fn desc(field: impl Into<String>) -> Self {
1476        RankBy::AttributeOrder {
1477            field: field.into(),
1478            direction: SortDirection::Desc,
1479        }
1480    }
1481
1482    /// Sum multiple ranking functions together
1483    pub fn sum(rankings: Vec<RankBy>) -> Self {
1484        RankBy::Sum(rankings)
1485    }
1486
1487    /// Take the max of multiple ranking functions
1488    pub fn max(rankings: Vec<RankBy>) -> Self {
1489        RankBy::Max(rankings)
1490    }
1491
1492    /// Apply a weight multiplier to a ranking function
1493    pub fn product(weight: f32, ranking: RankBy) -> Self {
1494        RankBy::Product {
1495            weight,
1496            ranking: Box::new(ranking),
1497        }
1498    }
1499}
1500
1501/// Unified query request with flexible ranking options (Turbopuffer-inspired)
1502///
1503/// # Example
1504///
1505/// ```rust
1506/// use dakera_client::UnifiedQueryRequest;
1507///
1508/// // Vector ANN search
1509/// let request = UnifiedQueryRequest::vector_search(vec![0.1, 0.2, 0.3], 10);
1510///
1511/// // Full-text BM25 search
1512/// let request = UnifiedQueryRequest::fulltext_search("content", "hello world", 10);
1513///
1514/// // Custom rank_by with filters
1515/// let request = UnifiedQueryRequest::vector_search(vec![0.1, 0.2, 0.3], 10)
1516///     .with_filter(serde_json::json!({"category": {"$eq": "science"}}));
1517/// ```
1518#[derive(Debug, Clone, Serialize, Deserialize)]
1519pub struct UnifiedQueryRequest {
1520    /// How to rank documents (required)
1521    pub rank_by: serde_json::Value,
1522    /// Number of results to return
1523    #[serde(default = "default_unified_top_k")]
1524    pub top_k: usize,
1525    /// Optional metadata filter
1526    #[serde(skip_serializing_if = "Option::is_none")]
1527    pub filter: Option<serde_json::Value>,
1528    /// Include metadata in results
1529    #[serde(default = "default_true")]
1530    pub include_metadata: bool,
1531    /// Include vectors in results
1532    #[serde(default)]
1533    pub include_vectors: bool,
1534    /// Distance metric for vector search (default: cosine)
1535    #[serde(default)]
1536    pub distance_metric: DistanceMetric,
1537}
1538
1539fn default_unified_top_k() -> usize {
1540    10
1541}
1542
1543impl UnifiedQueryRequest {
1544    /// Create a new unified query request with vector ANN search
1545    pub fn vector_search(query_vector: Vec<f32>, top_k: usize) -> Self {
1546        Self {
1547            rank_by: serde_json::json!(["ANN", query_vector]),
1548            top_k,
1549            filter: None,
1550            include_metadata: true,
1551            include_vectors: false,
1552            distance_metric: DistanceMetric::default(),
1553        }
1554    }
1555
1556    /// Create a new unified query request with vector kNN search
1557    pub fn vector_knn_search(query_vector: Vec<f32>, top_k: usize) -> Self {
1558        Self {
1559            rank_by: serde_json::json!(["kNN", query_vector]),
1560            top_k,
1561            filter: None,
1562            include_metadata: true,
1563            include_vectors: false,
1564            distance_metric: DistanceMetric::default(),
1565        }
1566    }
1567
1568    /// Create a new unified query request with full-text BM25 search
1569    pub fn fulltext_search(
1570        field: impl Into<String>,
1571        query: impl Into<String>,
1572        top_k: usize,
1573    ) -> Self {
1574        Self {
1575            rank_by: serde_json::json!([field.into(), "BM25", query.into()]),
1576            top_k,
1577            filter: None,
1578            include_metadata: true,
1579            include_vectors: false,
1580            distance_metric: DistanceMetric::default(),
1581        }
1582    }
1583
1584    /// Create a new unified query request with attribute ordering
1585    pub fn attribute_order(
1586        field: impl Into<String>,
1587        direction: SortDirection,
1588        top_k: usize,
1589    ) -> Self {
1590        let dir = match direction {
1591            SortDirection::Asc => "asc",
1592            SortDirection::Desc => "desc",
1593        };
1594        Self {
1595            rank_by: serde_json::json!([field.into(), dir]),
1596            top_k,
1597            filter: None,
1598            include_metadata: true,
1599            include_vectors: false,
1600            distance_metric: DistanceMetric::default(),
1601        }
1602    }
1603
1604    /// Create a unified query with a raw rank_by JSON value
1605    pub fn with_rank_by(rank_by: serde_json::Value, top_k: usize) -> Self {
1606        Self {
1607            rank_by,
1608            top_k,
1609            filter: None,
1610            include_metadata: true,
1611            include_vectors: false,
1612            distance_metric: DistanceMetric::default(),
1613        }
1614    }
1615
1616    /// Add a filter to the query
1617    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
1618        self.filter = Some(filter);
1619        self
1620    }
1621
1622    /// Set whether to include metadata
1623    pub fn include_metadata(mut self, include: bool) -> Self {
1624        self.include_metadata = include;
1625        self
1626    }
1627
1628    /// Set whether to include vector values
1629    pub fn include_vectors(mut self, include: bool) -> Self {
1630        self.include_vectors = include;
1631        self
1632    }
1633
1634    /// Set the distance metric
1635    pub fn with_distance_metric(mut self, metric: DistanceMetric) -> Self {
1636        self.distance_metric = metric;
1637        self
1638    }
1639
1640    /// Set the number of results to return
1641    pub fn with_top_k(mut self, top_k: usize) -> Self {
1642        self.top_k = top_k;
1643        self
1644    }
1645}
1646
1647/// Single result from unified query
1648#[derive(Debug, Clone, Serialize, Deserialize)]
1649pub struct UnifiedSearchResult {
1650    /// Vector/document ID
1651    pub id: String,
1652    /// Ranking score (distance for vector search, BM25 score for text)
1653    /// Named $dist for Turbopuffer compatibility
1654    #[serde(rename = "$dist", skip_serializing_if = "Option::is_none")]
1655    pub dist: Option<f32>,
1656    /// Metadata if requested
1657    #[serde(skip_serializing_if = "Option::is_none")]
1658    pub metadata: Option<serde_json::Value>,
1659    /// Vector values if requested
1660    #[serde(skip_serializing_if = "Option::is_none")]
1661    pub vector: Option<Vec<f32>>,
1662}
1663
1664/// Unified query response
1665#[derive(Debug, Clone, Serialize, Deserialize)]
1666pub struct UnifiedQueryResponse {
1667    /// Search results ordered by rank_by score
1668    pub results: Vec<UnifiedSearchResult>,
1669    /// Cursor for pagination (if more results available)
1670    #[serde(skip_serializing_if = "Option::is_none")]
1671    pub next_cursor: Option<String>,
1672}
1673
1674// ============================================================================
1675// Query Explain Types
1676// ============================================================================
1677
1678fn default_explain_top_k() -> usize {
1679    10
1680}
1681
1682/// Query type for explain
1683#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1684#[serde(rename_all = "snake_case")]
1685#[derive(Default)]
1686pub enum ExplainQueryType {
1687    /// Vector similarity search
1688    #[default]
1689    VectorSearch,
1690    /// Full-text search
1691    FullTextSearch,
1692    /// Hybrid search combining vector and text
1693    HybridSearch,
1694    /// Multi-vector search with positive/negative vectors
1695    MultiVector,
1696    /// Batch query execution
1697    BatchQuery,
1698}
1699
1700/// Query explain request
1701#[derive(Debug, Clone, Serialize, Deserialize)]
1702pub struct QueryExplainRequest {
1703    /// Type of query to explain
1704    #[serde(default)]
1705    pub query_type: ExplainQueryType,
1706    /// Query vector (for vector searches)
1707    #[serde(skip_serializing_if = "Option::is_none")]
1708    pub vector: Option<Vec<f32>>,
1709    /// Number of results to return
1710    #[serde(default = "default_explain_top_k")]
1711    pub top_k: usize,
1712    /// Optional metadata filter
1713    #[serde(skip_serializing_if = "Option::is_none")]
1714    pub filter: Option<serde_json::Value>,
1715    /// Optional text query for hybrid/fulltext search
1716    #[serde(skip_serializing_if = "Option::is_none")]
1717    pub text_query: Option<String>,
1718    /// Distance metric
1719    #[serde(default = "default_distance_metric")]
1720    pub distance_metric: String,
1721    /// Whether to actually execute the query for actual stats
1722    #[serde(default)]
1723    pub execute: bool,
1724    /// Include verbose output
1725    #[serde(default)]
1726    pub verbose: bool,
1727}
1728
1729fn default_distance_metric() -> String {
1730    "cosine".to_string()
1731}
1732
1733impl QueryExplainRequest {
1734    /// Create a new explain request for a vector search
1735    pub fn vector_search(vector: Vec<f32>, top_k: usize) -> Self {
1736        Self {
1737            query_type: ExplainQueryType::VectorSearch,
1738            vector: Some(vector),
1739            top_k,
1740            filter: None,
1741            text_query: None,
1742            distance_metric: "cosine".to_string(),
1743            execute: false,
1744            verbose: false,
1745        }
1746    }
1747
1748    /// Create a new explain request for a full-text search
1749    pub fn fulltext_search(text_query: impl Into<String>, top_k: usize) -> Self {
1750        Self {
1751            query_type: ExplainQueryType::FullTextSearch,
1752            vector: None,
1753            top_k,
1754            filter: None,
1755            text_query: Some(text_query.into()),
1756            distance_metric: "bm25".to_string(),
1757            execute: false,
1758            verbose: false,
1759        }
1760    }
1761
1762    /// Create a new explain request for a hybrid search
1763    pub fn hybrid_search(vector: Vec<f32>, text_query: impl Into<String>, top_k: usize) -> Self {
1764        Self {
1765            query_type: ExplainQueryType::HybridSearch,
1766            vector: Some(vector),
1767            top_k,
1768            filter: None,
1769            text_query: Some(text_query.into()),
1770            distance_metric: "hybrid".to_string(),
1771            execute: false,
1772            verbose: false,
1773        }
1774    }
1775
1776    /// Add a filter to the explain request
1777    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
1778        self.filter = Some(filter);
1779        self
1780    }
1781
1782    /// Set the distance metric
1783    pub fn with_distance_metric(mut self, metric: impl Into<String>) -> Self {
1784        self.distance_metric = metric.into();
1785        self
1786    }
1787
1788    /// Execute the query to get actual stats
1789    pub fn with_execution(mut self) -> Self {
1790        self.execute = true;
1791        self
1792    }
1793
1794    /// Enable verbose output
1795    pub fn with_verbose(mut self) -> Self {
1796        self.verbose = true;
1797        self
1798    }
1799}
1800
1801/// A stage in query execution
1802#[derive(Debug, Clone, Serialize, Deserialize)]
1803pub struct ExecutionStage {
1804    /// Stage name
1805    pub name: String,
1806    /// Stage description
1807    pub description: String,
1808    /// Stage order (1-based)
1809    pub order: u32,
1810    /// Estimated input rows
1811    pub estimated_input: u64,
1812    /// Estimated output rows
1813    pub estimated_output: u64,
1814    /// Estimated cost for this stage
1815    pub estimated_cost: f64,
1816    /// Stage-specific details
1817    #[serde(default)]
1818    pub details: HashMap<String, serde_json::Value>,
1819}
1820
1821/// Cost estimation
1822#[derive(Debug, Clone, Serialize, Deserialize)]
1823pub struct CostEstimate {
1824    /// Total estimated cost (abstract units)
1825    pub total_cost: f64,
1826    /// Estimated execution time in milliseconds
1827    pub estimated_time_ms: u64,
1828    /// Estimated memory usage in bytes
1829    pub estimated_memory_bytes: u64,
1830    /// Estimated I/O operations
1831    pub estimated_io_ops: u64,
1832    /// Cost breakdown by component
1833    #[serde(default)]
1834    pub cost_breakdown: HashMap<String, f64>,
1835    /// Confidence level (0.0-1.0)
1836    pub confidence: f64,
1837}
1838
1839/// Actual execution statistics (when execute=true)
1840#[derive(Debug, Clone, Serialize, Deserialize)]
1841pub struct ActualStats {
1842    /// Actual execution time in milliseconds
1843    pub execution_time_ms: u64,
1844    /// Actual results returned
1845    pub results_returned: usize,
1846    /// Vectors scanned
1847    pub vectors_scanned: u64,
1848    /// Vectors after filter
1849    pub vectors_after_filter: u64,
1850    /// Index lookups performed
1851    pub index_lookups: u64,
1852    /// Cache hits
1853    pub cache_hits: u64,
1854    /// Cache misses
1855    pub cache_misses: u64,
1856    /// Memory used in bytes
1857    pub memory_used_bytes: u64,
1858}
1859
1860/// Performance recommendation
1861#[derive(Debug, Clone, Serialize, Deserialize)]
1862pub struct Recommendation {
1863    /// Recommendation type
1864    pub recommendation_type: String,
1865    /// Priority (high, medium, low)
1866    pub priority: String,
1867    /// Recommendation description
1868    pub description: String,
1869    /// Expected improvement
1870    pub expected_improvement: String,
1871    /// How to implement
1872    pub implementation: String,
1873}
1874
1875/// Index selection details
1876#[derive(Debug, Clone, Serialize, Deserialize)]
1877pub struct IndexSelection {
1878    /// Index type that will be used
1879    pub index_type: String,
1880    /// Why this index was selected
1881    pub selection_reason: String,
1882    /// Alternative indexes considered
1883    #[serde(default)]
1884    pub alternatives_considered: Vec<IndexAlternative>,
1885    /// Index configuration
1886    #[serde(default)]
1887    pub index_config: HashMap<String, serde_json::Value>,
1888    /// Index statistics
1889    pub index_stats: IndexStatistics,
1890}
1891
1892/// Alternative index that was considered
1893#[derive(Debug, Clone, Serialize, Deserialize)]
1894pub struct IndexAlternative {
1895    /// Index type
1896    pub index_type: String,
1897    /// Why it wasn't selected
1898    pub rejection_reason: String,
1899    /// Estimated cost if this index was used
1900    pub estimated_cost: f64,
1901}
1902
1903/// Index statistics
1904#[derive(Debug, Clone, Serialize, Deserialize)]
1905pub struct IndexStatistics {
1906    /// Total vectors in index
1907    pub vector_count: u64,
1908    /// Vector dimension
1909    pub dimension: usize,
1910    /// Index memory usage (estimated)
1911    pub memory_bytes: u64,
1912    /// Index build time (if available)
1913    #[serde(skip_serializing_if = "Option::is_none")]
1914    pub build_time_ms: Option<u64>,
1915    /// Last updated timestamp
1916    #[serde(skip_serializing_if = "Option::is_none")]
1917    pub last_updated: Option<u64>,
1918}
1919
1920/// Query parameters for reference
1921#[derive(Debug, Clone, Serialize, Deserialize)]
1922pub struct QueryParams {
1923    /// Number of results requested
1924    pub top_k: usize,
1925    /// Whether a filter was applied
1926    pub has_filter: bool,
1927    /// Filter complexity level
1928    pub filter_complexity: String,
1929    /// Vector dimension (if applicable)
1930    #[serde(skip_serializing_if = "Option::is_none")]
1931    pub vector_dimension: Option<usize>,
1932    /// Distance metric used
1933    pub distance_metric: String,
1934    /// Text query length (if applicable)
1935    #[serde(skip_serializing_if = "Option::is_none")]
1936    pub text_query_length: Option<usize>,
1937}
1938
1939/// Query explain response - detailed execution plan
1940#[derive(Debug, Clone, Serialize, Deserialize)]
1941pub struct QueryExplainResponse {
1942    /// Query type being explained
1943    pub query_type: ExplainQueryType,
1944    /// Namespace being queried
1945    pub namespace: String,
1946    /// Index selection information
1947    pub index_selection: IndexSelection,
1948    /// Query execution stages
1949    pub stages: Vec<ExecutionStage>,
1950    /// Cost estimates
1951    pub cost_estimate: CostEstimate,
1952    /// Actual execution stats (if execute=true)
1953    #[serde(skip_serializing_if = "Option::is_none")]
1954    pub actual_stats: Option<ActualStats>,
1955    /// Performance recommendations
1956    #[serde(default)]
1957    pub recommendations: Vec<Recommendation>,
1958    /// Query plan summary
1959    pub summary: String,
1960    /// Raw query parameters
1961    pub query_params: QueryParams,
1962}
1963
1964// ============================================================================
1965// Text Auto-Embedding Types
1966// ============================================================================
1967
1968/// Supported embedding models for text-based operations.
1969#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
1970#[serde(rename_all = "kebab-case")]
1971pub enum EmbeddingModel {
1972    /// MiniLM-L6 — Fast, good quality (384 dimensions)
1973    #[default]
1974    Minilm,
1975    /// BGE-small — Balanced performance (384 dimensions)
1976    BgeSmall,
1977    /// E5-small — High quality (384 dimensions)
1978    E5Small,
1979}
1980
1981/// A text document to upsert with automatic embedding generation.
1982#[derive(Debug, Clone, Serialize, Deserialize)]
1983pub struct TextDocument {
1984    /// Unique identifier for the document.
1985    pub id: String,
1986    /// Raw text content to be embedded.
1987    pub text: String,
1988    /// Optional metadata for the document.
1989    #[serde(skip_serializing_if = "Option::is_none")]
1990    pub metadata: Option<HashMap<String, serde_json::Value>>,
1991    /// Optional TTL in seconds.
1992    #[serde(skip_serializing_if = "Option::is_none")]
1993    pub ttl_seconds: Option<u64>,
1994}
1995
1996impl TextDocument {
1997    /// Create a new text document with the given ID and text.
1998    pub fn new(id: impl Into<String>, text: impl Into<String>) -> Self {
1999        Self {
2000            id: id.into(),
2001            text: text.into(),
2002            metadata: None,
2003            ttl_seconds: None,
2004        }
2005    }
2006
2007    /// Add metadata to this document.
2008    pub fn with_metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
2009        self.metadata = Some(metadata);
2010        self
2011    }
2012
2013    /// Set a TTL on this document.
2014    pub fn with_ttl(mut self, ttl_seconds: u64) -> Self {
2015        self.ttl_seconds = Some(ttl_seconds);
2016        self
2017    }
2018}
2019
2020/// Request to upsert text documents with automatic embedding.
2021#[derive(Debug, Clone, Serialize, Deserialize)]
2022pub struct UpsertTextRequest {
2023    /// Documents to upsert.
2024    pub documents: Vec<TextDocument>,
2025    /// Embedding model to use (default: minilm).
2026    #[serde(skip_serializing_if = "Option::is_none")]
2027    pub model: Option<EmbeddingModel>,
2028}
2029
2030impl UpsertTextRequest {
2031    /// Create a new upsert-text request.
2032    pub fn new(documents: Vec<TextDocument>) -> Self {
2033        Self {
2034            documents,
2035            model: None,
2036        }
2037    }
2038
2039    /// Set the embedding model.
2040    pub fn with_model(mut self, model: EmbeddingModel) -> Self {
2041        self.model = Some(model);
2042        self
2043    }
2044}
2045
2046/// Response from a text upsert operation.
2047#[derive(Debug, Clone, Serialize, Deserialize)]
2048pub struct TextUpsertResponse {
2049    /// Number of documents upserted.
2050    pub upserted_count: u64,
2051    /// Approximate number of tokens processed.
2052    pub tokens_processed: u64,
2053    /// Embedding model used.
2054    pub model: EmbeddingModel,
2055    /// Time spent generating embeddings in milliseconds.
2056    pub embedding_time_ms: u64,
2057}
2058
2059/// A single text search result.
2060#[derive(Debug, Clone, Serialize, Deserialize)]
2061pub struct TextSearchResult {
2062    /// Document ID.
2063    pub id: String,
2064    /// Similarity score.
2065    pub score: f32,
2066    /// Original text (if `include_text` was true).
2067    #[serde(skip_serializing_if = "Option::is_none")]
2068    pub text: Option<String>,
2069    /// Document metadata.
2070    #[serde(skip_serializing_if = "Option::is_none")]
2071    pub metadata: Option<HashMap<String, serde_json::Value>>,
2072    /// Vector values (if `include_vectors` was true).
2073    #[serde(skip_serializing_if = "Option::is_none")]
2074    pub vector: Option<Vec<f32>>,
2075}
2076
2077/// Request to query using natural language text with automatic embedding.
2078#[derive(Debug, Clone, Serialize, Deserialize)]
2079pub struct QueryTextRequest {
2080    /// Query text.
2081    pub text: String,
2082    /// Number of results to return.
2083    pub top_k: u32,
2084    /// Optional metadata filter.
2085    #[serde(skip_serializing_if = "Option::is_none")]
2086    pub filter: Option<serde_json::Value>,
2087    /// Whether to include the original text in results.
2088    pub include_text: bool,
2089    /// Whether to include vectors in results.
2090    pub include_vectors: bool,
2091    /// Embedding model to use (default: minilm).
2092    #[serde(skip_serializing_if = "Option::is_none")]
2093    pub model: Option<EmbeddingModel>,
2094}
2095
2096impl QueryTextRequest {
2097    /// Create a new text query request.
2098    pub fn new(text: impl Into<String>, top_k: u32) -> Self {
2099        Self {
2100            text: text.into(),
2101            top_k,
2102            filter: None,
2103            include_text: true,
2104            include_vectors: false,
2105            model: None,
2106        }
2107    }
2108
2109    /// Add a metadata filter.
2110    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
2111        self.filter = Some(filter);
2112        self
2113    }
2114
2115    /// Set whether to include the original text in results.
2116    pub fn include_text(mut self, include: bool) -> Self {
2117        self.include_text = include;
2118        self
2119    }
2120
2121    /// Set whether to include vectors in results.
2122    pub fn include_vectors(mut self, include: bool) -> Self {
2123        self.include_vectors = include;
2124        self
2125    }
2126
2127    /// Set the embedding model.
2128    pub fn with_model(mut self, model: EmbeddingModel) -> Self {
2129        self.model = Some(model);
2130        self
2131    }
2132}
2133
2134/// Response from a text query operation.
2135#[derive(Debug, Clone, Serialize, Deserialize)]
2136pub struct TextQueryResponse {
2137    /// Search results.
2138    pub results: Vec<TextSearchResult>,
2139    /// Embedding model used.
2140    pub model: EmbeddingModel,
2141    /// Time spent generating the query embedding in milliseconds.
2142    pub embedding_time_ms: u64,
2143    /// Time spent searching in milliseconds.
2144    pub search_time_ms: u64,
2145}
2146
2147/// Request to execute multiple text queries with automatic embedding in a single call.
2148#[derive(Debug, Clone, Serialize, Deserialize)]
2149pub struct BatchQueryTextRequest {
2150    /// Text queries.
2151    pub queries: Vec<String>,
2152    /// Number of results per query.
2153    pub top_k: u32,
2154    /// Optional metadata filter applied to all queries.
2155    #[serde(skip_serializing_if = "Option::is_none")]
2156    pub filter: Option<serde_json::Value>,
2157    /// Whether to include vectors in results.
2158    pub include_vectors: bool,
2159    /// Embedding model to use (default: minilm).
2160    #[serde(skip_serializing_if = "Option::is_none")]
2161    pub model: Option<EmbeddingModel>,
2162}
2163
2164impl BatchQueryTextRequest {
2165    /// Create a new batch text query request.
2166    pub fn new(queries: Vec<String>, top_k: u32) -> Self {
2167        Self {
2168            queries,
2169            top_k,
2170            filter: None,
2171            include_vectors: false,
2172            model: None,
2173        }
2174    }
2175}
2176
2177/// Response from a batch text query operation.
2178#[derive(Debug, Clone, Serialize, Deserialize)]
2179pub struct BatchQueryTextResponse {
2180    /// Results for each query (in the same order as the request).
2181    pub results: Vec<Vec<TextSearchResult>>,
2182    /// Embedding model used.
2183    pub model: EmbeddingModel,
2184    /// Time spent generating all embeddings in milliseconds.
2185    pub embedding_time_ms: u64,
2186    /// Time spent on all searches in milliseconds.
2187    pub search_time_ms: u64,
2188}
2189
2190// ============================================================================
2191// Fetch by ID Types
2192// ============================================================================
2193
2194/// Request to fetch vectors by their IDs.
2195#[derive(Debug, Clone, Serialize, Deserialize)]
2196pub struct FetchRequest {
2197    /// IDs of vectors to fetch.
2198    pub ids: Vec<String>,
2199    /// Whether to include vector values.
2200    pub include_values: bool,
2201    /// Whether to include metadata.
2202    pub include_metadata: bool,
2203}
2204
2205impl FetchRequest {
2206    /// Create a new fetch request.
2207    pub fn new(ids: Vec<String>) -> Self {
2208        Self {
2209            ids,
2210            include_values: true,
2211            include_metadata: true,
2212        }
2213    }
2214}
2215
2216/// Response from a fetch-by-ID operation.
2217#[derive(Debug, Clone, Serialize, Deserialize)]
2218pub struct FetchResponse {
2219    /// Fetched vectors.
2220    pub vectors: Vec<Vector>,
2221}
2222
2223// ============================================================================
2224// Namespace Management Types
2225// ============================================================================
2226
2227/// Request to create a new namespace.
2228#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2229pub struct CreateNamespaceRequest {
2230    /// Vector dimensions (inferred from first upsert if omitted).
2231    #[serde(skip_serializing_if = "Option::is_none")]
2232    pub dimensions: Option<u32>,
2233    /// Index type (e.g. "hnsw", "flat").
2234    #[serde(skip_serializing_if = "Option::is_none")]
2235    pub index_type: Option<String>,
2236    /// Arbitrary namespace metadata.
2237    #[serde(skip_serializing_if = "Option::is_none")]
2238    pub metadata: Option<HashMap<String, serde_json::Value>>,
2239}
2240
2241impl CreateNamespaceRequest {
2242    /// Create a minimal request (server picks sensible defaults).
2243    pub fn new() -> Self {
2244        Self::default()
2245    }
2246
2247    /// Set the vector dimensions.
2248    pub fn with_dimensions(mut self, dimensions: u32) -> Self {
2249        self.dimensions = Some(dimensions);
2250        self
2251    }
2252
2253    /// Set the index type.
2254    pub fn with_index_type(mut self, index_type: impl Into<String>) -> Self {
2255        self.index_type = Some(index_type.into());
2256        self
2257    }
2258}
2259
2260/// Request body for `PUT /v1/namespaces/:namespace` — upsert semantics (v0.6.0).
2261///
2262/// Creates the namespace if it does not exist, or updates its configuration
2263/// if it already exists.  Requires `Scope::Write`.
2264#[derive(Debug, Clone, Serialize, Deserialize)]
2265pub struct ConfigureNamespaceRequest {
2266    /// Vector dimension.  Required on first creation; must match on subsequent calls.
2267    pub dimension: usize,
2268    /// Distance metric (defaults to cosine when omitted).
2269    #[serde(skip_serializing_if = "Option::is_none")]
2270    pub distance: Option<DistanceMetric>,
2271}
2272
2273impl ConfigureNamespaceRequest {
2274    /// Create a new configure-namespace request with the given dimension.
2275    pub fn new(dimension: usize) -> Self {
2276        Self {
2277            dimension,
2278            distance: None,
2279        }
2280    }
2281
2282    /// Set the distance metric.
2283    pub fn with_distance(mut self, distance: DistanceMetric) -> Self {
2284        self.distance = Some(distance);
2285        self
2286    }
2287}
2288
2289/// Response from `PUT /v1/namespaces/:namespace`.
2290#[derive(Debug, Clone, Serialize, Deserialize)]
2291pub struct ConfigureNamespaceResponse {
2292    /// Namespace name.
2293    pub namespace: String,
2294    /// Vector dimension.
2295    pub dimension: usize,
2296    /// Distance metric in use.
2297    pub distance: DistanceMetric,
2298    /// `true` if the namespace was newly created; `false` if it already existed.
2299    pub created: bool,
2300}