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