chroma_types/
api_types.rs

1use crate::collection_configuration::InternalCollectionConfiguration;
2use crate::collection_configuration::InternalUpdateCollectionConfiguration;
3use crate::error::QueryConversionError;
4use crate::operator::GetResult;
5use crate::operator::Key;
6use crate::operator::KnnBatchResult;
7use crate::operator::KnnProjectionRecord;
8use crate::operator::ProjectionRecord;
9use crate::operator::SearchResult;
10use crate::plan::PlanToProtoError;
11use crate::plan::SearchPayload;
12use crate::validators::{
13    validate_metadata_vec, validate_name, validate_non_empty_collection_update_metadata,
14    validate_optional_metadata, validate_schema, validate_update_metadata_vec,
15};
16use crate::Collection;
17use crate::CollectionConfigurationToInternalConfigurationError;
18use crate::CollectionConversionError;
19use crate::CollectionUuid;
20use crate::DistributedSpannParametersFromSegmentError;
21use crate::EmbeddingsPayload;
22use crate::HnswParametersFromSegmentError;
23use crate::Metadata;
24use crate::RawWhereFields;
25use crate::Schema;
26use crate::SchemaError;
27use crate::SegmentConversionError;
28use crate::SegmentScopeConversionError;
29use crate::UpdateEmbeddingsPayload;
30use crate::UpdateMetadata;
31use crate::Where;
32use crate::WhereValidationError;
33use chroma_error::ChromaValidationError;
34use chroma_error::{ChromaError, ErrorCodes};
35use serde::Deserialize;
36use serde::Serialize;
37use std::time::SystemTimeError;
38use thiserror::Error;
39use tonic::Status;
40use uuid::Uuid;
41use validator::Validate;
42use validator::ValidationError;
43
44#[cfg(feature = "pyo3")]
45use pyo3::types::PyAnyMethods;
46
47#[derive(Debug, Error)]
48pub enum GetSegmentsError {
49    #[error("Could not parse segment")]
50    SegmentConversion(#[from] SegmentConversionError),
51    #[error("Unknown segment scope")]
52    UnknownScope(#[from] SegmentScopeConversionError),
53    #[error(transparent)]
54    Internal(#[from] Box<dyn ChromaError>),
55}
56
57impl ChromaError for GetSegmentsError {
58    fn code(&self) -> ErrorCodes {
59        match self {
60            GetSegmentsError::SegmentConversion(_) => ErrorCodes::Internal,
61            GetSegmentsError::UnknownScope(_) => ErrorCodes::Internal,
62            GetSegmentsError::Internal(err) => err.code(),
63        }
64    }
65}
66
67#[derive(Debug, Error)]
68pub enum GetCollectionWithSegmentsError {
69    #[error("Failed to convert proto collection")]
70    CollectionConversionError(#[from] CollectionConversionError),
71    #[error("Duplicate segment")]
72    DuplicateSegment,
73    #[error("Missing field: [{0}]")]
74    Field(String),
75    #[error("Failed to convert proto segment")]
76    SegmentConversionError(#[from] SegmentConversionError),
77    #[error("Failed to get segments")]
78    GetSegmentsError(#[from] GetSegmentsError),
79    #[error("Grpc error: {0}")]
80    Grpc(#[from] Status),
81    #[error("Collection [{0}] does not exist.")]
82    NotFound(String),
83    #[error(transparent)]
84    Internal(#[from] Box<dyn ChromaError>),
85}
86
87impl ChromaError for GetCollectionWithSegmentsError {
88    fn code(&self) -> ErrorCodes {
89        match self {
90            GetCollectionWithSegmentsError::CollectionConversionError(
91                collection_conversion_error,
92            ) => collection_conversion_error.code(),
93            GetCollectionWithSegmentsError::DuplicateSegment => ErrorCodes::Internal,
94            GetCollectionWithSegmentsError::Field(_) => ErrorCodes::FailedPrecondition,
95            GetCollectionWithSegmentsError::SegmentConversionError(segment_conversion_error) => {
96                segment_conversion_error.code()
97            }
98            GetCollectionWithSegmentsError::Grpc(status) => status.code().into(),
99            GetCollectionWithSegmentsError::GetSegmentsError(get_segments_error) => {
100                get_segments_error.code()
101            }
102            GetCollectionWithSegmentsError::NotFound(_) => ErrorCodes::NotFound,
103            GetCollectionWithSegmentsError::Internal(err) => err.code(),
104        }
105    }
106
107    fn should_trace_error(&self) -> bool {
108        if let Self::Grpc(status) = self {
109            status.code() != ErrorCodes::NotFound.into()
110        } else {
111            true
112        }
113    }
114}
115
116#[derive(Debug, Error)]
117pub enum BatchGetCollectionVersionFilePathsError {
118    #[error("Grpc error: {0}")]
119    Grpc(#[from] Status),
120    #[error("Could not parse UUID from string {1}: {0}")]
121    Uuid(uuid::Error, String),
122}
123
124impl ChromaError for BatchGetCollectionVersionFilePathsError {
125    fn code(&self) -> ErrorCodes {
126        match self {
127            BatchGetCollectionVersionFilePathsError::Grpc(status) => status.code().into(),
128            BatchGetCollectionVersionFilePathsError::Uuid(_, _) => ErrorCodes::InvalidArgument,
129        }
130    }
131}
132
133#[derive(Debug, Error)]
134pub enum BatchGetCollectionSoftDeleteStatusError {
135    #[error("Grpc error: {0}")]
136    Grpc(#[from] Status),
137    #[error("Could not parse UUID from string {1}: {0}")]
138    Uuid(uuid::Error, String),
139}
140
141impl ChromaError for BatchGetCollectionSoftDeleteStatusError {
142    fn code(&self) -> ErrorCodes {
143        match self {
144            BatchGetCollectionSoftDeleteStatusError::Grpc(status) => status.code().into(),
145            BatchGetCollectionSoftDeleteStatusError::Uuid(_, _) => ErrorCodes::InvalidArgument,
146        }
147    }
148}
149
150#[derive(Serialize)]
151#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
152pub struct ResetResponse {}
153
154#[derive(Debug, Error)]
155pub enum ResetError {
156    #[error(transparent)]
157    Cache(Box<dyn ChromaError>),
158    #[error(transparent)]
159    Internal(#[from] Box<dyn ChromaError>),
160    #[error("Reset is disabled by config")]
161    NotAllowed,
162}
163
164impl ChromaError for ResetError {
165    fn code(&self) -> ErrorCodes {
166        match self {
167            ResetError::Cache(err) => err.code(),
168            ResetError::Internal(err) => err.code(),
169            ResetError::NotAllowed => ErrorCodes::PermissionDenied,
170        }
171    }
172}
173
174#[derive(Serialize)]
175#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
176pub struct ChecklistResponse {
177    pub max_batch_size: u32,
178    pub supports_base64_encoding: bool,
179}
180
181#[derive(Debug, Error)]
182#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
183pub enum HeartbeatError {
184    #[error("system time error: {0}")]
185    CouldNotGetTime(String),
186}
187
188impl From<SystemTimeError> for HeartbeatError {
189    fn from(err: SystemTimeError) -> Self {
190        HeartbeatError::CouldNotGetTime(err.to_string())
191    }
192}
193
194impl ChromaError for HeartbeatError {
195    fn code(&self) -> ErrorCodes {
196        ErrorCodes::Internal
197    }
198}
199
200#[non_exhaustive]
201#[derive(Serialize, Validate, Deserialize)]
202#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
203pub struct CreateTenantRequest {
204    #[validate(length(min = 3))]
205    pub name: String,
206}
207
208impl CreateTenantRequest {
209    pub fn try_new(name: String) -> Result<Self, ChromaValidationError> {
210        let request = Self { name };
211        request.validate().map_err(ChromaValidationError::from)?;
212        Ok(request)
213    }
214}
215
216#[derive(Serialize, Deserialize)]
217#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
218pub struct CreateTenantResponse {}
219
220#[derive(Debug, Error)]
221pub enum CreateTenantError {
222    #[error("Tenant [{0}] already exists")]
223    AlreadyExists(String),
224    #[error(transparent)]
225    Internal(#[from] Box<dyn ChromaError>),
226}
227
228impl ChromaError for CreateTenantError {
229    fn code(&self) -> ErrorCodes {
230        match self {
231            CreateTenantError::AlreadyExists(_) => ErrorCodes::AlreadyExists,
232            CreateTenantError::Internal(err) => err.code(),
233        }
234    }
235}
236
237#[non_exhaustive]
238#[derive(Validate, Serialize)]
239#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
240pub struct GetTenantRequest {
241    pub name: String,
242}
243
244impl GetTenantRequest {
245    pub fn try_new(name: String) -> Result<Self, ChromaValidationError> {
246        let request = Self { name };
247        request.validate().map_err(ChromaValidationError::from)?;
248        Ok(request)
249    }
250}
251
252#[derive(Serialize)]
253#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
254#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
255pub struct GetTenantResponse {
256    pub name: String,
257    pub resource_name: Option<String>,
258}
259
260#[cfg(feature = "pyo3")]
261#[pyo3::pymethods]
262impl GetTenantResponse {
263    #[getter]
264    pub fn name(&self) -> &String {
265        &self.name
266    }
267
268    #[getter]
269    pub fn resource_name(&self) -> Option<String> {
270        self.resource_name.clone()
271    }
272}
273
274#[derive(Debug, Error)]
275pub enum GetTenantError {
276    #[error(transparent)]
277    Internal(#[from] Box<dyn ChromaError>),
278    #[error("Tenant [{0}] not found")]
279    NotFound(String),
280}
281
282impl ChromaError for GetTenantError {
283    fn code(&self) -> ErrorCodes {
284        match self {
285            GetTenantError::Internal(err) => err.code(),
286            GetTenantError::NotFound(_) => ErrorCodes::NotFound,
287        }
288    }
289}
290
291#[non_exhaustive]
292#[derive(Validate, Serialize)]
293#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
294pub struct UpdateTenantRequest {
295    pub tenant_id: String,
296    pub resource_name: String,
297}
298
299impl UpdateTenantRequest {
300    pub fn try_new(
301        tenant_id: String,
302        resource_name: String,
303    ) -> Result<Self, ChromaValidationError> {
304        let request = Self {
305            tenant_id,
306            resource_name,
307        };
308        request.validate().map_err(ChromaValidationError::from)?;
309        Ok(request)
310    }
311}
312
313#[derive(Serialize)]
314#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
315#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
316pub struct UpdateTenantResponse {}
317
318#[cfg(feature = "pyo3")]
319#[pyo3::pymethods]
320impl UpdateTenantResponse {}
321
322#[derive(Error, Debug)]
323pub enum UpdateTenantError {
324    #[error("Failed to set resource name")]
325    FailedToSetResourceName(#[from] tonic::Status),
326    #[error(transparent)]
327    Internal(#[from] Box<dyn ChromaError>),
328    #[error("Tenant [{0}] not found")]
329    NotFound(String),
330}
331
332impl ChromaError for UpdateTenantError {
333    fn code(&self) -> ErrorCodes {
334        match self {
335            UpdateTenantError::FailedToSetResourceName(_) => ErrorCodes::AlreadyExists,
336            UpdateTenantError::Internal(err) => err.code(),
337            UpdateTenantError::NotFound(_) => ErrorCodes::NotFound,
338        }
339    }
340}
341
342#[non_exhaustive]
343#[derive(Validate, Serialize)]
344#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
345pub struct CreateDatabaseRequest {
346    pub database_id: Uuid,
347    pub tenant_id: String,
348    #[validate(length(min = 3))]
349    pub database_name: String,
350}
351
352impl CreateDatabaseRequest {
353    pub fn try_new(
354        tenant_id: String,
355        database_name: String,
356    ) -> Result<Self, ChromaValidationError> {
357        let database_id = Uuid::new_v4();
358        let request = Self {
359            database_id,
360            tenant_id,
361            database_name,
362        };
363        request.validate().map_err(ChromaValidationError::from)?;
364        Ok(request)
365    }
366}
367
368#[derive(Serialize)]
369#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
370pub struct CreateDatabaseResponse {}
371
372#[derive(Error, Debug)]
373pub enum CreateDatabaseError {
374    #[error("Database [{0}] already exists")]
375    AlreadyExists(String),
376    #[error(transparent)]
377    Internal(#[from] Box<dyn ChromaError>),
378}
379
380impl ChromaError for CreateDatabaseError {
381    fn code(&self) -> ErrorCodes {
382        match self {
383            CreateDatabaseError::AlreadyExists(_) => ErrorCodes::AlreadyExists,
384            CreateDatabaseError::Internal(status) => status.code(),
385        }
386    }
387}
388
389#[derive(Serialize, Deserialize, Debug, Clone, Default)]
390#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
391#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
392pub struct Database {
393    pub id: Uuid,
394    pub name: String,
395    pub tenant: String,
396}
397
398#[cfg(feature = "pyo3")]
399#[pyo3::pymethods]
400impl Database {
401    #[getter]
402    fn id<'py>(&self, py: pyo3::Python<'py>) -> pyo3::PyResult<pyo3::Bound<'py, pyo3::PyAny>> {
403        let res = pyo3::prelude::PyModule::import(py, "uuid")?
404            .getattr("UUID")?
405            .call1((self.id.to_string(),))?;
406        Ok(res)
407    }
408
409    #[getter]
410    pub fn name(&self) -> &str {
411        &self.name
412    }
413
414    #[getter]
415    pub fn tenant(&self) -> &str {
416        &self.tenant
417    }
418}
419
420#[non_exhaustive]
421#[derive(Validate, Serialize)]
422#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
423pub struct ListDatabasesRequest {
424    pub tenant_id: String,
425    pub limit: Option<u32>,
426    pub offset: u32,
427}
428
429impl ListDatabasesRequest {
430    pub fn try_new(
431        tenant_id: String,
432        limit: Option<u32>,
433        offset: u32,
434    ) -> Result<Self, ChromaValidationError> {
435        let request = Self {
436            tenant_id,
437            limit,
438            offset,
439        };
440        request.validate().map_err(ChromaValidationError::from)?;
441        Ok(request)
442    }
443}
444
445pub type ListDatabasesResponse = Vec<Database>;
446
447#[derive(Debug, Error)]
448pub enum ListDatabasesError {
449    #[error(transparent)]
450    Internal(#[from] Box<dyn ChromaError>),
451    #[error("Invalid database id [{0}]")]
452    InvalidID(String),
453}
454
455impl ChromaError for ListDatabasesError {
456    fn code(&self) -> ErrorCodes {
457        match self {
458            ListDatabasesError::Internal(status) => status.code(),
459            ListDatabasesError::InvalidID(_) => ErrorCodes::InvalidArgument,
460        }
461    }
462}
463
464#[non_exhaustive]
465#[derive(Validate, Serialize)]
466#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
467pub struct GetDatabaseRequest {
468    pub tenant_id: String,
469    pub database_name: String,
470}
471
472impl GetDatabaseRequest {
473    pub fn try_new(
474        tenant_id: String,
475        database_name: String,
476    ) -> Result<Self, ChromaValidationError> {
477        let request = Self {
478            tenant_id,
479            database_name,
480        };
481        request.validate().map_err(ChromaValidationError::from)?;
482        Ok(request)
483    }
484}
485
486pub type GetDatabaseResponse = Database;
487
488#[derive(Error, Debug)]
489pub enum GetDatabaseError {
490    #[error(transparent)]
491    Internal(#[from] Box<dyn ChromaError>),
492    #[error("Invalid database id [{0}]")]
493    InvalidID(String),
494    #[error("Database [{0}] not found. Are you sure it exists?")]
495    NotFound(String),
496}
497
498impl ChromaError for GetDatabaseError {
499    fn code(&self) -> ErrorCodes {
500        match self {
501            GetDatabaseError::Internal(err) => err.code(),
502            GetDatabaseError::InvalidID(_) => ErrorCodes::InvalidArgument,
503            GetDatabaseError::NotFound(_) => ErrorCodes::NotFound,
504        }
505    }
506}
507
508#[non_exhaustive]
509#[derive(Validate, Serialize)]
510#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
511pub struct DeleteDatabaseRequest {
512    pub tenant_id: String,
513    pub database_name: String,
514}
515
516impl DeleteDatabaseRequest {
517    pub fn try_new(
518        tenant_id: String,
519        database_name: String,
520    ) -> Result<Self, ChromaValidationError> {
521        let request = Self {
522            tenant_id,
523            database_name,
524        };
525        request.validate().map_err(ChromaValidationError::from)?;
526        Ok(request)
527    }
528}
529
530#[derive(Serialize)]
531#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
532pub struct DeleteDatabaseResponse {}
533
534#[derive(Debug, Error)]
535pub enum DeleteDatabaseError {
536    #[error(transparent)]
537    Internal(#[from] Box<dyn ChromaError>),
538    #[error("Invalid database id [{0}]")]
539    InvalidID(String),
540    #[error("Database [{0}] not found")]
541    NotFound(String),
542}
543
544impl ChromaError for DeleteDatabaseError {
545    fn code(&self) -> ErrorCodes {
546        match self {
547            DeleteDatabaseError::Internal(err) => err.code(),
548            DeleteDatabaseError::InvalidID(_) => ErrorCodes::InvalidArgument,
549            DeleteDatabaseError::NotFound(_) => ErrorCodes::NotFound,
550        }
551    }
552}
553
554#[derive(Debug, Error)]
555pub enum FinishDatabaseDeletionError {
556    #[error(transparent)]
557    Internal(#[from] Box<dyn ChromaError>),
558}
559
560impl ChromaError for FinishDatabaseDeletionError {
561    fn code(&self) -> ErrorCodes {
562        match self {
563            FinishDatabaseDeletionError::Internal(err) => err.code(),
564        }
565    }
566}
567
568#[non_exhaustive]
569#[derive(Validate, Debug, Serialize)]
570#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
571pub struct ListCollectionsRequest {
572    pub tenant_id: String,
573    pub database_name: String,
574    pub limit: Option<u32>,
575    pub offset: u32,
576}
577
578impl ListCollectionsRequest {
579    pub fn try_new(
580        tenant_id: String,
581        database_name: String,
582        limit: Option<u32>,
583        offset: u32,
584    ) -> Result<Self, ChromaValidationError> {
585        let request = Self {
586            tenant_id,
587            database_name,
588            limit,
589            offset,
590        };
591        request.validate().map_err(ChromaValidationError::from)?;
592        Ok(request)
593    }
594}
595
596pub type ListCollectionsResponse = Vec<Collection>;
597
598#[non_exhaustive]
599#[derive(Validate, Serialize)]
600#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
601pub struct CountCollectionsRequest {
602    pub tenant_id: String,
603    pub database_name: String,
604}
605
606impl CountCollectionsRequest {
607    pub fn try_new(
608        tenant_id: String,
609        database_name: String,
610    ) -> Result<Self, ChromaValidationError> {
611        let request = Self {
612            tenant_id,
613            database_name,
614        };
615        request.validate().map_err(ChromaValidationError::from)?;
616        Ok(request)
617    }
618}
619
620pub type CountCollectionsResponse = u32;
621
622#[non_exhaustive]
623#[derive(Validate, Clone, Serialize)]
624#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
625pub struct GetCollectionRequest {
626    pub tenant_id: String,
627    pub database_name: String,
628    pub collection_name: String,
629}
630
631impl GetCollectionRequest {
632    pub fn try_new(
633        tenant_id: String,
634        database_name: String,
635        collection_name: String,
636    ) -> Result<Self, ChromaValidationError> {
637        let request = Self {
638            tenant_id,
639            database_name,
640            collection_name,
641        };
642        request.validate().map_err(ChromaValidationError::from)?;
643        Ok(request)
644    }
645}
646
647pub type GetCollectionResponse = Collection;
648
649#[derive(Debug, Error)]
650pub enum GetCollectionError {
651    #[error("Failed to reconcile schema: {0}")]
652    InvalidSchema(#[from] SchemaError),
653    #[error(transparent)]
654    Internal(#[from] Box<dyn ChromaError>),
655    #[error("Collection [{0}] does not exist")]
656    NotFound(String),
657}
658
659impl ChromaError for GetCollectionError {
660    fn code(&self) -> ErrorCodes {
661        match self {
662            GetCollectionError::InvalidSchema(e) => e.code(),
663            GetCollectionError::Internal(err) => err.code(),
664            GetCollectionError::NotFound(_) => ErrorCodes::NotFound,
665        }
666    }
667}
668
669#[non_exhaustive]
670#[derive(Clone, Debug, Validate, Serialize)]
671#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
672pub struct CreateCollectionRequest {
673    pub tenant_id: String,
674    pub database_name: String,
675    #[validate(custom(function = "validate_name"))]
676    pub name: String,
677    #[validate(custom(function = "validate_optional_metadata"))]
678    pub metadata: Option<Metadata>,
679    pub configuration: Option<InternalCollectionConfiguration>,
680    #[validate(custom(function = "validate_schema"))]
681    pub schema: Option<Schema>,
682    pub get_or_create: bool,
683}
684
685impl CreateCollectionRequest {
686    pub fn try_new(
687        tenant_id: String,
688        database_name: String,
689        name: String,
690        metadata: Option<Metadata>,
691        configuration: Option<InternalCollectionConfiguration>,
692        schema: Option<Schema>,
693        get_or_create: bool,
694    ) -> Result<Self, ChromaValidationError> {
695        let request = Self {
696            tenant_id,
697            database_name,
698            name,
699            metadata,
700            configuration,
701            schema,
702            get_or_create,
703        };
704        request.validate().map_err(ChromaValidationError::from)?;
705        Ok(request)
706    }
707}
708
709pub type CreateCollectionResponse = Collection;
710
711#[derive(Debug, Error)]
712pub enum CreateCollectionError {
713    #[error("Invalid HNSW parameters: {0}")]
714    InvalidHnswParameters(#[from] HnswParametersFromSegmentError),
715    #[error("Could not parse config: {0}")]
716    InvalidConfig(#[from] CollectionConfigurationToInternalConfigurationError),
717    #[error("Invalid Spann parameters: {0}")]
718    InvalidSpannParameters(#[from] DistributedSpannParametersFromSegmentError),
719    #[error("Collection [{0}] already exists")]
720    AlreadyExists(String),
721    #[error("Database [{0}] does not exist")]
722    DatabaseNotFound(String),
723    #[error("Could not fetch collections: {0}")]
724    Get(#[from] GetCollectionsError),
725    #[error("Could not deserialize configuration: {0}")]
726    Configuration(serde_json::Error),
727    #[error("Could not serialize schema: {0}")]
728    Schema(#[source] SchemaError),
729    #[error(transparent)]
730    Internal(#[from] Box<dyn ChromaError>),
731    #[error("The operation was aborted, {0}")]
732    Aborted(String),
733    #[error("SPANN is still in development. Not allowed to created spann indexes")]
734    SpannNotImplemented,
735    #[error("HNSW is not supported on this platform")]
736    HnswNotSupported,
737    #[error("Failed to parse db id")]
738    DatabaseIdParseError,
739    #[error("Failed to reconcile schema: {0}")]
740    InvalidSchema(#[source] SchemaError),
741}
742
743impl ChromaError for CreateCollectionError {
744    fn code(&self) -> ErrorCodes {
745        match self {
746            CreateCollectionError::InvalidHnswParameters(_) => ErrorCodes::InvalidArgument,
747            CreateCollectionError::InvalidConfig(_) => ErrorCodes::InvalidArgument,
748            CreateCollectionError::InvalidSpannParameters(_) => ErrorCodes::InvalidArgument,
749            CreateCollectionError::AlreadyExists(_) => ErrorCodes::AlreadyExists,
750            CreateCollectionError::DatabaseNotFound(_) => ErrorCodes::InvalidArgument,
751            CreateCollectionError::Get(err) => err.code(),
752            CreateCollectionError::Configuration(_) => ErrorCodes::Internal,
753            CreateCollectionError::Internal(err) => err.code(),
754            CreateCollectionError::Aborted(_) => ErrorCodes::Aborted,
755            CreateCollectionError::SpannNotImplemented => ErrorCodes::InvalidArgument,
756            CreateCollectionError::HnswNotSupported => ErrorCodes::InvalidArgument,
757            CreateCollectionError::DatabaseIdParseError => ErrorCodes::Internal,
758            CreateCollectionError::InvalidSchema(e) => e.code(),
759            CreateCollectionError::Schema(e) => e.code(),
760        }
761    }
762}
763
764#[derive(Debug, Error)]
765pub enum CountCollectionsError {
766    #[error("Internal error in getting count")]
767    Internal,
768}
769
770impl ChromaError for CountCollectionsError {
771    fn code(&self) -> ErrorCodes {
772        match self {
773            CountCollectionsError::Internal => ErrorCodes::Internal,
774        }
775    }
776}
777
778#[derive(Debug, Error)]
779pub enum GetCollectionsError {
780    #[error("Failed to reconcile schema: {0}")]
781    InvalidSchema(#[from] SchemaError),
782    #[error(transparent)]
783    Internal(#[from] Box<dyn ChromaError>),
784    #[error("Could not deserialize configuration")]
785    Configuration(#[from] serde_json::Error),
786    #[error("Could not deserialize collection ID")]
787    CollectionId(#[from] uuid::Error),
788    #[error("Could not deserialize database ID")]
789    DatabaseId,
790}
791
792impl ChromaError for GetCollectionsError {
793    fn code(&self) -> ErrorCodes {
794        match self {
795            GetCollectionsError::InvalidSchema(e) => e.code(),
796            GetCollectionsError::Internal(err) => err.code(),
797            GetCollectionsError::Configuration(_) => ErrorCodes::Internal,
798            GetCollectionsError::CollectionId(_) => ErrorCodes::Internal,
799            GetCollectionsError::DatabaseId => ErrorCodes::Internal,
800        }
801    }
802}
803
804#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
805#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
806pub struct ChromaResourceName {
807    pub tenant_resource_name: String,
808    pub database_name: String,
809    pub collection_name: String,
810}
811#[non_exhaustive]
812#[derive(Clone, Serialize)]
813#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
814pub struct GetCollectionByCrnRequest {
815    pub parsed_crn: ChromaResourceName,
816}
817
818impl GetCollectionByCrnRequest {
819    pub fn try_new(crn: String) -> Result<Self, ChromaValidationError> {
820        let parsed_crn = parse_and_validate_crn(&crn)?;
821        Ok(Self { parsed_crn })
822    }
823}
824
825fn parse_and_validate_crn(crn: &str) -> Result<ChromaResourceName, ChromaValidationError> {
826    let mut parts = crn.splitn(4, ':');
827    if let (Some(p1), Some(p2), Some(p3), None) =
828        (parts.next(), parts.next(), parts.next(), parts.next())
829    {
830        if !p1.is_empty() && !p2.is_empty() && !p3.is_empty() {
831            return Ok(ChromaResourceName {
832                tenant_resource_name: p1.to_string(),
833                database_name: p2.to_string(),
834                collection_name: p3.to_string(),
835            });
836        }
837    }
838    let mut err = ValidationError::new("invalid_crn_format");
839    err.message = Some(
840        "CRN must be in the format <tenant_resource_name>:<database_name>:<collection_name> with non-empty parts"
841            .into(),
842    );
843    Err(ChromaValidationError::from(("crn", err)))
844}
845
846pub type GetCollectionByCrnResponse = Collection;
847
848#[derive(Debug, Error)]
849pub enum GetCollectionByCrnError {
850    #[error("Failed to reconcile schema: {0}")]
851    InvalidSchema(#[from] SchemaError),
852    #[error(transparent)]
853    Internal(#[from] Box<dyn ChromaError>),
854    #[error("Collection [{0}] does not exist")]
855    NotFound(String),
856}
857
858impl ChromaError for GetCollectionByCrnError {
859    fn code(&self) -> ErrorCodes {
860        match self {
861            GetCollectionByCrnError::InvalidSchema(e) => e.code(),
862            GetCollectionByCrnError::Internal(err) => err.code(),
863            GetCollectionByCrnError::NotFound(_) => ErrorCodes::NotFound,
864        }
865    }
866}
867
868#[derive(Clone, Deserialize, Serialize, Debug)]
869#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
870pub enum CollectionMetadataUpdate {
871    ResetMetadata,
872    UpdateMetadata(UpdateMetadata),
873}
874
875#[non_exhaustive]
876#[derive(Clone, Validate, Debug, Serialize)]
877#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
878pub struct UpdateCollectionRequest {
879    pub collection_id: CollectionUuid,
880    #[validate(custom(function = "validate_name"))]
881    pub new_name: Option<String>,
882    #[validate(custom(function = "validate_non_empty_collection_update_metadata"))]
883    pub new_metadata: Option<CollectionMetadataUpdate>,
884    pub new_configuration: Option<InternalUpdateCollectionConfiguration>,
885}
886
887impl UpdateCollectionRequest {
888    pub fn try_new(
889        collection_id: CollectionUuid,
890        new_name: Option<String>,
891        new_metadata: Option<CollectionMetadataUpdate>,
892        new_configuration: Option<InternalUpdateCollectionConfiguration>,
893    ) -> Result<Self, ChromaValidationError> {
894        let request = Self {
895            collection_id,
896            new_name,
897            new_metadata,
898            new_configuration,
899        };
900        request.validate().map_err(ChromaValidationError::from)?;
901        Ok(request)
902    }
903}
904
905#[derive(Serialize)]
906#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
907pub struct UpdateCollectionResponse {}
908
909#[derive(Error, Debug)]
910pub enum UpdateCollectionError {
911    #[error("Collection [{0}] does not exist")]
912    NotFound(String),
913    #[error("Metadata reset unsupported")]
914    MetadataResetUnsupported,
915    #[error("Could not serialize configuration")]
916    Configuration(#[from] serde_json::Error),
917    #[error(transparent)]
918    Internal(#[from] Box<dyn ChromaError>),
919    #[error("Could not parse config: {0}")]
920    InvalidConfig(#[from] CollectionConfigurationToInternalConfigurationError),
921    #[error("SPANN is still in development. Not allowed to created spann indexes")]
922    SpannNotImplemented,
923}
924
925impl ChromaError for UpdateCollectionError {
926    fn code(&self) -> ErrorCodes {
927        match self {
928            UpdateCollectionError::NotFound(_) => ErrorCodes::NotFound,
929            UpdateCollectionError::MetadataResetUnsupported => ErrorCodes::InvalidArgument,
930            UpdateCollectionError::Configuration(_) => ErrorCodes::Internal,
931            UpdateCollectionError::Internal(err) => err.code(),
932            UpdateCollectionError::InvalidConfig(_) => ErrorCodes::InvalidArgument,
933            UpdateCollectionError::SpannNotImplemented => ErrorCodes::InvalidArgument,
934        }
935    }
936}
937
938#[non_exhaustive]
939#[derive(Clone, Validate, Serialize)]
940#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
941pub struct DeleteCollectionRequest {
942    pub tenant_id: String,
943    pub database_name: String,
944    pub collection_name: String,
945}
946
947impl DeleteCollectionRequest {
948    pub fn try_new(
949        tenant_id: String,
950        database_name: String,
951        collection_name: String,
952    ) -> Result<Self, ChromaValidationError> {
953        let request = Self {
954            tenant_id,
955            database_name,
956            collection_name,
957        };
958        request.validate().map_err(ChromaValidationError::from)?;
959        Ok(request)
960    }
961}
962
963#[derive(Serialize)]
964#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
965pub struct DeleteCollectionResponse {}
966
967#[derive(Error, Debug)]
968pub enum DeleteCollectionError {
969    #[error("Collection [{0}] does not exist")]
970    NotFound(String),
971    #[error(transparent)]
972    Validation(#[from] ChromaValidationError),
973    #[error(transparent)]
974    Get(#[from] GetCollectionError),
975    #[error(transparent)]
976    Internal(#[from] Box<dyn ChromaError>),
977}
978
979impl ChromaError for DeleteCollectionError {
980    fn code(&self) -> ErrorCodes {
981        match self {
982            DeleteCollectionError::Validation(err) => err.code(),
983            DeleteCollectionError::NotFound(_) => ErrorCodes::NotFound,
984            DeleteCollectionError::Get(err) => err.code(),
985            DeleteCollectionError::Internal(err) => err.code(),
986        }
987    }
988}
989
990#[non_exhaustive]
991#[derive(Clone, Validate, Serialize)]
992#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
993pub struct ForkCollectionRequest {
994    pub tenant_id: String,
995    pub database_name: String,
996    pub source_collection_id: CollectionUuid,
997    pub target_collection_name: String,
998}
999
1000impl ForkCollectionRequest {
1001    pub fn try_new(
1002        tenant_id: String,
1003        database_name: String,
1004        source_collection_id: CollectionUuid,
1005        target_collection_name: String,
1006    ) -> Result<Self, ChromaValidationError> {
1007        let request = Self {
1008            tenant_id,
1009            database_name,
1010            source_collection_id,
1011            target_collection_name,
1012        };
1013        request.validate().map_err(ChromaValidationError::from)?;
1014        Ok(request)
1015    }
1016}
1017
1018pub type ForkCollectionResponse = Collection;
1019
1020#[derive(Clone, Debug)]
1021pub struct ForkLogsResponse {
1022    pub compaction_offset: u64,
1023    pub enumeration_offset: u64,
1024}
1025
1026#[derive(Error, Debug)]
1027pub enum ForkCollectionError {
1028    #[error("Collection [{0}] already exists")]
1029    AlreadyExists(String),
1030    #[error("Failed to convert proto collection")]
1031    CollectionConversionError(#[from] CollectionConversionError),
1032    #[error("Duplicate segment")]
1033    DuplicateSegment,
1034    #[error("Missing field: [{0}]")]
1035    Field(String),
1036    #[error("Collection forking is unsupported for local chroma")]
1037    Local,
1038    #[error(transparent)]
1039    Internal(#[from] Box<dyn ChromaError>),
1040    #[error("Collection [{0}] does not exist")]
1041    NotFound(String),
1042    #[error("Failed to convert proto segment")]
1043    SegmentConversionError(#[from] SegmentConversionError),
1044    #[error("Failed to reconcile schema: {0}")]
1045    InvalidSchema(#[from] SchemaError),
1046}
1047
1048impl ChromaError for ForkCollectionError {
1049    fn code(&self) -> ErrorCodes {
1050        match self {
1051            ForkCollectionError::NotFound(_) => ErrorCodes::NotFound,
1052            ForkCollectionError::AlreadyExists(_) => ErrorCodes::AlreadyExists,
1053            ForkCollectionError::CollectionConversionError(e) => e.code(),
1054            ForkCollectionError::DuplicateSegment => ErrorCodes::Internal,
1055            ForkCollectionError::Field(_) => ErrorCodes::FailedPrecondition,
1056            ForkCollectionError::Local => ErrorCodes::Unimplemented,
1057            ForkCollectionError::Internal(e) => e.code(),
1058            ForkCollectionError::SegmentConversionError(e) => e.code(),
1059            ForkCollectionError::InvalidSchema(e) => e.code(),
1060        }
1061    }
1062}
1063
1064#[derive(Debug, Error)]
1065pub enum CountForksError {
1066    #[error("Collection [{0}] does not exist")]
1067    NotFound(String),
1068    #[error(transparent)]
1069    Internal(#[from] Box<dyn ChromaError>),
1070    #[error("Count forks is unsupported for local chroma")]
1071    Local,
1072}
1073
1074impl ChromaError for CountForksError {
1075    fn code(&self) -> ErrorCodes {
1076        match self {
1077            CountForksError::NotFound(_) => ErrorCodes::NotFound,
1078            CountForksError::Internal(chroma_error) => chroma_error.code(),
1079            CountForksError::Local => ErrorCodes::Unimplemented,
1080        }
1081    }
1082}
1083
1084#[derive(Debug, Error)]
1085pub enum GetCollectionSizeError {
1086    #[error(transparent)]
1087    Internal(#[from] Box<dyn ChromaError>),
1088    #[error("Collection [{0}] does not exist")]
1089    NotFound(String),
1090}
1091
1092impl ChromaError for GetCollectionSizeError {
1093    fn code(&self) -> ErrorCodes {
1094        match self {
1095            GetCollectionSizeError::Internal(err) => err.code(),
1096            GetCollectionSizeError::NotFound(_) => ErrorCodes::NotFound,
1097        }
1098    }
1099}
1100
1101#[derive(Error, Debug)]
1102pub enum ListCollectionVersionsError {
1103    #[error(transparent)]
1104    Internal(#[from] Box<dyn ChromaError>),
1105    #[error("Collection [{0}] does not exist")]
1106    NotFound(String),
1107}
1108
1109impl ChromaError for ListCollectionVersionsError {
1110    fn code(&self) -> ErrorCodes {
1111        match self {
1112            ListCollectionVersionsError::Internal(err) => err.code(),
1113            ListCollectionVersionsError::NotFound(_) => ErrorCodes::NotFound,
1114        }
1115    }
1116}
1117
1118////////////////////////// Metadata Key Constants //////////////////////////
1119
1120pub const CHROMA_KEY: &str = "chroma:";
1121pub const CHROMA_DOCUMENT_KEY: &str = "chroma:document";
1122pub const CHROMA_URI_KEY: &str = "chroma:uri";
1123
1124////////////////////////// AddCollectionRecords //////////////////////////
1125
1126#[derive(Serialize, Deserialize, Debug, Clone)]
1127#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1128pub struct AddCollectionRecordsPayload {
1129    pub ids: Vec<String>,
1130    pub embeddings: EmbeddingsPayload,
1131    pub documents: Option<Vec<Option<String>>>,
1132    pub uris: Option<Vec<Option<String>>>,
1133    pub metadatas: Option<Vec<Option<Metadata>>>,
1134}
1135
1136impl AddCollectionRecordsPayload {
1137    pub fn new(
1138        ids: Vec<String>,
1139        embeddings: Vec<Vec<f32>>,
1140        documents: Option<Vec<Option<String>>>,
1141        uris: Option<Vec<Option<String>>>,
1142        metadatas: Option<Vec<Option<Metadata>>>,
1143    ) -> Self {
1144        Self {
1145            ids,
1146            embeddings: EmbeddingsPayload::JsonArrays(embeddings),
1147            documents,
1148            uris,
1149            metadatas,
1150        }
1151    }
1152}
1153
1154#[non_exhaustive]
1155#[derive(Debug, Clone, Validate, Serialize)]
1156#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1157pub struct AddCollectionRecordsRequest {
1158    pub tenant_id: String,
1159    pub database_name: String,
1160    pub collection_id: CollectionUuid,
1161    pub ids: Vec<String>,
1162    #[validate(custom(function = "validate_embeddings"))]
1163    pub embeddings: Vec<Vec<f32>>,
1164    pub documents: Option<Vec<Option<String>>>,
1165    pub uris: Option<Vec<Option<String>>>,
1166    #[validate(custom(function = "validate_metadata_vec"))]
1167    pub metadatas: Option<Vec<Option<Metadata>>>,
1168}
1169
1170impl AddCollectionRecordsRequest {
1171    #[allow(clippy::too_many_arguments)]
1172    pub fn try_new(
1173        tenant_id: String,
1174        database_name: String,
1175        collection_id: CollectionUuid,
1176        ids: Vec<String>,
1177        embeddings: Vec<Vec<f32>>,
1178        documents: Option<Vec<Option<String>>>,
1179        uris: Option<Vec<Option<String>>>,
1180        metadatas: Option<Vec<Option<Metadata>>>,
1181    ) -> Result<Self, ChromaValidationError> {
1182        let request = Self {
1183            tenant_id,
1184            database_name,
1185            collection_id,
1186            ids,
1187            embeddings,
1188            documents,
1189            uris,
1190            metadatas,
1191        };
1192        request.validate().map_err(ChromaValidationError::from)?;
1193        Ok(request)
1194    }
1195
1196    pub fn into_payload(self) -> AddCollectionRecordsPayload {
1197        AddCollectionRecordsPayload {
1198            ids: self.ids,
1199            embeddings: EmbeddingsPayload::JsonArrays(self.embeddings),
1200            documents: self.documents,
1201            uris: self.uris,
1202            metadatas: self.metadatas,
1203        }
1204    }
1205}
1206
1207fn validate_embeddings(embeddings: &[Vec<f32>]) -> Result<(), ValidationError> {
1208    if embeddings.iter().any(|e| e.is_empty()) {
1209        return Err(ValidationError::new("embedding_minimum_dimensions")
1210            .with_message("Each embedding must have at least 1 dimension".into()));
1211    }
1212    Ok(())
1213}
1214
1215#[derive(Serialize, Default, Deserialize)]
1216#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1217pub struct AddCollectionRecordsResponse {}
1218
1219#[derive(Error, Debug)]
1220pub enum AddCollectionRecordsError {
1221    #[error("Failed to get collection: {0}")]
1222    Collection(#[from] GetCollectionError),
1223    #[error("Backoff and retry")]
1224    Backoff,
1225    #[error(transparent)]
1226    Other(#[from] Box<dyn ChromaError>),
1227}
1228
1229impl ChromaError for AddCollectionRecordsError {
1230    fn code(&self) -> ErrorCodes {
1231        match self {
1232            AddCollectionRecordsError::Collection(err) => err.code(),
1233            AddCollectionRecordsError::Backoff => ErrorCodes::ResourceExhausted,
1234            AddCollectionRecordsError::Other(err) => err.code(),
1235        }
1236    }
1237}
1238
1239////////////////////////// UpdateCollectionRecords //////////////////////////
1240
1241#[derive(Deserialize, Debug, Clone, Serialize)]
1242#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1243pub struct UpdateCollectionRecordsPayload {
1244    pub ids: Vec<String>,
1245    pub embeddings: Option<UpdateEmbeddingsPayload>,
1246    pub documents: Option<Vec<Option<String>>>,
1247    pub uris: Option<Vec<Option<String>>>,
1248    pub metadatas: Option<Vec<Option<UpdateMetadata>>>,
1249}
1250
1251#[non_exhaustive]
1252#[derive(Debug, Clone, Validate, Serialize)]
1253#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1254pub struct UpdateCollectionRecordsRequest {
1255    pub tenant_id: String,
1256    pub database_name: String,
1257    pub collection_id: CollectionUuid,
1258    pub ids: Vec<String>,
1259    pub embeddings: Option<Vec<Option<Vec<f32>>>>,
1260    pub documents: Option<Vec<Option<String>>>,
1261    pub uris: Option<Vec<Option<String>>>,
1262    #[validate(custom(function = "validate_update_metadata_vec"))]
1263    pub metadatas: Option<Vec<Option<UpdateMetadata>>>,
1264}
1265
1266impl UpdateCollectionRecordsRequest {
1267    #[allow(clippy::too_many_arguments)]
1268    pub fn try_new(
1269        tenant_id: String,
1270        database_name: String,
1271        collection_id: CollectionUuid,
1272        ids: Vec<String>,
1273        embeddings: Option<Vec<Option<Vec<f32>>>>,
1274        documents: Option<Vec<Option<String>>>,
1275        uris: Option<Vec<Option<String>>>,
1276        metadatas: Option<Vec<Option<UpdateMetadata>>>,
1277    ) -> Result<Self, ChromaValidationError> {
1278        let request = Self {
1279            tenant_id,
1280            database_name,
1281            collection_id,
1282            ids,
1283            embeddings,
1284            documents,
1285            uris,
1286            metadatas,
1287        };
1288        request.validate().map_err(ChromaValidationError::from)?;
1289        Ok(request)
1290    }
1291
1292    pub fn into_payload(self) -> UpdateCollectionRecordsPayload {
1293        UpdateCollectionRecordsPayload {
1294            ids: self.ids,
1295            embeddings: self.embeddings.map(UpdateEmbeddingsPayload::JsonArrays),
1296            documents: self.documents,
1297            uris: self.uris,
1298            metadatas: self.metadatas,
1299        }
1300    }
1301}
1302
1303#[derive(Serialize, Deserialize)]
1304#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1305pub struct UpdateCollectionRecordsResponse {}
1306
1307#[derive(Error, Debug)]
1308pub enum UpdateCollectionRecordsError {
1309    #[error("Backoff and retry")]
1310    Backoff,
1311    #[error(transparent)]
1312    Other(#[from] Box<dyn ChromaError>),
1313}
1314
1315impl ChromaError for UpdateCollectionRecordsError {
1316    fn code(&self) -> ErrorCodes {
1317        match self {
1318            UpdateCollectionRecordsError::Backoff => ErrorCodes::ResourceExhausted,
1319            UpdateCollectionRecordsError::Other(err) => err.code(),
1320        }
1321    }
1322}
1323
1324////////////////////////// UpsertCollectionRecords //////////////////////////
1325
1326#[derive(Deserialize, Debug, Clone, Serialize)]
1327#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1328pub struct UpsertCollectionRecordsPayload {
1329    pub ids: Vec<String>,
1330    pub embeddings: EmbeddingsPayload,
1331    pub documents: Option<Vec<Option<String>>>,
1332    pub uris: Option<Vec<Option<String>>>,
1333    pub metadatas: Option<Vec<Option<UpdateMetadata>>>,
1334}
1335
1336#[non_exhaustive]
1337#[derive(Debug, Clone, Validate, Serialize)]
1338#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1339pub struct UpsertCollectionRecordsRequest {
1340    pub tenant_id: String,
1341    pub database_name: String,
1342    pub collection_id: CollectionUuid,
1343    pub ids: Vec<String>,
1344    #[validate(custom(function = "validate_embeddings"))]
1345    pub embeddings: Vec<Vec<f32>>,
1346    pub documents: Option<Vec<Option<String>>>,
1347    pub uris: Option<Vec<Option<String>>>,
1348    #[validate(custom(function = "validate_update_metadata_vec"))]
1349    pub metadatas: Option<Vec<Option<UpdateMetadata>>>,
1350}
1351
1352impl UpsertCollectionRecordsRequest {
1353    #[allow(clippy::too_many_arguments)]
1354    pub fn try_new(
1355        tenant_id: String,
1356        database_name: String,
1357        collection_id: CollectionUuid,
1358        ids: Vec<String>,
1359        embeddings: Vec<Vec<f32>>,
1360        documents: Option<Vec<Option<String>>>,
1361        uris: Option<Vec<Option<String>>>,
1362        metadatas: Option<Vec<Option<UpdateMetadata>>>,
1363    ) -> Result<Self, ChromaValidationError> {
1364        let request = Self {
1365            tenant_id,
1366            database_name,
1367            collection_id,
1368            ids,
1369            embeddings,
1370            documents,
1371            uris,
1372            metadatas,
1373        };
1374        request.validate().map_err(ChromaValidationError::from)?;
1375        Ok(request)
1376    }
1377
1378    pub fn into_payload(self) -> UpsertCollectionRecordsPayload {
1379        UpsertCollectionRecordsPayload {
1380            ids: self.ids.clone(),
1381            embeddings: EmbeddingsPayload::JsonArrays(self.embeddings.clone()),
1382            documents: self.documents.clone(),
1383            uris: self.uris.clone(),
1384            metadatas: self.metadatas.clone(),
1385        }
1386    }
1387}
1388
1389#[derive(Serialize, Deserialize)]
1390#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1391pub struct UpsertCollectionRecordsResponse {}
1392
1393#[derive(Error, Debug)]
1394pub enum UpsertCollectionRecordsError {
1395    #[error("Backoff and retry")]
1396    Backoff,
1397    #[error(transparent)]
1398    Other(#[from] Box<dyn ChromaError>),
1399}
1400
1401impl ChromaError for UpsertCollectionRecordsError {
1402    fn code(&self) -> ErrorCodes {
1403        match self {
1404            UpsertCollectionRecordsError::Backoff => ErrorCodes::ResourceExhausted,
1405            UpsertCollectionRecordsError::Other(err) => err.code(),
1406        }
1407    }
1408}
1409
1410////////////////////////// DeleteCollectionRecords //////////////////////////
1411
1412#[derive(Deserialize, Debug, Clone, Serialize)]
1413#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1414pub struct DeleteCollectionRecordsPayload {
1415    pub ids: Option<Vec<String>>,
1416    #[serde(flatten)]
1417    pub where_fields: RawWhereFields,
1418}
1419
1420#[non_exhaustive]
1421#[derive(Debug, Clone, Validate, Serialize)]
1422#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1423pub struct DeleteCollectionRecordsRequest {
1424    pub tenant_id: String,
1425    pub database_name: String,
1426    pub collection_id: CollectionUuid,
1427    pub ids: Option<Vec<String>>,
1428    pub r#where: Option<Where>,
1429}
1430
1431impl DeleteCollectionRecordsRequest {
1432    pub fn try_new(
1433        tenant_id: String,
1434        database_name: String,
1435        collection_id: CollectionUuid,
1436        ids: Option<Vec<String>>,
1437        r#where: Option<Where>,
1438    ) -> Result<Self, ChromaValidationError> {
1439        if ids.as_ref().map(|ids| ids.is_empty()).unwrap_or(false) && r#where.is_none() {
1440            return Err(ChromaValidationError::from((
1441                ("ids, where"),
1442                ValidationError::new("filter")
1443                    .with_message("Either ids or where must be specified".into()),
1444            )));
1445        }
1446
1447        let request = Self {
1448            tenant_id,
1449            database_name,
1450            collection_id,
1451            ids,
1452            r#where,
1453        };
1454        request.validate().map_err(ChromaValidationError::from)?;
1455        Ok(request)
1456    }
1457
1458    pub fn into_payload(self) -> Result<DeleteCollectionRecordsPayload, WhereError> {
1459        let where_fields = if let Some(r#where) = self.r#where.as_ref() {
1460            RawWhereFields::from_json_str(Some(&serde_json::to_string(r#where)?), None)?
1461        } else {
1462            RawWhereFields::default()
1463        };
1464        Ok(DeleteCollectionRecordsPayload {
1465            ids: self.ids.clone(),
1466            where_fields,
1467        })
1468    }
1469}
1470
1471#[derive(Serialize, Deserialize)]
1472#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1473pub struct DeleteCollectionRecordsResponse {}
1474
1475#[derive(Error, Debug)]
1476pub enum DeleteCollectionRecordsError {
1477    #[error("Failed to resolve records for deletion: {0}")]
1478    Get(#[from] ExecutorError),
1479    #[error("Backoff and retry")]
1480    Backoff,
1481    #[error(transparent)]
1482    Internal(#[from] Box<dyn ChromaError>),
1483}
1484
1485impl ChromaError for DeleteCollectionRecordsError {
1486    fn code(&self) -> ErrorCodes {
1487        match self {
1488            DeleteCollectionRecordsError::Get(err) => err.code(),
1489            DeleteCollectionRecordsError::Backoff => ErrorCodes::ResourceExhausted,
1490            DeleteCollectionRecordsError::Internal(err) => err.code(),
1491        }
1492    }
1493}
1494
1495////////////////////////// Include //////////////////////////
1496
1497#[derive(Error, Debug)]
1498#[error("Invalid include value: {0}")]
1499pub struct IncludeParsingError(String);
1500
1501impl ChromaError for IncludeParsingError {
1502    fn code(&self) -> ErrorCodes {
1503        ErrorCodes::InvalidArgument
1504    }
1505}
1506
1507#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1508#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1509pub enum Include {
1510    #[serde(rename = "distances")]
1511    Distance,
1512    #[serde(rename = "documents")]
1513    Document,
1514    #[serde(rename = "embeddings")]
1515    Embedding,
1516    #[serde(rename = "metadatas")]
1517    Metadata,
1518    #[serde(rename = "uris")]
1519    Uri,
1520}
1521
1522impl TryFrom<&str> for Include {
1523    type Error = IncludeParsingError;
1524
1525    fn try_from(value: &str) -> Result<Self, Self::Error> {
1526        match value {
1527            "distances" => Ok(Include::Distance),
1528            "documents" => Ok(Include::Document),
1529            "embeddings" => Ok(Include::Embedding),
1530            "metadatas" => Ok(Include::Metadata),
1531            "uris" => Ok(Include::Uri),
1532            _ => Err(IncludeParsingError(value.to_string())),
1533        }
1534    }
1535}
1536
1537#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
1538#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1539#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
1540pub struct IncludeList(pub Vec<Include>);
1541
1542impl IncludeList {
1543    pub fn empty() -> Self {
1544        Self(Vec::new())
1545    }
1546
1547    pub fn default_query() -> Self {
1548        Self(vec![
1549            Include::Document,
1550            Include::Metadata,
1551            Include::Distance,
1552        ])
1553    }
1554    pub fn default_get() -> Self {
1555        Self(vec![Include::Document, Include::Metadata])
1556    }
1557    pub fn all() -> Self {
1558        Self(vec![
1559            Include::Document,
1560            Include::Metadata,
1561            Include::Distance,
1562            Include::Embedding,
1563            Include::Uri,
1564        ])
1565    }
1566}
1567
1568impl TryFrom<Vec<String>> for IncludeList {
1569    type Error = IncludeParsingError;
1570
1571    fn try_from(value: Vec<String>) -> Result<Self, Self::Error> {
1572        let mut includes = Vec::new();
1573        for v in value {
1574            // "data" is only used by single node Chroma
1575            if v == "data" {
1576                includes.push(Include::Metadata);
1577                continue;
1578            }
1579
1580            includes.push(Include::try_from(v.as_str())?);
1581        }
1582        Ok(IncludeList(includes))
1583    }
1584}
1585
1586////////////////////////// Count //////////////////////////
1587
1588#[non_exhaustive]
1589#[derive(Clone, Deserialize, Serialize, Validate)]
1590pub struct CountRequest {
1591    pub tenant_id: String,
1592    pub database_name: String,
1593    pub collection_id: CollectionUuid,
1594}
1595
1596impl CountRequest {
1597    pub fn try_new(
1598        tenant_id: String,
1599        database_name: String,
1600        collection_id: CollectionUuid,
1601    ) -> Result<Self, ChromaValidationError> {
1602        let request = Self {
1603            tenant_id,
1604            database_name,
1605            collection_id,
1606        };
1607        request.validate().map_err(ChromaValidationError::from)?;
1608        Ok(request)
1609    }
1610}
1611
1612pub type CountResponse = u32;
1613
1614//////////////////////// Payload Err ////////////////////
1615
1616#[derive(Debug, thiserror::Error)]
1617pub enum WhereError {
1618    #[error("serialization: {0}")]
1619    Serialization(#[from] serde_json::Error),
1620    #[error("validation: {0}")]
1621    Validation(#[from] WhereValidationError),
1622}
1623
1624////////////////////////// Get //////////////////////////
1625
1626#[derive(Debug, Clone, Deserialize, Serialize)]
1627#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1628pub struct GetRequestPayload {
1629    pub ids: Option<Vec<String>>,
1630    #[serde(flatten)]
1631    pub where_fields: RawWhereFields,
1632    pub limit: Option<u32>,
1633    pub offset: Option<u32>,
1634    #[serde(default = "IncludeList::default_get")]
1635    pub include: IncludeList,
1636}
1637
1638#[non_exhaustive]
1639#[derive(Debug, Clone, Validate, Serialize)]
1640#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1641pub struct GetRequest {
1642    pub tenant_id: String,
1643    pub database_name: String,
1644    pub collection_id: CollectionUuid,
1645    pub ids: Option<Vec<String>>,
1646    pub r#where: Option<Where>,
1647    pub limit: Option<u32>,
1648    pub offset: u32,
1649    pub include: IncludeList,
1650}
1651
1652impl GetRequest {
1653    #[allow(clippy::too_many_arguments)]
1654    pub fn try_new(
1655        tenant_id: String,
1656        database_name: String,
1657        collection_id: CollectionUuid,
1658        ids: Option<Vec<String>>,
1659        r#where: Option<Where>,
1660        limit: Option<u32>,
1661        offset: u32,
1662        include: IncludeList,
1663    ) -> Result<Self, ChromaValidationError> {
1664        let request = Self {
1665            tenant_id,
1666            database_name,
1667            collection_id,
1668            ids,
1669            r#where,
1670            limit,
1671            offset,
1672            include,
1673        };
1674        request.validate().map_err(ChromaValidationError::from)?;
1675        Ok(request)
1676    }
1677
1678    pub fn into_payload(self) -> Result<GetRequestPayload, WhereError> {
1679        let where_fields = if let Some(r#where) = self.r#where.as_ref() {
1680            RawWhereFields::from_json_str(Some(&serde_json::to_string(r#where)?), None)?
1681        } else {
1682            RawWhereFields::default()
1683        };
1684        Ok(GetRequestPayload {
1685            ids: self.ids,
1686            where_fields,
1687            limit: self.limit,
1688            offset: Some(self.offset),
1689            include: self.include,
1690        })
1691    }
1692}
1693
1694#[derive(Clone, Deserialize, Serialize, Debug, Default)]
1695#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1696#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
1697pub struct GetResponse {
1698    pub ids: Vec<String>,
1699    pub embeddings: Option<Vec<Vec<f32>>>,
1700    pub documents: Option<Vec<Option<String>>>,
1701    pub uris: Option<Vec<Option<String>>>,
1702    // TODO(hammadb): Add metadata & include to the response
1703    pub metadatas: Option<Vec<Option<Metadata>>>,
1704    pub include: Vec<Include>,
1705}
1706
1707impl GetResponse {
1708    pub fn sort_by_ids(&mut self) {
1709        let mut indices: Vec<usize> = (0..self.ids.len()).collect();
1710        indices.sort_by(|&a, &b| self.ids[a].cmp(&self.ids[b]));
1711
1712        let sorted_ids = indices.iter().map(|&i| self.ids[i].clone()).collect();
1713        self.ids = sorted_ids;
1714
1715        if let Some(ref mut embeddings) = self.embeddings {
1716            let sorted_embeddings = indices.iter().map(|&i| embeddings[i].clone()).collect();
1717            *embeddings = sorted_embeddings;
1718        }
1719
1720        if let Some(ref mut documents) = self.documents {
1721            let sorted_docs = indices.iter().map(|&i| documents[i].clone()).collect();
1722            *documents = sorted_docs;
1723        }
1724
1725        if let Some(ref mut uris) = self.uris {
1726            let sorted_uris = indices.iter().map(|&i| uris[i].clone()).collect();
1727            *uris = sorted_uris;
1728        }
1729
1730        if let Some(ref mut metadatas) = self.metadatas {
1731            let sorted_metas = indices.iter().map(|&i| metadatas[i].clone()).collect();
1732            *metadatas = sorted_metas;
1733        }
1734    }
1735}
1736
1737#[cfg(feature = "pyo3")]
1738#[pyo3::pymethods]
1739impl GetResponse {
1740    #[getter]
1741    pub fn ids(&self) -> &Vec<String> {
1742        &self.ids
1743    }
1744
1745    #[getter]
1746    pub fn embeddings(&self) -> Option<Vec<Vec<f32>>> {
1747        self.embeddings.clone()
1748    }
1749
1750    #[getter]
1751    pub fn documents(&self) -> Option<Vec<Option<String>>> {
1752        self.documents.clone()
1753    }
1754
1755    #[getter]
1756    pub fn uris(&self) -> Option<Vec<Option<String>>> {
1757        self.uris.clone()
1758    }
1759
1760    #[getter]
1761    pub fn metadatas(&self) -> Option<Vec<Option<Metadata>>> {
1762        self.metadatas.clone()
1763    }
1764}
1765
1766impl From<(GetResult, IncludeList)> for GetResponse {
1767    fn from((result, IncludeList(include_vec)): (GetResult, IncludeList)) -> Self {
1768        let mut res = Self {
1769            ids: Vec::new(),
1770            embeddings: include_vec
1771                .contains(&Include::Embedding)
1772                .then_some(Vec::new()),
1773            documents: include_vec
1774                .contains(&Include::Document)
1775                .then_some(Vec::new()),
1776            uris: include_vec.contains(&Include::Uri).then_some(Vec::new()),
1777            metadatas: include_vec
1778                .contains(&Include::Metadata)
1779                .then_some(Vec::new()),
1780            include: include_vec,
1781        };
1782        for ProjectionRecord {
1783            id,
1784            document,
1785            embedding,
1786            mut metadata,
1787        } in result.result.records
1788        {
1789            res.ids.push(id);
1790            if let (Some(emb), Some(embeddings)) = (embedding, res.embeddings.as_mut()) {
1791                embeddings.push(emb);
1792            }
1793            if let Some(documents) = res.documents.as_mut() {
1794                documents.push(document);
1795            }
1796            let uri = metadata.as_mut().and_then(|meta| {
1797                meta.remove(CHROMA_URI_KEY).and_then(|v| {
1798                    if let crate::MetadataValue::Str(uri) = v {
1799                        Some(uri)
1800                    } else {
1801                        None
1802                    }
1803                })
1804            });
1805            if let Some(uris) = res.uris.as_mut() {
1806                uris.push(uri);
1807            }
1808
1809            let metadata = metadata.map(|m| {
1810                m.into_iter()
1811                    .filter(|(k, _)| !k.starts_with(CHROMA_KEY))
1812                    .collect()
1813            });
1814            if let Some(metadatas) = res.metadatas.as_mut() {
1815                metadatas.push(metadata);
1816            }
1817        }
1818        res
1819    }
1820}
1821
1822////////////////////////// Query //////////////////////////
1823
1824#[derive(Deserialize, Debug, Clone, Serialize)]
1825#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1826pub struct QueryRequestPayload {
1827    pub ids: Option<Vec<String>>,
1828    #[serde(flatten)]
1829    pub where_fields: RawWhereFields,
1830    pub query_embeddings: Vec<Vec<f32>>,
1831    pub n_results: Option<u32>,
1832    #[serde(default = "IncludeList::default_query")]
1833    pub include: IncludeList,
1834}
1835
1836#[non_exhaustive]
1837#[derive(Debug, Clone, Validate, Serialize)]
1838#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1839pub struct QueryRequest {
1840    pub tenant_id: String,
1841    pub database_name: String,
1842    pub collection_id: CollectionUuid,
1843    pub ids: Option<Vec<String>>,
1844    pub r#where: Option<Where>,
1845    pub embeddings: Vec<Vec<f32>>,
1846    pub n_results: u32,
1847    pub include: IncludeList,
1848}
1849
1850impl QueryRequest {
1851    #[allow(clippy::too_many_arguments)]
1852    pub fn try_new(
1853        tenant_id: String,
1854        database_name: String,
1855        collection_id: CollectionUuid,
1856        ids: Option<Vec<String>>,
1857        r#where: Option<Where>,
1858        embeddings: Vec<Vec<f32>>,
1859        n_results: u32,
1860        include: IncludeList,
1861    ) -> Result<Self, ChromaValidationError> {
1862        let request = Self {
1863            tenant_id,
1864            database_name,
1865            collection_id,
1866            ids,
1867            r#where,
1868            embeddings,
1869            n_results,
1870            include,
1871        };
1872        request.validate().map_err(ChromaValidationError::from)?;
1873        Ok(request)
1874    }
1875
1876    pub fn into_payload(self) -> Result<QueryRequestPayload, WhereError> {
1877        let where_fields = if let Some(r#where) = self.r#where.as_ref() {
1878            RawWhereFields::from_json_str(Some(&serde_json::to_string(r#where)?), None)?
1879        } else {
1880            RawWhereFields::default()
1881        };
1882        Ok(QueryRequestPayload {
1883            ids: self.ids,
1884            where_fields,
1885            query_embeddings: self.embeddings,
1886            n_results: Some(self.n_results),
1887            include: self.include,
1888        })
1889    }
1890}
1891
1892#[derive(Clone, Deserialize, Serialize, Debug)]
1893#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1894#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
1895pub struct QueryResponse {
1896    pub ids: Vec<Vec<String>>,
1897    pub embeddings: Option<Vec<Vec<Option<Vec<f32>>>>>,
1898    pub documents: Option<Vec<Vec<Option<String>>>>,
1899    pub uris: Option<Vec<Vec<Option<String>>>>,
1900    pub metadatas: Option<Vec<Vec<Option<Metadata>>>>,
1901    pub distances: Option<Vec<Vec<Option<f32>>>>,
1902    pub include: Vec<Include>,
1903}
1904
1905impl QueryResponse {
1906    pub fn sort_by_ids(&mut self) {
1907        fn reorder<T: Clone>(v: &mut [T], indices: &[usize]) {
1908            let old = v.to_owned();
1909            for (new_pos, &i) in indices.iter().enumerate() {
1910                v[new_pos] = old[i].clone();
1911            }
1912        }
1913
1914        for i in 0..self.ids.len() {
1915            let mut indices: Vec<usize> = (0..self.ids[i].len()).collect();
1916
1917            indices.sort_unstable_by(|&a, &b| self.ids[i][a].cmp(&self.ids[i][b]));
1918
1919            reorder(&mut self.ids[i], &indices);
1920
1921            if let Some(embeddings) = &mut self.embeddings {
1922                reorder(&mut embeddings[i], &indices);
1923            }
1924
1925            if let Some(documents) = &mut self.documents {
1926                reorder(&mut documents[i], &indices);
1927            }
1928
1929            if let Some(uris) = &mut self.uris {
1930                reorder(&mut uris[i], &indices);
1931            }
1932
1933            if let Some(metadatas) = &mut self.metadatas {
1934                reorder(&mut metadatas[i], &indices);
1935            }
1936
1937            if let Some(distances) = &mut self.distances {
1938                reorder(&mut distances[i], &indices);
1939            }
1940        }
1941    }
1942}
1943
1944#[cfg(feature = "pyo3")]
1945#[pyo3::pymethods]
1946impl QueryResponse {
1947    #[getter]
1948    pub fn ids(&self) -> &Vec<Vec<String>> {
1949        &self.ids
1950    }
1951
1952    #[getter]
1953    pub fn embeddings(&self) -> Option<Vec<Vec<Option<Vec<f32>>>>> {
1954        self.embeddings.clone()
1955    }
1956
1957    #[getter]
1958    pub fn documents(&self) -> Option<Vec<Vec<Option<String>>>> {
1959        self.documents.clone()
1960    }
1961
1962    #[getter]
1963    pub fn uris(&self) -> Option<Vec<Vec<Option<String>>>> {
1964        self.uris.clone()
1965    }
1966
1967    #[getter]
1968    pub fn metadatas(&self) -> Option<Vec<Vec<Option<Metadata>>>> {
1969        self.metadatas.clone()
1970    }
1971
1972    #[getter]
1973    pub fn distances(&self) -> Option<Vec<Vec<Option<f32>>>> {
1974        self.distances.clone()
1975    }
1976}
1977
1978impl From<(KnnBatchResult, IncludeList)> for QueryResponse {
1979    fn from((result, IncludeList(include_vec)): (KnnBatchResult, IncludeList)) -> Self {
1980        let mut res = Self {
1981            ids: Vec::new(),
1982            embeddings: include_vec
1983                .contains(&Include::Embedding)
1984                .then_some(Vec::new()),
1985            documents: include_vec
1986                .contains(&Include::Document)
1987                .then_some(Vec::new()),
1988            uris: include_vec.contains(&Include::Uri).then_some(Vec::new()),
1989            metadatas: include_vec
1990                .contains(&Include::Metadata)
1991                .then_some(Vec::new()),
1992            distances: include_vec
1993                .contains(&Include::Distance)
1994                .then_some(Vec::new()),
1995            include: include_vec,
1996        };
1997        for query_result in result.results {
1998            let mut ids = Vec::new();
1999            let mut embeddings = Vec::new();
2000            let mut documents = Vec::new();
2001            let mut uris = Vec::new();
2002            let mut metadatas = Vec::new();
2003            let mut distances = Vec::new();
2004            for KnnProjectionRecord {
2005                record:
2006                    ProjectionRecord {
2007                        id,
2008                        document,
2009                        embedding,
2010                        mut metadata,
2011                    },
2012                distance,
2013            } in query_result.records
2014            {
2015                ids.push(id);
2016                embeddings.push(embedding);
2017                documents.push(document);
2018
2019                let uri = metadata.as_mut().and_then(|meta| {
2020                    meta.remove(CHROMA_URI_KEY).and_then(|v| {
2021                        if let crate::MetadataValue::Str(uri) = v {
2022                            Some(uri)
2023                        } else {
2024                            None
2025                        }
2026                    })
2027                });
2028                uris.push(uri);
2029
2030                let metadata = metadata.map(|m| {
2031                    m.into_iter()
2032                        .filter(|(k, _)| !k.starts_with(CHROMA_KEY))
2033                        .collect()
2034                });
2035                metadatas.push(metadata);
2036
2037                distances.push(distance);
2038            }
2039            res.ids.push(ids);
2040
2041            if let Some(res_embs) = res.embeddings.as_mut() {
2042                res_embs.push(embeddings);
2043            }
2044            if let Some(res_docs) = res.documents.as_mut() {
2045                res_docs.push(documents);
2046            }
2047            if let Some(res_uri) = res.uris.as_mut() {
2048                res_uri.push(uris);
2049            }
2050            if let Some(res_metas) = res.metadatas.as_mut() {
2051                res_metas.push(metadatas);
2052            }
2053            if let Some(res_dists) = res.distances.as_mut() {
2054                res_dists.push(distances);
2055            }
2056        }
2057        res
2058    }
2059}
2060
2061#[derive(Debug, Clone, Deserialize, Serialize)]
2062#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2063pub struct SearchRequestPayload {
2064    pub searches: Vec<SearchPayload>,
2065}
2066
2067#[non_exhaustive]
2068#[derive(Clone, Debug, Serialize, Validate)]
2069#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2070pub struct SearchRequest {
2071    pub tenant_id: String,
2072    pub database_name: String,
2073    pub collection_id: CollectionUuid,
2074    pub searches: Vec<SearchPayload>,
2075}
2076
2077impl SearchRequest {
2078    pub fn try_new(
2079        tenant_id: String,
2080        database_name: String,
2081        collection_id: CollectionUuid,
2082        searches: Vec<SearchPayload>,
2083    ) -> Result<Self, ChromaValidationError> {
2084        let request = Self {
2085            tenant_id,
2086            database_name,
2087            collection_id,
2088            searches,
2089        };
2090        request.validate().map_err(ChromaValidationError::from)?;
2091        Ok(request)
2092    }
2093
2094    pub fn into_payload(self) -> SearchRequestPayload {
2095        SearchRequestPayload {
2096            searches: self.searches,
2097        }
2098    }
2099}
2100
2101#[derive(Clone, Deserialize, Serialize, Debug)]
2102#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2103pub struct SearchResponse {
2104    pub ids: Vec<Vec<String>>,
2105    pub documents: Vec<Option<Vec<Option<String>>>>,
2106    pub embeddings: Vec<Option<Vec<Option<Vec<f32>>>>>,
2107    pub metadatas: Vec<Option<Vec<Option<Metadata>>>>,
2108    pub scores: Vec<Option<Vec<Option<f32>>>>,
2109    pub select: Vec<Vec<Key>>,
2110}
2111
2112impl From<(SearchResult, Vec<SearchPayload>)> for SearchResponse {
2113    fn from((result, payloads): (SearchResult, Vec<SearchPayload>)) -> Self {
2114        let num_payloads = payloads.len();
2115        let mut res = Self {
2116            ids: Vec::with_capacity(num_payloads),
2117            documents: Vec::with_capacity(num_payloads),
2118            embeddings: Vec::with_capacity(num_payloads),
2119            metadatas: Vec::with_capacity(num_payloads),
2120            scores: Vec::with_capacity(num_payloads),
2121            select: Vec::with_capacity(num_payloads),
2122        };
2123
2124        for (payload_result, payload) in result.results.into_iter().zip(payloads) {
2125            // Get the sorted keys for this payload
2126            let mut payload_select = Vec::from_iter(payload.select.keys.iter().cloned());
2127            payload_select.sort();
2128
2129            let num_records = payload_result.records.len();
2130            let mut ids = Vec::with_capacity(num_records);
2131            let mut documents = Vec::with_capacity(num_records);
2132            let mut embeddings = Vec::with_capacity(num_records);
2133            let mut metadatas = Vec::with_capacity(num_records);
2134            let mut scores = Vec::with_capacity(num_records);
2135
2136            for record in payload_result.records {
2137                ids.push(record.id);
2138                documents.push(record.document);
2139                embeddings.push(record.embedding);
2140                metadatas.push(record.metadata);
2141                scores.push(record.score);
2142            }
2143
2144            res.ids.push(ids);
2145            res.select.push(payload_select.clone());
2146
2147            // Push documents if requested by this payload, otherwise None
2148            res.documents.push(
2149                payload_select
2150                    .binary_search(&Key::Document)
2151                    .is_ok()
2152                    .then_some(documents),
2153            );
2154
2155            // Push embeddings if requested by this payload, otherwise None
2156            res.embeddings.push(
2157                payload_select
2158                    .binary_search(&Key::Embedding)
2159                    .is_ok()
2160                    .then_some(embeddings),
2161            );
2162
2163            // Push metadatas if requested by this payload, otherwise None
2164            // Include if either Key::Metadata is present or any Key::MetadataField(_)
2165            let has_metadata = payload_select.binary_search(&Key::Metadata).is_ok()
2166                || payload_select
2167                    .last()
2168                    .is_some_and(|field| matches!(field, Key::MetadataField(_)));
2169            res.metadatas.push(has_metadata.then_some(metadatas));
2170
2171            // Push scores if requested by this payload, otherwise None
2172            res.scores.push(
2173                payload_select
2174                    .binary_search(&Key::Score)
2175                    .is_ok()
2176                    .then_some(scores),
2177            );
2178        }
2179
2180        res
2181    }
2182}
2183
2184#[derive(Error, Debug)]
2185pub enum QueryError {
2186    #[error("Error executing plan: {0}")]
2187    Executor(#[from] ExecutorError),
2188    #[error(transparent)]
2189    Other(#[from] Box<dyn ChromaError>),
2190}
2191
2192impl ChromaError for QueryError {
2193    fn code(&self) -> ErrorCodes {
2194        match self {
2195            QueryError::Executor(e) => e.code(),
2196            QueryError::Other(err) => err.code(),
2197        }
2198    }
2199}
2200
2201#[derive(Serialize)]
2202#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2203pub struct HealthCheckResponse {
2204    pub is_executor_ready: bool,
2205    pub is_log_client_ready: bool,
2206}
2207
2208impl HealthCheckResponse {
2209    pub fn get_status_code(&self) -> tonic::Code {
2210        if self.is_executor_ready && self.is_log_client_ready {
2211            tonic::Code::Ok
2212        } else {
2213            tonic::Code::Unavailable
2214        }
2215    }
2216}
2217
2218#[derive(Debug, Error)]
2219pub enum ExecutorError {
2220    #[error("Error converting: {0}")]
2221    Conversion(#[from] QueryConversionError),
2222    #[error("Error converting plan to proto: {0}")]
2223    PlanToProto(#[from] PlanToProtoError),
2224    #[error(transparent)]
2225    Grpc(#[from] Status),
2226    #[error("Inconsistent data")]
2227    InconsistentData,
2228    #[error("Collection is missing HNSW configuration")]
2229    CollectionMissingHnswConfiguration,
2230    #[error("Internal error: {0}")]
2231    Internal(Box<dyn ChromaError>),
2232    #[error("Error sending backfill request to compactor: {0}")]
2233    BackfillError(Box<dyn ChromaError>),
2234    #[error("Not implemented: {0}")]
2235    NotImplemented(String),
2236}
2237
2238impl ChromaError for ExecutorError {
2239    fn code(&self) -> ErrorCodes {
2240        match self {
2241            ExecutorError::Conversion(_) => ErrorCodes::InvalidArgument,
2242            ExecutorError::PlanToProto(_) => ErrorCodes::Internal,
2243            ExecutorError::Grpc(e) => e.code().into(),
2244            ExecutorError::InconsistentData => ErrorCodes::Internal,
2245            ExecutorError::CollectionMissingHnswConfiguration => ErrorCodes::Internal,
2246            ExecutorError::Internal(e) => e.code(),
2247            ExecutorError::BackfillError(e) => e.code(),
2248            ExecutorError::NotImplemented(_) => ErrorCodes::Unimplemented,
2249        }
2250    }
2251}
2252
2253////////////////////////// Task Operations //////////////////////////
2254
2255#[non_exhaustive]
2256#[derive(Clone, Debug, Deserialize, Serialize, Validate)]
2257#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2258pub struct CreateTaskRequest {
2259    #[validate(length(min = 1))]
2260    pub task_name: String,
2261    pub operator_name: String,
2262    pub output_collection_name: String,
2263    #[serde(default = "default_empty_json_object")]
2264    pub params: serde_json::Value,
2265}
2266
2267fn default_empty_json_object() -> serde_json::Value {
2268    serde_json::json!({})
2269}
2270
2271impl CreateTaskRequest {
2272    pub fn try_new(
2273        task_name: String,
2274        operator_name: String,
2275        output_collection_name: String,
2276        params: serde_json::Value,
2277    ) -> Result<Self, ChromaValidationError> {
2278        let request = Self {
2279            task_name,
2280            operator_name,
2281            output_collection_name,
2282            params,
2283        };
2284        request.validate().map_err(ChromaValidationError::from)?;
2285        Ok(request)
2286    }
2287}
2288
2289#[derive(Clone, Debug, Serialize)]
2290#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2291pub struct CreateTaskResponse {
2292    pub success: bool,
2293    pub task_id: String,
2294}
2295
2296#[derive(Error, Debug)]
2297pub enum AddTaskError {
2298    #[error("Task with name [{0}] already exists")]
2299    AlreadyExists(String),
2300    #[error("Input collection [{0}] does not exist")]
2301    InputCollectionNotFound(String),
2302    #[error("Output collection [{0}] already exists")]
2303    OutputCollectionExists(String),
2304    #[error(transparent)]
2305    Validation(#[from] ChromaValidationError),
2306    #[error(transparent)]
2307    Internal(#[from] Box<dyn ChromaError>),
2308}
2309
2310impl ChromaError for AddTaskError {
2311    fn code(&self) -> ErrorCodes {
2312        match self {
2313            AddTaskError::AlreadyExists(_) => ErrorCodes::AlreadyExists,
2314            AddTaskError::InputCollectionNotFound(_) => ErrorCodes::NotFound,
2315            AddTaskError::OutputCollectionExists(_) => ErrorCodes::AlreadyExists,
2316            AddTaskError::Validation(err) => err.code(),
2317            AddTaskError::Internal(err) => err.code(),
2318        }
2319    }
2320}
2321
2322#[non_exhaustive]
2323#[derive(Clone, Debug, Deserialize, Validate, Serialize)]
2324#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2325pub struct RemoveTaskRequest {
2326    #[validate(length(min = 1))]
2327    pub task_name: String,
2328    /// Whether to delete the output collection as well
2329    #[serde(default)]
2330    pub delete_output: bool,
2331}
2332
2333impl RemoveTaskRequest {
2334    pub fn try_new(task_name: String, delete_output: bool) -> Result<Self, ChromaValidationError> {
2335        let request = Self {
2336            task_name,
2337            delete_output,
2338        };
2339        request.validate().map_err(ChromaValidationError::from)?;
2340        Ok(request)
2341    }
2342}
2343
2344#[derive(Clone, Debug, Serialize)]
2345#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2346pub struct RemoveTaskResponse {
2347    pub success: bool,
2348}
2349
2350#[derive(Error, Debug)]
2351pub enum RemoveTaskError {
2352    #[error("Task with name [{0}] does not exist")]
2353    NotFound(String),
2354    #[error(transparent)]
2355    Validation(#[from] ChromaValidationError),
2356    #[error(transparent)]
2357    Internal(#[from] Box<dyn ChromaError>),
2358}
2359
2360impl ChromaError for RemoveTaskError {
2361    fn code(&self) -> ErrorCodes {
2362        match self {
2363            RemoveTaskError::NotFound(_) => ErrorCodes::NotFound,
2364            RemoveTaskError::Validation(err) => err.code(),
2365            RemoveTaskError::Internal(err) => err.code(),
2366        }
2367    }
2368}
2369
2370#[cfg(test)]
2371mod test {
2372    use super::*;
2373    use crate::{MetadataValue, SparseVector, UpdateMetadataValue};
2374    use std::collections::HashMap;
2375
2376    #[test]
2377    fn test_create_database_min_length() {
2378        let request = CreateDatabaseRequest::try_new("default_tenant".to_string(), "a".to_string());
2379        assert!(request.is_err());
2380    }
2381
2382    #[test]
2383    fn test_create_tenant_min_length() {
2384        let request = CreateTenantRequest::try_new("a".to_string());
2385        assert!(request.is_err());
2386    }
2387
2388    #[test]
2389    fn test_add_request_validates_sparse_vectors() {
2390        let mut metadata = HashMap::new();
2391        // Add unsorted sparse vector - should fail validation
2392        metadata.insert(
2393            "sparse".to_string(),
2394            MetadataValue::SparseVector(SparseVector::new(vec![3, 1, 2], vec![0.3, 0.1, 0.2])),
2395        );
2396
2397        let result = AddCollectionRecordsRequest::try_new(
2398            "tenant".to_string(),
2399            "database".to_string(),
2400            CollectionUuid(uuid::Uuid::new_v4()),
2401            vec!["id1".to_string()],
2402            vec![vec![0.1, 0.2]],
2403            None,
2404            None,
2405            Some(vec![Some(metadata)]),
2406        );
2407
2408        // Should fail because sparse vector is not sorted
2409        assert!(result.is_err());
2410    }
2411
2412    #[test]
2413    fn test_update_request_validates_sparse_vectors() {
2414        let mut metadata = HashMap::new();
2415        // Add unsorted sparse vector - should fail validation
2416        metadata.insert(
2417            "sparse".to_string(),
2418            UpdateMetadataValue::SparseVector(SparseVector::new(
2419                vec![3, 1, 2],
2420                vec![0.3, 0.1, 0.2],
2421            )),
2422        );
2423
2424        let result = UpdateCollectionRecordsRequest::try_new(
2425            "tenant".to_string(),
2426            "database".to_string(),
2427            CollectionUuid(uuid::Uuid::new_v4()),
2428            vec!["id1".to_string()],
2429            None,
2430            None,
2431            None,
2432            Some(vec![Some(metadata)]),
2433        );
2434
2435        // Should fail because sparse vector is not sorted
2436        assert!(result.is_err());
2437    }
2438
2439    #[test]
2440    fn test_upsert_request_validates_sparse_vectors() {
2441        let mut metadata = HashMap::new();
2442        // Add unsorted sparse vector - should fail validation
2443        metadata.insert(
2444            "sparse".to_string(),
2445            UpdateMetadataValue::SparseVector(SparseVector::new(
2446                vec![3, 1, 2],
2447                vec![0.3, 0.1, 0.2],
2448            )),
2449        );
2450
2451        let result = UpsertCollectionRecordsRequest::try_new(
2452            "tenant".to_string(),
2453            "database".to_string(),
2454            CollectionUuid(uuid::Uuid::new_v4()),
2455            vec!["id1".to_string()],
2456            vec![vec![0.1, 0.2]],
2457            None,
2458            None,
2459            Some(vec![Some(metadata)]),
2460        );
2461
2462        // Should fail because sparse vector is not sorted
2463        assert!(result.is_err());
2464    }
2465}