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