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