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