Skip to main content

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