1use std::collections::BTreeMap;
4use std::error::Error;
5use std::fmt::{self, Display, Formatter};
6use std::io::Write;
7use std::path::{Path, PathBuf};
8use std::str::FromStr;
9use std::sync::OnceLock;
10use std::time::{Duration, Instant};
11
12use base64::Engine as _;
13use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
14use serde::de::{self, MapAccess, SeqAccess, Visitor};
15use serde::{Deserialize, Deserializer, Serialize, Serializer};
16
17mod build_identity;
18mod derived_overload;
19mod identity;
20mod lifecycle_completion;
21mod local_development;
22mod metric;
23mod mutation_commit;
24mod mutation_lifecycle;
25mod operation_locator;
26mod performance;
27mod profile;
28mod relationship;
29mod replayable_source;
30mod resource_spec;
31mod shared_root_history;
32mod state_outcomes;
33mod workload_class;
34
35pub use build_identity::*;
36pub use derived_overload::*;
37pub use identity::{
38 BackendAuthorityId, BackendAuthorityPair, ClusterIdentity, ClusterStorageId, CollectionId, CollectionIndexRef,
39 FenceToken, IdentityError, IndexAddress, IndexId, IndexName, NodeIncarnation, ObjectIncarnation, PrimaryTerm,
40 ProtectionSnapshotId, RecoveryAttemptId, RecoveryEra, RecoveryGeneration, ShardKey,
41};
42pub use lifecycle_completion::*;
43pub use local_development::{LocalDevelopmentInstanceId, LocalDevelopmentInstanceIdError};
44pub use metric::*;
45pub use mutation_commit::*;
46pub use mutation_lifecycle::*;
47pub use operation_locator::{
48 ARTIFACT_CHUNK_ALIGNMENT, ARTIFACT_LOCATOR_FORMAT_VERSION, ArtifactLocatorV1, ArtifactReadRangeV1,
49 MAX_ARTIFACT_CHUNK_BYTES, OPERATION_LOCATOR_FORMAT_VERSION, OperationAuthorityKindV1, OperationLocatorError,
50 OperationLocatorV1, VerifiedArtifactChunkV1, verify_artifact_chunk_v1,
51};
52pub use performance::{
53 BoundedStageSnapshotV2, PerformanceObservationV2, PerformanceStageReadingV2, PerformanceStageV2,
54 bounded_stage_snapshot_v2, record_performance_stage_v2,
55};
56pub use profile::*;
57pub use relationship::*;
58pub use replayable_source::*;
59pub use resource_spec::*;
60pub use shared_root_history::*;
61pub use state_outcomes::*;
62pub use workload_class::*;
63
64#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
66#[serde(rename_all = "snake_case")]
67pub enum AwsPartition {
68 Aws,
69 AwsCn,
70 AwsUsGov,
71}
72
73#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
76#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
77pub enum AwsBucketKind {
78 GeneralPurpose,
79 Directory { availability_zone_id: String },
80}
81
82impl AwsBucketKind {
83 #[must_use]
84 pub const fn is_directory(&self) -> bool {
85 matches!(self, Self::Directory { .. })
86 }
87
88 #[must_use]
89 pub fn availability_zone_id(&self) -> Option<&str> {
90 match self {
91 Self::GeneralPurpose => None,
92 Self::Directory { availability_zone_id } => Some(availability_zone_id),
93 }
94 }
95}
96
97macro_rules! semantic_number {
98 ($name:ident, $representation:ty, $documentation:literal) => {
99 #[doc = $documentation]
100 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
101 #[serde(transparent)]
102 pub struct $name($representation);
103
104 impl $name {
105 #[must_use]
107 pub const fn new(value: $representation) -> Self {
108 Self(value)
109 }
110
111 #[must_use]
113 pub const fn get(self) -> $representation {
114 self.0
115 }
116
117 #[must_use]
119 pub const fn checked_next(self) -> Option<Self> {
120 match self.0.checked_add(1) {
121 Some(value) => Some(Self(value)),
122 None => None,
123 }
124 }
125 }
126
127 impl Display for $name {
128 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
129 Display::fmt(&self.0, formatter)
130 }
131 }
132 };
133}
134
135semantic_number!(
136 ShardId,
137 u16,
138 "A semantic identifier for a shard in deterministic models and protocols."
139);
140semantic_number!(Epoch, u32, "A monotonically increasing shard-writer fencing epoch.");
141semantic_number!(
142 SeqNo,
143 u64,
144 "A per-shard monotonically increasing operation sequence number."
145);
146semantic_number!(
147 ManifestGeneration,
148 u64,
149 "A monotonically increasing immutable-manifest generation."
150);
151semantic_number!(
152 ContinuousEvaluationGeneration,
153 u64,
154 "An immutable semantic generation of one continuous temporal evaluation."
155);
156semantic_number!(
157 ContinuousEvaluationRevision,
158 u64,
159 "The CAS revision of one continuous temporal evaluation's durable progress."
160);
161semantic_number!(
162 SealedRootProofGeneration,
163 u64,
164 "A monotonically increasing sealed-root proof receipt generation. Zero is never issued, so it is the reserved never-proved value."
165);
166semantic_number!(
167 ManifestGraphProofGeneration,
168 u64,
169 "A monotonically increasing post-recovery manifest-graph proof generation. Zero is never issued."
170);
171semantic_number!(
172 RoutingHash,
173 u32,
174 "The stored routing hash used for shard-range selection."
175);
176semantic_number!(
177 IdHash,
178 u128,
179 "The mutable-index document identity hash used by visibility overlays."
180);
181semantic_number!(SegmentId, u128, "A stable immutable segment identifier.");
182semantic_number!(
183 StateIncarnation,
184 u64,
185 "A fenced lifetime identity for one state primary/projection pair."
186);
187#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
190#[serde(transparent)]
191pub struct VectorFieldId(u32);
192
193impl VectorFieldId {
194 pub const fn new(value: u32) -> Result<Self, VectorFieldIdError> {
196 if value == 0 {
197 return Err(VectorFieldIdError);
198 }
199 Ok(Self(value))
200 }
201
202 #[must_use]
204 pub const fn get(self) -> u32 {
205 self.0
206 }
207}
208
209impl Display for VectorFieldId {
210 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
211 Display::fmt(&self.0, formatter)
212 }
213}
214
215#[derive(Clone, Copy, Debug, Eq, PartialEq)]
217pub struct VectorFieldIdError;
218
219impl Display for VectorFieldIdError {
220 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
221 formatter.write_str("vector field identity must be nonzero")
222 }
223}
224
225impl Error for VectorFieldIdError {}
226
227impl<'de> Deserialize<'de> for VectorFieldId {
228 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
229 where
230 D: Deserializer<'de>,
231 {
232 Self::new(u32::deserialize(deserializer)?).map_err(de::Error::custom)
233 }
234}
235pub const RESERVED_SEGMENT_ID_NAMESPACE_START: u128 = 1_u128 << 127;
239
240#[must_use]
241pub const fn is_reserved_segment_id(segment_id: SegmentId) -> bool {
242 segment_id.get() >= RESERVED_SEGMENT_ID_NAMESPACE_START
243}
244
245pub fn next_ordinary_segment_id<I>(current: I) -> Option<SegmentId>
249where
250 I: IntoIterator<Item = SegmentId>,
251{
252 let maximum = current
253 .into_iter()
254 .filter(|segment_id| !is_reserved_segment_id(*segment_id))
255 .max();
256 let next = maximum.map_or(Some(SegmentId::new(1)), SegmentId::checked_next)?;
257 (!is_reserved_segment_id(next)).then_some(next)
258}
259semantic_number!(
260 ChunkId,
261 u64,
262 "A node-local monotonically increasing WAL chunk identifier."
263);
264semantic_number!(NodeId, u64, "A semantic indexer-node identifier.");
265semantic_number!(
266 ControllerEpoch,
267 u64,
268 "A nonzero monotonically increasing placement-controller claim epoch."
269);
270semantic_number!(
271 NodeLifecycleGeneration,
272 u64,
273 "A monotonically increasing generation of one node lifecycle record."
274);
275semantic_number!(
276 CredentialGeneration,
277 u64,
278 "A monotonically increasing workload-credential binding generation."
279);
280semantic_number!(
281 BootstrapGeneration,
282 u64,
283 "A monotonically increasing irreversible cluster-bootstrap generation."
284);
285semantic_number!(
286 MembershipGeneration,
287 u64,
288 "A monotonically increasing external Raft membership generation."
289);
290semantic_number!(
291 PlacementProgressGeneration,
292 u64,
293 "A monotonically increasing committed placement reconciliation progress generation."
294);
295semantic_number!(
296 RangeTransitionGeneration,
297 u64,
298 "A monotonically increasing online split or merge transition generation."
299);
300semantic_number!(
301 AssignmentGeneration,
302 u64,
303 "A monotonically increasing generation for one exact shard-route assignment."
304);
305semantic_number!(
306 ReadLeaseGeneration,
307 u64,
308 "A monotonically increasing generation for one replicated generation-read lease."
309);
310semantic_number!(
311 SchemaGeneration,
312 u32,
313 "A monotonically increasing mapping/schema generation."
314);
315semantic_number!(
316 PolicyGeneration,
317 u64,
318 "A monotonically increasing authoritative index-policy generation."
319);
320semantic_number!(
321 ResourceRevision,
322 u64,
323 "A monotonically increasing revision of one index resource specification."
324);
325semantic_number!(
326 CrdGeneration,
327 u64,
328 "A nonzero Kubernetes metadata generation for one exact custom-resource incarnation."
329);
330semantic_number!(
331 CatalogManagementGeneration,
332 u64,
333 "A monotonically increasing catalog-owned CAS generation for API/CRD management ownership."
334);
335semantic_number!(
336 IndexRevision,
337 u64,
338 "A monotonically increasing revision of one immutable index incarnation's catalog record."
339);
340semantic_number!(
341 HistoryLedgerRevision,
342 u64,
343 "A monotonically increasing CAS revision of one index's retained-history ledger."
344);
345semantic_number!(
346 AdmissionGeneration,
347 u64,
348 "A monotonically increasing catalog admission fence for one index incarnation."
349);
350semantic_number!(
351 RoutingRevision,
352 u64,
353 "A monotonically increasing revision of one index's complete routing partition."
354);
355semantic_number!(
356 MemtableGeneration,
357 u64,
358 "A node-local active or frozen memtable generation."
359);
360semantic_number!(
361 BackendAuthority,
362 u64,
363 "A stable scheduler partition for one authoritative durability backend."
364);
365semantic_number!(
366 ClusterRevision,
367 u64,
368 "A monotonically increasing revision of the replicated cluster view."
369);
370semantic_number!(
371 RootRevision,
372 u64,
373 "A monotonically increasing revision of the authoritative durable root set."
374);
375semantic_number!(
376 LogicalMillis,
377 u64,
378 "Metastore-owned logical milliseconds used for leases without wall-clock dependence."
379);
380semantic_number!(
381 TaskCheckpointGeneration,
382 u64,
383 "A monotonically increasing CAS generation for one durable task checkpoint."
384);
385semantic_number!(
386 AdministrationRevision,
387 u64,
388 "A monotonically increasing revision of the collection-scoped administration catalog."
389);
390semantic_number!(
391 AliasRevision,
392 u64,
393 "A monotonically increasing revision of one exact alias record."
394);
395semantic_number!(
396 ComponentTemplateRevision,
397 u64,
398 "A monotonically increasing revision of one component-template incarnation."
399);
400semantic_number!(
401 IndexTemplateRevision,
402 u64,
403 "A monotonically increasing revision of one index-template incarnation."
404);
405semantic_number!(
406 IngestPipelineRevision,
407 u64,
408 "A monotonically increasing revision of one catalog-owned ingest pipeline."
409);
410semantic_number!(
411 DataStreamRevision,
412 u64,
413 "A monotonically increasing revision of one data-stream incarnation."
414);
415semantic_number!(
416 LifecyclePolicyRevision,
417 u64,
418 "A monotonically increasing revision of one lifecycle-policy incarnation."
419);
420semantic_number!(
421 SecurityPolicyRevision,
422 u64,
423 "A monotonically increasing revision of the sole coordinated security-policy snapshot."
424);
425semantic_number!(
426 CollectionSecurityGeneration,
427 u64,
428 "A monotonically increasing authorization generation for one Collection's public-security records."
429);
430semantic_number!(
431 QuotaPolicyGeneration,
432 u64,
433 "A monotonically increasing quota-policy generation scoped to one Collection."
434);
435semantic_number!(
436 SecurityRoleGeneration,
437 u64,
438 "A monotonically increasing generation of one Collection-local security role."
439);
440semantic_number!(
441 SecurityPrincipalGeneration,
442 u64,
443 "A monotonically increasing generation of one Collection-local security principal."
444);
445semantic_number!(
446 SecurityDirectSequence,
447 u64,
448 "A monotonically increasing position in the coordinated security direct-command replay journal."
449);
450semantic_number!(
451 ApiKeyGeneration,
452 u64,
453 "A monotonically increasing generation of one API-key verifier record."
454);
455
456pub const AUTHORIZATION_BINDING_FORMAT_VERSION: u16 = 1;
457
458#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
465#[serde(deny_unknown_fields)]
466pub struct AuthorizationBindingV1 {
467 pub format_version: u16,
468 pub recovery_era: RecoveryEra,
469 pub public_security_root_digest: [u8; 32],
470 pub ordering_revision: SecurityPolicyRevision,
471 pub collection_id: CollectionId,
472 pub collection_security_generation: CollectionSecurityGeneration,
473 pub principal_id: PrincipalId,
474 pub principal_generation: SecurityPrincipalGeneration,
475 pub api_key_id: ApiKeyId,
476 pub api_key_generation: ApiKeyGeneration,
477 pub role_generations_digest: [u8; 32],
478 pub quota_policy_generation: QuotaPolicyGeneration,
479 pub authorized_scope_digest: [u8; 32],
480}
481
482impl AuthorizationBindingV1 {
483 #[must_use]
484 pub fn is_valid(self) -> bool {
485 self.format_version == AUTHORIZATION_BINDING_FORMAT_VERSION
486 && self.public_security_root_digest != [0; 32]
487 && self.ordering_revision.get() != 0
488 && self.collection_security_generation.get() != 0
489 && self.principal_id.get() != 0
490 && self.principal_generation.get() != 0
491 && self.api_key_id.get() != 0
492 && self.api_key_generation.get() != 0
493 && self.role_generations_digest != [0; 32]
494 && self.quota_policy_generation.get() != 0
495 && self.authorized_scope_digest != [0; 32]
496 }
497
498 #[must_use]
502 pub fn same_local_authority(self, other: Self) -> bool {
503 self.same_generation_authority(other)
504 && self.role_generations_digest == other.role_generations_digest
505 && self.authorized_scope_digest == other.authorized_scope_digest
506 }
507
508 #[must_use]
513 pub fn same_generation_authority(self, other: Self) -> bool {
514 self.is_valid()
515 && other.is_valid()
516 && self.format_version == other.format_version
517 && self.recovery_era == other.recovery_era
518 && self.collection_id == other.collection_id
519 && self.collection_security_generation == other.collection_security_generation
520 && self.principal_id == other.principal_id
521 && self.principal_generation == other.principal_generation
522 && self.api_key_id == other.api_key_id
523 && self.api_key_generation == other.api_key_generation
524 }
525}
526semantic_number!(
527 AuditSequence,
528 u64,
529 "A recovery-backed sequence in the tamper-evident security audit chain."
530);
531semantic_number!(
532 AuditRangeId,
533 u64,
534 "A recovery-backed identity for one fenced Gateway audit sequence range."
535);
536semantic_number!(
537 QuotaGrantId,
538 u64,
539 "A recovery-backed identity for one aggregate Gateway quota grant."
540);
541semantic_number!(
542 QuotaWindow,
543 u64,
544 "A coordinated logical quota-window identity that cannot be supplied by an adapter."
545);
546semantic_number!(
547 ProcessMonotonicMillis,
548 u64,
549 "Process-monotonic milliseconds used only for local freshness, timeout, and lifetime bounds."
550);
551semantic_number!(
552 UnixMillis,
553 u64,
554 "Unix milliseconds used only for certificate validity observations."
555);
556
557#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
563#[serde(transparent)]
564pub struct GatewayIncarnation([u8; 32]);
565
566impl GatewayIncarnation {
567 pub fn new(value: [u8; 32]) -> Result<Self, IdentityError> {
568 if value == [0; 32] {
569 return Err(IdentityError::Zero {
570 kind: "GatewayIncarnation",
571 });
572 }
573 Ok(Self(value))
574 }
575
576 #[must_use]
577 pub const fn get(self) -> [u8; 32] {
578 self.0
579 }
580
581 #[must_use]
582 pub const fn as_bytes(&self) -> &[u8; 32] {
583 &self.0
584 }
585}
586
587impl<'de> Deserialize<'de> for GatewayIncarnation {
588 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
589 where
590 D: Deserializer<'de>,
591 {
592 let value = <[u8; 32]>::deserialize(deserializer)?;
593 Self::new(value).map_err(de::Error::custom)
594 }
595}
596
597#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
599#[serde(rename_all = "snake_case")]
600pub enum PublicTlsProfileV1 {
601 Http,
602 Grpc,
603 Native,
604 Resp,
605}
606
607#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
609#[serde(rename_all = "snake_case")]
610pub enum PublicGatewayListenerV1 {
611 #[serde(rename = "api")]
612 LodestoneApi,
613 ElasticsearchHttp,
614 OtlpHttp,
615 JaegerHttp,
616 OtlpGrpc,
617 SubscriptionGrpc,
618 SubscriptionWebSocket,
619 Resp,
620}
621
622impl PublicGatewayListenerV1 {
623 #[must_use]
624 pub const fn required_tls_profile(self) -> PublicTlsProfileV1 {
625 match self {
626 Self::LodestoneApi => PublicTlsProfileV1::Native,
627 Self::ElasticsearchHttp | Self::OtlpHttp | Self::JaegerHttp | Self::SubscriptionWebSocket => {
628 PublicTlsProfileV1::Http
629 }
630 Self::OtlpGrpc | Self::SubscriptionGrpc => PublicTlsProfileV1::Grpc,
631 Self::Resp => PublicTlsProfileV1::Resp,
632 }
633 }
634}
635
636#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
640#[serde(rename_all = "snake_case")]
641pub enum NodeRole {
642 MetastoreVoter,
643 Placement,
644 Orchestrator,
645 Gateway,
646 Indexer,
647 Searcher,
648 Compactor,
649}
650
651#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
653#[serde(deny_unknown_fields)]
654pub struct NodeToken {
655 pub recovery_era: RecoveryEra,
656 pub node_id: NodeId,
657 pub node_incarnation: NodeIncarnation,
658}
659
660#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
666#[serde(transparent)]
667pub struct KubernetesObjectUidDigest([u8; 32]);
668
669impl KubernetesObjectUidDigest {
670 pub fn new(value: [u8; 32]) -> Result<Self, IdentityError> {
671 if value == [0; 32] {
672 return Err(IdentityError::Zero {
673 kind: "KubernetesObjectUidDigest",
674 });
675 }
676 Ok(Self(value))
677 }
678
679 #[must_use]
680 pub const fn get(self) -> [u8; 32] {
681 self.0
682 }
683
684 #[must_use]
685 pub const fn as_bytes(&self) -> &[u8; 32] {
686 &self.0
687 }
688}
689
690impl<'de> Deserialize<'de> for KubernetesObjectUidDigest {
691 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
692 where
693 D: Deserializer<'de>,
694 {
695 let value = <[u8; 32]>::deserialize(deserializer)?;
696 Self::new(value).map_err(de::Error::custom)
697 }
698}
699
700impl NodeToken {
701 pub fn new(
702 recovery_era: RecoveryEra,
703 node_id: NodeId,
704 node_incarnation: NodeIncarnation,
705 ) -> Result<Self, IdentityError> {
706 if node_id.get() == 0 {
707 return Err(IdentityError::Zero { kind: "NodeId" });
708 }
709 Ok(Self {
710 recovery_era,
711 node_id,
712 node_incarnation,
713 })
714 }
715}
716
717#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
719#[serde(deny_unknown_fields)]
720pub struct ControllerToken {
721 pub recovery_era: RecoveryEra,
722 pub controller_epoch: ControllerEpoch,
723}
724
725impl ControllerToken {
726 pub fn new(recovery_era: RecoveryEra, controller_epoch: ControllerEpoch) -> Result<Self, IdentityError> {
727 if controller_epoch.get() == 0 {
728 return Err(IdentityError::Zero {
729 kind: "ControllerEpoch",
730 });
731 }
732 Ok(Self {
733 recovery_era,
734 controller_epoch,
735 })
736 }
737}
738
739#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
744pub struct TaskId(u128);
745
746impl TaskId {
747 #[must_use]
748 pub const fn new(value: u128) -> Self {
749 Self(value)
750 }
751
752 #[must_use]
753 pub const fn get(self) -> u128 {
754 self.0
755 }
756
757 #[must_use]
758 pub const fn checked_next(self) -> Option<Self> {
759 match self.0.checked_add(1) {
760 Some(value) => Some(Self(value)),
761 None => None,
762 }
763 }
764}
765
766impl Display for TaskId {
767 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
768 Display::fmt(&self.0, formatter)
769 }
770}
771
772impl Serialize for TaskId {
773 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
774 where
775 S: Serializer,
776 {
777 serializer.collect_str(self)
778 }
779}
780
781impl<'de> Deserialize<'de> for TaskId {
782 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
783 where
784 D: Deserializer<'de>,
785 {
786 struct TaskIdVisitor;
787
788 impl Visitor<'_> for TaskIdVisitor {
789 type Value = TaskId;
790
791 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
792 formatter.write_str("a decimal task-id string")
793 }
794
795 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
796 where
797 E: de::Error,
798 {
799 value
800 .parse::<u128>()
801 .map(TaskId)
802 .map_err(|_| E::custom("task id is outside the u128 domain"))
803 }
804 }
805
806 deserializer.deserialize_str(TaskIdVisitor)
807 }
808}
809
810macro_rules! semantic_decimal_identity {
811 ($name:ident, $documentation:literal) => {
812 #[doc = $documentation]
813 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
814 #[serde(transparent)]
815 pub struct $name(TaskId);
816
817 impl $name {
818 #[must_use]
820 pub const fn new(value: u128) -> Self {
821 Self(TaskId::new(value))
822 }
823
824 #[must_use]
826 pub const fn get(self) -> u128 {
827 self.0.get()
828 }
829 }
830
831 impl Display for $name {
832 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
833 Display::fmt(&self.0, formatter)
834 }
835 }
836 };
837}
838
839semantic_decimal_identity!(
840 ComponentTemplateId,
841 "A recovery-backed identity for one component-template incarnation."
842);
843semantic_decimal_identity!(
844 IndexTemplateId,
845 "A recovery-backed identity for one index-template incarnation."
846);
847semantic_decimal_identity!(
848 IngestPipelineId,
849 "A recovery-backed identity for one ingest-pipeline incarnation."
850);
851semantic_decimal_identity!(
852 DataStreamId,
853 "A recovery-backed identity for one data-stream incarnation."
854);
855semantic_decimal_identity!(
856 LifecyclePolicyId,
857 "A recovery-backed identity for one lifecycle-policy incarnation."
858);
859semantic_decimal_identity!(
860 ContinuousEvaluationId,
861 "A recovery-backed identity for one Collection-owned continuous temporal evaluation."
862);
863semantic_decimal_identity!(
864 ExternalKeyResolverCapabilityId,
865 "An opaque capability identity authorizing one external content-key resolver class."
866);
867semantic_decimal_identity!(
868 PrincipalId,
869 "A recovery-backed, never-reused security-principal identity."
870);
871semantic_decimal_identity!(ApiKeyId, "A recovery-backed, never-reused API-key identity.");
872semantic_decimal_identity!(BackupId, "A recovery-backed, never-reused portable-backup identity.");
873
874#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
876#[serde(transparent)]
877pub struct AdministrationActionId([u8; 32]);
878
879impl AdministrationActionId {
880 pub fn new(value: [u8; 32]) -> Result<Self, IdentityError> {
882 if value == [0; 32] {
883 return Err(IdentityError::Zero {
884 kind: "AdministrationActionId",
885 });
886 }
887 Ok(Self(value))
888 }
889
890 #[must_use]
892 pub const fn get(self) -> [u8; 32] {
893 self.0
894 }
895
896 #[must_use]
898 pub const fn as_bytes(&self) -> &[u8; 32] {
899 &self.0
900 }
901}
902
903impl<'de> Deserialize<'de> for AdministrationActionId {
904 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
905 where
906 D: Deserializer<'de>,
907 {
908 let value = <[u8; 32]>::deserialize(deserializer)?;
909 Self::new(value).map_err(de::Error::custom)
910 }
911}
912
913#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
915#[serde(transparent)]
916pub struct SecurityActionId([u8; 32]);
917
918impl SecurityActionId {
919 pub fn new(value: [u8; 32]) -> Result<Self, IdentityError> {
921 if value == [0; 32] {
922 return Err(IdentityError::Zero {
923 kind: "SecurityActionId",
924 });
925 }
926 Ok(Self(value))
927 }
928
929 #[must_use]
931 pub const fn get(self) -> [u8; 32] {
932 self.0
933 }
934
935 #[must_use]
937 pub const fn as_bytes(&self) -> &[u8; 32] {
938 &self.0
939 }
940}
941
942impl<'de> Deserialize<'de> for SecurityActionId {
943 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
944 where
945 D: Deserializer<'de>,
946 {
947 let value = <[u8; 32]>::deserialize(deserializer)?;
948 Self::new(value).map_err(de::Error::custom)
949 }
950}
951
952#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
954#[serde(transparent)]
955pub struct BackupActionId([u8; 32]);
956
957impl BackupActionId {
958 pub fn new(value: [u8; 32]) -> Result<Self, IdentityError> {
960 if value == [0; 32] {
961 return Err(IdentityError::Zero { kind: "BackupActionId" });
962 }
963 Ok(Self(value))
964 }
965
966 #[must_use]
968 pub const fn get(self) -> [u8; 32] {
969 self.0
970 }
971
972 #[must_use]
974 pub const fn as_bytes(&self) -> &[u8; 32] {
975 &self.0
976 }
977}
978
979impl<'de> Deserialize<'de> for BackupActionId {
980 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
981 where
982 D: Deserializer<'de>,
983 {
984 let value = <[u8; 32]>::deserialize(deserializer)?;
985 Self::new(value).map_err(de::Error::custom)
986 }
987}
988
989#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
994#[serde(transparent)]
995pub struct RoutingRangeEnd(u64);
996
997impl RoutingRangeEnd {
998 pub const FULL: Self = Self(u32::MAX as u64 + 1);
1000
1001 pub fn new(value: u64) -> Result<Self, RoutingRangeError> {
1003 if value == 0 || value > Self::FULL.0 {
1004 Err(RoutingRangeError::InvalidEnd(value))
1005 } else {
1006 Ok(Self(value))
1007 }
1008 }
1009
1010 #[must_use]
1012 pub const fn get(self) -> u64 {
1013 self.0
1014 }
1015}
1016
1017#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1019pub struct ShardDescriptor {
1020 pub shard_id: ShardId,
1021 pub epoch: Epoch,
1022 pub hash_start: RoutingHash,
1023 pub hash_end: RoutingRangeEnd,
1024}
1025
1026impl ShardDescriptor {
1027 pub fn new(
1029 shard_id: ShardId,
1030 epoch: Epoch,
1031 hash_start: RoutingHash,
1032 hash_end: RoutingRangeEnd,
1033 ) -> Result<Self, RoutingRangeError> {
1034 if u64::from(hash_start.get()) >= hash_end.get() {
1035 return Err(RoutingRangeError::EmptyOrReversed {
1036 start: hash_start,
1037 end: hash_end,
1038 });
1039 }
1040 Ok(Self {
1041 shard_id,
1042 epoch,
1043 hash_start,
1044 hash_end,
1045 })
1046 }
1047
1048 #[must_use]
1050 pub const fn contains(self, routing_hash: RoutingHash) -> bool {
1051 let value = routing_hash.get() as u64;
1052 value >= self.hash_start.get() as u64 && value < self.hash_end.get()
1053 }
1054}
1055
1056#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1058pub enum RoutingRangeError {
1059 InvalidEnd(u64),
1060 EmptyOrReversed { start: RoutingHash, end: RoutingRangeEnd },
1061}
1062
1063impl Display for RoutingRangeError {
1064 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
1065 match self {
1066 Self::InvalidEnd(end) => write!(
1067 formatter,
1068 "routing range end {end} is outside 1..={}",
1069 RoutingRangeEnd::FULL.get()
1070 ),
1071 Self::EmptyOrReversed { start, end } => write!(
1072 formatter,
1073 "routing range start {start} must be below exclusive end {}",
1074 end.get()
1075 ),
1076 }
1077 }
1078}
1079
1080impl Error for RoutingRangeError {}
1081
1082#[non_exhaustive]
1089#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
1090#[serde(rename_all = "snake_case")]
1091pub enum DurabilityClass {
1092 RegionalObject,
1093 ZonalLowLatency,
1094 Relaxed,
1095 Ephemeral,
1096}
1097
1098impl DurabilityClass {
1099 #[must_use]
1101 pub const fn as_str(self) -> &'static str {
1102 match self {
1103 Self::RegionalObject => "regional_object",
1104 Self::ZonalLowLatency => "zonal_low_latency",
1105 Self::Relaxed => "relaxed",
1106 Self::Ephemeral => "ephemeral",
1107 }
1108 }
1109
1110 pub const fn require_available(self) -> Result<(), DurabilityClassUnavailable> {
1112 match self {
1113 Self::RegionalObject | Self::ZonalLowLatency => Ok(()),
1114 Self::Relaxed | Self::Ephemeral => Err(DurabilityClassUnavailable { class: self }),
1115 }
1116 }
1117
1118 pub const fn require_state_available(self) -> Result<(), DurabilityClassUnavailable> {
1123 match self {
1124 Self::RegionalObject | Self::ZonalLowLatency | Self::Ephemeral => Ok(()),
1125 Self::Relaxed => Err(DurabilityClassUnavailable { class: self }),
1126 }
1127 }
1128}
1129
1130impl Display for DurabilityClass {
1131 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
1132 formatter.write_str(self.as_str())
1133 }
1134}
1135
1136#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1138pub struct DurabilityClassUnavailable {
1139 pub class: DurabilityClass,
1140}
1141
1142impl Display for DurabilityClassUnavailable {
1143 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
1144 write!(
1145 formatter,
1146 "durability class '{}' is recognized but unavailable",
1147 self.class
1148 )
1149 }
1150}
1151
1152impl Error for DurabilityClassUnavailable {}
1153
1154#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
1156#[serde(rename_all = "snake_case")]
1157pub enum VisibilityMode {
1158 #[default]
1159 Applied,
1160 WaitFor,
1161}
1162
1163#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1165#[serde(rename_all = "snake_case")]
1166pub enum DurabilityBoundary {
1167 Registered,
1168 IncarnationMemory,
1170}
1171
1172#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1174#[serde(rename_all = "snake_case", tag = "kind")]
1175pub enum DurabilityLoss {
1176 Zero,
1177 WholeIncarnation,
1178}
1179
1180#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
1182#[serde(rename_all = "snake_case")]
1183pub enum WriteStage {
1184 Validated,
1185 Admitted,
1186 Sequenced,
1187 ObjectPut,
1188 Registered,
1189 AppliedToActiveMemtable,
1190 MicroCommitted,
1191 Sealed,
1192}
1193
1194#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1196#[serde(rename_all = "snake_case")]
1197pub enum IndexMode {
1198 Mutable,
1199 AppendLog,
1200 State,
1203}
1204
1205impl IndexMode {
1206 #[must_use]
1208 pub const fn has_mutation_visibility(self) -> bool {
1209 matches!(self, Self::Mutable | Self::State)
1210 }
1211
1212 #[must_use]
1213 pub const fn is_state(self) -> bool {
1214 matches!(self, Self::State)
1215 }
1216}
1217
1218pub const MAPPING_FORMAT_VERSION: u16 = 10;
1224
1225pub const MAX_MAPPING_FIELDS: usize = 16_384;
1227
1228pub const VECTOR_MAPPING_FORMAT_VERSION: u16 = 2;
1230
1231pub const SPARSE_VECTOR_MAPPING_FORMAT_VERSION: u16 = 1;
1233
1234pub const VECTOR_SET_MAPPING_FORMAT_VERSION: u16 = 1;
1236
1237pub const MAX_VECTOR_MAPPING_FIELDS: usize = 8;
1239
1240pub const MAX_VECTOR_DIMENSIONS: u16 = 16_384;
1243
1244pub const MAX_VECTOR_BYTES_PER_FIELD: usize = 64 * 1_024;
1246
1247pub const MAX_VECTOR_BYTES_PER_DOCUMENT: usize = 64 * 1_024;
1249
1250pub const MAX_VECTOR_BYTES_PER_QUERY: usize = 64 * 1_024;
1252
1253pub const MAX_VECTOR_BYTES_PER_REQUEST: usize = 16 * MAX_VECTOR_BYTES_PER_QUERY;
1255
1256pub const MAX_SPARSE_VECTOR_DIMENSION: u32 = 16_777_215;
1258
1259pub const MAX_SPARSE_VECTOR_NONZERO: usize = 4_096;
1261
1262pub const MAX_SPARSE_VECTOR_BYTES: usize = 32 * 1_024;
1265
1266pub const MAX_VECTOR_SET_DOCUMENT_VECTORS: usize = 128;
1268
1269pub const MAX_VECTOR_SET_QUERY_VECTORS: usize = 32;
1271
1272pub const MAX_VECTOR_SET_BYTES_PER_FIELD: usize = 64 * 1_024;
1274
1275pub const MAX_VECTOR_SET_BYTES_PER_QUERY: usize = 64 * 1_024;
1277
1278pub const MAX_FRESH_VECTOR_RECORDS: usize = 16_384;
1280
1281pub const MAX_FRESH_VECTOR_BYTES: usize = 128 * 1_024 * 1_024;
1284
1285pub const MAX_VECTOR_BUILD_SCRATCH_BYTES: u64 = 16 * 1_024 * 1_024 * 1_024;
1288
1289pub const MAX_VECTOR_DECODED_CACHE_BYTES: u64 = 16 * 1_024 * 1_024 * 1_024;
1292
1293pub const MAX_VECTOR_BYTES_PER_RESPONSE: usize = 64 * 1_024;
1296
1297#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
1300#[serde(rename_all = "snake_case")]
1301pub enum DenseVectorElementType {
1302 F32,
1303 F16,
1304 I8,
1305}
1306
1307#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
1310#[serde(rename_all = "snake_case")]
1311pub enum SparseVectorWeightType {
1312 F32,
1313 F16,
1314}
1315
1316impl SparseVectorWeightType {
1317 #[must_use]
1318 pub const fn width_bytes(self) -> usize {
1319 match self {
1320 Self::F32 => 4,
1321 Self::F16 => 2,
1322 }
1323 }
1324}
1325
1326#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
1329#[serde(deny_unknown_fields)]
1330pub struct SparseVectorEntry {
1331 pub dimension: u32,
1332 #[serde(deserialize_with = "deserialize_sparse_weight")]
1333 pub weight: f64,
1334}
1335
1336fn deserialize_sparse_weight<'de, D>(deserializer: D) -> Result<f64, D::Error>
1337where
1338 D: Deserializer<'de>,
1339{
1340 serde_json::Number::deserialize(deserializer)?
1341 .to_string()
1342 .parse::<f64>()
1343 .map_err(de::Error::custom)
1344}
1345
1346#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1349#[serde(deny_unknown_fields)]
1350pub struct SparseVectorValue {
1351 #[serde(deserialize_with = "deserialize_bounded_sparse_entries")]
1352 pub entries: Vec<SparseVectorEntry>,
1353}
1354
1355#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1357pub struct SparseVectorDefinition {
1358 pub maximum_dimension: u32,
1359 pub weight_type: SparseVectorWeightType,
1360}
1361
1362#[derive(Clone, Copy, Debug, PartialEq)]
1364pub struct DecodedSparseVectorEntry {
1365 pub dimension: u32,
1366 pub weight: f32,
1367}
1368
1369impl SparseVectorDefinition {
1370 pub fn validate(self) -> Result<(), SparseVectorValueError> {
1371 if self.maximum_dimension > MAX_SPARSE_VECTOR_DIMENSION {
1372 return Err(SparseVectorValueError::InvalidMaximumDimension(self.maximum_dimension));
1373 }
1374 let maximum_bytes = MAX_SPARSE_VECTOR_NONZERO
1375 .checked_mul(4_usize.saturating_add(self.weight_type.width_bytes()))
1376 .ok_or(SparseVectorValueError::ByteLengthOverflow)?;
1377 if maximum_bytes > MAX_SPARSE_VECTOR_BYTES {
1378 return Err(SparseVectorValueError::FieldBytesExceeded(maximum_bytes));
1379 }
1380 Ok(())
1381 }
1382}
1383
1384impl SparseVectorValue {
1385 pub fn decode(
1386 &self,
1387 definition: SparseVectorDefinition,
1388 ) -> Result<Vec<DecodedSparseVectorEntry>, SparseVectorValueError> {
1389 definition.validate()?;
1390 self.validate_bounded(definition, MAX_SPARSE_VECTOR_BYTES)?;
1391 self.entries
1392 .iter()
1393 .enumerate()
1394 .map(|(ordinal, entry)| {
1395 let weight = entry.weight as f32;
1396 if !weight.is_finite() {
1397 return Err(SparseVectorValueError::NonFiniteWeight(ordinal));
1398 }
1399 let weight = match definition.weight_type {
1400 SparseVectorWeightType::F32 => canonical_vector_zero(weight),
1401 SparseVectorWeightType::F16 => {
1402 let bits = f32_to_f16_rne(weight).ok_or(SparseVectorValueError::F16Overflow(ordinal))?;
1403 canonical_vector_zero(f16_to_f32(bits))
1404 }
1405 };
1406 if weight == 0.0 {
1407 return Err(SparseVectorValueError::ZeroWeight(ordinal));
1408 }
1409 Ok(DecodedSparseVectorEntry {
1410 dimension: entry.dimension,
1411 weight,
1412 })
1413 })
1414 .collect()
1415 }
1416
1417 pub fn validate_bounded(
1418 &self,
1419 definition: SparseVectorDefinition,
1420 maximum_bytes: usize,
1421 ) -> Result<usize, SparseVectorValueError> {
1422 definition.validate()?;
1423 if self.entries.is_empty() {
1424 return Err(SparseVectorValueError::Empty);
1425 }
1426 if self.entries.len() > MAX_SPARSE_VECTOR_NONZERO {
1427 return Err(SparseVectorValueError::TooManyEntries(self.entries.len()));
1428 }
1429 let bytes = self
1430 .entries
1431 .len()
1432 .checked_mul(4_usize.saturating_add(definition.weight_type.width_bytes()))
1433 .ok_or(SparseVectorValueError::ByteLengthOverflow)?;
1434 if bytes > maximum_bytes.min(MAX_SPARSE_VECTOR_BYTES) {
1435 return Err(SparseVectorValueError::FieldBytesExceeded(bytes));
1436 }
1437 let mut previous = None;
1438 for (ordinal, entry) in self.entries.iter().enumerate() {
1439 if entry.dimension > definition.maximum_dimension {
1440 return Err(SparseVectorValueError::DimensionOutOfRange {
1441 ordinal,
1442 dimension: entry.dimension,
1443 maximum: definition.maximum_dimension,
1444 });
1445 }
1446 if previous.is_some_and(|dimension| entry.dimension <= dimension) {
1447 return Err(SparseVectorValueError::DimensionsNotStrict(ordinal));
1448 }
1449 if !entry.weight.is_finite() {
1450 return Err(SparseVectorValueError::NonFiniteWeight(ordinal));
1451 }
1452 if entry.weight == 0.0 {
1453 return Err(SparseVectorValueError::ZeroWeight(ordinal));
1454 }
1455 previous = Some(entry.dimension);
1456 }
1457 Ok(bytes)
1458 }
1459
1460 pub fn canonical_native_bytes(
1461 &self,
1462 definition: SparseVectorDefinition,
1463 ) -> Result<Vec<u8>, SparseVectorValueError> {
1464 let decoded = self.decode(definition)?;
1465 let mut bytes = Vec::with_capacity(
1466 decoded
1467 .len()
1468 .saturating_mul(4_usize.saturating_add(definition.weight_type.width_bytes())),
1469 );
1470 for entry in decoded {
1471 bytes.extend_from_slice(&entry.dimension.to_le_bytes());
1472 match definition.weight_type {
1473 SparseVectorWeightType::F32 => bytes.extend_from_slice(&entry.weight.to_bits().to_le_bytes()),
1474 SparseVectorWeightType::F16 => {
1475 let bits = f32_to_f16_rne(entry.weight).ok_or(SparseVectorValueError::F16Overflow(0))?;
1476 bytes.extend_from_slice(&bits.to_le_bytes());
1477 }
1478 }
1479 }
1480 Ok(bytes)
1481 }
1482}
1483
1484pub fn decode_sparse_native_bytes(
1488 definition: SparseVectorDefinition,
1489 bytes: &[u8],
1490) -> Result<Vec<DecodedSparseVectorEntry>, SparseVectorValueError> {
1491 definition.validate()?;
1492 let entry_bytes = 4_usize.saturating_add(definition.weight_type.width_bytes());
1493 if bytes.is_empty() || !bytes.len().is_multiple_of(entry_bytes) {
1494 return Err(SparseVectorValueError::FieldBytesExceeded(bytes.len()));
1495 }
1496 let count = bytes.len() / entry_bytes;
1497 if count > MAX_SPARSE_VECTOR_NONZERO || bytes.len() > MAX_SPARSE_VECTOR_BYTES {
1498 return Err(SparseVectorValueError::FieldBytesExceeded(bytes.len()));
1499 }
1500 let mut entries = Vec::with_capacity(count);
1501 let mut previous = None;
1502 for (ordinal, chunk) in bytes.chunks_exact(entry_bytes).enumerate() {
1503 let dimension = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
1504 if dimension > definition.maximum_dimension {
1505 return Err(SparseVectorValueError::DimensionOutOfRange {
1506 ordinal,
1507 dimension,
1508 maximum: definition.maximum_dimension,
1509 });
1510 }
1511 if previous.is_some_and(|value| dimension <= value) {
1512 return Err(SparseVectorValueError::DimensionsNotStrict(ordinal));
1513 }
1514 let weight = match definition.weight_type {
1515 SparseVectorWeightType::F32 => f32::from_le_bytes([chunk[4], chunk[5], chunk[6], chunk[7]]),
1516 SparseVectorWeightType::F16 => f16_to_f32(u16::from_le_bytes([chunk[4], chunk[5]])),
1517 };
1518 if !weight.is_finite() {
1519 return Err(SparseVectorValueError::NonFiniteWeight(ordinal));
1520 }
1521 let weight = canonical_vector_zero(weight);
1522 if weight == 0.0 {
1523 return Err(SparseVectorValueError::ZeroWeight(ordinal));
1524 }
1525 entries.push(DecodedSparseVectorEntry { dimension, weight });
1526 previous = Some(dimension);
1527 }
1528 Ok(entries)
1529}
1530
1531#[derive(Clone, Debug, Eq, PartialEq)]
1532pub enum SparseVectorValueError {
1533 Empty,
1534 InvalidMaximumDimension(u32),
1535 TooManyEntries(usize),
1536 ByteLengthOverflow,
1537 FieldBytesExceeded(usize),
1538 DimensionsNotStrict(usize),
1539 DimensionOutOfRange {
1540 ordinal: usize,
1541 dimension: u32,
1542 maximum: u32,
1543 },
1544 NonFiniteWeight(usize),
1545 ZeroWeight(usize),
1546 F16Overflow(usize),
1547}
1548
1549impl Display for SparseVectorValueError {
1550 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
1551 match self {
1552 Self::Empty => formatter.write_str("sparse vector value is empty"),
1553 Self::InvalidMaximumDimension(value) => {
1554 write!(formatter, "sparse maximum dimension {value} is invalid")
1555 }
1556 Self::TooManyEntries(value) => write!(formatter, "sparse vector has {value} non-zero entries"),
1557 Self::ByteLengthOverflow => formatter.write_str("sparse vector byte length overflowed"),
1558 Self::FieldBytesExceeded(value) => {
1559 write!(formatter, "sparse vector uses {value} bytes above its field ceiling")
1560 }
1561 Self::DimensionsNotStrict(ordinal) => {
1562 write!(formatter, "sparse vector dimension {ordinal} is duplicate or unsorted")
1563 }
1564 Self::DimensionOutOfRange {
1565 ordinal,
1566 dimension,
1567 maximum,
1568 } => write!(
1569 formatter,
1570 "sparse vector dimension {ordinal} value {dimension} exceeds {maximum}"
1571 ),
1572 Self::NonFiniteWeight(ordinal) => write!(formatter, "sparse vector weight {ordinal} is non-finite"),
1573 Self::ZeroWeight(ordinal) => write!(formatter, "sparse vector weight {ordinal} is zero"),
1574 Self::F16Overflow(ordinal) => write!(formatter, "sparse f16 weight {ordinal} overflows binary16"),
1575 }
1576 }
1577}
1578
1579impl Error for SparseVectorValueError {}
1580
1581fn deserialize_bounded_sparse_entries<'de, D>(deserializer: D) -> Result<Vec<SparseVectorEntry>, D::Error>
1582where
1583 D: Deserializer<'de>,
1584{
1585 struct SparseEntriesVisitor;
1586
1587 impl<'de> serde::de::Visitor<'de> for SparseEntriesVisitor {
1588 type Value = Vec<SparseVectorEntry>;
1589
1590 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
1591 formatter.write_str("a bounded sparse entry sequence")
1592 }
1593
1594 fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
1595 where
1596 A: serde::de::SeqAccess<'de>,
1597 {
1598 if sequence
1599 .size_hint()
1600 .is_some_and(|length| length > MAX_SPARSE_VECTOR_NONZERO)
1601 {
1602 return Err(de::Error::custom("sparse vector exceeds the non-zero ceiling"));
1603 }
1604 let mut entries =
1605 Vec::with_capacity(sequence.size_hint().unwrap_or_default().min(MAX_SPARSE_VECTOR_NONZERO));
1606 while let Some(entry) = sequence.next_element::<SparseVectorEntry>()? {
1607 if entries.len() == MAX_SPARSE_VECTOR_NONZERO {
1608 return Err(de::Error::custom("sparse vector exceeds the non-zero ceiling"));
1609 }
1610 entries.push(entry);
1611 }
1612 Ok(entries)
1613 }
1614 }
1615
1616 deserializer.deserialize_seq(SparseEntriesVisitor)
1617}
1618
1619impl DenseVectorElementType {
1620 #[must_use]
1621 pub const fn width_bytes(self) -> usize {
1622 match self {
1623 Self::F32 => 4,
1624 Self::F16 => 2,
1625 Self::I8 => 1,
1626 }
1627 }
1628}
1629
1630#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
1633pub struct VectorQuantizationScale(u32);
1634
1635impl VectorQuantizationScale {
1636 pub fn new(value: f32) -> Result<Self, DenseVectorValueError> {
1637 if !value.is_finite() || value <= 0.0 {
1638 return Err(DenseVectorValueError::InvalidQuantizationScale);
1639 }
1640 Ok(Self(value.to_bits()))
1641 }
1642
1643 #[must_use]
1644 pub const fn get(self) -> f32 {
1645 f32::from_bits(self.0)
1646 }
1647}
1648
1649impl Serialize for VectorQuantizationScale {
1650 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1651 where
1652 S: Serializer,
1653 {
1654 serializer.serialize_str(&self.get().to_string())
1655 }
1656}
1657
1658impl<'de> Deserialize<'de> for VectorQuantizationScale {
1659 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1660 where
1661 D: Deserializer<'de>,
1662 {
1663 let text = String::deserialize(deserializer)?;
1664 let value = text.parse::<f32>().map_err(de::Error::custom)?;
1665 if value.to_string() != text {
1666 return Err(de::Error::custom("vector quantization scale is not canonical"));
1667 }
1668 Self::new(value).map_err(de::Error::custom)
1669 }
1670}
1671
1672#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1674#[serde(rename_all = "snake_case")]
1675pub enum DenseVectorByteOrder {
1676 LittleEndian,
1677}
1678
1679#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1681#[serde(deny_unknown_fields)]
1682pub struct DenseVectorBinaryValue {
1683 pub element_type: DenseVectorElementType,
1684 pub byte_order: DenseVectorByteOrder,
1685 pub data_base64: String,
1686 #[serde(default, skip_serializing_if = "Option::is_none")]
1687 pub quantization_scale: Option<VectorQuantizationScale>,
1688}
1689
1690#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1693#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1694pub enum DenseVectorValue {
1695 Numeric {
1696 #[serde(deserialize_with = "deserialize_bounded_dense_numbers")]
1697 values: Vec<f64>,
1698 },
1699 Binary {
1700 #[serde(flatten)]
1701 value: DenseVectorBinaryValue,
1702 },
1703}
1704
1705impl DenseVectorValue {
1706 pub fn decode(&self, definition: DenseVectorDefinition) -> Result<Vec<f32>, DenseVectorValueError> {
1707 definition.validate()?;
1708 match self {
1709 Self::Numeric { values } => decode_dense_numeric_value(definition, values),
1710 Self::Binary { value } => decode_dense_binary_value(definition, value),
1711 }
1712 }
1713
1714 pub fn canonical_native_bytes(&self, definition: DenseVectorDefinition) -> Result<Vec<u8>, DenseVectorValueError> {
1719 definition.validate()?;
1720 match self {
1721 Self::Numeric { values } => encode_dense_numeric_native_bytes(definition, values),
1722 Self::Binary { value } => decode_dense_binary_native_bytes(definition, value),
1723 }
1724 }
1725
1726 #[must_use]
1727 pub fn element_count(&self) -> Option<usize> {
1728 match self {
1729 Self::Numeric { values } => Some(values.len()),
1730 Self::Binary { .. } => None,
1731 }
1732 }
1733
1734 pub fn validate_bounded(&self, maximum_bytes: usize) -> Result<usize, DenseVectorValueError> {
1735 match self {
1736 Self::Numeric { values } => {
1737 if values.is_empty() {
1738 return Err(DenseVectorValueError::Empty);
1739 }
1740 let bytes = values
1741 .len()
1742 .checked_mul(DenseVectorElementType::F32.width_bytes())
1743 .ok_or(DenseVectorValueError::ByteLengthOverflow)?;
1744 if values.len() > usize::from(MAX_VECTOR_DIMENSIONS) || bytes > maximum_bytes {
1745 return Err(DenseVectorValueError::FieldBytesExceeded(bytes));
1746 }
1747 if let Some(ordinal) = values.iter().position(|value| !value.is_finite()) {
1748 return Err(DenseVectorValueError::NonFiniteElement(ordinal));
1749 }
1750 Ok(bytes)
1751 }
1752 Self::Binary { value } => {
1753 match (value.element_type, value.quantization_scale) {
1754 (DenseVectorElementType::I8, Some(_))
1755 | (DenseVectorElementType::F32 | DenseVectorElementType::F16, None) => {}
1756 (DenseVectorElementType::I8, None) => {
1757 return Err(DenseVectorValueError::MissingQuantizationScale);
1758 }
1759 (DenseVectorElementType::F32 | DenseVectorElementType::F16, Some(_)) => {
1760 return Err(DenseVectorValueError::UnexpectedQuantizationScale);
1761 }
1762 }
1763 if value.data_base64.is_empty() || value.data_base64.len() > maximum_bytes.div_ceil(3) * 4 {
1764 return Err(DenseVectorValueError::FieldBytesExceeded(value.data_base64.len()));
1765 }
1766 let bytes = BASE64_STANDARD
1767 .decode(value.data_base64.as_bytes())
1768 .map_err(|_| DenseVectorValueError::InvalidBase64)?;
1769 if bytes.is_empty()
1770 || bytes.len() > maximum_bytes
1771 || !bytes.len().is_multiple_of(value.element_type.width_bytes())
1772 || bytes.len() / value.element_type.width_bytes() > usize::from(MAX_VECTOR_DIMENSIONS)
1773 {
1774 return Err(DenseVectorValueError::FieldBytesExceeded(bytes.len()));
1775 }
1776 if BASE64_STANDARD.encode(&bytes) != value.data_base64 {
1777 return Err(DenseVectorValueError::InvalidBase64);
1778 }
1779 Ok(bytes.len())
1780 }
1781 }
1782 }
1783}
1784
1785#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1787pub struct DenseVectorDefinition {
1788 pub dimensions: u16,
1789 pub element_type: DenseVectorElementType,
1790 pub metric: VectorMetric,
1791 pub quantization_scale: Option<VectorQuantizationScale>,
1792}
1793
1794impl DenseVectorDefinition {
1795 pub fn validate(self) -> Result<(), DenseVectorValueError> {
1796 if self.dimensions == 0 || self.dimensions > MAX_VECTOR_DIMENSIONS {
1797 return Err(DenseVectorValueError::InvalidDimensions(self.dimensions));
1798 }
1799 let bytes = usize::from(self.dimensions)
1800 .checked_mul(self.element_type.width_bytes())
1801 .ok_or(DenseVectorValueError::ByteLengthOverflow)?;
1802 if bytes > MAX_VECTOR_BYTES_PER_FIELD {
1803 return Err(DenseVectorValueError::FieldBytesExceeded(bytes));
1804 }
1805 match (self.element_type, self.quantization_scale) {
1806 (DenseVectorElementType::I8, Some(_))
1807 | (DenseVectorElementType::F32 | DenseVectorElementType::F16, None) => Ok(()),
1808 (DenseVectorElementType::I8, None) => Err(DenseVectorValueError::MissingQuantizationScale),
1809 (DenseVectorElementType::F32 | DenseVectorElementType::F16, Some(_)) => {
1810 Err(DenseVectorValueError::UnexpectedQuantizationScale)
1811 }
1812 }
1813 }
1814
1815 #[must_use]
1816 pub const fn encoded_bytes(self) -> usize {
1817 self.dimensions as usize * self.element_type.width_bytes()
1818 }
1819}
1820
1821#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1824#[serde(deny_unknown_fields)]
1825pub struct VectorSetValue {
1826 #[serde(deserialize_with = "deserialize_bounded_vector_set")]
1827 pub vectors: Vec<DenseVectorValue>,
1828}
1829
1830#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1832pub struct VectorSetDefinition {
1833 pub dense: DenseVectorDefinition,
1834 pub maximum_vectors: u16,
1835}
1836
1837#[derive(Clone, Debug, PartialEq)]
1839pub struct DecodedVectorSet {
1840 pub vectors: Vec<Vec<f32>>,
1841 pub native_vectors: Vec<Vec<u8>>,
1842 pub native_bytes: usize,
1843}
1844
1845impl VectorSetDefinition {
1846 pub fn validate(self) -> Result<(), VectorSetValueError> {
1847 self.dense.validate().map_err(VectorSetValueError::Dense)?;
1848 if self.maximum_vectors == 0 || usize::from(self.maximum_vectors) > MAX_VECTOR_SET_DOCUMENT_VECTORS {
1849 return Err(VectorSetValueError::InvalidMaximumVectors(self.maximum_vectors));
1850 }
1851 let maximum_bytes = usize::from(self.maximum_vectors)
1852 .checked_mul(self.dense.encoded_bytes())
1853 .ok_or(VectorSetValueError::ByteLengthOverflow)?;
1854 if maximum_bytes > MAX_VECTOR_SET_BYTES_PER_FIELD {
1855 return Err(VectorSetValueError::FieldBytesExceeded(maximum_bytes));
1856 }
1857 Ok(())
1858 }
1859}
1860
1861impl VectorSetValue {
1862 pub fn decode(
1863 &self,
1864 definition: VectorSetDefinition,
1865 maximum_vectors: usize,
1866 maximum_bytes: usize,
1867 ) -> Result<DecodedVectorSet, VectorSetValueError> {
1868 definition.validate()?;
1869 let admitted_vectors = maximum_vectors
1870 .min(usize::from(definition.maximum_vectors))
1871 .min(MAX_VECTOR_SET_DOCUMENT_VECTORS);
1872 if self.vectors.is_empty() {
1873 return Err(VectorSetValueError::Empty);
1874 }
1875 if self.vectors.len() > admitted_vectors {
1876 return Err(VectorSetValueError::TooManyVectors(self.vectors.len()));
1877 }
1878 let native_bytes = self
1879 .vectors
1880 .len()
1881 .checked_mul(definition.dense.encoded_bytes())
1882 .ok_or(VectorSetValueError::ByteLengthOverflow)?;
1883 if native_bytes > maximum_bytes.min(MAX_VECTOR_SET_BYTES_PER_FIELD) {
1884 return Err(VectorSetValueError::FieldBytesExceeded(native_bytes));
1885 }
1886 let mut vectors = Vec::with_capacity(self.vectors.len());
1887 let mut native_vectors = Vec::with_capacity(self.vectors.len());
1888 for value in &self.vectors {
1889 vectors.push(value.decode(definition.dense).map_err(VectorSetValueError::Dense)?);
1890 native_vectors.push(
1891 value
1892 .canonical_native_bytes(definition.dense)
1893 .map_err(VectorSetValueError::Dense)?,
1894 );
1895 }
1896 Ok(DecodedVectorSet {
1897 vectors,
1898 native_vectors,
1899 native_bytes,
1900 })
1901 }
1902}
1903
1904fn deserialize_bounded_vector_set<'de, D>(deserializer: D) -> Result<Vec<DenseVectorValue>, D::Error>
1905where
1906 D: Deserializer<'de>,
1907{
1908 struct VectorSetVisitor;
1909
1910 impl<'de> serde::de::Visitor<'de> for VectorSetVisitor {
1911 type Value = Vec<DenseVectorValue>;
1912
1913 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
1914 formatter.write_str("a bounded sequence of dense vector values")
1915 }
1916
1917 fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
1918 where
1919 A: serde::de::SeqAccess<'de>,
1920 {
1921 if sequence
1922 .size_hint()
1923 .is_some_and(|length| length > MAX_VECTOR_SET_DOCUMENT_VECTORS)
1924 {
1925 return Err(de::Error::custom(
1926 "vector set exceeds the document vector-count ceiling",
1927 ));
1928 }
1929 let mut vectors = Vec::with_capacity(
1930 sequence
1931 .size_hint()
1932 .unwrap_or_default()
1933 .min(MAX_VECTOR_SET_DOCUMENT_VECTORS),
1934 );
1935 while let Some(vector) = sequence.next_element::<DenseVectorValue>()? {
1936 if vectors.len() == MAX_VECTOR_SET_DOCUMENT_VECTORS {
1937 return Err(de::Error::custom(
1938 "vector set exceeds the document vector-count ceiling",
1939 ));
1940 }
1941 vectors.push(vector);
1942 }
1943 Ok(vectors)
1944 }
1945 }
1946
1947 deserializer.deserialize_seq(VectorSetVisitor)
1948}
1949
1950#[derive(Clone, Debug, Eq, PartialEq)]
1951pub enum VectorSetValueError {
1952 Empty,
1953 InvalidMaximumVectors(u16),
1954 TooManyVectors(usize),
1955 FieldBytesExceeded(usize),
1956 ByteLengthOverflow,
1957 Dense(DenseVectorValueError),
1958}
1959
1960impl Display for VectorSetValueError {
1961 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
1962 match self {
1963 Self::Empty => formatter.write_str("vector set cannot be empty"),
1964 Self::InvalidMaximumVectors(value) => {
1965 write!(formatter, "vector-set maximum vector count {value} is invalid")
1966 }
1967 Self::TooManyVectors(value) => write!(formatter, "vector set contains {value} vectors"),
1968 Self::FieldBytesExceeded(value) => write!(formatter, "vector set uses {value} element bytes"),
1969 Self::ByteLengthOverflow => formatter.write_str("vector-set byte length overflowed"),
1970 Self::Dense(error) => write!(formatter, "vector-set member is invalid: {error}"),
1971 }
1972 }
1973}
1974
1975impl Error for VectorSetValueError {}
1976
1977#[derive(Clone, Debug, Eq, PartialEq)]
1978pub enum DenseVectorValueError {
1979 Empty,
1980 InvalidDimensions(u16),
1981 DimensionMismatch { expected: u16, observed: usize },
1982 InvalidQuantizationScale,
1983 MissingQuantizationScale,
1984 UnexpectedQuantizationScale,
1985 QuantizationScaleMismatch,
1986 ElementTypeMismatch,
1987 ByteLengthOverflow,
1988 FieldBytesExceeded(usize),
1989 NonFiniteElement(usize),
1990 F16Overflow(usize),
1991 I8ElementOutOfRange(usize),
1992 InvalidBase64Length { expected: usize, observed: usize },
1993 InvalidBase64,
1994 DecodedByteLength { expected: usize, observed: usize },
1995 ZeroNorm,
1996}
1997
1998impl Display for DenseVectorValueError {
1999 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
2000 match self {
2001 Self::Empty => formatter.write_str("dense vector value is empty"),
2002 Self::InvalidDimensions(value) => write!(formatter, "dense vector dimensions {value} are invalid"),
2003 Self::DimensionMismatch { expected, observed } => {
2004 write!(formatter, "dense vector has {observed} elements; expected {expected}")
2005 }
2006 Self::InvalidQuantizationScale => {
2007 formatter.write_str("dense i8 quantization scale must be positive and finite")
2008 }
2009 Self::MissingQuantizationScale => {
2010 formatter.write_str("dense i8 value requires quantization scale metadata")
2011 }
2012 Self::UnexpectedQuantizationScale => {
2013 formatter.write_str("dense f32/f16 value cannot carry quantization metadata")
2014 }
2015 Self::QuantizationScaleMismatch => {
2016 formatter.write_str("dense i8 value quantization scale differs from its mapping")
2017 }
2018 Self::ElementTypeMismatch => {
2019 formatter.write_str("dense binary value element type differs from its mapping")
2020 }
2021 Self::ByteLengthOverflow => formatter.write_str("dense vector byte length overflowed"),
2022 Self::FieldBytesExceeded(value) => {
2023 write!(formatter, "dense vector uses {value} bytes above its field ceiling")
2024 }
2025 Self::NonFiniteElement(ordinal) => write!(formatter, "dense vector element {ordinal} is non-finite"),
2026 Self::F16Overflow(ordinal) => write!(formatter, "dense f16 element {ordinal} overflows binary16"),
2027 Self::I8ElementOutOfRange(ordinal) => {
2028 write!(
2029 formatter,
2030 "dense i8 element {ordinal} is not an exact integer in -128..=127"
2031 )
2032 }
2033 Self::InvalidBase64Length { expected, observed } => {
2034 write!(
2035 formatter,
2036 "dense binary base64 has {observed} bytes; expected {expected}"
2037 )
2038 }
2039 Self::InvalidBase64 => formatter.write_str("dense binary value is not canonical base64"),
2040 Self::DecodedByteLength { expected, observed } => {
2041 write!(
2042 formatter,
2043 "dense binary value has {observed} decoded bytes; expected {expected}"
2044 )
2045 }
2046 Self::ZeroNorm => formatter.write_str("dense cosine vector has zero norm"),
2047 }
2048 }
2049}
2050
2051impl Error for DenseVectorValueError {}
2052
2053fn deserialize_bounded_dense_numbers<'de, D>(deserializer: D) -> Result<Vec<f64>, D::Error>
2054where
2055 D: Deserializer<'de>,
2056{
2057 struct DenseNumbersVisitor;
2058
2059 impl<'de> Visitor<'de> for DenseNumbersVisitor {
2060 type Value = Vec<f64>;
2061
2062 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
2063 write!(formatter, "at most {MAX_VECTOR_DIMENSIONS} dense vector numbers")
2064 }
2065
2066 fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
2067 where
2068 A: SeqAccess<'de>,
2069 {
2070 if sequence
2071 .size_hint()
2072 .is_some_and(|length| length > usize::from(MAX_VECTOR_DIMENSIONS))
2073 {
2074 return Err(de::Error::custom("dense vector exceeds the element ceiling"));
2075 }
2076 let mut values = Vec::with_capacity(
2077 sequence
2078 .size_hint()
2079 .unwrap_or_default()
2080 .min(usize::from(MAX_VECTOR_DIMENSIONS)),
2081 );
2082 while let Some(value) = sequence.next_element::<f64>()? {
2083 if values.len() == usize::from(MAX_VECTOR_DIMENSIONS) {
2084 return Err(de::Error::custom("dense vector exceeds the element ceiling"));
2085 }
2086 values.push(value);
2087 }
2088 Ok(values)
2089 }
2090 }
2091
2092 deserializer.deserialize_seq(DenseNumbersVisitor)
2093}
2094
2095pub fn decode_dense_numeric_value(
2096 definition: DenseVectorDefinition,
2097 values: &[f64],
2098) -> Result<Vec<f32>, DenseVectorValueError> {
2099 let bytes = encode_dense_numeric_native_bytes(definition, values)?;
2100 decode_dense_native_bytes(definition, &bytes)
2101}
2102
2103pub fn decode_dense_binary_value(
2104 definition: DenseVectorDefinition,
2105 value: &DenseVectorBinaryValue,
2106) -> Result<Vec<f32>, DenseVectorValueError> {
2107 let bytes = decode_dense_binary_native_bytes(definition, value)?;
2108 decode_dense_native_bytes(definition, &bytes)
2109}
2110
2111fn decode_dense_binary_native_bytes(
2112 definition: DenseVectorDefinition,
2113 value: &DenseVectorBinaryValue,
2114) -> Result<Vec<u8>, DenseVectorValueError> {
2115 definition.validate()?;
2116 if value.element_type != definition.element_type {
2117 return Err(DenseVectorValueError::ElementTypeMismatch);
2118 }
2119 if value.quantization_scale != definition.quantization_scale {
2120 return Err(DenseVectorValueError::QuantizationScaleMismatch);
2121 }
2122 let expected_bytes = definition.encoded_bytes();
2123 let expected_base64 = expected_bytes.div_ceil(3) * 4;
2124 if value.data_base64.len() != expected_base64 {
2125 return Err(DenseVectorValueError::InvalidBase64Length {
2126 expected: expected_base64,
2127 observed: value.data_base64.len(),
2128 });
2129 }
2130 let bytes = BASE64_STANDARD
2131 .decode(value.data_base64.as_bytes())
2132 .map_err(|_| DenseVectorValueError::InvalidBase64)?;
2133 if bytes.len() != expected_bytes {
2134 return Err(DenseVectorValueError::DecodedByteLength {
2135 expected: expected_bytes,
2136 observed: bytes.len(),
2137 });
2138 }
2139 if BASE64_STANDARD.encode(&bytes) != value.data_base64 {
2140 return Err(DenseVectorValueError::InvalidBase64);
2141 }
2142 Ok(bytes)
2143}
2144
2145fn encode_dense_numeric_native_bytes(
2146 definition: DenseVectorDefinition,
2147 values: &[f64],
2148) -> Result<Vec<u8>, DenseVectorValueError> {
2149 definition.validate()?;
2150 if values.len() != usize::from(definition.dimensions) {
2151 return Err(DenseVectorValueError::DimensionMismatch {
2152 expected: definition.dimensions,
2153 observed: values.len(),
2154 });
2155 }
2156 let mut bytes = Vec::with_capacity(definition.encoded_bytes());
2157 for (ordinal, value) in values.iter().copied().enumerate() {
2158 if !value.is_finite() {
2159 return Err(DenseVectorValueError::NonFiniteElement(ordinal));
2160 }
2161 match definition.element_type {
2162 DenseVectorElementType::F32 => {
2163 let value = canonical_vector_zero(value as f32);
2164 if !value.is_finite() {
2165 return Err(DenseVectorValueError::NonFiniteElement(ordinal));
2166 }
2167 bytes.extend_from_slice(&value.to_bits().to_le_bytes());
2168 }
2169 DenseVectorElementType::F16 => {
2170 let value = value as f32;
2171 if !value.is_finite() {
2172 return Err(DenseVectorValueError::F16Overflow(ordinal));
2173 }
2174 let bits = f32_to_f16_rne(value).ok_or(DenseVectorValueError::F16Overflow(ordinal))?;
2175 bytes.extend_from_slice(&bits.to_le_bytes());
2176 }
2177 DenseVectorElementType::I8 => {
2178 if value.fract() != 0.0 || !(-128.0..=127.0).contains(&value) {
2179 return Err(DenseVectorValueError::I8ElementOutOfRange(ordinal));
2180 }
2181 bytes.push(value as i8 as u8);
2182 }
2183 }
2184 }
2185 Ok(bytes)
2186}
2187
2188pub fn decode_dense_native_bytes(
2192 definition: DenseVectorDefinition,
2193 bytes: &[u8],
2194) -> Result<Vec<f32>, DenseVectorValueError> {
2195 definition.validate()?;
2196 let expected = definition.encoded_bytes();
2197 if bytes.len() != expected {
2198 return Err(DenseVectorValueError::DecodedByteLength {
2199 expected,
2200 observed: bytes.len(),
2201 });
2202 }
2203 let mut decoded = Vec::with_capacity(usize::from(definition.dimensions));
2204 match definition.element_type {
2205 DenseVectorElementType::F32 => {
2206 for (ordinal, chunk) in bytes.as_chunks::<4>().0.iter().enumerate() {
2207 let value = f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
2208 if !value.is_finite() {
2209 return Err(DenseVectorValueError::NonFiniteElement(ordinal));
2210 }
2211 decoded.push(canonical_vector_zero(value));
2212 }
2213 }
2214 DenseVectorElementType::F16 => {
2215 for (ordinal, chunk) in bytes.as_chunks::<2>().0.iter().enumerate() {
2216 let value = f16_to_f32(u16::from_le_bytes([chunk[0], chunk[1]]));
2217 if !value.is_finite() {
2218 return Err(DenseVectorValueError::NonFiniteElement(ordinal));
2219 }
2220 decoded.push(canonical_vector_zero(value));
2221 }
2222 }
2223 DenseVectorElementType::I8 => {
2224 let scale = definition
2225 .quantization_scale
2226 .ok_or(DenseVectorValueError::MissingQuantizationScale)?
2227 .get();
2228 decoded.extend(
2229 bytes
2230 .iter()
2231 .map(|value| canonical_vector_zero((*value as i8 as f32) * scale)),
2232 );
2233 }
2234 }
2235 normalize_dense_for_metric(definition.metric, &mut decoded)?;
2236 Ok(decoded)
2237}
2238
2239fn normalize_dense_for_metric(metric: VectorMetric, values: &mut [f32]) -> Result<(), DenseVectorValueError> {
2240 if metric != VectorMetric::Cosine {
2241 return Ok(());
2242 }
2243 let squared_norm = values.iter().try_fold(0.0_f64, |sum, value| {
2244 let next = sum + f64::from(*value) * f64::from(*value);
2245 next.is_finite().then_some(next)
2246 });
2247 let norm = squared_norm
2248 .map(f64::sqrt)
2249 .filter(|value| *value > 0.0 && value.is_finite())
2250 .ok_or(DenseVectorValueError::ZeroNorm)?;
2251 for value in values {
2252 *value = canonical_vector_zero((f64::from(*value) / norm) as f32);
2253 if !value.is_finite() {
2254 return Err(DenseVectorValueError::NonFiniteElement(0));
2255 }
2256 }
2257 Ok(())
2258}
2259
2260const fn canonical_vector_zero(value: f32) -> f32 {
2261 if value == 0.0 { 0.0 } else { value }
2262}
2263
2264fn f32_to_f16_rne(value: f32) -> Option<u16> {
2265 if !value.is_finite() {
2266 return None;
2267 }
2268 let bits = canonical_vector_zero(value).to_bits();
2269 let sign = ((bits >> 16) & 0x8000) as u16;
2270 let exponent = ((bits >> 23) & 0xff) as i32 - 127;
2271 let fraction = bits & 0x7f_ffff;
2272 if exponent > 15 {
2273 return None;
2274 }
2275 if exponent >= -14 {
2276 let mut half_exponent = (exponent + 15) as u16;
2277 let mut half_fraction = (fraction >> 13) as u16;
2278 let remainder = fraction & 0x1fff;
2279 if remainder > 0x1000 || remainder == 0x1000 && half_fraction & 1 == 1 {
2280 half_fraction += 1;
2281 if half_fraction == 0x400 {
2282 half_fraction = 0;
2283 half_exponent += 1;
2284 if half_exponent >= 0x1f {
2285 return None;
2286 }
2287 }
2288 }
2289 return Some(sign | (half_exponent << 10) | half_fraction);
2290 }
2291 let significand = u64::from((1_u32 << 23) | fraction);
2292 let shift = u32::try_from(-exponent - 1).ok()?;
2293 let rounded = round_shift_right_ties_even(significand, shift);
2294 if rounded >= 0x400 {
2295 Some(sign | 0x0400)
2296 } else {
2297 Some(sign | rounded as u16)
2298 }
2299}
2300
2301fn round_shift_right_ties_even(value: u64, shift: u32) -> u64 {
2302 if shift == 0 {
2303 return value;
2304 }
2305 if shift >= 64 {
2306 return 0;
2307 }
2308 let quotient = value >> shift;
2309 let remainder_mask = (1_u64 << shift) - 1;
2310 let remainder = value & remainder_mask;
2311 let halfway = 1_u64 << (shift - 1);
2312 quotient + u64::from(remainder > halfway || remainder == halfway && quotient & 1 == 1)
2313}
2314
2315fn f16_to_f32(bits: u16) -> f32 {
2316 let sign = u32::from(bits & 0x8000) << 16;
2317 let exponent = (bits >> 10) & 0x1f;
2318 let fraction = bits & 0x03ff;
2319 match (exponent, fraction) {
2320 (0, 0) => f32::from_bits(sign),
2321 (0, _) => {
2322 let value = f32::from(fraction) * 2.0_f32.powi(-24);
2323 if sign == 0 { value } else { -value }
2324 }
2325 (0x1f, _) => f32::from_bits(sign | 0x7f80_0000 | (u32::from(fraction) << 13)),
2326 _ => {
2327 let f32_exponent = u32::from(exponent) + 112;
2328 f32::from_bits(sign | (f32_exponent << 23) | (u32::from(fraction) << 13))
2329 }
2330 }
2331}
2332
2333pub const MAX_MAPPING_FIELD_NAME_BYTES: usize = 1_024;
2335
2336#[derive(Clone, Debug, Eq, PartialEq)]
2341pub struct Mapping {
2342 pub format_version: u16,
2343 pub schema_generation: SchemaGeneration,
2344 pub mode: IndexMode,
2345 pub fields: BTreeMap<String, FieldMapping>,
2346 pub extensions: MappingExtensions,
2348}
2349
2350impl Mapping {
2351 #[must_use]
2352 pub fn empty(mode: IndexMode) -> Self {
2353 Self {
2354 format_version: MAPPING_FORMAT_VERSION,
2355 schema_generation: SchemaGeneration::new(1),
2356 mode,
2357 fields: BTreeMap::new(),
2358 extensions: crate::MappingExtensions::default(),
2359 }
2360 }
2361
2362 #[must_use]
2365 pub fn normalized(mut self) -> Self {
2366 if self.format_version == MAPPING_FORMAT_VERSION {
2367 if self.extensions.analyzer_identities.is_empty() {
2368 self.extensions.analyzer_identities = self
2369 .fields
2370 .iter()
2371 .filter(|(_, definition)| definition.field_type == FieldType::Text)
2372 .map(|(field, _)| AnalyzerIdentity {
2373 field: field.clone(),
2374 index_analyzer: AnalyzerName::Standard,
2375 search_analyzer: AnalyzerName::Standard,
2376 })
2377 .collect();
2378 }
2379 if self.extensions.keyword_limits.is_empty() {
2380 self.extensions.keyword_limits = self
2381 .fields
2382 .iter()
2383 .filter(|(_, definition)| definition.field_type == FieldType::Keyword)
2384 .map(|(field, _)| KeywordFieldPolicy {
2385 field: field.clone(),
2386 max_bytes: DEFAULT_KEYWORD_IGNORE_ABOVE,
2387 normalizer: KeywordNormalizer::Keyword,
2388 })
2389 .collect();
2390 }
2391 if self.extensions.nested_paths.is_empty() {
2392 self.extensions.nested_paths = self
2393 .fields
2394 .iter()
2395 .filter(|(_, definition)| definition.field_type == FieldType::Nested)
2396 .map(|(field, _)| field.clone())
2397 .collect();
2398 }
2399 }
2400 self
2401 }
2402
2403 pub fn decode_canonical(bytes: &[u8]) -> Result<Self, MappingCodecError> {
2406 if bytes.len() > MAX_MAPPING_BYTES {
2407 return Err(MappingCodecError::TooLarge(bytes.len()));
2408 }
2409 let mut probe = serde_json::Deserializer::from_slice(bytes);
2410 let version = MappingVersionProbe::deserialize(&mut probe).map_err(|error| {
2411 let message = error.to_string();
2412 if message.starts_with("duplicate mapping key '") {
2413 MappingCodecError::DuplicateKey(message)
2414 } else {
2415 MappingCodecError::Malformed(message)
2416 }
2417 })?;
2418 probe
2419 .end()
2420 .map_err(|error| MappingCodecError::Malformed(error.to_string()))?;
2421 if version != MAPPING_FORMAT_VERSION {
2422 return Err(MappingCodecError::UnsupportedVersion(version));
2423 }
2424 let mapping: Self =
2425 serde_json::from_slice(bytes).map_err(|error| MappingCodecError::Malformed(error.to_string()))?;
2426 mapping.validate().map_err(MappingCodecError::Invalid)?;
2427 let canonical =
2428 serde_json::to_vec(&mapping).map_err(|error| MappingCodecError::Malformed(error.to_string()))?;
2429 if canonical != bytes {
2430 return Err(MappingCodecError::NonCanonical);
2431 }
2432 Ok(mapping)
2433 }
2434
2435 pub fn encode_canonical(&self) -> Result<Vec<u8>, MappingCodecError> {
2436 self.validate().map_err(MappingCodecError::Invalid)?;
2437 serde_json::to_vec(self).map_err(|error| MappingCodecError::Malformed(error.to_string()))
2438 }
2439
2440 pub fn validate(&self) -> Result<(), MappingValidationError> {
2442 if self.format_version != MAPPING_FORMAT_VERSION {
2443 return Err(MappingValidationError::UnsupportedVersion(self.format_version));
2444 }
2445 let normalized = self.clone().normalized();
2446 let mapping = &normalized;
2447 if mapping.schema_generation.get() == 0 {
2448 return Err(MappingValidationError::ZeroSchemaGeneration);
2449 }
2450 if mapping.fields.len() > MAX_MAPPING_FIELDS {
2451 return Err(MappingValidationError::TooManyFields(mapping.fields.len()));
2452 }
2453 let mut vector_field_ids = std::collections::BTreeSet::new();
2454 let mut vector_fields = 0_usize;
2455 for (name, definition) in &mapping.fields {
2456 validate_mapping_field_name(name)?;
2457 for (separator, _) in name.match_indices('.') {
2458 let ancestor = &name[..separator];
2459 if mapping.fields.contains_key(ancestor)
2460 && !mapping.extensions.nested_paths.iter().any(|path| path == ancestor)
2461 && mapping
2462 .extensions
2463 .multi_field_projections
2464 .iter()
2465 .find(|projection| projection.child == *name)
2466 .is_none_or(|projection| projection.parent != ancestor)
2467 {
2468 return Err(MappingValidationError::ConflictingFieldPaths {
2469 ancestor: ancestor.to_owned(),
2470 descendant: name.clone(),
2471 });
2472 }
2473 }
2474 match definition.field_type {
2475 FieldType::Vector {
2476 format_version,
2477 field_id,
2478 dimensions,
2479 metric,
2480 element_type,
2481 quantization_scale,
2482 } => {
2483 vector_fields = vector_fields.saturating_add(1);
2484 if format_version != VECTOR_MAPPING_FORMAT_VERSION {
2485 return Err(MappingValidationError::UnsupportedVectorVersion(format_version));
2486 }
2487 if dimensions == 0 || dimensions > MAX_VECTOR_DIMENSIONS {
2488 return Err(MappingValidationError::InvalidVectorDimensions {
2489 field: name.clone(),
2490 dimensions,
2491 });
2492 }
2493 DenseVectorDefinition {
2494 dimensions,
2495 element_type,
2496 metric,
2497 quantization_scale,
2498 }
2499 .validate()
2500 .map_err(|_| MappingValidationError::InvalidVectorElementContract(name.clone()))?;
2501 if definition.stored {
2502 return Err(MappingValidationError::StoredVectorField(name.clone()));
2503 }
2504 if !vector_field_ids.insert(field_id) {
2505 return Err(MappingValidationError::DuplicateVectorFieldId(field_id));
2506 }
2507 }
2508 FieldType::SparseVector {
2509 format_version,
2510 field_id,
2511 maximum_dimension,
2512 weight_type,
2513 } => {
2514 vector_fields = vector_fields.saturating_add(1);
2515 if format_version != SPARSE_VECTOR_MAPPING_FORMAT_VERSION {
2516 return Err(MappingValidationError::UnsupportedSparseVectorVersion(format_version));
2517 }
2518 SparseVectorDefinition {
2519 maximum_dimension,
2520 weight_type,
2521 }
2522 .validate()
2523 .map_err(|_| MappingValidationError::InvalidSparseVectorContract(name.clone()))?;
2524 if definition.stored {
2525 return Err(MappingValidationError::StoredVectorField(name.clone()));
2526 }
2527 if !vector_field_ids.insert(field_id) {
2528 return Err(MappingValidationError::DuplicateVectorFieldId(field_id));
2529 }
2530 }
2531 FieldType::VectorSet {
2532 format_version,
2533 field_id,
2534 dimensions,
2535 metric,
2536 element_type,
2537 quantization_scale,
2538 maximum_vectors,
2539 } => {
2540 vector_fields = vector_fields.saturating_add(1);
2541 if format_version != VECTOR_SET_MAPPING_FORMAT_VERSION {
2542 return Err(MappingValidationError::UnsupportedVectorSetVersion(format_version));
2543 }
2544 VectorSetDefinition {
2545 dense: DenseVectorDefinition {
2546 dimensions,
2547 element_type,
2548 metric,
2549 quantization_scale,
2550 },
2551 maximum_vectors,
2552 }
2553 .validate()
2554 .map_err(|_| MappingValidationError::InvalidVectorSetContract(name.clone()))?;
2555 if definition.stored {
2556 return Err(MappingValidationError::StoredVectorField(name.clone()));
2557 }
2558 if !vector_field_ids.insert(field_id) {
2559 return Err(MappingValidationError::DuplicateVectorFieldId(field_id));
2560 }
2561 }
2562 _ => {}
2563 }
2564 definition.validate(name)?;
2565 }
2566 if vector_fields > MAX_VECTOR_MAPPING_FIELDS {
2567 return Err(MappingValidationError::TooManyVectorFields(vector_fields));
2568 }
2569 mapping.extensions.validate(mapping)?;
2570 mapping
2571 .extensions
2572 .record_model
2573 .validate(mapping.mode)
2574 .map_err(|error| MappingValidationError::InvalidRecordModel(error.to_string()))?;
2575 Ok(())
2576 }
2577
2578 pub fn validate_current(&self) -> Result<(), MappingValidationError> {
2580 self.validate()
2581 }
2582
2583 #[must_use]
2590 pub fn is_additive_successor_of(&self, predecessor: &Self) -> bool {
2591 let predecessor = predecessor.clone().normalized();
2592 let successor = self.clone().normalized();
2593 predecessor.format_version == MAPPING_FORMAT_VERSION
2594 && successor.format_version == MAPPING_FORMAT_VERSION
2595 && predecessor.mode == successor.mode
2596 && predecessor.extensions.record_model == successor.extensions.record_model
2597 && predecessor.extensions.dynamic_policy == successor.extensions.dynamic_policy
2598 && predecessor
2599 .fields
2600 .iter()
2601 .all(|(path, field)| successor.fields.get(path) == Some(field))
2602 && predecessor
2603 .extensions
2604 .multi_field_projections
2605 .iter()
2606 .all(|entry| successor.extensions.multi_field_projections.contains(entry))
2607 && predecessor
2608 .extensions
2609 .analyzer_identities
2610 .iter()
2611 .all(|entry| successor.extensions.analyzer_identities.contains(entry))
2612 && predecessor
2613 .extensions
2614 .keyword_limits
2615 .iter()
2616 .all(|entry| successor.extensions.keyword_limits.contains(entry))
2617 && predecessor
2618 .extensions
2619 .nested_paths
2620 .iter()
2621 .all(|entry| successor.extensions.nested_paths.contains(entry))
2622 && successor
2623 .extensions
2624 .nested_paths
2625 .iter()
2626 .filter(|path| !predecessor.extensions.nested_paths.contains(path))
2627 .all(|path| {
2628 predecessor.fields.keys().all(|field| {
2629 field != path
2630 && !field
2631 .strip_prefix(path.as_str())
2632 .is_some_and(|suffix| suffix.starts_with('.'))
2633 })
2634 })
2635 }
2636}
2637
2638struct MappingVersionProbe;
2639
2640impl MappingVersionProbe {
2641 fn deserialize<'de, D>(deserializer: D) -> Result<u16, D::Error>
2642 where
2643 D: serde::Deserializer<'de>,
2644 {
2645 struct VersionVisitor;
2646
2647 impl<'de> Visitor<'de> for VersionVisitor {
2648 type Value = u16;
2649
2650 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
2651 formatter.write_str("a mapping JSON object")
2652 }
2653
2654 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
2655 where
2656 A: MapAccess<'de>,
2657 {
2658 let mut keys = std::collections::BTreeSet::new();
2659 let mut version = None;
2660 while let Some(key) = map.next_key::<String>()? {
2661 if !keys.insert(key.clone()) {
2662 return Err(de::Error::custom(format!("duplicate mapping key '{key}'")));
2663 }
2664 let raw = map.next_value::<Box<serde_json::value::RawValue>>()?;
2665 if key == "format_version" {
2666 version = Some(
2667 serde_json::from_str::<u16>(raw.get())
2668 .map_err(|_| de::Error::custom("mapping format_version is invalid"))?,
2669 );
2670 }
2671 }
2672 version.ok_or_else(|| de::Error::custom("mapping format_version is missing"))
2673 }
2674 }
2675
2676 deserializer.deserialize_map(VersionVisitor)
2677 }
2678}
2679
2680impl Serialize for Mapping {
2681 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2682 where
2683 S: serde::Serializer,
2684 {
2685 use serde::ser::Error as _;
2686
2687 if self.format_version != MAPPING_FORMAT_VERSION {
2688 return Err(S::Error::custom(format!(
2689 "unsupported mapping format version {}",
2690 self.format_version
2691 )));
2692 }
2693 let normalized = self.clone().normalized();
2694 MappingWireRef {
2695 format_version: normalized.format_version,
2696 generation: normalized.schema_generation,
2697 mode: normalized.mode,
2698 record_model: &normalized.extensions.record_model,
2699 dynamic_policy: &normalized.extensions.dynamic_policy,
2700 fields: &normalized.fields,
2701 multi_field_projections: &normalized.extensions.multi_field_projections,
2702 analyzer_identities: &normalized.extensions.analyzer_identities,
2703 keyword_limits: &normalized.extensions.keyword_limits,
2704 nested_paths: &normalized.extensions.nested_paths,
2705 }
2706 .serialize(serializer)
2707 }
2708}
2709
2710impl<'de> Deserialize<'de> for Mapping {
2711 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2712 where
2713 D: serde::Deserializer<'de>,
2714 {
2715 use serde::de::Error as _;
2716
2717 let value = serde_json::Value::deserialize(deserializer)?;
2718 let version = value
2719 .get("format_version")
2720 .and_then(serde_json::Value::as_u64)
2721 .and_then(|value| u16::try_from(value).ok())
2722 .ok_or_else(|| D::Error::custom("mapping format_version is missing or invalid"))?;
2723 if version != MAPPING_FORMAT_VERSION {
2724 return Err(D::Error::custom(format!(
2725 "unsupported mapping format version {version}"
2726 )));
2727 }
2728 let wire: MappingWire = serde_json::from_value(value).map_err(D::Error::custom)?;
2729 Ok(Self {
2730 format_version: wire.format_version,
2731 schema_generation: wire.generation,
2732 mode: wire.mode,
2733 fields: wire.fields,
2734 extensions: MappingExtensions {
2735 record_model: wire.record_model,
2736 dynamic_policy: wire.dynamic_policy,
2737 multi_field_projections: wire.multi_field_projections,
2738 analyzer_identities: wire.analyzer_identities,
2739 keyword_limits: wire.keyword_limits,
2740 nested_paths: wire.nested_paths,
2741 },
2742 })
2743 }
2744}
2745
2746#[derive(Serialize)]
2747struct MappingWireRef<'a> {
2748 format_version: u16,
2749 generation: SchemaGeneration,
2750 mode: IndexMode,
2751 record_model: &'a IndexRecordModel,
2752 dynamic_policy: &'a DynamicMappingPolicy,
2753 fields: &'a BTreeMap<String, FieldMapping>,
2754 multi_field_projections: &'a Vec<MultiFieldProjection>,
2755 analyzer_identities: &'a Vec<AnalyzerIdentity>,
2756 keyword_limits: &'a Vec<KeywordFieldPolicy>,
2757 nested_paths: &'a Vec<String>,
2758}
2759
2760#[derive(Deserialize)]
2761#[serde(deny_unknown_fields)]
2762struct MappingWire {
2763 format_version: u16,
2764 generation: SchemaGeneration,
2765 mode: IndexMode,
2766 record_model: IndexRecordModel,
2767 dynamic_policy: DynamicMappingPolicy,
2768 fields: BTreeMap<String, FieldMapping>,
2769 multi_field_projections: Vec<MultiFieldProjection>,
2770 analyzer_identities: Vec<AnalyzerIdentity>,
2771 keyword_limits: Vec<KeywordFieldPolicy>,
2772 nested_paths: Vec<String>,
2773}
2774
2775#[derive(Clone, Debug, Default, Eq, PartialEq)]
2777pub struct MappingExtensions {
2778 pub record_model: IndexRecordModel,
2779 pub dynamic_policy: DynamicMappingPolicy,
2780 pub multi_field_projections: Vec<MultiFieldProjection>,
2781 pub analyzer_identities: Vec<AnalyzerIdentity>,
2782 pub keyword_limits: Vec<KeywordFieldPolicy>,
2783 pub nested_paths: Vec<String>,
2784}
2785
2786impl MappingExtensions {
2787 fn validate(&self, mapping: &Mapping) -> Result<(), MappingValidationError> {
2788 if self.multi_field_projections.len() > MAX_MAPPING_FIELDS
2789 || self.analyzer_identities.len() > MAX_MAPPING_FIELDS
2790 || self.keyword_limits.len() > MAX_MAPPING_FIELDS
2791 {
2792 return Err(MappingValidationError::TooManyMappingMetadata);
2793 }
2794 if self.nested_paths.len() > MAX_NESTED_MAPPING_PATHS {
2795 return Err(MappingValidationError::TooManyNestedPaths(self.nested_paths.len()));
2796 }
2797 require_sorted_unique(
2798 self.multi_field_projections
2799 .iter()
2800 .map(|projection| projection.child.as_str()),
2801 "multi_field_projections",
2802 )?;
2803 require_sorted_unique(
2804 self.analyzer_identities.iter().map(|identity| identity.field.as_str()),
2805 "analyzer_identities",
2806 )?;
2807 require_sorted_unique(
2808 self.keyword_limits.iter().map(|policy| policy.field.as_str()),
2809 "keyword_limits",
2810 )?;
2811 require_sorted_unique(self.nested_paths.iter().map(String::as_str), "nested_paths")?;
2812 let nested_paths = self
2813 .nested_paths
2814 .iter()
2815 .map(String::as_str)
2816 .collect::<std::collections::BTreeSet<_>>();
2817 for child in &self.nested_paths {
2818 for (separator, _) in child.match_indices('.') {
2819 let parent = &child[..separator];
2820 if nested_paths.contains(parent) {
2821 return Err(MappingValidationError::NestedPathOverlap {
2822 parent: parent.to_owned(),
2823 child: child.clone(),
2824 });
2825 }
2826 }
2827 }
2828 for projection in &self.multi_field_projections {
2829 validate_mapping_field_name(&projection.child)?;
2830 validate_mapping_field_name(&projection.parent)?;
2831 let Some(child) = mapping.fields.get(&projection.child) else {
2832 return Err(MappingValidationError::MissingMultiField(projection.child.clone()));
2833 };
2834 let Some(parent) = mapping.fields.get(&projection.parent) else {
2835 return Err(MappingValidationError::MissingMultiFieldSource {
2836 field: projection.child.clone(),
2837 source: projection.parent.clone(),
2838 });
2839 };
2840 if !projection
2841 .child
2842 .strip_prefix(&projection.parent)
2843 .is_some_and(|suffix| suffix.starts_with('.') && suffix.len() > 1)
2844 {
2845 return Err(MappingValidationError::InvalidMultiFieldPath(projection.child.clone()));
2846 }
2847 if matches!(
2848 child.field_type,
2849 FieldType::Vector { .. }
2850 | FieldType::SparseVector { .. }
2851 | FieldType::VectorSet { .. }
2852 | FieldType::Nested
2853 ) || matches!(
2854 parent.field_type,
2855 FieldType::Vector { .. }
2856 | FieldType::SparseVector { .. }
2857 | FieldType::VectorSet { .. }
2858 | FieldType::Nested
2859 ) {
2860 return Err(MappingValidationError::InvalidMultiFieldEndpoint(
2861 projection.child.clone(),
2862 ));
2863 }
2864 if !multi_field_types_are_compatible(parent.field_type, child.field_type) {
2865 return Err(MappingValidationError::InvalidMultiFieldEndpoint(
2866 projection.child.clone(),
2867 ));
2868 }
2869 }
2870 for identity in &self.analyzer_identities {
2871 let Some(field) = mapping.fields.get(&identity.field) else {
2872 return Err(MappingValidationError::UnknownFieldMetadata(identity.field.clone()));
2873 };
2874 if field.field_type != FieldType::Text {
2875 return Err(MappingValidationError::AnalyzerOnNonTextField(identity.field.clone()));
2876 }
2877 }
2878 for policy in &self.keyword_limits {
2879 let Some(field) = mapping.fields.get(&policy.field) else {
2880 return Err(MappingValidationError::UnknownFieldMetadata(policy.field.clone()));
2881 };
2882 if field.field_type != FieldType::Keyword {
2883 return Err(MappingValidationError::KeywordPolicyOnNonKeywordField(
2884 policy.field.clone(),
2885 ));
2886 }
2887 policy.validate()?;
2888 if let Some(MappingScalar::Keyword(value)) = &field.null_value
2889 && value.len() > policy.max_bytes as usize
2890 {
2891 return Err(MappingValidationError::InvalidKeywordNullValue(policy.field.clone()));
2892 }
2893 }
2894 for path in &self.nested_paths {
2895 if mapping
2896 .fields
2897 .get(path)
2898 .is_none_or(|field| field.field_type != FieldType::Nested)
2899 {
2900 return Err(MappingValidationError::InvalidNestedPath(path.clone()));
2901 }
2902 if path.split('.').count() > MAX_NESTED_MAPPING_DEPTH {
2903 return Err(MappingValidationError::NestedPathTooDeep(path.clone()));
2904 }
2905 }
2906 let text_fields = mapping
2907 .fields
2908 .iter()
2909 .filter_map(|(path, field)| (field.field_type == FieldType::Text).then_some(path.as_str()))
2910 .collect::<std::collections::BTreeSet<_>>();
2911 let analyzed_fields = self
2912 .analyzer_identities
2913 .iter()
2914 .map(|identity| identity.field.as_str())
2915 .collect::<std::collections::BTreeSet<_>>();
2916 if text_fields != analyzed_fields {
2917 return Err(MappingValidationError::IncompleteAnalyzerCoverage);
2918 }
2919 let keyword_fields = mapping
2920 .fields
2921 .iter()
2922 .filter_map(|(path, field)| (field.field_type == FieldType::Keyword).then_some(path.as_str()))
2923 .collect::<std::collections::BTreeSet<_>>();
2924 let limited_fields = self
2925 .keyword_limits
2926 .iter()
2927 .map(|policy| policy.field.as_str())
2928 .collect::<std::collections::BTreeSet<_>>();
2929 if keyword_fields != limited_fields {
2930 return Err(MappingValidationError::IncompleteKeywordCoverage);
2931 }
2932 let declared_nested = mapping
2933 .fields
2934 .iter()
2935 .filter_map(|(path, field)| (field.field_type == FieldType::Nested).then_some(path.as_str()))
2936 .collect::<std::collections::BTreeSet<_>>();
2937 let listed_nested = self
2938 .nested_paths
2939 .iter()
2940 .map(String::as_str)
2941 .collect::<std::collections::BTreeSet<_>>();
2942 if declared_nested != listed_nested {
2943 return Err(MappingValidationError::IncompleteNestedCoverage);
2944 }
2945 Ok(())
2946 }
2947}
2948
2949const fn multi_field_types_are_compatible(parent: FieldType, child: FieldType) -> bool {
2950 matches!(
2951 (parent, child),
2952 (
2953 FieldType::Text | FieldType::Keyword,
2954 FieldType::Text | FieldType::Keyword
2955 ) | (
2956 FieldType::U64 | FieldType::Date,
2957 FieldType::U64 | FieldType::Date | FieldType::F64
2958 ) | (FieldType::I64, FieldType::I64 | FieldType::F64)
2959 | (FieldType::F64, FieldType::F64)
2960 | (FieldType::Bool, FieldType::Bool)
2961 )
2962}
2963
2964fn require_sorted_unique<'a>(
2965 values: impl IntoIterator<Item = &'a str>,
2966 section: &'static str,
2967) -> Result<(), MappingValidationError> {
2968 let mut previous = None;
2969 for value in values {
2970 if previous.is_some_and(|prior| prior >= value) {
2971 return Err(MappingValidationError::MetadataNotSorted(section));
2972 }
2973 previous = Some(value);
2974 }
2975 Ok(())
2976}
2977
2978pub const MAX_DYNAMIC_FIELDS_PER_REQUEST: usize = 128;
2979pub const MAX_NESTED_MAPPING_DEPTH: usize = 8;
2980pub const MAX_NESTED_MAPPING_PATHS: usize = 64;
2981pub const DEFAULT_KEYWORD_IGNORE_ABOVE: u32 = 32_766;
2982pub const MAX_SCALAR_ARRAY_DEPTH: usize = 32;
2983pub const MAX_SCALAR_ARRAY_NODES: usize = 4_096;
2984pub const MAX_SCALAR_ARRAY_VALUES: usize = 2_048;
2985pub const MAX_MAPPING_BYTES: usize = 1_048_576;
2986pub const DYNAMIC_MAPPING_PROPOSAL_FORMAT_VERSION: u16 = 1;
2987
2988#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
2989#[serde(rename_all = "snake_case")]
2990pub enum DynamicMappingPolicy {
2991 True,
2992 #[default]
2993 False,
2994 Strict,
2995}
2996
2997#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2999#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
3000pub enum DynamicProposedTypeV1 {
3001 Boolean,
3002 I64,
3003 U64,
3004 F64,
3005 TextWithKeyword {
3006 index_analyzer: AnalyzerName,
3007 search_analyzer: AnalyzerName,
3008 keyword_child: String,
3009 keyword_normalizer: KeywordNormalizer,
3010 keyword_max_bytes: u32,
3011 },
3012}
3013
3014impl DynamicProposedTypeV1 {
3015 pub fn from_first_candidate(
3016 field_path: &str,
3017 candidate: &MappingScalar,
3018 ) -> Result<Self, DynamicMappingProposalError> {
3019 validate_mapping_field_name(field_path).map_err(DynamicMappingProposalError::InvalidField)?;
3020 Ok(match candidate {
3021 MappingScalar::Boolean(_) => Self::Boolean,
3022 MappingScalar::I64(_) => Self::I64,
3023 MappingScalar::U64(_) => Self::U64,
3024 MappingScalar::F64Bits(bits) => {
3025 let value = f64::from_bits(*bits);
3026 if !value.is_finite() {
3027 return Err(DynamicMappingProposalError::InvalidCandidate);
3028 }
3029 Self::F64
3030 }
3031 MappingScalar::Keyword(_) => Self::TextWithKeyword {
3032 index_analyzer: AnalyzerName::Standard,
3033 search_analyzer: AnalyzerName::Standard,
3034 keyword_child: format!("{field_path}.keyword"),
3035 keyword_normalizer: KeywordNormalizer::Keyword,
3036 keyword_max_bytes: DEFAULT_KEYWORD_IGNORE_ABOVE,
3037 },
3038 })
3039 }
3040
3041 fn validate(&self, field_path: &str) -> Result<(), DynamicMappingProposalError> {
3042 if let Self::TextWithKeyword {
3043 keyword_child,
3044 keyword_max_bytes,
3045 ..
3046 } = self
3047 && (keyword_child != &format!("{field_path}.keyword")
3048 || *keyword_max_bytes == 0
3049 || *keyword_max_bytes > DEFAULT_KEYWORD_IGNORE_ABOVE)
3050 {
3051 return Err(DynamicMappingProposalError::InvalidProposedType);
3052 }
3053 Ok(())
3054 }
3055}
3056
3057#[derive(Serialize)]
3058struct DynamicMappingProposalDigestInput<'a> {
3059 format_version: u16,
3060 collection_index_ref: CollectionIndexRef,
3061 base_generation: SchemaGeneration,
3062 field_path: &'a str,
3063 first_non_null_candidate: &'a MappingScalar,
3064 proposed_type: &'a DynamicProposedTypeV1,
3065}
3066
3067#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
3071#[serde(deny_unknown_fields)]
3072pub struct DynamicMappingProposalV1 {
3073 pub format_version: u16,
3074 pub collection_index_ref: CollectionIndexRef,
3075 pub base_generation: SchemaGeneration,
3076 pub field_path: String,
3077 pub first_non_null_candidate: MappingScalar,
3078 pub proposed_type: DynamicProposedTypeV1,
3079 pub proposal_digest: u64,
3080}
3081
3082impl DynamicMappingProposalV1 {
3083 pub fn new(
3084 collection_index_ref: CollectionIndexRef,
3085 base_generation: SchemaGeneration,
3086 field_path: String,
3087 mut first_non_null_candidate: MappingScalar,
3088 ) -> Result<Self, DynamicMappingProposalError> {
3089 if let MappingScalar::F64Bits(bits) = &mut first_non_null_candidate
3090 && f64::from_bits(*bits) == 0.0
3091 {
3092 *bits = 0.0_f64.to_bits();
3093 }
3094 let proposed_type = DynamicProposedTypeV1::from_first_candidate(&field_path, &first_non_null_candidate)?;
3095 let mut proposal = Self {
3096 format_version: DYNAMIC_MAPPING_PROPOSAL_FORMAT_VERSION,
3097 collection_index_ref,
3098 base_generation,
3099 field_path,
3100 first_non_null_candidate,
3101 proposed_type,
3102 proposal_digest: 0,
3103 };
3104 proposal.proposal_digest = proposal.exact_digest()?;
3105 proposal.validate()?;
3106 Ok(proposal)
3107 }
3108
3109 pub fn exact_digest(&self) -> Result<u64, DynamicMappingProposalError> {
3110 let input = DynamicMappingProposalDigestInput {
3111 format_version: self.format_version,
3112 collection_index_ref: self.collection_index_ref,
3113 base_generation: self.base_generation,
3114 field_path: &self.field_path,
3115 first_non_null_candidate: &self.first_non_null_candidate,
3116 proposed_type: &self.proposed_type,
3117 };
3118 let bytes =
3119 serde_json::to_vec(&input).map_err(|error| DynamicMappingProposalError::Codec(error.to_string()))?;
3120 if bytes.len() > MAX_MAPPING_BYTES {
3121 return Err(DynamicMappingProposalError::TooLarge(bytes.len()));
3122 }
3123 Ok(bytes.iter().fold(0xcbf2_9ce4_8422_2325_u64, |digest, byte| {
3124 (digest ^ u64::from(*byte)).wrapping_mul(0x0000_0100_0000_01b3)
3125 }))
3126 }
3127
3128 pub fn validate(&self) -> Result<(), DynamicMappingProposalError> {
3129 if self.format_version != DYNAMIC_MAPPING_PROPOSAL_FORMAT_VERSION {
3130 return Err(DynamicMappingProposalError::UnsupportedVersion(self.format_version));
3131 }
3132 validate_mapping_field_name(&self.field_path).map_err(DynamicMappingProposalError::InvalidField)?;
3133 if matches!(
3134 self.first_non_null_candidate,
3135 MappingScalar::F64Bits(bits) if f64::from_bits(bits) == 0.0 && bits != 0.0_f64.to_bits()
3136 ) {
3137 return Err(DynamicMappingProposalError::InvalidCandidate);
3138 }
3139 self.proposed_type.validate(&self.field_path)?;
3140 if DynamicProposedTypeV1::from_first_candidate(&self.field_path, &self.first_non_null_candidate)?
3141 != self.proposed_type
3142 || self.exact_digest()? != self.proposal_digest
3143 {
3144 return Err(DynamicMappingProposalError::IdentityMismatch);
3145 }
3146 Ok(())
3147 }
3148
3149 #[must_use]
3152 pub fn matches_successor(&self, predecessor: &Mapping, successor: &Mapping) -> bool {
3153 let Some(next_generation) = predecessor.schema_generation.checked_next() else {
3154 return false;
3155 };
3156 if self.validate().is_err()
3157 || self.base_generation != predecessor.schema_generation
3158 || successor.schema_generation != next_generation
3159 {
3160 return false;
3161 }
3162 let mut expected = predecessor.clone().normalized();
3163 expected.schema_generation = successor.schema_generation;
3164 let (field_type, text) = match &self.proposed_type {
3165 DynamicProposedTypeV1::Boolean => (FieldType::Bool, None),
3166 DynamicProposedTypeV1::I64 => (FieldType::I64, None),
3167 DynamicProposedTypeV1::U64 => (FieldType::U64, None),
3168 DynamicProposedTypeV1::F64 => (FieldType::F64, None),
3169 DynamicProposedTypeV1::TextWithKeyword {
3170 index_analyzer,
3171 search_analyzer,
3172 keyword_child,
3173 keyword_normalizer,
3174 keyword_max_bytes,
3175 } => (
3176 FieldType::Text,
3177 Some((
3178 *index_analyzer,
3179 *search_analyzer,
3180 keyword_child,
3181 *keyword_normalizer,
3182 *keyword_max_bytes,
3183 )),
3184 ),
3185 };
3186 if expected
3187 .fields
3188 .insert(
3189 self.field_path.clone(),
3190 FieldMapping {
3191 field_type,
3192 stored: false,
3193 null_value: None,
3194 },
3195 )
3196 .is_some()
3197 {
3198 return false;
3199 }
3200 if let Some((index_analyzer, search_analyzer, keyword_child, keyword_normalizer, keyword_max_bytes)) = text {
3201 if expected
3202 .fields
3203 .insert(
3204 keyword_child.clone(),
3205 FieldMapping {
3206 field_type: FieldType::Keyword,
3207 stored: false,
3208 null_value: None,
3209 },
3210 )
3211 .is_some()
3212 {
3213 return false;
3214 }
3215 expected.extensions.multi_field_projections.push(MultiFieldProjection {
3216 parent: self.field_path.clone(),
3217 child: keyword_child.clone(),
3218 });
3219 expected.extensions.analyzer_identities.push(AnalyzerIdentity {
3220 field: self.field_path.clone(),
3221 index_analyzer,
3222 search_analyzer,
3223 });
3224 expected.extensions.keyword_limits.push(KeywordFieldPolicy {
3225 field: keyword_child.clone(),
3226 max_bytes: keyword_max_bytes,
3227 normalizer: keyword_normalizer,
3228 });
3229 expected
3230 .extensions
3231 .multi_field_projections
3232 .sort_by(|left, right| left.child.cmp(&right.child));
3233 expected
3234 .extensions
3235 .analyzer_identities
3236 .sort_by(|left, right| left.field.cmp(&right.field));
3237 expected
3238 .extensions
3239 .keyword_limits
3240 .sort_by(|left, right| left.field.cmp(&right.field));
3241 }
3242 expected == successor.clone().normalized()
3243 }
3244
3245 pub fn encode_canonical(&self) -> Result<Vec<u8>, DynamicMappingProposalError> {
3246 self.validate()?;
3247 let bytes = serde_json::to_vec(self).map_err(|error| DynamicMappingProposalError::Codec(error.to_string()))?;
3248 if bytes.len() > MAX_MAPPING_BYTES {
3249 return Err(DynamicMappingProposalError::TooLarge(bytes.len()));
3250 }
3251 Ok(bytes)
3252 }
3253
3254 pub fn decode_canonical(bytes: &[u8]) -> Result<Self, DynamicMappingProposalError> {
3255 if bytes.len() > MAX_MAPPING_BYTES {
3256 return Err(DynamicMappingProposalError::TooLarge(bytes.len()));
3257 }
3258 let mut probe = serde_json::Deserializer::from_slice(bytes);
3259 let version = MappingVersionProbe::deserialize(&mut probe)
3260 .map_err(|error| DynamicMappingProposalError::Codec(error.to_string()))?;
3261 probe
3262 .end()
3263 .map_err(|error| DynamicMappingProposalError::Codec(error.to_string()))?;
3264 if version != DYNAMIC_MAPPING_PROPOSAL_FORMAT_VERSION {
3265 return Err(DynamicMappingProposalError::UnsupportedVersion(version));
3266 }
3267 let proposal: Self =
3268 serde_json::from_slice(bytes).map_err(|error| DynamicMappingProposalError::Codec(error.to_string()))?;
3269 proposal.validate()?;
3270 if proposal.encode_canonical()? != bytes {
3271 return Err(DynamicMappingProposalError::NonCanonical);
3272 }
3273 Ok(proposal)
3274 }
3275}
3276
3277#[derive(Debug)]
3278pub enum DynamicMappingProposalError {
3279 UnsupportedVersion(u16),
3280 InvalidField(MappingValidationError),
3281 InvalidCandidate,
3282 InvalidProposedType,
3283 IdentityMismatch,
3284 TooLarge(usize),
3285 NonCanonical,
3286 Codec(String),
3287}
3288
3289impl Display for DynamicMappingProposalError {
3290 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
3291 match self {
3292 Self::UnsupportedVersion(version) => {
3293 write!(formatter, "unsupported dynamic mapping proposal version {version}")
3294 }
3295 Self::InvalidField(error) => write!(formatter, "invalid dynamic mapping field: {error}"),
3296 Self::InvalidCandidate => formatter.write_str("invalid dynamic mapping first candidate"),
3297 Self::InvalidProposedType => formatter.write_str("invalid dynamic mapping proposed type"),
3298 Self::IdentityMismatch => formatter.write_str("dynamic mapping proposal identity mismatch"),
3299 Self::TooLarge(bytes) => write!(formatter, "dynamic mapping proposal is too large: {bytes} bytes"),
3300 Self::NonCanonical => formatter.write_str("dynamic mapping proposal is not canonical"),
3301 Self::Codec(error) => write!(formatter, "dynamic mapping proposal codec error: {error}"),
3302 }
3303 }
3304}
3305
3306impl Error for DynamicMappingProposalError {}
3307
3308#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
3309#[serde(deny_unknown_fields)]
3310pub struct MultiFieldProjection {
3311 pub parent: String,
3312 pub child: String,
3313}
3314
3315#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
3316#[serde(deny_unknown_fields)]
3317pub struct AnalyzerIdentity {
3318 pub field: String,
3319 pub index_analyzer: AnalyzerName,
3320 pub search_analyzer: AnalyzerName,
3321}
3322
3323#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
3324#[serde(rename_all = "snake_case")]
3325pub enum AnalyzerName {
3326 Keyword,
3327 Simple,
3328 Standard,
3329 Whitespace,
3330}
3331
3332#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
3333#[serde(deny_unknown_fields)]
3334pub struct KeywordFieldPolicy {
3335 pub field: String,
3336 pub max_bytes: u32,
3337 pub normalizer: KeywordNormalizer,
3338}
3339
3340impl KeywordFieldPolicy {
3341 fn validate(&self) -> Result<(), MappingValidationError> {
3342 if self.max_bytes == 0 || self.max_bytes > DEFAULT_KEYWORD_IGNORE_ABOVE {
3343 return Err(MappingValidationError::InvalidKeywordLimit {
3344 field: self.field.clone(),
3345 limit: self.max_bytes,
3346 });
3347 }
3348 Ok(())
3349 }
3350}
3351
3352#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
3353#[serde(rename_all = "snake_case")]
3354pub enum KeywordNormalizer {
3355 Keyword,
3356 Lowercase,
3357}
3358
3359#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
3361#[serde(deny_unknown_fields)]
3362pub struct FieldMapping {
3363 #[serde(rename = "type")]
3364 pub field_type: FieldType,
3365 #[serde(default)]
3366 pub stored: bool,
3367 #[serde(default, skip_serializing_if = "Option::is_none")]
3368 pub null_value: Option<MappingScalar>,
3369}
3370
3371impl FieldMapping {
3372 fn validate(&self, field: &str) -> Result<(), MappingValidationError> {
3373 let Some(value) = &self.null_value else {
3374 return Ok(());
3375 };
3376 let valid = match (self.field_type, value) {
3377 (FieldType::Text | FieldType::Keyword, MappingScalar::Keyword(_))
3378 | (FieldType::U64 | FieldType::Date, MappingScalar::U64(_))
3379 | (FieldType::I64, MappingScalar::I64(_))
3380 | (FieldType::Bool, MappingScalar::Boolean(_)) => true,
3381 (FieldType::U64 | FieldType::Date, MappingScalar::I64(value)) => *value >= 0,
3382 (FieldType::F64, MappingScalar::F64Bits(bits)) => f64::from_bits(*bits).is_finite(),
3383 (FieldType::F64, MappingScalar::I64(_) | MappingScalar::U64(_)) => true,
3384 _ => false,
3385 };
3386 if valid {
3387 Ok(())
3388 } else {
3389 Err(MappingValidationError::InvalidNullValue(field.to_owned()))
3390 }
3391 }
3392}
3393
3394#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
3395#[serde(tag = "kind", content = "value", rename_all = "snake_case", deny_unknown_fields)]
3396pub enum MappingScalar {
3397 Boolean(bool),
3398 I64(i64),
3399 U64(u64),
3400 F64Bits(u64),
3401 Keyword(String),
3402}
3403
3404#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
3406#[serde(rename_all = "snake_case")]
3407pub enum FieldType {
3408 Text,
3409 Keyword,
3410 U64,
3411 Date,
3413 I64,
3414 F64,
3415 Bool,
3416 Nested,
3419 Vector {
3422 format_version: u16,
3423 field_id: VectorFieldId,
3424 dimensions: u16,
3425 metric: VectorMetric,
3426 element_type: DenseVectorElementType,
3427 #[serde(default, skip_serializing_if = "Option::is_none")]
3428 quantization_scale: Option<VectorQuantizationScale>,
3429 },
3430 SparseVector {
3433 format_version: u16,
3434 field_id: VectorFieldId,
3435 maximum_dimension: u32,
3436 weight_type: SparseVectorWeightType,
3437 },
3438 VectorSet {
3441 format_version: u16,
3442 field_id: VectorFieldId,
3443 dimensions: u16,
3444 metric: VectorMetric,
3445 element_type: DenseVectorElementType,
3446 #[serde(default, skip_serializing_if = "Option::is_none")]
3447 quantization_scale: Option<VectorQuantizationScale>,
3448 maximum_vectors: u16,
3449 },
3450}
3451
3452#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
3454#[serde(rename_all = "snake_case")]
3455pub enum VectorMetric {
3456 Cosine,
3457 DotProduct,
3458 SquaredL2,
3459}
3460
3461#[must_use]
3468pub fn is_canonical_field_path(path: &str) -> bool {
3469 !path.is_empty()
3470 && path.len() <= MAX_MAPPING_FIELD_NAME_BYTES
3471 && !path.bytes().any(|byte| byte <= 0x1f || byte == 0x7f)
3472 && path.split('.').all(|component| !component.is_empty())
3473}
3474
3475pub fn resolve_field_path<'a, T>(
3482 root: &'a T,
3483 path: &str,
3484 member: impl FnMut(&'a T, &str) -> Option<&'a T>,
3485) -> Option<&'a T> {
3486 if !is_canonical_field_path(path) {
3487 return None;
3488 }
3489 path.split('.').try_fold(root, member)
3490}
3491
3492#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3494pub enum FieldPathResolution<'a, T> {
3495 Missing,
3496 Found(&'a T),
3497 Blocked,
3500}
3501
3502pub fn resolve_field_path_checked<'a, T>(
3505 root: &'a T,
3506 path: &str,
3507 mut member: impl FnMut(&'a T, &str) -> Option<&'a T>,
3508 is_container: impl Fn(&T) -> bool,
3509) -> FieldPathResolution<'a, T> {
3510 if !is_canonical_field_path(path) {
3511 return FieldPathResolution::Missing;
3512 }
3513 let mut current = root;
3514 for component in path.split('.') {
3515 if !is_container(current) {
3516 return FieldPathResolution::Blocked;
3517 }
3518 let Some(next) = member(current, component) else {
3519 return FieldPathResolution::Missing;
3520 };
3521 current = next;
3522 }
3523 FieldPathResolution::Found(current)
3524}
3525
3526pub fn validate_mapping_field_name(name: &str) -> Result<(), MappingValidationError> {
3528 if !is_canonical_field_path(name) || name.starts_with('_') {
3529 return Err(MappingValidationError::InvalidFieldName(name.to_owned()));
3530 }
3531 Ok(())
3532}
3533
3534#[derive(Clone, Debug, Eq, PartialEq)]
3536pub enum MappingValidationError {
3537 UnsupportedVersion(u16),
3538 ZeroSchemaGeneration,
3539 TooManyFields(usize),
3540 InvalidFieldName(String),
3541 ConflictingFieldPaths { ancestor: String, descendant: String },
3542 UnsupportedVectorVersion(u16),
3543 UnsupportedSparseVectorVersion(u16),
3544 UnsupportedVectorSetVersion(u16),
3545 TooManyVectorFields(usize),
3546 InvalidVectorDimensions { field: String, dimensions: u16 },
3547 InvalidVectorElementContract(String),
3548 InvalidSparseVectorContract(String),
3549 InvalidVectorSetContract(String),
3550 DuplicateVectorFieldId(VectorFieldId),
3551 StoredVectorField(String),
3552 MissingMultiField(String),
3553 MissingMultiFieldSource { field: String, source: String },
3554 InvalidMultiFieldPath(String),
3555 InvalidMultiFieldEndpoint(String),
3556 UnknownFieldMetadata(String),
3557 AnalyzerOnNonTextField(String),
3558 KeywordPolicyOnNonKeywordField(String),
3559 InvalidKeywordLimit { field: String, limit: u32 },
3560 InvalidKeywordNullValue(String),
3561 InvalidNullValue(String),
3562 InvalidNestedPath(String),
3563 InvalidRecordModel(String),
3564 NestedPathTooDeep(String),
3565 NestedPathOverlap { parent: String, child: String },
3566 IncompleteAnalyzerCoverage,
3567 IncompleteKeywordCoverage,
3568 IncompleteNestedCoverage,
3569 MetadataNotSorted(&'static str),
3570 TooManyMappingMetadata,
3571 TooManyNestedPaths(usize),
3572}
3573
3574impl Display for MappingValidationError {
3575 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
3576 match self {
3577 Self::UnsupportedVersion(version) => write!(formatter, "unsupported mapping format version {version}"),
3578 Self::ZeroSchemaGeneration => formatter.write_str("mapping schema generation must be nonzero"),
3579 Self::TooManyFields(count) => write!(formatter, "mapping has {count} fields"),
3580 Self::InvalidFieldName(name) => write!(formatter, "invalid mapped field name '{name}'"),
3581 Self::ConflictingFieldPaths { ancestor, descendant } => write!(
3582 formatter,
3583 "mapped scalar path '{ancestor}' conflicts with descendant '{descendant}'"
3584 ),
3585 Self::UnsupportedVectorVersion(version) => {
3586 write!(formatter, "unsupported vector mapping format version {version}")
3587 }
3588 Self::UnsupportedSparseVectorVersion(version) => {
3589 write!(formatter, "unsupported sparse-vector mapping format version {version}")
3590 }
3591 Self::UnsupportedVectorSetVersion(version) => {
3592 write!(formatter, "unsupported vector-set mapping format version {version}")
3593 }
3594 Self::TooManyVectorFields(count) => write!(formatter, "mapping has {count} vector fields"),
3595 Self::InvalidVectorDimensions { field, dimensions } => {
3596 write!(formatter, "vector field '{field}' has invalid dimensions {dimensions}")
3597 }
3598 Self::InvalidVectorElementContract(field) => {
3599 write!(
3600 formatter,
3601 "vector field '{field}' has invalid element or quantization semantics"
3602 )
3603 }
3604 Self::InvalidSparseVectorContract(field) => {
3605 write!(
3606 formatter,
3607 "sparse vector field '{field}' has invalid dimension or weight semantics"
3608 )
3609 }
3610 Self::InvalidVectorSetContract(field) => {
3611 write!(
3612 formatter,
3613 "vector-set field '{field}' has invalid element, dimension, or count semantics"
3614 )
3615 }
3616 Self::DuplicateVectorFieldId(field_id) => {
3617 write!(formatter, "vector field identity {field_id} is duplicated")
3618 }
3619 Self::StoredVectorField(field) => {
3620 write!(
3621 formatter,
3622 "vector field '{field}' cannot use Tantivy stored-field semantics"
3623 )
3624 }
3625 Self::MissingMultiField(field) => write!(formatter, "multi-field projection '{field}' has no field"),
3626 Self::MissingMultiFieldSource { field, source } => {
3627 write!(
3628 formatter,
3629 "multi-field projection '{field}' has missing source '{source}'"
3630 )
3631 }
3632 Self::InvalidMultiFieldPath(field) => {
3633 write!(
3634 formatter,
3635 "multi-field projection '{field}' is not a direct child of its source"
3636 )
3637 }
3638 Self::InvalidMultiFieldEndpoint(field) => {
3639 write!(
3640 formatter,
3641 "multi-field projection '{field}' has a nested or vector endpoint"
3642 )
3643 }
3644 Self::UnknownFieldMetadata(field) => {
3645 write!(formatter, "mapping metadata references unknown field '{field}'")
3646 }
3647 Self::AnalyzerOnNonTextField(field) => {
3648 write!(formatter, "analyzer identity is attached to non-text field '{field}'")
3649 }
3650 Self::KeywordPolicyOnNonKeywordField(field) => {
3651 write!(formatter, "keyword policy is attached to non-keyword field '{field}'")
3652 }
3653 Self::InvalidKeywordLimit { field, limit } => {
3654 write!(formatter, "keyword field '{field}' has invalid ignore_above {limit}")
3655 }
3656 Self::InvalidKeywordNullValue(field) => {
3657 write!(
3658 formatter,
3659 "keyword field '{field}' has a null_value beyond ignore_above"
3660 )
3661 }
3662 Self::InvalidNullValue(field) => write!(formatter, "field '{field}' has an invalid null_value"),
3663 Self::InvalidNestedPath(path) => write!(formatter, "nested path '{path}' has no nested field"),
3664 Self::InvalidRecordModel(reason) => write!(formatter, "invalid index record model: {reason}"),
3665 Self::NestedPathTooDeep(path) => write!(formatter, "nested path '{path}' exceeds maximum depth"),
3666 Self::NestedPathOverlap { parent, child } => {
3667 write!(formatter, "nested path '{child}' is below nested path '{parent}'")
3668 }
3669 Self::IncompleteAnalyzerCoverage => {
3670 formatter.write_str("every text field must have exactly one analyzer identity")
3671 }
3672 Self::IncompleteKeywordCoverage => {
3673 formatter.write_str("every keyword field must have exactly one keyword limit")
3674 }
3675 Self::IncompleteNestedCoverage => formatter.write_str("nested_paths must exactly cover nested fields"),
3676 Self::MetadataNotSorted(section) => {
3677 write!(formatter, "mapping section '{section}' is not sorted and unique")
3678 }
3679 Self::TooManyMappingMetadata => formatter.write_str("mapping metadata exceeds the field bound"),
3680 Self::TooManyNestedPaths(count) => write!(formatter, "mapping has {count} nested paths"),
3681 }
3682 }
3683}
3684
3685impl Error for MappingValidationError {}
3686
3687#[derive(Clone, Debug, Eq, PartialEq)]
3688pub enum MappingCodecError {
3689 TooLarge(usize),
3690 DuplicateKey(String),
3691 Malformed(String),
3692 UnsupportedVersion(u16),
3693 Invalid(MappingValidationError),
3694 NonCanonical,
3695}
3696
3697impl Display for MappingCodecError {
3698 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
3699 match self {
3700 Self::TooLarge(bytes) => write!(formatter, "mapping payload has {bytes} bytes"),
3701 Self::DuplicateKey(reason) | Self::Malformed(reason) => formatter.write_str(reason),
3702 Self::UnsupportedVersion(version) => write!(formatter, "unsupported mapping format version {version}"),
3703 Self::Invalid(error) => write!(formatter, "invalid mapping: {error}"),
3704 Self::NonCanonical => formatter.write_str("mapping payload is not canonical JSON"),
3705 }
3706 }
3707}
3708
3709impl Error for MappingCodecError {
3710 fn source(&self) -> Option<&(dyn Error + 'static)> {
3711 match self {
3712 Self::Invalid(error) => Some(error),
3713 _ => None,
3714 }
3715 }
3716}
3717
3718#[non_exhaustive]
3723#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
3724#[serde(rename_all = "snake_case")]
3725pub enum StorageClass {
3726 #[default]
3727 Standard,
3728 Cold,
3729}
3730
3731#[non_exhaustive]
3733#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
3734#[serde(rename_all = "snake_case")]
3735pub enum CacheClass {
3736 Pinned,
3737 #[default]
3738 Normal,
3739 ColdTolerant,
3740}
3741
3742impl CacheClass {
3743 #[must_use]
3745 pub const fn as_str(self) -> &'static str {
3746 match self {
3747 Self::Pinned => "pinned",
3748 Self::Normal => "normal",
3749 Self::ColdTolerant => "cold_tolerant",
3750 }
3751 }
3752}
3753
3754impl Display for CacheClass {
3755 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
3756 formatter.write_str(self.as_str())
3757 }
3758}
3759
3760impl StorageClass {
3761 #[must_use]
3763 pub const fn is_standard(&self) -> bool {
3764 matches!(self, Self::Standard)
3765 }
3766
3767 #[must_use]
3769 pub const fn as_str(self) -> &'static str {
3770 match self {
3771 Self::Standard => "standard",
3772 Self::Cold => "cold",
3773 }
3774 }
3775}
3776
3777impl Display for StorageClass {
3778 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
3779 formatter.write_str(self.as_str())
3780 }
3781}
3782
3783#[non_exhaustive]
3788#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
3789#[serde(rename_all = "snake_case")]
3790pub enum LifecycleVerb {
3791 Transition,
3792 AggregateThenDelete,
3793}
3794
3795#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
3797#[serde(transparent)]
3798pub struct DocumentId(String);
3799
3800impl DocumentId {
3801 pub fn new(value: impl Into<String>) -> Result<Self, IdentifierError> {
3803 let value = value.into();
3804 if value.is_empty() {
3805 return Err(IdentifierError::EmptyDocumentId);
3806 }
3807 if value.len() > 512 {
3808 return Err(IdentifierError::DocumentIdTooLong(value.len()));
3809 }
3810 Ok(Self(value))
3811 }
3812
3813 #[must_use]
3815 pub fn as_str(&self) -> &str {
3816 &self.0
3817 }
3818
3819 #[must_use]
3821 pub fn into_inner(self) -> String {
3822 self.0
3823 }
3824}
3825
3826impl<'de> Deserialize<'de> for DocumentId {
3827 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3828 where
3829 D: Deserializer<'de>,
3830 {
3831 let value = String::deserialize(deserializer)?;
3832 Self::new(value).map_err(serde::de::Error::custom)
3833 }
3834}
3835
3836impl Display for DocumentId {
3837 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
3838 formatter.write_str(self.as_str())
3839 }
3840}
3841
3842#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
3844#[serde(transparent)]
3845pub struct IdempotencyKey(String);
3846
3847impl IdempotencyKey {
3848 pub fn new(value: impl Into<String>) -> Result<Self, IdentifierError> {
3850 let value = value.into();
3851 if value.is_empty() {
3852 return Err(IdentifierError::EmptyIdempotencyKey);
3853 }
3854 if value.len() > 256 {
3855 return Err(IdentifierError::IdempotencyKeyTooLong(value.len()));
3856 }
3857 Ok(Self(value))
3858 }
3859
3860 #[must_use]
3861 pub fn as_str(&self) -> &str {
3862 &self.0
3863 }
3864}
3865
3866impl<'de> Deserialize<'de> for IdempotencyKey {
3867 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3868 where
3869 D: Deserializer<'de>,
3870 {
3871 let value = String::deserialize(deserializer)?;
3872 Self::new(value).map_err(serde::de::Error::custom)
3873 }
3874}
3875
3876impl Display for IdempotencyKey {
3877 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
3878 formatter.write_str(self.as_str())
3879 }
3880}
3881
3882#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
3884#[serde(transparent)]
3885pub struct PitId(String);
3886
3887impl PitId {
3888 pub fn new(value: impl Into<String>) -> Result<Self, IdentifierError> {
3890 let value = value.into();
3891 if value.is_empty() {
3892 return Err(IdentifierError::EmptyPitId);
3893 }
3894 if value.len() > 512 {
3895 return Err(IdentifierError::PitIdTooLong(value.len()));
3896 }
3897 Ok(Self(value))
3898 }
3899
3900 #[must_use]
3902 pub fn as_str(&self) -> &str {
3903 &self.0
3904 }
3905}
3906
3907impl<'de> Deserialize<'de> for PitId {
3908 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3909 where
3910 D: Deserializer<'de>,
3911 {
3912 let value = String::deserialize(deserializer)?;
3913 Self::new(value).map_err(serde::de::Error::custom)
3914 }
3915}
3916
3917impl Display for PitId {
3918 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
3919 formatter.write_str(self.as_str())
3920 }
3921}
3922
3923pub const SCOPED_PIT_ID_FORMAT_VERSION: u16 = 1;
3925
3926#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)]
3931#[serde(deny_unknown_fields)]
3932pub struct ScopedPitId {
3933 format_version: u16,
3934 recovery_era: RecoveryEra,
3935 nonce: PitId,
3936}
3937
3938impl ScopedPitId {
3939 #[must_use]
3940 pub const fn new(recovery_era: RecoveryEra, nonce: PitId) -> Self {
3941 Self {
3942 format_version: SCOPED_PIT_ID_FORMAT_VERSION,
3943 recovery_era,
3944 nonce,
3945 }
3946 }
3947
3948 #[must_use]
3949 pub const fn recovery_era(&self) -> RecoveryEra {
3950 self.recovery_era
3951 }
3952
3953 #[must_use]
3954 pub const fn nonce(&self) -> &PitId {
3955 &self.nonce
3956 }
3957}
3958
3959impl Ord for ScopedPitId {
3960 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3961 self.recovery_era
3962 .to_be_bytes()
3963 .cmp(&other.recovery_era.to_be_bytes())
3964 .then_with(|| self.nonce.cmp(&other.nonce))
3965 }
3966}
3967
3968impl PartialOrd for ScopedPitId {
3969 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
3970 Some(self.cmp(other))
3971 }
3972}
3973
3974impl<'de> Deserialize<'de> for ScopedPitId {
3975 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3976 where
3977 D: Deserializer<'de>,
3978 {
3979 #[derive(Deserialize)]
3980 #[serde(deny_unknown_fields)]
3981 struct Wire {
3982 format_version: u16,
3983 recovery_era: RecoveryEra,
3984 nonce: PitId,
3985 }
3986
3987 let wire = Wire::deserialize(deserializer)?;
3988 if wire.format_version != SCOPED_PIT_ID_FORMAT_VERSION {
3989 return Err(serde::de::Error::custom(
3990 IdentifierError::UnsupportedScopedPitIdVersion(wire.format_version),
3991 ));
3992 }
3993 Ok(Self::new(wire.recovery_era, wire.nonce))
3994 }
3995}
3996
3997#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
3999#[serde(transparent)]
4000pub struct OperationFingerprint([u8; 32]);
4001
4002impl OperationFingerprint {
4003 #[must_use]
4004 pub const fn new(bytes: [u8; 32]) -> Self {
4005 Self(bytes)
4006 }
4007
4008 #[must_use]
4009 pub fn from_payload(bytes: &[u8]) -> Self {
4010 Self(*blake3::hash(bytes).as_bytes())
4011 }
4012
4013 #[must_use]
4014 pub const fn as_bytes(&self) -> &[u8; 32] {
4015 &self.0
4016 }
4017}
4018
4019#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
4021#[serde(transparent)]
4022pub struct ObjectKey(String);
4023
4024impl ObjectKey {
4025 pub fn new(value: impl Into<String>) -> Result<Self, IdentifierError> {
4027 let value = value.into();
4028 if value.is_empty() {
4029 return Err(IdentifierError::EmptyObjectKey);
4030 }
4031 if value.starts_with('/') || value.contains('\\') {
4032 return Err(IdentifierError::InvalidObjectKey(value));
4033 }
4034 if value
4035 .split('/')
4036 .any(|component| component.is_empty() || component == "." || component == "..")
4037 {
4038 return Err(IdentifierError::InvalidObjectKey(value));
4039 }
4040 Ok(Self(value))
4041 }
4042
4043 #[must_use]
4045 pub fn as_str(&self) -> &str {
4046 &self.0
4047 }
4048
4049 #[must_use]
4054 pub fn into_inner(self) -> String {
4055 self.0
4056 }
4057
4058 #[must_use]
4060 pub fn allocated_capacity_bytes(&self) -> usize {
4061 self.0.capacity()
4062 }
4063}
4064
4065impl<'de> Deserialize<'de> for ObjectKey {
4066 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
4067 where
4068 D: Deserializer<'de>,
4069 {
4070 let value = String::deserialize(deserializer)?;
4071 Self::new(value).map_err(serde::de::Error::custom)
4072 }
4073}
4074
4075impl Display for ObjectKey {
4076 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
4077 formatter.write_str(self.as_str())
4078 }
4079}
4080
4081pub const ROOT_SET_REF_FORMAT_VERSION: u16 = 2;
4084pub const MAX_ROOT_SET_PAGES: u32 = 256;
4085pub const MAX_ROOT_SET_SCHEMA_GENERATIONS: usize = 256;
4086pub const MAX_ROOT_SET_OBJECT_KEY_BYTES: usize = 1_024;
4087pub const PINNED_MANIFEST_BINDING_FORMAT_VERSION: u16 = 1;
4088
4089#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
4091#[serde(deny_unknown_fields)]
4092pub struct PhysicalObjectId {
4093 pub backend_authority: BackendAuthorityId,
4094 pub object_incarnation: ObjectIncarnation,
4095 pub object_key: ObjectKey,
4096}
4097
4098impl PhysicalObjectId {
4099 pub fn validate(&self) -> Result<(), RootSetRefError> {
4100 let length = self.object_key.as_str().len();
4101 if !(1..=MAX_ROOT_SET_OBJECT_KEY_BYTES).contains(&length) {
4102 return Err(RootSetRefError::ObjectKeyLength(length));
4103 }
4104 Ok(())
4105 }
4106}
4107
4108#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
4110#[serde(deny_unknown_fields)]
4111pub struct ObjectIdentity {
4112 pub physical: PhysicalObjectId,
4113 pub content_checksum: [u8; 32],
4114}
4115
4116impl ObjectIdentity {
4117 pub fn validate(&self) -> Result<(), RootSetRefError> {
4118 self.physical.validate()
4119 }
4120}
4121
4122#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
4124#[serde(deny_unknown_fields)]
4125pub struct RootSetRef {
4126 pub format_version: u16,
4127 pub root: PhysicalObjectId,
4128 pub content_digest: [u8; 32],
4129 pub item_count: u64,
4130 pub page_count: u32,
4131 pub schema_generations: Vec<SchemaGeneration>,
4132}
4133
4134impl RootSetRef {
4135 pub fn validate(&self) -> Result<(), RootSetRefError> {
4136 if self.format_version != ROOT_SET_REF_FORMAT_VERSION {
4137 return Err(RootSetRefError::UnsupportedVersion(self.format_version));
4138 }
4139 self.root.validate()?;
4140 if self.item_count == 0
4141 || self.page_count == 0
4142 || self.page_count > MAX_ROOT_SET_PAGES
4143 || u64::from(self.page_count) > self.item_count
4144 {
4145 return Err(RootSetRefError::InvalidCounts {
4146 items: self.item_count,
4147 pages: self.page_count,
4148 });
4149 }
4150 if self.schema_generations.len() > MAX_ROOT_SET_SCHEMA_GENERATIONS
4151 || self.schema_generations.windows(2).any(|pair| pair[0] >= pair[1])
4152 {
4153 return Err(RootSetRefError::InvalidSchemaGenerations);
4154 }
4155 Ok(())
4156 }
4157}
4158
4159#[derive(Clone, Debug, Eq, PartialEq)]
4160pub enum RootSetRefError {
4161 UnsupportedVersion(u16),
4162 ObjectKeyLength(usize),
4163 InvalidCounts { items: u64, pages: u32 },
4164 InvalidSchemaGenerations,
4165}
4166
4167impl Display for RootSetRefError {
4168 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
4169 match self {
4170 Self::UnsupportedVersion(version) => write!(formatter, "unsupported root-set reference version {version}"),
4171 Self::ObjectKeyLength(length) => {
4172 write!(formatter, "root-set object key is {length} bytes; expected 1..=1024")
4173 }
4174 Self::InvalidCounts { items, pages } => {
4175 write!(formatter, "root-set counts are invalid: items={items}, pages={pages}")
4176 }
4177 Self::InvalidSchemaGenerations => {
4178 formatter.write_str("root-set schema generations are not bounded strict ascending values")
4179 }
4180 }
4181 }
4182}
4183
4184impl Error for RootSetRefError {}
4185
4186#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
4188#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
4189pub enum PinnedManifestDecodeProvenanceV1 {
4190 ScopedManifestV3,
4191}
4192
4193#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
4199#[serde(deny_unknown_fields)]
4200pub struct PinnedManifestBindingV1 {
4201 pub format_version: u16,
4202 pub shard_key: ShardKey,
4203 pub admission_generation: AdmissionGeneration,
4204 pub routing_revision: RoutingRevision,
4205 pub data_backend_authority: BackendAuthorityId,
4206 pub generation: ManifestGeneration,
4207 pub fence: FenceToken,
4208 pub primary_term: PrimaryTerm,
4209 pub manifest: ObjectIdentity,
4210 pub root_set: RootSetRef,
4211 pub committed_through: SeqNo,
4212 pub decode_provenance: PinnedManifestDecodeProvenanceV1,
4213}
4214
4215impl PinnedManifestBindingV1 {
4216 pub fn validate(&self) -> Result<(), PinnedManifestBindingError> {
4217 if self.format_version != PINNED_MANIFEST_BINDING_FORMAT_VERSION {
4218 return Err(PinnedManifestBindingError::UnsupportedVersion(self.format_version));
4219 }
4220 if self.admission_generation.get() == 0
4221 || self.routing_revision.get() == 0
4222 || self.generation.get() == 0
4223 || self.fence.epoch().get() == 0
4224 || self.fence.epoch() != self.primary_term.epoch_component()
4225 {
4226 return Err(PinnedManifestBindingError::InvalidSemanticIdentity);
4227 }
4228 self.manifest
4229 .validate()
4230 .map_err(|error| PinnedManifestBindingError::InvalidObject(error.to_string()))?;
4231 self.root_set
4232 .validate()
4233 .map_err(|error| PinnedManifestBindingError::InvalidRootSet(error.to_string()))?;
4234 if self.manifest.physical.backend_authority != self.data_backend_authority
4235 || self.root_set.root.backend_authority != self.data_backend_authority
4236 {
4237 return Err(PinnedManifestBindingError::BackendAuthorityMismatch);
4238 }
4239 Ok(())
4240 }
4241}
4242
4243pub fn vector_snapshot_digest_v1(
4249 scope: CollectionIndexRef,
4250 schema_generation: SchemaGeneration,
4251 roots: &[PinnedManifestBindingV1],
4252) -> Result<[u8; 32], PinnedManifestBindingError> {
4253 if schema_generation.get() == 0
4254 || roots
4255 .iter()
4256 .any(|root| root.shard_key.collection_index_ref() != scope || root.validate().is_err())
4257 || roots
4258 .windows(2)
4259 .any(|pair| pair[0].shard_key.shard_id() >= pair[1].shard_key.shard_id())
4260 {
4261 return Err(PinnedManifestBindingError::InvalidSemanticIdentity);
4262 }
4263 let mut hasher = blake3::Hasher::new();
4264 hasher.update(b"lodestone-vector-snapshot-v1\0");
4265 hasher.update(&scope.collection_id().get().to_be_bytes());
4266 hasher.update(&scope.index_id().get().to_be_bytes());
4267 hasher.update(&schema_generation.get().to_be_bytes());
4268 let count = u32::try_from(roots.len()).map_err(|_| PinnedManifestBindingError::InvalidSemanticIdentity)?;
4269 hasher.update(&count.to_be_bytes());
4270 for root in roots {
4271 hasher.update(&root.shard_key.shard_id().get().to_be_bytes());
4272 hasher.update(&root.admission_generation.get().to_be_bytes());
4273 hasher.update(&root.routing_revision.get().to_be_bytes());
4274 hasher.update(&root.generation.get().to_be_bytes());
4275 hasher.update(&root.fence.epoch().get().to_be_bytes());
4276 hasher.update(&root.primary_term.get().to_be_bytes());
4277 hasher.update(&root.manifest.content_checksum);
4278 hasher.update(&root.root_set.content_digest);
4279 hasher.update(&root.committed_through.get().to_be_bytes());
4280 }
4281 Ok(*hasher.finalize().as_bytes())
4282}
4283
4284#[derive(Clone, Debug, Eq, PartialEq)]
4285pub enum PinnedManifestBindingError {
4286 UnsupportedVersion(u16),
4287 InvalidSemanticIdentity,
4288 InvalidObject(String),
4289 InvalidRootSet(String),
4290 BackendAuthorityMismatch,
4291}
4292
4293impl Display for PinnedManifestBindingError {
4294 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
4295 match self {
4296 Self::UnsupportedVersion(version) => {
4297 write!(formatter, "unsupported pinned manifest binding version {version}")
4298 }
4299 Self::InvalidSemanticIdentity => formatter.write_str("pinned manifest semantic identity is invalid"),
4300 Self::InvalidObject(reason) => write!(formatter, "pinned manifest object is invalid: {reason}"),
4301 Self::InvalidRootSet(reason) => write!(formatter, "pinned manifest root set is invalid: {reason}"),
4302 Self::BackendAuthorityMismatch => formatter.write_str("pinned manifest authorities do not match"),
4303 }
4304 }
4305}
4306
4307impl Error for PinnedManifestBindingError {}
4308
4309#[derive(Clone, Debug, Eq, PartialEq)]
4311pub enum IdentifierError {
4312 EmptyDocumentId,
4313 DocumentIdTooLong(usize),
4314 EmptyIdempotencyKey,
4315 IdempotencyKeyTooLong(usize),
4316 EmptyPitId,
4317 PitIdTooLong(usize),
4318 UnsupportedScopedPitIdVersion(u16),
4319 EmptyObjectKey,
4320 InvalidObjectKey(String),
4321}
4322
4323impl Display for IdentifierError {
4324 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
4325 match self {
4326 Self::EmptyDocumentId => formatter.write_str("document id must not be empty"),
4327 Self::DocumentIdTooLong(length) => {
4328 write!(formatter, "document id is {length} bytes; maximum is 512")
4329 }
4330 Self::EmptyIdempotencyKey => formatter.write_str("idempotency key must not be empty"),
4331 Self::IdempotencyKeyTooLong(length) => {
4332 write!(formatter, "idempotency key is {length} bytes; maximum is 256")
4333 }
4334 Self::EmptyPitId => formatter.write_str("PIT id must not be empty"),
4335 Self::PitIdTooLong(length) => {
4336 write!(formatter, "PIT id is {length} bytes; maximum is 512")
4337 }
4338 Self::UnsupportedScopedPitIdVersion(version) => {
4339 write!(formatter, "unsupported scoped PIT identifier version {version}")
4340 }
4341 Self::EmptyObjectKey => formatter.write_str("object key must not be empty"),
4342 Self::InvalidObjectKey(key) => {
4343 write!(formatter, "invalid relative object key '{key}'")
4344 }
4345 }
4346 }
4347}
4348
4349impl Error for IdentifierError {}
4350
4351#[must_use]
4353pub const fn routing_hash_v1(bytes: &[u8]) -> RoutingHash {
4354 let mut hash = 0x811c_9dc5_u32;
4355 let mut offset = 0;
4356 while offset < bytes.len() {
4357 hash ^= bytes[offset] as u32;
4358 hash = hash.wrapping_mul(0x0100_0193);
4359 offset += 1;
4360 }
4361 RoutingHash::new(hash)
4362}
4363
4364#[must_use]
4366pub fn identity_hash_v1(bytes: &[u8]) -> IdHash {
4367 let digest = blake3::hash(bytes);
4368 let digest_bytes = digest.as_bytes();
4369 IdHash::new(u128::from_le_bytes([
4370 digest_bytes[0],
4371 digest_bytes[1],
4372 digest_bytes[2],
4373 digest_bytes[3],
4374 digest_bytes[4],
4375 digest_bytes[5],
4376 digest_bytes[6],
4377 digest_bytes[7],
4378 digest_bytes[8],
4379 digest_bytes[9],
4380 digest_bytes[10],
4381 digest_bytes[11],
4382 digest_bytes[12],
4383 digest_bytes[13],
4384 digest_bytes[14],
4385 digest_bytes[15],
4386 ]))
4387}
4388
4389#[cfg(test)]
4390mod policy_axis_tests {
4391 use super::{LifecycleVerb, StorageClass};
4392
4393 #[test]
4394 fn reserved_policy_axis_names_are_stable() {
4395 assert!(serde_json::to_string(&StorageClass::Standard).is_ok_and(|value| value == "\"standard\""));
4396 assert!(serde_json::to_string(&StorageClass::Cold).is_ok_and(|value| value == "\"cold\""));
4397 assert!(serde_json::to_string(&LifecycleVerb::Transition).is_ok_and(|value| value == "\"transition\""));
4398 assert!(
4399 serde_json::to_string(&LifecycleVerb::AggregateThenDelete)
4400 .is_ok_and(|value| value == "\"aggregate_then_delete\"")
4401 );
4402 }
4403}
4404
4405#[must_use]
4407pub fn crc32c(bytes: &[u8]) -> u32 {
4408 let mut crc = u32::MAX;
4409 for byte in bytes {
4410 crc ^= u32::from(*byte);
4411 for _ in 0..8 {
4412 let mask = (crc & 1).wrapping_neg();
4413 crc = (crc >> 1) ^ (0x82f6_3b78 & mask);
4414 }
4415 }
4416 !crc
4417}
4418
4419#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
4421pub enum Role {
4422 Gateway,
4423 Indexer,
4424 Searcher,
4425 Compactor,
4426 Metastore,
4427}
4428
4429impl Role {
4430 pub const ALL: [Self; 5] = [
4432 Self::Gateway,
4433 Self::Indexer,
4434 Self::Searcher,
4435 Self::Compactor,
4436 Self::Metastore,
4437 ];
4438
4439 #[must_use]
4441 pub const fn as_str(self) -> &'static str {
4442 match self {
4443 Self::Gateway => "gateway",
4444 Self::Indexer => "indexer",
4445 Self::Searcher => "searcher",
4446 Self::Compactor => "compactor",
4447 Self::Metastore => "metastore",
4448 }
4449 }
4450}
4451
4452impl Display for Role {
4453 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
4454 formatter.write_str(self.as_str())
4455 }
4456}
4457
4458impl FromStr for Role {
4459 type Err = RoleParseError;
4460
4461 fn from_str(value: &str) -> Result<Self, Self::Err> {
4462 match value {
4463 "gateway" => Ok(Self::Gateway),
4464 "indexer" => Ok(Self::Indexer),
4465 "searcher" => Ok(Self::Searcher),
4466 "compactor" => Ok(Self::Compactor),
4467 "metastore" => Ok(Self::Metastore),
4468 _ => Err(RoleParseError::Unknown(value.to_owned())),
4469 }
4470 }
4471}
4472
4473#[derive(Clone, Debug, Eq, PartialEq)]
4475pub struct RoleSelection {
4476 roles: Vec<Role>,
4477}
4478
4479impl RoleSelection {
4480 #[must_use]
4482 pub fn all() -> Self {
4483 Self {
4484 roles: Role::ALL.to_vec(),
4485 }
4486 }
4487
4488 pub fn parse(value: &str) -> Result<Self, RoleParseError> {
4490 if value == "all" {
4491 return Ok(Self::all());
4492 }
4493
4494 let mut roles = Vec::new();
4495 for candidate in value.split(',') {
4496 if candidate.is_empty() {
4497 return Err(RoleParseError::Empty);
4498 }
4499
4500 let role = candidate.parse::<Role>()?;
4501 if !roles.contains(&role) {
4502 roles.push(role);
4503 }
4504 }
4505
4506 if roles.is_empty() {
4507 return Err(RoleParseError::Empty);
4508 }
4509
4510 roles.sort_unstable();
4511 Ok(Self { roles })
4512 }
4513
4514 #[must_use]
4516 pub fn roles(&self) -> &[Role] {
4517 &self.roles
4518 }
4519
4520 #[must_use]
4522 pub fn display_list(&self) -> String {
4523 self.roles
4524 .iter()
4525 .map(|role| role.as_str())
4526 .collect::<Vec<_>>()
4527 .join(",")
4528 }
4529}
4530
4531impl Default for RoleSelection {
4532 fn default() -> Self {
4533 Self::all()
4534 }
4535}
4536
4537#[derive(Clone, Debug, Eq, PartialEq)]
4539pub enum RoleParseError {
4540 Empty,
4541 Unknown(String),
4542}
4543
4544impl Display for RoleParseError {
4545 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
4546 match self {
4547 Self::Empty => formatter.write_str("role selection must not be empty"),
4548 Self::Unknown(role) => write!(
4549 formatter,
4550 "unknown role '{role}'; expected all, gateway, indexer, searcher, compactor, or metastore"
4551 ),
4552 }
4553 }
4554}
4555
4556impl Error for RoleParseError {}
4557
4558pub fn process_fault_point(point: &str) {
4565 process_fault_point_inner(point, None);
4566}
4567
4568pub fn process_fault_point_with_evidence(point: &str, evidence: &str) {
4575 const MAX_EVIDENCE_BYTES: usize = 512;
4576 if evidence.is_empty()
4577 || evidence.len() > MAX_EVIDENCE_BYTES
4578 || !evidence
4579 .bytes()
4580 .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-' | b'=' | b'.'))
4581 {
4582 return;
4583 }
4584 process_fault_point_inner(point, Some(evidence));
4585}
4586
4587pub const GC_PROTECTION_OBSERVATION_PATH_ENV: &str = "LOD_TEST_P52_GC_PROTECTION_OBSERVATION_PATH";
4594
4595pub const MAX_GC_PROTECTION_OBSERVATION_BYTES: usize = 8 * 1024 * 1024;
4597
4598#[derive(Debug)]
4600pub enum GcProtectionObservationError {
4601 InvalidPath,
4602 Empty,
4603 TooLarge {
4604 actual: usize,
4605 maximum: usize,
4606 },
4607 AlreadyPublished(PathBuf),
4608 Io {
4609 operation: &'static str,
4610 path: PathBuf,
4611 source: std::io::Error,
4612 },
4613}
4614
4615impl Display for GcProtectionObservationError {
4616 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
4617 match self {
4618 Self::InvalidPath => formatter.write_str("GC protection observation path has no file name"),
4619 Self::Empty => formatter.write_str("GC protection observation is empty"),
4620 Self::TooLarge { actual, maximum } => write!(
4621 formatter,
4622 "GC protection observation is {actual} bytes; maximum is {maximum}"
4623 ),
4624 Self::AlreadyPublished(path) => {
4625 write!(
4626 formatter,
4627 "GC protection observation was already published at {}",
4628 path.display()
4629 )
4630 }
4631 Self::Io {
4632 operation,
4633 path,
4634 source,
4635 } => write!(
4636 formatter,
4637 "GC protection observation {operation} failed for {}: {source}",
4638 path.display()
4639 ),
4640 }
4641 }
4642}
4643
4644impl Error for GcProtectionObservationError {
4645 fn source(&self) -> Option<&(dyn Error + 'static)> {
4646 match self {
4647 Self::Io { source, .. } => Some(source),
4648 _ => None,
4649 }
4650 }
4651}
4652
4653#[must_use]
4655pub fn gc_protection_observation_path() -> Option<PathBuf> {
4656 std::env::var_os(GC_PROTECTION_OBSERVATION_PATH_ENV)
4657 .filter(|value| !value.is_empty())
4658 .map(PathBuf::from)
4659}
4660
4661pub fn write_gc_protection_observation(path: &Path, encoded: &[u8]) -> Result<(), GcProtectionObservationError> {
4668 if encoded.is_empty() {
4669 return Err(GcProtectionObservationError::Empty);
4670 }
4671 if encoded.len() > MAX_GC_PROTECTION_OBSERVATION_BYTES {
4672 return Err(GcProtectionObservationError::TooLarge {
4673 actual: encoded.len(),
4674 maximum: MAX_GC_PROTECTION_OBSERVATION_BYTES,
4675 });
4676 }
4677 let file_name = path
4678 .file_name()
4679 .and_then(|name| name.to_str())
4680 .filter(|name| !name.is_empty())
4681 .ok_or(GcProtectionObservationError::InvalidPath)?;
4682 let temporary = path.with_file_name(format!(".{file_name}.{}.tmp", std::process::id()));
4683 let mut file = std::fs::OpenOptions::new()
4684 .write(true)
4685 .create_new(true)
4686 .open(&temporary)
4687 .map_err(|source| GcProtectionObservationError::Io {
4688 operation: "temporary-file creation",
4689 path: temporary.clone(),
4690 source,
4691 })?;
4692 let write_result = file.write_all(encoded).and_then(|()| file.sync_all());
4693 drop(file);
4694 if let Err(source) = write_result {
4695 let _cleanup_result = std::fs::remove_file(&temporary);
4696 return Err(GcProtectionObservationError::Io {
4697 operation: "temporary-file write",
4698 path: temporary,
4699 source,
4700 });
4701 }
4702 if let Err(source) = std::fs::hard_link(&temporary, path) {
4703 let _cleanup_result = std::fs::remove_file(&temporary);
4704 if source.kind() == std::io::ErrorKind::AlreadyExists {
4705 return Err(GcProtectionObservationError::AlreadyPublished(path.to_path_buf()));
4706 }
4707 return Err(GcProtectionObservationError::Io {
4708 operation: "atomic publication",
4709 path: path.to_path_buf(),
4710 source,
4711 });
4712 }
4713 std::fs::remove_file(&temporary).map_err(|source| GcProtectionObservationError::Io {
4714 operation: "temporary-file cleanup",
4715 path: temporary,
4716 source,
4717 })?;
4718 Ok(())
4719}
4720
4721fn process_fault_point_inner(point: &str, evidence: Option<&str>) {
4722 struct FaultConfig {
4723 point: String,
4724 ready_path: PathBuf,
4725 release_path: PathBuf,
4726 timeout: Duration,
4727 }
4728 static CONFIG: OnceLock<Option<FaultConfig>> = OnceLock::new();
4729 let config = CONFIG.get_or_init(|| {
4730 let point = std::env::var("LOD_TEST_LISTENER_FAULT_POINT").ok()?;
4731 let ready_path = std::env::var_os("LOD_TEST_LISTENER_FAULT_READY").map(PathBuf::from)?;
4732 let release_path = std::env::var_os("LOD_TEST_LISTENER_FAULT_RELEASE").map(PathBuf::from)?;
4733 let timeout_millis = std::env::var("LOD_TEST_LISTENER_FAULT_TIMEOUT_MILLIS")
4734 .ok()
4735 .and_then(|value| value.parse::<u64>().ok())
4736 .unwrap_or(30_000)
4737 .min(60_000);
4738 Some(FaultConfig {
4739 point,
4740 ready_path,
4741 release_path,
4742 timeout: Duration::from_millis(timeout_millis),
4743 })
4744 });
4745 let Some(config) = config.as_ref().filter(|config| config.point == point) else {
4746 return;
4747 };
4748 let payload = evidence.map_or_else(|| point.to_owned(), |fact| format!("{point}\n{fact}"));
4749 if std::fs::write(&config.ready_path, payload.as_bytes()).is_err() {
4750 return;
4751 }
4752 let deadline = Instant::now() + config.timeout;
4753 while !config.release_path.exists() && Instant::now() < deadline {
4754 std::thread::yield_now();
4755 }
4756}
4757
4758#[cfg(test)]
4759mod hash_backend_tests;
4760
4761#[cfg(test)]
4762mod tests {
4763 use std::collections::BTreeMap;
4764 use std::sync::atomic::{AtomicU64, Ordering};
4765
4766 use super::{
4767 CollectionId, CollectionIndexRef, DocumentId, DurabilityClass, DynamicMappingProposalError,
4768 DynamicMappingProposalV1, Epoch, FieldMapping, FieldType, GcProtectionObservationError, IndexId, IndexMode,
4769 MAPPING_FORMAT_VERSION, MAX_GC_PROTECTION_OBSERVATION_BYTES, MAX_MAPPING_FIELD_NAME_BYTES, MAX_MAPPING_FIELDS,
4770 ManifestGeneration, Mapping, MappingExtensions, MappingScalar, MappingValidationError, ObjectKey, PitId, Role,
4771 RoleSelection, RoutingHash, RoutingRangeEnd, SchemaGeneration, ScopedPitId, SeqNo, ShardDescriptor, ShardId,
4772 TaskId, VisibilityMode, crc32c, validate_mapping_field_name, write_gc_protection_observation,
4773 };
4774
4775 static OBSERVATION_TEST_SEQUENCE: AtomicU64 = AtomicU64::new(0);
4776
4777 #[test]
4778 fn object_key_memory_charge_preserves_spare_string_capacity() -> Result<(), Box<dyn std::error::Error>> {
4779 let mut value = String::with_capacity(4096);
4780 value.push_str("a/b");
4781 let capacity = value.capacity();
4782 let key = ObjectKey::new(value)?;
4783 assert_eq!(key.as_str(), "a/b");
4784 assert_eq!(key.allocated_capacity_bytes(), capacity);
4785 assert!(key.allocated_capacity_bytes() > key.as_str().len());
4786 assert_eq!(serde_json::to_string(&key)?, "\"a/b\"");
4787 Ok(())
4788 }
4789
4790 #[test]
4791 fn all_expands_to_every_role() {
4792 let selection = RoleSelection::parse("all");
4793
4794 assert_eq!(selection.as_ref().map(|value| value.roles()), Ok(Role::ALL.as_slice()));
4795 }
4796
4797 #[test]
4798 fn explicit_roles_are_deduplicated_and_stable() {
4799 let selection = RoleSelection::parse("searcher,gateway,searcher");
4800
4801 assert_eq!(
4802 selection.as_ref().map(|value| value.roles()),
4803 Ok([Role::Gateway, Role::Searcher].as_slice())
4804 );
4805 }
4806
4807 #[test]
4808 fn unknown_role_is_rejected() {
4809 let selection = RoleSelection::parse("unknown");
4810
4811 assert!(selection.is_err());
4812 }
4813
4814 #[test]
4815 fn semantic_numbers_advance_without_crossing_types() {
4816 assert_eq!(Epoch::new(7).checked_next(), Some(Epoch::new(8)));
4817 assert_eq!(SeqNo::new(41).checked_next(), Some(SeqNo::new(42)));
4818 assert_eq!(ManifestGeneration::new(u64::MAX).checked_next(), None);
4819 }
4820
4821 #[test]
4822 fn sparse_values_are_canonical_bounded_and_mapping_typed() -> Result<(), Box<dyn std::error::Error>> {
4823 let definition = super::SparseVectorDefinition {
4824 maximum_dimension: 12,
4825 weight_type: super::SparseVectorWeightType::F16,
4826 };
4827 let value = super::SparseVectorValue {
4828 entries: vec![
4829 super::SparseVectorEntry {
4830 dimension: 1,
4831 weight: 1.5,
4832 },
4833 super::SparseVectorEntry {
4834 dimension: 9,
4835 weight: -2.0,
4836 },
4837 ],
4838 };
4839 let decoded = value.decode(definition)?;
4840 let native = value.canonical_native_bytes(definition)?;
4841 assert_eq!(super::decode_sparse_native_bytes(definition, &native)?, decoded);
4842 assert_eq!(native.len(), 12);
4843 let encoded = serde_json::to_vec(&value)?;
4844 assert_eq!(serde_json::from_slice::<super::SparseVectorValue>(&encoded)?, value);
4845 #[derive(Debug, serde::Deserialize, PartialEq, serde::Serialize)]
4846 #[serde(tag = "kind", rename_all = "snake_case")]
4847 enum TaggedSparse {
4848 Sparse {
4849 query_vector: super::SparseVectorValue,
4850 #[serde(deserialize_with = "super::deserialize_sparse_weight")]
4851 weight: f64,
4852 },
4853 }
4854 let tagged = TaggedSparse::Sparse {
4855 query_vector: value.clone(),
4856 weight: 0.5,
4857 };
4858 let encoded = serde_json::to_vec(&tagged)?;
4859 assert_eq!(serde_json::from_slice::<TaggedSparse>(&encoded)?, tagged);
4860
4861 for invalid in [
4862 super::SparseVectorValue {
4863 entries: vec![
4864 super::SparseVectorEntry {
4865 dimension: 1,
4866 weight: 1.0,
4867 },
4868 super::SparseVectorEntry {
4869 dimension: 1,
4870 weight: 2.0,
4871 },
4872 ],
4873 },
4874 super::SparseVectorValue {
4875 entries: vec![super::SparseVectorEntry {
4876 dimension: 2,
4877 weight: 0.0,
4878 }],
4879 },
4880 super::SparseVectorValue {
4881 entries: vec![super::SparseVectorEntry {
4882 dimension: 13,
4883 weight: 1.0,
4884 }],
4885 },
4886 super::SparseVectorValue {
4887 entries: vec![super::SparseVectorEntry {
4888 dimension: 2,
4889 weight: f64::NAN,
4890 }],
4891 },
4892 ] {
4893 assert!(invalid.decode(definition).is_err());
4894 }
4895 Ok(())
4896 }
4897
4898 #[test]
4899 fn vector_set_values_are_explicit_typed_and_bounded() -> Result<(), Box<dyn std::error::Error>> {
4900 let definition = super::VectorSetDefinition {
4901 dense: super::DenseVectorDefinition {
4902 dimensions: 2,
4903 element_type: super::DenseVectorElementType::F32,
4904 metric: super::VectorMetric::DotProduct,
4905 quantization_scale: None,
4906 },
4907 maximum_vectors: 2,
4908 };
4909 let value = super::VectorSetValue {
4910 vectors: vec![
4911 super::DenseVectorValue::Numeric { values: vec![1.0, 0.0] },
4912 super::DenseVectorValue::Numeric { values: vec![0.0, 1.0] },
4913 ],
4914 };
4915 let decoded = value.decode(
4916 definition,
4917 super::MAX_VECTOR_SET_DOCUMENT_VECTORS,
4918 super::MAX_VECTOR_SET_BYTES_PER_FIELD,
4919 )?;
4920 assert_eq!(decoded.vectors, vec![vec![1.0, 0.0], vec![0.0, 1.0]]);
4921 assert_eq!(decoded.native_bytes, 16);
4922 assert!(
4923 super::VectorSetValue { vectors: Vec::new() }
4924 .decode(definition, 2, super::MAX_VECTOR_SET_BYTES_PER_FIELD)
4925 .is_err()
4926 );
4927 assert!(
4928 super::VectorSetValue {
4929 vectors: vec![super::DenseVectorValue::Numeric { values: vec![1.0, 0.0] }; 3],
4930 }
4931 .decode(definition, 2, super::MAX_VECTOR_SET_BYTES_PER_FIELD)
4932 .is_err()
4933 );
4934 assert!(
4935 super::VectorSetValue {
4936 vectors: vec![super::DenseVectorValue::Numeric { values: vec![1.0] }],
4937 }
4938 .decode(definition, 2, super::MAX_VECTOR_SET_BYTES_PER_FIELD)
4939 .is_err()
4940 );
4941 let oversized_json = serde_json::json!({
4942 "vectors": (0..=super::MAX_VECTOR_SET_DOCUMENT_VECTORS)
4943 .map(|_| serde_json::json!({"kind": "numeric", "values": [1.0, 0.0]}))
4944 .collect::<Vec<_>>()
4945 });
4946 assert!(serde_json::from_value::<super::VectorSetValue>(oversized_json).is_err());
4947 Ok(())
4948 }
4949
4950 #[test]
4951 fn task_ids_round_trip_the_complete_u128_domain_and_reject_numbers() {
4952 let task_id = TaskId::new(u128::MAX - 7);
4953 let encoded = serde_json::to_string(&task_id);
4954 let expected = format!("\"{}\"", u128::MAX - 7);
4955 assert_eq!(encoded.as_ref().ok().map(String::as_str), Some(expected.as_str()));
4956 assert_eq!(
4957 encoded
4958 .as_deref()
4959 .ok()
4960 .and_then(|encoded| serde_json::from_str(encoded).ok()),
4961 Some(task_id)
4962 );
4963 assert!(serde_json::from_str::<TaskId>("41").is_err());
4964 }
4965
4966 #[test]
4967 fn gc_protection_observation_writer_is_bounded_atomic_and_single_assignment()
4968 -> Result<(), Box<dyn std::error::Error>> {
4969 let sequence = OBSERVATION_TEST_SEQUENCE.fetch_add(1, Ordering::Relaxed);
4970 let root = std::env::temp_dir().join(format!(
4971 "lodestone-p52-gc-protection-observation-{}-{sequence}",
4972 std::process::id()
4973 ));
4974 std::fs::create_dir(&root)?;
4975 let path = root.join("applied.json");
4976 let encoded = br#"{"format_version":1}"#;
4977
4978 write_gc_protection_observation(&path, encoded)?;
4979 assert_eq!(std::fs::read(&path)?, encoded);
4980 assert!(matches!(
4981 write_gc_protection_observation(&path, encoded),
4982 Err(GcProtectionObservationError::AlreadyPublished(actual)) if actual == path
4983 ));
4984 assert!(matches!(
4985 write_gc_protection_observation(
4986 &root.join("oversized.json"),
4987 &vec![0; MAX_GC_PROTECTION_OBSERVATION_BYTES.saturating_add(1)]
4988 ),
4989 Err(GcProtectionObservationError::TooLarge { .. })
4990 ));
4991 assert!(!root.join("oversized.json").exists());
4992
4993 std::fs::remove_dir_all(root)?;
4994 Ok(())
4995 }
4996
4997 #[test]
4998 fn durable_identifiers_reject_ambiguous_values() {
4999 assert!(DocumentId::new("").is_err());
5000 assert!(PitId::new("").is_err());
5001 assert!(PitId::new("opaque-pit-lease").is_ok());
5002 assert!(ObjectKey::new("../escape").is_err());
5003 assert!(ObjectKey::new("valid/prefix/object").is_ok());
5004 }
5005
5006 #[test]
5007 fn scoped_pit_id_round_trips_and_rejects_newer_formats() -> Result<(), Box<dyn std::error::Error>> {
5008 let pit_id = ScopedPitId::new(super::RecoveryEra::new(7)?, PitId::new("opaque-pit-lease")?);
5009 let encoded = serde_json::to_vec(&pit_id)?;
5010 assert_eq!(serde_json::from_slice::<ScopedPitId>(&encoded)?, pit_id);
5011
5012 let mut unsupported: serde_json::Value = serde_json::from_slice(&encoded)?;
5013 let Some(object) = unsupported.as_object_mut() else {
5014 return Err("scoped PIT identifier was not an object".into());
5015 };
5016 object.insert("format_version".to_owned(), serde_json::json!(2));
5017 assert!(serde_json::from_value::<ScopedPitId>(unsupported).is_err());
5018 Ok(())
5019 }
5020
5021 #[test]
5022 fn crc32c_matches_the_standard_check_value() {
5023 assert_eq!(crc32c(b"123456789"), 0xe306_9283);
5024 }
5025
5026 #[test]
5027 fn durability_names_round_trip_and_admitted_classes_are_available() {
5028 let classes = [
5029 DurabilityClass::RegionalObject,
5030 DurabilityClass::ZonalLowLatency,
5031 DurabilityClass::Relaxed,
5032 DurabilityClass::Ephemeral,
5033 ];
5034 for class in classes {
5035 let encoded = serde_json::to_string(&class);
5036 assert_eq!(
5037 encoded.as_ref().ok().and_then(|bytes| serde_json::from_str(bytes).ok()),
5038 Some(class)
5039 );
5040 }
5041 assert!(DurabilityClass::RegionalObject.require_available().is_ok());
5042 assert!(DurabilityClass::ZonalLowLatency.require_available().is_ok());
5043 assert!(DurabilityClass::Relaxed.require_available().is_err());
5044 assert!(DurabilityClass::Ephemeral.require_available().is_err());
5045 assert!(DurabilityClass::RegionalObject.require_state_available().is_ok());
5046 assert!(DurabilityClass::ZonalLowLatency.require_state_available().is_ok());
5047 assert!(DurabilityClass::Ephemeral.require_state_available().is_ok());
5048 assert!(DurabilityClass::Relaxed.require_state_available().is_err());
5049 assert!(
5050 serde_json::to_string(&super::DurabilityBoundary::IncarnationMemory)
5051 .is_ok_and(|encoded| encoded == "\"incarnation_memory\"")
5052 );
5053 assert!(serde_json::from_str::<DurabilityClass>("\"future\"").is_err());
5054 assert!(matches!(
5055 serde_json::from_str::<VisibilityMode>("\"wait_for\""),
5056 Ok(VisibilityMode::WaitFor)
5057 ));
5058 }
5059
5060 #[test]
5061 fn shard_descriptor_covers_exactly_its_hash_range() {
5062 let descriptor = ShardDescriptor::new(
5063 ShardId::new(7),
5064 Epoch::new(3),
5065 RoutingHash::new(10),
5066 RoutingRangeEnd::new(20).unwrap_or(RoutingRangeEnd::FULL),
5067 );
5068 assert!(descriptor.is_ok());
5069 let descriptor = descriptor.unwrap_or(ShardDescriptor {
5070 shard_id: ShardId::new(7),
5071 epoch: Epoch::new(3),
5072 hash_start: RoutingHash::new(10),
5073 hash_end: RoutingRangeEnd::FULL,
5074 });
5075 assert!(!descriptor.contains(RoutingHash::new(9)));
5076 assert!(descriptor.contains(RoutingHash::new(10)));
5077 assert!(descriptor.contains(RoutingHash::new(19)));
5078 assert!(!descriptor.contains(RoutingHash::new(20)));
5079 }
5080
5081 #[test]
5082 fn shared_mapping_model_validates_semantics_and_refuses_retired_versions_and_unknown_fields()
5083 -> Result<(), Box<dyn std::error::Error>> {
5084 let mapping = Mapping {
5085 format_version: MAPPING_FORMAT_VERSION,
5086 schema_generation: SchemaGeneration::new(1),
5087 mode: IndexMode::Mutable,
5088 fields: BTreeMap::from([(
5089 "message".to_owned(),
5090 FieldMapping {
5091 field_type: FieldType::Text,
5092 stored: false,
5093 null_value: None,
5094 },
5095 )]),
5096 extensions: crate::MappingExtensions::default(),
5097 };
5098 assert_eq!(mapping.validate(), Ok(()));
5099 assert_eq!(mapping.validate_current(), Ok(()));
5100 let canonical = serde_json::to_value(&mapping)?;
5101 for retired_version in 1..MAPPING_FORMAT_VERSION {
5102 let retired = Mapping {
5103 format_version: retired_version,
5104 ..mapping.clone()
5105 };
5106 assert_eq!(
5107 retired.validate(),
5108 Err(MappingValidationError::UnsupportedVersion(retired_version))
5109 );
5110 assert!(matches!(
5111 retired.encode_canonical(),
5112 Err(super::MappingCodecError::Invalid(
5113 MappingValidationError::UnsupportedVersion(version)
5114 )) if version == retired_version
5115 ));
5116
5117 let mut retired_bytes = canonical.clone();
5118 retired_bytes["format_version"] = serde_json::json!(retired_version);
5119 let retired_bytes = serde_json::to_vec(&retired_bytes)?;
5120 let original = retired_bytes.clone();
5121 assert!(matches!(
5122 Mapping::decode_canonical(&retired_bytes),
5123 Err(super::MappingCodecError::UnsupportedVersion(version)) if version == retired_version
5124 ));
5125 assert_eq!(retired_bytes, original);
5126 assert!(serde_json::from_slice::<Mapping>(&retired_bytes).is_err());
5127 }
5128
5129 let future = Mapping {
5130 format_version: MAPPING_FORMAT_VERSION + 1,
5131 ..mapping.clone()
5132 };
5133 assert_eq!(
5134 future.validate(),
5135 Err(MappingValidationError::UnsupportedVersion(MAPPING_FORMAT_VERSION + 1))
5136 );
5137
5138 let invalid = Mapping {
5139 schema_generation: SchemaGeneration::new(0),
5140 ..mapping.clone()
5141 };
5142 assert_eq!(invalid.validate(), Err(MappingValidationError::ZeroSchemaGeneration));
5143 assert!(
5144 serde_json::from_str::<Mapping>(
5145 r#"{"format_version":1,"schema_generation":1,"mode":"mutable","fields":{},"future":true}"#
5146 )
5147 .is_err()
5148 );
5149 for invalid in [".message", "message.", "object..message"] {
5150 let mut invalid_path = mapping.clone();
5151 invalid_path.fields = BTreeMap::from([(
5152 invalid.to_owned(),
5153 FieldMapping {
5154 field_type: FieldType::Text,
5155 stored: false,
5156 null_value: None,
5157 },
5158 )]);
5159 assert_eq!(
5160 invalid_path.validate(),
5161 Err(MappingValidationError::InvalidFieldName(invalid.to_owned()))
5162 );
5163 }
5164 for (ancestor, descendant) in [("a", "a.b"), ("a.b", "a.b.c")] {
5165 let mut conflicting = mapping.clone();
5166 conflicting.fields = BTreeMap::from([
5167 (
5168 ancestor.to_owned(),
5169 FieldMapping {
5170 field_type: FieldType::Keyword,
5171 stored: false,
5172 null_value: None,
5173 },
5174 ),
5175 (
5176 descendant.to_owned(),
5177 FieldMapping {
5178 field_type: FieldType::U64,
5179 stored: false,
5180 null_value: None,
5181 },
5182 ),
5183 ]);
5184 assert_eq!(
5185 conflicting.validate(),
5186 Err(MappingValidationError::ConflictingFieldPaths {
5187 ancestor: ancestor.to_owned(),
5188 descendant: descendant.to_owned(),
5189 })
5190 );
5191 }
5192 let mut siblings = mapping;
5193 siblings.fields = BTreeMap::from([
5194 (
5195 "a.b".to_owned(),
5196 FieldMapping {
5197 field_type: FieldType::Keyword,
5198 stored: false,
5199 null_value: None,
5200 },
5201 ),
5202 (
5203 "a.bc".to_owned(),
5204 FieldMapping {
5205 field_type: FieldType::U64,
5206 stored: false,
5207 null_value: None,
5208 },
5209 ),
5210 ]);
5211 assert_eq!(siblings.validate(), Ok(()));
5212 Ok(())
5213 }
5214
5215 #[test]
5216 fn ec6_mapping_refuses_nested_paths_below_nested_paths() {
5217 let nested = || FieldMapping {
5218 field_type: FieldType::Nested,
5219 stored: false,
5220 null_value: None,
5221 };
5222 let mapping = Mapping {
5223 format_version: MAPPING_FORMAT_VERSION,
5224 schema_generation: SchemaGeneration::new(1),
5225 mode: IndexMode::Mutable,
5226 fields: BTreeMap::from([
5227 ("items".to_owned(), nested()),
5228 ("items-options".to_owned(), nested()),
5229 ("items.options".to_owned(), nested()),
5230 ]),
5231 extensions: MappingExtensions {
5232 nested_paths: vec![
5233 "items".to_owned(),
5234 "items-options".to_owned(),
5235 "items.options".to_owned(),
5236 ],
5237 ..MappingExtensions::default()
5238 },
5239 };
5240 assert_eq!(
5241 mapping.validate(),
5242 Err(MappingValidationError::NestedPathOverlap {
5243 parent: "items".to_owned(),
5244 child: "items.options".to_owned(),
5245 })
5246 );
5247
5248 let mut predecessor = Mapping::empty(IndexMode::Mutable);
5249 predecessor.fields.insert(
5250 "items.color".to_owned(),
5251 FieldMapping {
5252 field_type: FieldType::Keyword,
5253 stored: false,
5254 null_value: None,
5255 },
5256 );
5257 let mut successor = predecessor.clone();
5258 successor.schema_generation = SchemaGeneration::new(2);
5259 successor.fields.insert("items".to_owned(), nested());
5260 successor.extensions.nested_paths = vec!["items".to_owned()];
5261 assert!(!successor.is_additive_successor_of(&predecessor));
5262
5263 let mut safe_successor = predecessor.clone();
5264 safe_successor.schema_generation = SchemaGeneration::new(2);
5265 safe_successor.fields.insert("other".to_owned(), nested());
5266 safe_successor.fields.insert(
5267 "other.color".to_owned(),
5268 FieldMapping {
5269 field_type: FieldType::Keyword,
5270 stored: false,
5271 null_value: None,
5272 },
5273 );
5274 safe_successor.extensions.nested_paths = vec!["other".to_owned()];
5275 assert!(safe_successor.is_additive_successor_of(&predecessor));
5276 }
5277
5278 #[test]
5279 fn ec5_dynamic_mapping_proposal_normalizes_signed_zero_and_refuses_noncanonical_decode()
5280 -> Result<(), Box<dyn std::error::Error>> {
5281 let proposal = DynamicMappingProposalV1::new(
5282 CollectionIndexRef::new(CollectionId::new(7)?, IndexId::new(3)?),
5283 SchemaGeneration::new(1),
5284 "temperature".to_owned(),
5285 MappingScalar::F64Bits((-0.0_f64).to_bits()),
5286 )?;
5287 assert_eq!(
5288 proposal.first_non_null_candidate,
5289 MappingScalar::F64Bits(0.0_f64.to_bits())
5290 );
5291
5292 let mut noncanonical = proposal;
5293 noncanonical.first_non_null_candidate = MappingScalar::F64Bits((-0.0_f64).to_bits());
5294 noncanonical.proposal_digest = noncanonical.exact_digest()?;
5295 let encoded = serde_json::to_vec(&noncanonical)?;
5296 assert!(matches!(
5297 DynamicMappingProposalV1::decode_canonical(&encoded),
5298 Err(DynamicMappingProposalError::InvalidCandidate)
5299 ));
5300 Ok(())
5301 }
5302
5303 #[test]
5304 fn ec5_deployment_mapping_fixtures_use_the_current_validated_format() -> Result<(), Box<dyn std::error::Error>> {
5305 for encoded in [
5306 include_str!("../../../fixtures/runtime/secure-strict-mapping.json"),
5307 include_str!("../../../fixtures/performance/search-mapping.json"),
5308 include_str!("../../../fixtures/reindex/mapping.json"),
5309 include_str!("../../../tests/performance/es-comparison-mapping-v1.json"),
5310 ] {
5311 let mapping: Mapping = serde_json::from_str(encoded)?;
5312 mapping.validate_current()?;
5313 assert_eq!(mapping.schema_generation, SchemaGeneration::new(1));
5314 }
5315 Ok(())
5316 }
5317
5318 #[test]
5319 fn public_replayable_source_mapping_contract_matches_current_model() -> Result<(), serde_json::Error> {
5320 let contract: serde_json::Value = serde_json::from_str(include_str!(
5321 "../../../public/development/replayable-source-mapping-v1.json"
5322 ))?;
5323 assert_eq!(contract["mapping"]["format_version"], MAPPING_FORMAT_VERSION);
5324 assert_eq!(contract["mapping"]["maximum_fields"], MAX_MAPPING_FIELDS);
5325 assert_eq!(
5326 contract["mapping"]["maximum_field_name_bytes"],
5327 MAX_MAPPING_FIELD_NAME_BYTES
5328 );
5329 assert_eq!(
5330 contract["mapping"]["scalar_field_types"],
5331 serde_json::json!(["text", "keyword", "u64", "date", "i64", "f64", "bool"])
5332 );
5333
5334 let mapping = Mapping {
5335 format_version: MAPPING_FORMAT_VERSION,
5336 schema_generation: SchemaGeneration::new(1),
5337 mode: IndexMode::AppendLog,
5338 fields: BTreeMap::from([
5339 (
5340 "message".to_owned(),
5341 FieldMapping {
5342 field_type: FieldType::Text,
5343 stored: false,
5344 null_value: None,
5345 },
5346 ),
5347 (
5348 "record_id".to_owned(),
5349 FieldMapping {
5350 field_type: FieldType::Keyword,
5351 stored: false,
5352 null_value: None,
5353 },
5354 ),
5355 ]),
5356 extensions: crate::MappingExtensions::default(),
5357 };
5358 assert_eq!(
5359 serde_json::Value::String(serde_json::to_string(&mapping)?),
5360 contract["example"]
5361 );
5362 for invalid in ["", ".message", "message.", "object..message", "_reserved", "a\u{7f}b"] {
5363 assert_eq!(
5364 validate_mapping_field_name(invalid),
5365 Err(MappingValidationError::InvalidFieldName(invalid.to_owned()))
5366 );
5367 }
5368 Ok(())
5369 }
5370
5371 #[test]
5372 fn shared_field_path_resolver_uses_canonical_nested_components() {
5373 #[derive(Default)]
5374 struct Node {
5375 children: BTreeMap<&'static str, Node>,
5376 value: Option<u64>,
5377 }
5378
5379 let root = Node {
5380 children: BTreeMap::from([(
5381 "metrics",
5382 Node {
5383 children: BTreeMap::from([(
5384 "count",
5385 Node {
5386 children: BTreeMap::new(),
5387 value: Some(7),
5388 },
5389 )]),
5390 value: None,
5391 },
5392 )]),
5393 value: None,
5394 };
5395 assert_eq!(
5396 super::resolve_field_path(&root, "metrics.count", |node, component| node.children.get(component))
5397 .and_then(|node| node.value),
5398 Some(7)
5399 );
5400 assert!(
5401 super::resolve_field_path(&root, "metrics..count", |node, component| node.children.get(component))
5402 .is_none()
5403 );
5404 }
5405
5406 #[test]
5407 fn root_set_reference_is_exact_bounded_and_canonical() -> Result<(), Box<dyn std::error::Error>> {
5408 let root = super::RootSetRef {
5409 format_version: super::ROOT_SET_REF_FORMAT_VERSION,
5410 root: super::PhysicalObjectId {
5411 backend_authority: super::BackendAuthorityId::new("standard-data")?,
5412 object_incarnation: super::ObjectIncarnation::new(9)?,
5413 object_key: super::ObjectKey::new("root-sets/0009.json")?,
5414 },
5415 content_digest: [7; 32],
5416 item_count: 4,
5417 page_count: 2,
5418 schema_generations: vec![super::SchemaGeneration::new(1), super::SchemaGeneration::new(3)],
5419 };
5420 root.validate()?;
5421 assert_eq!(
5422 serde_json::from_slice::<super::RootSetRef>(&serde_json::to_vec(&root)?)?,
5423 root
5424 );
5425
5426 let mut duplicate = root.clone();
5427 duplicate.schema_generations = vec![super::SchemaGeneration::new(1), super::SchemaGeneration::new(1)];
5428 assert_eq!(
5429 duplicate.validate(),
5430 Err(super::RootSetRefError::InvalidSchemaGenerations)
5431 );
5432 let mut empty = root;
5433 empty.item_count = 0;
5434 assert_eq!(
5435 empty.validate(),
5436 Err(super::RootSetRefError::InvalidCounts { items: 0, pages: 2 })
5437 );
5438 let mut single_page = empty;
5439 single_page.item_count = 1;
5440 single_page.page_count = 1;
5441 single_page.validate()?;
5442 let mut too_many_pages = single_page.clone();
5443 too_many_pages.page_count = 2;
5444 assert_eq!(
5445 too_many_pages.validate(),
5446 Err(super::RootSetRefError::InvalidCounts { items: 1, pages: 2 })
5447 );
5448 let mut predecessor = single_page;
5449 predecessor.format_version = 1;
5450 assert_eq!(
5451 predecessor.validate(),
5452 Err(super::RootSetRefError::UnsupportedVersion(1))
5453 );
5454 Ok(())
5455 }
5456
5457 #[test]
5458 fn kubernetes_object_uid_digest_refuses_zero_on_construction_and_decode() -> Result<(), Box<dyn std::error::Error>>
5459 {
5460 assert!(super::KubernetesObjectUidDigest::new([0; 32]).is_err());
5461 assert!(
5462 serde_json::from_str::<super::KubernetesObjectUidDigest>(&format!("[{}]", "0,".repeat(31) + "0")).is_err()
5463 );
5464 let digest = super::KubernetesObjectUidDigest::new([7; 32])?;
5465 let decoded: super::KubernetesObjectUidDigest = serde_json::from_slice(&serde_json::to_vec(&digest)?)?;
5466 assert_eq!(decoded, digest);
5467 Ok(())
5468 }
5469
5470 #[test]
5471 fn gateway_d1_semantic_identities_and_listener_profiles_are_exact() -> Result<(), Box<dyn std::error::Error>> {
5472 assert!(super::GatewayIncarnation::new([0; 32]).is_err());
5473 assert!(serde_json::from_str::<super::GatewayIncarnation>(&format!("[{}]", "0,".repeat(31) + "0")).is_err());
5474 let incarnation = super::GatewayIncarnation::new([7; 32])?;
5475 assert_eq!(
5476 serde_json::from_slice::<super::GatewayIncarnation>(&serde_json::to_vec(&incarnation)?)?,
5477 incarnation
5478 );
5479 assert_eq!(super::AuditRangeId::new(4).get(), 4);
5480 assert_eq!(super::QuotaGrantId::new(5).get(), 5);
5481 assert_eq!(super::QuotaWindow::new(6).get(), 6);
5482 assert_eq!(super::ProcessMonotonicMillis::new(7).get(), 7);
5483 assert_eq!(super::UnixMillis::new(8).get(), 8);
5484 for (listener, profile) in [
5485 (
5486 super::PublicGatewayListenerV1::LodestoneApi,
5487 super::PublicTlsProfileV1::Native,
5488 ),
5489 (
5490 super::PublicGatewayListenerV1::ElasticsearchHttp,
5491 super::PublicTlsProfileV1::Http,
5492 ),
5493 (
5494 super::PublicGatewayListenerV1::OtlpHttp,
5495 super::PublicTlsProfileV1::Http,
5496 ),
5497 (
5498 super::PublicGatewayListenerV1::JaegerHttp,
5499 super::PublicTlsProfileV1::Http,
5500 ),
5501 (
5502 super::PublicGatewayListenerV1::OtlpGrpc,
5503 super::PublicTlsProfileV1::Grpc,
5504 ),
5505 (
5506 super::PublicGatewayListenerV1::SubscriptionGrpc,
5507 super::PublicTlsProfileV1::Grpc,
5508 ),
5509 (
5510 super::PublicGatewayListenerV1::SubscriptionWebSocket,
5511 super::PublicTlsProfileV1::Http,
5512 ),
5513 (super::PublicGatewayListenerV1::Resp, super::PublicTlsProfileV1::Resp),
5514 ] {
5515 assert_eq!(listener.required_tls_profile(), profile);
5516 }
5517 Ok(())
5518 }
5519
5520 #[test]
5521 fn ordinary_segment_allocation_ignores_reserved_ids_and_stops_at_boundary() {
5522 let reserved = super::SegmentId::new(super::RESERVED_SEGMENT_ID_NAMESPACE_START + 17);
5523 assert_eq!(
5524 super::next_ordinary_segment_id([super::SegmentId::new(7), reserved, super::SegmentId::new(9),]),
5525 Some(super::SegmentId::new(10))
5526 );
5527 assert_eq!(
5528 super::next_ordinary_segment_id([reserved]),
5529 Some(super::SegmentId::new(1))
5530 );
5531 assert_eq!(
5532 super::next_ordinary_segment_id([super::SegmentId::new(super::RESERVED_SEGMENT_ID_NAMESPACE_START - 1,)]),
5533 None
5534 );
5535 }
5536
5537 #[test]
5538 fn dense_numeric_and_binary_values_decode_to_identical_semantics() -> Result<(), Box<dyn std::error::Error>> {
5539 use base64::Engine as _;
5540
5541 for (element_type, scale, numeric, bytes) in [
5542 (
5543 super::DenseVectorElementType::F32,
5544 None,
5545 vec![1.5, -2.0],
5546 [1.5_f32.to_le_bytes(), (-2.0_f32).to_le_bytes()].concat(),
5547 ),
5548 (
5549 super::DenseVectorElementType::F16,
5550 None,
5551 vec![1.5, -2.0],
5552 [0x3e00_u16.to_le_bytes(), 0xc000_u16.to_le_bytes()].concat(),
5553 ),
5554 (
5555 super::DenseVectorElementType::I8,
5556 Some(super::VectorQuantizationScale::new(0.25)?),
5557 vec![6.0, -8.0],
5558 vec![6_u8, (-8_i8) as u8],
5559 ),
5560 ] {
5561 let definition = super::DenseVectorDefinition {
5562 dimensions: 2,
5563 element_type,
5564 metric: super::VectorMetric::DotProduct,
5565 quantization_scale: scale,
5566 };
5567 let numeric = super::decode_dense_numeric_value(definition, &numeric)?;
5568 let binary = super::decode_dense_binary_value(
5569 definition,
5570 &super::DenseVectorBinaryValue {
5571 element_type,
5572 byte_order: super::DenseVectorByteOrder::LittleEndian,
5573 data_base64: super::BASE64_STANDARD.encode(bytes),
5574 quantization_scale: scale,
5575 },
5576 )?;
5577 assert_eq!(numeric, binary);
5578 }
5579 Ok(())
5580 }
5581
5582 #[test]
5583 fn dense_value_refuses_narrowing_and_noncanonical_binary_controls() -> Result<(), Box<dyn std::error::Error>> {
5584 use base64::Engine as _;
5585
5586 let f16 = super::DenseVectorDefinition {
5587 dimensions: 1,
5588 element_type: super::DenseVectorElementType::F16,
5589 metric: super::VectorMetric::DotProduct,
5590 quantization_scale: None,
5591 };
5592 assert_eq!(super::decode_dense_numeric_value(f16, &[1.000_488_281_25])?, vec![1.0]);
5593 assert_eq!(
5594 super::decode_dense_numeric_value(f16, &[1.001_464_843_75])?,
5595 vec![1.001_953_1]
5596 );
5597 assert!(matches!(
5598 super::decode_dense_numeric_value(f16, &[70_000.0]),
5599 Err(super::DenseVectorValueError::F16Overflow(0))
5600 ));
5601
5602 let i8_definition = super::DenseVectorDefinition {
5603 dimensions: 1,
5604 element_type: super::DenseVectorElementType::I8,
5605 metric: super::VectorMetric::DotProduct,
5606 quantization_scale: Some(super::VectorQuantizationScale::new(0.5)?),
5607 };
5608 assert!(matches!(
5609 super::decode_dense_numeric_value(i8_definition, &[1.5]),
5610 Err(super::DenseVectorValueError::I8ElementOutOfRange(0))
5611 ));
5612 let encoded = super::BASE64_STANDARD.encode([1_u8]);
5613 let mut binary = super::DenseVectorBinaryValue {
5614 element_type: super::DenseVectorElementType::I8,
5615 byte_order: super::DenseVectorByteOrder::LittleEndian,
5616 data_base64: encoded,
5617 quantization_scale: Some(super::VectorQuantizationScale::new(0.25)?),
5618 };
5619 assert_eq!(
5620 super::decode_dense_binary_value(i8_definition, &binary),
5621 Err(super::DenseVectorValueError::QuantizationScaleMismatch)
5622 );
5623 binary.quantization_scale = i8_definition.quantization_scale;
5624 binary.data_base64.push('=');
5625 assert!(matches!(
5626 super::decode_dense_binary_value(i8_definition, &binary),
5627 Err(super::DenseVectorValueError::InvalidBase64Length { .. })
5628 ));
5629
5630 let wrong_order =
5631 br#"{"kind":"binary","element_type":"f32","byte_order":"big_endian","data_base64":"AAAAAA=="}"#;
5632 assert!(serde_json::from_slice::<super::DenseVectorValue>(wrong_order).is_err());
5633 let signed_zero = super::decode_dense_numeric_value(
5634 super::DenseVectorDefinition {
5635 dimensions: 2,
5636 element_type: super::DenseVectorElementType::F32,
5637 metric: super::VectorMetric::DotProduct,
5638 quantization_scale: None,
5639 },
5640 &[-0.0, 0.0],
5641 )?;
5642 assert!(signed_zero.iter().all(|value| value.to_bits() == 0));
5643 assert_eq!(
5644 super::decode_dense_numeric_value(
5645 super::DenseVectorDefinition {
5646 dimensions: 2,
5647 element_type: super::DenseVectorElementType::F32,
5648 metric: super::VectorMetric::Cosine,
5649 quantization_scale: None,
5650 },
5651 &[0.0, -0.0],
5652 ),
5653 Err(super::DenseVectorValueError::ZeroNorm)
5654 );
5655 Ok(())
5656 }
5657
5658 #[test]
5659 fn dense_value_refuses_length_scale_and_numeric_boundary_controls() -> Result<(), Box<dyn std::error::Error>> {
5660 use base64::Engine as _;
5661
5662 let f32_definition = super::DenseVectorDefinition {
5663 dimensions: 2,
5664 element_type: super::DenseVectorElementType::F32,
5665 metric: super::VectorMetric::DotProduct,
5666 quantization_scale: None,
5667 };
5668 for bytes in [vec![0_u8; 4], vec![0_u8; 12]] {
5669 let value = super::DenseVectorBinaryValue {
5670 element_type: super::DenseVectorElementType::F32,
5671 byte_order: super::DenseVectorByteOrder::LittleEndian,
5672 data_base64: super::BASE64_STANDARD.encode(bytes),
5673 quantization_scale: None,
5674 };
5675 assert!(matches!(
5676 super::decode_dense_binary_value(f32_definition, &value),
5677 Err(super::DenseVectorValueError::InvalidBase64Length { .. })
5678 ));
5679 }
5680 let wrong_type = super::DenseVectorBinaryValue {
5681 element_type: super::DenseVectorElementType::F16,
5682 byte_order: super::DenseVectorByteOrder::LittleEndian,
5683 data_base64: super::BASE64_STANDARD.encode([0_u8; 8]),
5684 quantization_scale: None,
5685 };
5686 assert_eq!(
5687 super::decode_dense_binary_value(f32_definition, &wrong_type),
5688 Err(super::DenseVectorValueError::ElementTypeMismatch)
5689 );
5690 assert!(matches!(
5691 super::decode_dense_numeric_value(f32_definition, &[f64::INFINITY, 0.0]),
5692 Err(super::DenseVectorValueError::NonFiniteElement(0))
5693 ));
5694
5695 let f16_definition = super::DenseVectorDefinition {
5696 dimensions: 1,
5697 element_type: super::DenseVectorElementType::F16,
5698 metric: super::VectorMetric::DotProduct,
5699 quantization_scale: None,
5700 };
5701 assert_eq!(
5702 super::decode_dense_numeric_value(f16_definition, &[2.0_f64.powi(-25)])?[0].to_bits(),
5703 0
5704 );
5705 assert_eq!(
5706 super::decode_dense_numeric_value(f16_definition, &[2.0_f64.powi(-24)])?[0],
5707 2.0_f32.powi(-24)
5708 );
5709 assert_eq!(
5710 super::decode_dense_numeric_value(f16_definition, &[3.0 * 2.0_f64.powi(-25)])?[0],
5711 2.0_f32.powi(-23)
5712 );
5713 let infinite_half = super::DenseVectorBinaryValue {
5714 element_type: super::DenseVectorElementType::F16,
5715 byte_order: super::DenseVectorByteOrder::LittleEndian,
5716 data_base64: super::BASE64_STANDARD.encode(0x7c00_u16.to_le_bytes()),
5717 quantization_scale: None,
5718 };
5719 assert!(matches!(
5720 super::decode_dense_binary_value(f16_definition, &infinite_half),
5721 Err(super::DenseVectorValueError::NonFiniteElement(0))
5722 ));
5723
5724 let i8_definition = super::DenseVectorDefinition {
5725 dimensions: 1,
5726 element_type: super::DenseVectorElementType::I8,
5727 metric: super::VectorMetric::DotProduct,
5728 quantization_scale: Some(super::VectorQuantizationScale::new(1.0)?),
5729 };
5730 for value in [-129.0, 128.0] {
5731 assert!(matches!(
5732 super::decode_dense_numeric_value(i8_definition, &[value]),
5733 Err(super::DenseVectorValueError::I8ElementOutOfRange(0))
5734 ));
5735 }
5736 for scale in [r#""0""#, r#""-1""#, r#""1.0""#] {
5737 assert!(serde_json::from_str::<super::VectorQuantizationScale>(scale).is_err());
5738 }
5739 Ok(())
5740 }
5741}