1#![no_std]
28
29#[cfg(feature = "build")]
30extern crate alloc;
31
32#[cfg(kani)]
33extern crate kani;
34
35#[cfg(kani)]
36mod proofs;
37
38#[cfg(feature = "build")]
39pub mod build;
40
41use core::{fmt, marker::PhantomData};
42
43use oxgraph_graph::{
44 ContainsElement, ContainsRelation, EdgeTargetGraph, ElementIndex, ElementSuccessors,
45 GraphCounts, OutgoingEdgeCount, OutgoingGraph, RelationIndex, TopologyBase, TopologyCounts,
46};
47use oxgraph_layout_util::{
48 IdSlice, LocalId, NodeAxis, OffsetIntegrityIssue, SnapshotWidth, check_offset_section,
49 check_value_range,
50};
51pub use oxgraph_layout_util::{LayoutIndex, LayoutSnapshotWord, LayoutWord};
52use oxgraph_snapshot::{SectionBindError, SectionViewError, Snapshot};
53
54pub const SNAPSHOT_KIND_CSR_OFFSETS_U16: u32 = 0x0001;
56pub const SNAPSHOT_KIND_CSR_OFFSETS_U32: u32 = 0x0002;
58pub const SNAPSHOT_KIND_CSR_OFFSETS_U64: u32 = 0x0003;
60
61pub const SNAPSHOT_KIND_CSR_TARGETS_U16: u32 = 0x0004;
63pub const SNAPSHOT_KIND_CSR_TARGETS_U32: u32 = 0x0005;
65pub const SNAPSHOT_KIND_CSR_TARGETS_U64: u32 = 0x0006;
67
68pub const SNAPSHOT_CSR_SECTION_VERSION: u32 = 1;
70
71pub trait CsrSnapshotIndex: SnapshotWidth {
87 const OFFSETS_KIND: u32;
93
94 const TARGETS_KIND: u32;
100
101 const SECTION_VERSION: u32;
107}
108
109macro_rules! impl_csr_snapshot_index {
111 ($index:ty, $offsets_kind:expr, $targets_kind:expr) => {
112 impl CsrSnapshotIndex for $index {
113 const OFFSETS_KIND: u32 = $offsets_kind;
114 const TARGETS_KIND: u32 = $targets_kind;
115 const SECTION_VERSION: u32 = SNAPSHOT_CSR_SECTION_VERSION;
116 }
117 };
118}
119
120impl_csr_snapshot_index!(
121 u16,
122 SNAPSHOT_KIND_CSR_OFFSETS_U16,
123 SNAPSHOT_KIND_CSR_TARGETS_U16
124);
125impl_csr_snapshot_index!(
126 u32,
127 SNAPSHOT_KIND_CSR_OFFSETS_U32,
128 SNAPSHOT_KIND_CSR_TARGETS_U32
129);
130impl_csr_snapshot_index!(
131 u64,
132 SNAPSHOT_KIND_CSR_OFFSETS_U64,
133 SNAPSHOT_KIND_CSR_TARGETS_U64
134);
135
136pub type CsrNativeGraph<'view, NodeIndex, EdgeIndex> =
145 CsrGraph<'view, NodeIndex, EdgeIndex, EdgeIndex, NodeIndex>;
146
147pub type CsrSnapshotGraph<'view, NodeIndex, EdgeIndex> = CsrGraph<
157 'view,
158 NodeIndex,
159 EdgeIndex,
160 <EdgeIndex as SnapshotWidth>::LittleEndianWord,
161 <NodeIndex as SnapshotWidth>::LittleEndianWord,
162>;
163
164pub type CsrNodeId<Index> = LocalId<NodeAxis, Index>;
177
178pub type CsrEdgeId<Index> = LocalId<oxgraph_layout_util::EdgeAxis, Index>;
191
192#[derive(Clone, Copy, Debug)]
194struct Unchecked;
195
196#[derive(Clone, Copy, Debug)]
198struct Checked;
199
200#[derive(Clone, Copy, Debug)]
202struct NodeSlot<State, Index> {
203 raw: Index,
205 slot: usize,
207 state: PhantomData<fn() -> State>,
209}
210
211impl<Index> NodeSlot<Unchecked, Index> {
212 fn from_id(id: CsrNodeId<Index>) -> Self
218 where
219 Index: Copy,
220 {
221 Self {
222 raw: id.get(),
223 slot: 0,
224 state: PhantomData,
225 }
226 }
227}
228
229impl<Index> NodeSlot<Checked, Index> {
230 fn from_raw_slot(raw: Index, slot: usize) -> Self {
236 Self {
237 raw,
238 slot,
239 state: PhantomData,
240 }
241 }
242
243 const fn index(&self) -> usize {
249 self.slot
250 }
251}
252
253#[derive(Clone, Copy, Debug)]
255struct EdgeSlot<State, Index> {
256 raw: Index,
258 slot: usize,
260 state: PhantomData<fn() -> State>,
262}
263
264impl<Index> EdgeSlot<Unchecked, Index> {
265 fn from_id(id: CsrEdgeId<Index>) -> Self
271 where
272 Index: Copy,
273 {
274 Self {
275 raw: id.get(),
276 slot: 0,
277 state: PhantomData,
278 }
279 }
280}
281
282impl<Index> EdgeSlot<Checked, Index> {
283 fn from_raw_slot(raw: Index, slot: usize) -> Self {
289 Self {
290 raw,
291 slot,
292 state: PhantomData,
293 }
294 }
295
296 fn from_csr_range_slot(slot: usize) -> Option<Self>
308 where
309 Index: LayoutIndex,
310 {
311 let raw = oxgraph_layout_util::usize_to_index_validated::<Index>(slot)?;
312 Some(Self::from_raw_slot(raw, slot))
313 }
314
315 fn from_csr_range_slot_unchecked(slot: usize) -> Self
327 where
328 Index: LayoutIndex,
329 {
330 Self::from_csr_range_slot(slot)
331 .unwrap_or_else(|| unreachable!("checked CSR edge slot must fit index type"))
332 }
333
334 const fn index(&self) -> usize {
340 self.slot
341 }
342
343 const fn id(&self) -> CsrEdgeId<Index>
349 where
350 Index: Copy,
351 {
352 CsrEdgeId::new(self.raw)
353 }
354}
355
356#[derive(Clone, Copy, Debug)]
358struct EdgeRange<State, Index> {
359 start: usize,
361 end: usize,
363 state: PhantomData<fn() -> State>,
365 index: PhantomData<fn() -> Index>,
367}
368
369impl<Index> EdgeRange<Checked, Index>
370where
371 Index: LayoutIndex,
372{
373 fn from_bounds(start: usize, end: usize) -> Self {
379 Self {
380 start,
381 end,
382 state: PhantomData,
383 index: PhantomData,
384 }
385 }
386
387 const fn as_range(&self) -> core::ops::Range<usize> {
393 self.start..self.end
394 }
395
396 const fn len(&self) -> usize {
402 self.end - self.start
403 }
404
405 fn next_slot(&mut self) -> Option<EdgeSlot<Checked, Index>> {
411 if self.start == self.end {
412 return None;
413 }
414
415 let slot = EdgeSlot::from_csr_range_slot_unchecked(self.start);
416 self.start += 1;
417 Some(slot)
418 }
419}
420
421#[derive(Clone, Copy, Debug)]
436pub struct CsrGraph<'view, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
437where
438 NodeIndex: LayoutIndex,
439 EdgeIndex: LayoutIndex,
440 OffsetWord: LayoutWord<Index = EdgeIndex>,
441 TargetWord: LayoutWord<Index = NodeIndex>,
442{
443 node_count: NodeIndex,
445 node_bound: usize,
447 offsets: &'view [OffsetWord],
449 targets: &'view [TargetWord],
451}
452
453impl<'view, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
454 CsrGraph<'view, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
455where
456 NodeIndex: LayoutIndex,
457 EdgeIndex: LayoutIndex,
458 OffsetWord: LayoutWord<Index = EdgeIndex>,
459 TargetWord: LayoutWord<Index = NodeIndex>,
460{
461 pub fn validate(
474 node_count: NodeIndex,
475 offsets: &'view [OffsetWord],
476 targets: &'view [TargetWord],
477 ) -> Result<Self, CsrError<NodeIndex, EdgeIndex>> {
478 let node_bound = node_count
479 .to_usize()
480 .ok_or(CsrError::NodeUsizeOverflow { value: node_count })?;
481 if node_bound.checked_add(1).is_none() {
482 return Err(CsrError::OffsetLengthOverflow { node_count });
483 }
484
485 check_offset_section(offsets, node_bound, targets.len())
486 .map_err(|issue| map_offsets_issue::<NodeIndex, EdgeIndex, _>(offsets, issue))?;
487 check_value_range(targets, node_bound).map_err(|issue| {
488 map_targets_issue::<NodeIndex, EdgeIndex, _>(targets, node_count, issue)
489 })?;
490
491 Ok(Self {
492 node_count,
493 node_bound,
494 offsets,
495 targets,
496 })
497 }
498
499 #[must_use]
505 pub const fn offsets(&self) -> &'view [OffsetWord] {
506 self.offsets
507 }
508
509 #[must_use]
515 pub const fn targets(&self) -> &'view [TargetWord] {
516 self.targets
517 }
518
519 pub fn for_each_out_target(
527 &self,
528 source: CsrNodeId<NodeIndex>,
529 mut visit: impl FnMut(CsrNodeId<NodeIndex>) -> bool,
530 ) -> bool {
531 let Some(node) = self.try_node_slot(source) else {
532 return false;
533 };
534 let Some(index) = node.raw.to_usize() else {
535 return false;
536 };
537 let Some(start) = self.offsets[index].get().to_usize() else {
538 return false;
539 };
540 let Some(end) = self.offsets[index + 1].get().to_usize() else {
541 return false;
542 };
543 for word in &self.targets[start..end] {
544 if visit(CsrNodeId::new(word.get())) {
545 return true;
546 }
547 }
548 false
549 }
550
551 #[must_use]
557 pub fn contains_node(&self, node: CsrNodeId<NodeIndex>) -> bool {
558 self.try_node_slot(node).is_some()
559 }
560
561 #[must_use]
567 pub fn contains_edge(&self, edge: CsrEdgeId<EdgeIndex>) -> bool {
568 self.try_edge_slot(edge).is_some()
569 }
570
571 #[must_use]
577 pub fn try_target(&self, edge: CsrEdgeId<EdgeIndex>) -> Option<CsrNodeId<NodeIndex>> {
578 self.try_edge_slot(edge)
579 .map(|checked| self.target_node(checked))
580 }
581
582 fn try_node_slot(&self, node: CsrNodeId<NodeIndex>) -> Option<NodeSlot<Checked, NodeIndex>> {
588 self.check_node_slot(NodeSlot::from_id(node))
589 }
590
591 fn check_node_slot(
597 &self,
598 node: NodeSlot<Unchecked, NodeIndex>,
599 ) -> Option<NodeSlot<Checked, NodeIndex>> {
600 let slot = node.raw.to_usize()?;
601 if node.raw < self.node_count && slot < self.node_bound {
602 Some(NodeSlot::from_raw_slot(node.raw, slot))
603 } else {
604 None
605 }
606 }
607
608 fn checked_node_slot(&self, node: CsrNodeId<NodeIndex>) -> NodeSlot<Checked, NodeIndex> {
619 self.try_node_slot(node)
620 .unwrap_or_else(|| panic!("CSR node ID {node:?} is invalid for this graph"))
621 }
622
623 fn try_edge_slot(&self, edge: CsrEdgeId<EdgeIndex>) -> Option<EdgeSlot<Checked, EdgeIndex>> {
629 self.check_edge_slot(EdgeSlot::from_id(edge))
630 }
631
632 fn check_edge_slot(
638 &self,
639 edge: EdgeSlot<Unchecked, EdgeIndex>,
640 ) -> Option<EdgeSlot<Checked, EdgeIndex>> {
641 let slot = edge.raw.to_usize()?;
642 if slot < self.targets.len() {
643 Some(EdgeSlot::from_raw_slot(edge.raw, slot))
644 } else {
645 None
646 }
647 }
648
649 fn checked_edge_slot(&self, edge: CsrEdgeId<EdgeIndex>) -> EdgeSlot<Checked, EdgeIndex> {
660 self.try_edge_slot(edge)
661 .unwrap_or_else(|| panic!("CSR edge ID {edge:?} is invalid for this graph"))
662 }
663
664 fn checked_offset_slot(offset: EdgeIndex) -> usize {
677 oxgraph_layout_util::index_to_usize_validated(offset)
678 .unwrap_or_else(|| unreachable!("checked CSR offset must fit usize"))
679 }
680
681 fn target_node(&self, edge: EdgeSlot<Checked, EdgeIndex>) -> CsrNodeId<NodeIndex> {
687 CsrNodeId::new(self.targets[edge.index()].get())
688 }
689
690 fn outgoing_range(&self, node: NodeSlot<Checked, NodeIndex>) -> EdgeRange<Checked, EdgeIndex> {
696 let index = node.index();
697 EdgeRange::from_bounds(
698 Self::checked_offset_slot(self.offsets[index].get()),
699 Self::checked_offset_slot(self.offsets[index + 1].get()),
700 )
701 }
702}
703
704impl<'view, NodeIndex, EdgeIndex>
705 CsrGraph<
706 'view,
707 NodeIndex,
708 EdgeIndex,
709 <EdgeIndex as SnapshotWidth>::LittleEndianWord,
710 <NodeIndex as SnapshotWidth>::LittleEndianWord,
711 >
712where
713 NodeIndex: CsrSnapshotIndex,
714 EdgeIndex: CsrSnapshotIndex,
715{
716 pub fn from_snapshot(
739 snapshot: &Snapshot<'view>,
740 ) -> Result<Self, CsrSnapshotError<NodeIndex, EdgeIndex>> {
741 Self::from_snapshot_with_kinds(
742 snapshot,
743 EdgeIndex::OFFSETS_KIND,
744 NodeIndex::TARGETS_KIND,
745 EdgeIndex::SECTION_VERSION,
746 )
747 }
748
749 pub fn from_snapshot_with_kinds(
767 snapshot: &Snapshot<'view>,
768 offsets_kind: u32,
769 targets_kind: u32,
770 version: u32,
771 ) -> Result<Self, CsrSnapshotError<NodeIndex, EdgeIndex>> {
772 let offsets = snapshot
773 .typed_section::<EdgeIndex>(offsets_kind, version)
774 .map_err(|error| map_offsets_bind(offsets_kind, error))?;
775 let targets = snapshot
776 .typed_section::<NodeIndex>(targets_kind, version)
777 .map_err(|error| map_targets_bind(targets_kind, error))?;
778
779 if offsets.is_empty() {
780 return Err(CsrSnapshotError::OffsetsEmpty);
781 }
782
783 let node_count_usize = offsets.len() - 1;
784 let node_count =
785 NodeIndex::from_usize(node_count_usize).ok_or(CsrSnapshotError::NodeCountOverflow {
786 offsets_len: offsets.len(),
787 })?;
788
789 Ok(Self::validate(node_count, offsets, targets)?)
790 }
791}
792
793const fn map_offsets_bind<NodeIndex, EdgeIndex>(
795 kind: u32,
796 error: SectionBindError,
797) -> CsrSnapshotError<NodeIndex, EdgeIndex> {
798 match error {
799 SectionBindError::Missing { .. } => CsrSnapshotError::MissingOffsets,
800 SectionBindError::VersionMismatch {
801 expected, actual, ..
802 } => CsrSnapshotError::OffsetsVersion {
803 kind,
804 expected,
805 actual,
806 },
807 SectionBindError::View { error, .. } => CsrSnapshotError::OffsetsView(error),
808 }
809}
810
811const fn map_targets_bind<NodeIndex, EdgeIndex>(
813 kind: u32,
814 error: SectionBindError,
815) -> CsrSnapshotError<NodeIndex, EdgeIndex> {
816 match error {
817 SectionBindError::Missing { .. } => CsrSnapshotError::MissingTargets,
818 SectionBindError::VersionMismatch {
819 expected, actual, ..
820 } => CsrSnapshotError::TargetsVersion {
821 kind,
822 expected,
823 actual,
824 },
825 SectionBindError::View { error, .. } => CsrSnapshotError::TargetsView(error),
826 }
827}
828
829impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> TopologyBase
830 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
831where
832 NodeIndex: LayoutIndex,
833 EdgeIndex: LayoutIndex,
834 OffsetWord: LayoutWord<Index = EdgeIndex>,
835 TargetWord: LayoutWord<Index = NodeIndex>,
836{
837 type ElementId = CsrNodeId<NodeIndex>;
838 type RelationId = CsrEdgeId<EdgeIndex>;
839}
840
841impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> TopologyCounts
842 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
843where
844 NodeIndex: LayoutIndex,
845 EdgeIndex: LayoutIndex,
846 OffsetWord: LayoutWord<Index = EdgeIndex>,
847 TargetWord: LayoutWord<Index = NodeIndex>,
848{
849 fn element_count(&self) -> usize {
850 self.node_bound
851 }
852
853 fn relation_count(&self) -> usize {
854 self.targets.len()
855 }
856}
857
858impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> GraphCounts
859 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
860where
861 NodeIndex: LayoutIndex,
862 EdgeIndex: LayoutIndex,
863 OffsetWord: LayoutWord<Index = EdgeIndex>,
864 TargetWord: LayoutWord<Index = NodeIndex>,
865{
866}
867
868impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> ElementIndex
869 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
870where
871 NodeIndex: LayoutIndex,
872 EdgeIndex: LayoutIndex,
873 OffsetWord: LayoutWord<Index = EdgeIndex>,
874 TargetWord: LayoutWord<Index = NodeIndex>,
875{
876 fn element_bound(&self) -> usize {
877 self.node_bound
878 }
879
880 fn element_index(&self, element: CsrNodeId<NodeIndex>) -> usize {
881 self.checked_node_slot(element).index()
882 }
883}
884
885impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> RelationIndex
886 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
887where
888 NodeIndex: LayoutIndex,
889 EdgeIndex: LayoutIndex,
890 OffsetWord: LayoutWord<Index = EdgeIndex>,
891 TargetWord: LayoutWord<Index = NodeIndex>,
892{
893 fn relation_bound(&self) -> usize {
894 self.targets.len()
895 }
896
897 fn relation_index(&self, relation: CsrEdgeId<EdgeIndex>) -> usize {
898 self.checked_edge_slot(relation).index()
899 }
900}
901
902impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> ContainsElement
903 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
904where
905 NodeIndex: LayoutIndex,
906 EdgeIndex: LayoutIndex,
907 OffsetWord: LayoutWord<Index = EdgeIndex>,
908 TargetWord: LayoutWord<Index = NodeIndex>,
909{
910 fn contains_element(&self, element: CsrNodeId<NodeIndex>) -> bool {
911 self.contains_node(element)
912 }
913}
914
915impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> ContainsRelation
916 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
917where
918 NodeIndex: LayoutIndex,
919 EdgeIndex: LayoutIndex,
920 OffsetWord: LayoutWord<Index = EdgeIndex>,
921 TargetWord: LayoutWord<Index = NodeIndex>,
922{
923 fn contains_relation(&self, relation: CsrEdgeId<EdgeIndex>) -> bool {
924 self.contains_edge(relation)
925 }
926}
927
928impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> EdgeTargetGraph
929 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
930where
931 NodeIndex: LayoutIndex,
932 EdgeIndex: LayoutIndex,
933 OffsetWord: LayoutWord<Index = EdgeIndex>,
934 TargetWord: LayoutWord<Index = NodeIndex>,
935{
936 fn target(&self, edge: CsrEdgeId<EdgeIndex>) -> CsrNodeId<NodeIndex> {
937 self.target_node(self.checked_edge_slot(edge))
938 }
939}
940
941impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> OutgoingGraph
942 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
943where
944 NodeIndex: LayoutIndex,
945 EdgeIndex: LayoutIndex,
946 OffsetWord: LayoutWord<Index = EdgeIndex>,
947 TargetWord: LayoutWord<Index = NodeIndex>,
948{
949 type OutEdges<'view>
950 = CsrOutEdges<EdgeIndex>
951 where
952 Self: 'view;
953
954 fn outgoing_edges(&self, node: CsrNodeId<NodeIndex>) -> Self::OutEdges<'_> {
955 CsrOutEdges {
956 range: self.outgoing_range(self.checked_node_slot(node)),
957 }
958 }
959}
960
961impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> OutgoingEdgeCount
962 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
963where
964 NodeIndex: LayoutIndex,
965 EdgeIndex: LayoutIndex,
966 OffsetWord: LayoutWord<Index = EdgeIndex>,
967 TargetWord: LayoutWord<Index = NodeIndex>,
968{
969 fn out_degree(&self, node: CsrNodeId<NodeIndex>) -> usize {
970 self.outgoing_range(self.checked_node_slot(node)).len()
971 }
972}
973
974impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> ElementSuccessors
975 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
976where
977 NodeIndex: LayoutIndex,
978 EdgeIndex: LayoutIndex,
979 OffsetWord: LayoutWord<Index = EdgeIndex>,
980 TargetWord: LayoutWord<Index = NodeIndex>,
981{
982 type Successors<'view>
983 = IdSlice<'view, TargetWord, CsrNodeId<NodeIndex>>
984 where
985 Self: 'view;
986
987 fn element_successors(&self, node: CsrNodeId<NodeIndex>) -> Self::Successors<'_> {
988 let range = self.outgoing_range(self.checked_node_slot(node));
989 IdSlice::new(&self.targets[range.as_range()])
990 }
991}
992
993#[derive(Clone, Debug)]
999pub struct CsrOutEdges<Index> {
1000 range: EdgeRange<Checked, Index>,
1002}
1003
1004impl<Index> Iterator for CsrOutEdges<Index>
1005where
1006 Index: LayoutIndex,
1007{
1008 type Item = CsrEdgeId<Index>;
1009
1010 fn next(&mut self) -> Option<Self::Item> {
1011 self.range.next_slot().map(|slot| slot.id())
1012 }
1013}
1014
1015impl<Index> ExactSizeIterator for CsrOutEdges<Index>
1016where
1017 Index: LayoutIndex,
1018{
1019 fn len(&self) -> usize {
1020 self.range.len()
1021 }
1022}
1023
1024fn map_offsets_issue<NodeIndex, EdgeIndex, OffsetWord>(
1031 offsets: &[OffsetWord],
1032 issue: OffsetIntegrityIssue,
1033) -> CsrError<NodeIndex, EdgeIndex>
1034where
1035 NodeIndex: LayoutIndex,
1036 EdgeIndex: LayoutIndex,
1037 OffsetWord: LayoutWord<Index = EdgeIndex>,
1038{
1039 match issue {
1040 OffsetIntegrityIssue::Length { expected, actual } => {
1041 CsrError::OffsetLength { expected, actual }
1042 }
1043 OffsetIntegrityIssue::FirstNonZero { .. } => CsrError::FirstOffset {
1044 actual: offsets[0].get(),
1045 },
1046 OffsetIntegrityIssue::NonMonotonic { index, .. } => CsrError::NonMonotonicOffset {
1047 index,
1048 previous: offsets[index - 1].get(),
1049 actual: offsets[index].get(),
1050 },
1051 OffsetIntegrityIssue::FinalMismatch { value_len, .. } => CsrError::FinalOffset {
1052 final_offset: offsets[offsets.len() - 1].get(),
1053 target_len: value_len,
1054 },
1055 OffsetIntegrityIssue::UsizeOverflow { index } => CsrError::EdgeUsizeOverflow {
1056 value: offsets[index].get(),
1057 },
1058 OffsetIntegrityIssue::ValueOutOfRange { .. } => {
1059 CsrError::EdgeUsizeOverflow {
1061 value: EdgeIndex::ZERO,
1062 }
1063 }
1064 _ => CsrError::EdgeUsizeOverflow {
1065 value: EdgeIndex::ZERO,
1066 },
1067 }
1068}
1069
1070fn map_targets_issue<NodeIndex, EdgeIndex, TargetWord>(
1074 targets: &[TargetWord],
1075 node_count: NodeIndex,
1076 issue: OffsetIntegrityIssue,
1077) -> CsrError<NodeIndex, EdgeIndex>
1078where
1079 NodeIndex: LayoutIndex,
1080 EdgeIndex: LayoutIndex,
1081 TargetWord: LayoutWord<Index = NodeIndex>,
1082{
1083 match issue {
1084 OffsetIntegrityIssue::ValueOutOfRange { index, .. } => CsrError::TargetOutOfRange {
1085 index,
1086 target: targets[index].get(),
1087 node_count,
1088 },
1089 OffsetIntegrityIssue::UsizeOverflow { index } => CsrError::TargetUsizeOverflow {
1093 index,
1094 value: targets[index].get(),
1095 },
1096 _ => CsrError::TargetOutOfRange {
1097 index: 0,
1098 target: NodeIndex::ZERO,
1099 node_count,
1100 },
1101 }
1102}
1103
1104#[derive(Clone, Debug, Eq, PartialEq)]
1110pub enum CsrError<NodeIndex, EdgeIndex> {
1111 OffsetLengthOverflow {
1113 node_count: NodeIndex,
1115 },
1116 OffsetLength {
1118 expected: usize,
1120 actual: usize,
1122 },
1123 FirstOffset {
1125 actual: EdgeIndex,
1127 },
1128 NonMonotonicOffset {
1130 index: usize,
1132 previous: EdgeIndex,
1134 actual: EdgeIndex,
1136 },
1137 FinalOffset {
1139 final_offset: EdgeIndex,
1141 target_len: usize,
1143 },
1144 TargetOutOfRange {
1146 index: usize,
1148 target: NodeIndex,
1150 node_count: NodeIndex,
1152 },
1153 TargetUsizeOverflow {
1156 index: usize,
1158 value: NodeIndex,
1160 },
1161 NodeUsizeOverflow {
1163 value: NodeIndex,
1165 },
1166 EdgeUsizeOverflow {
1168 value: EdgeIndex,
1170 },
1171}
1172
1173impl<NodeIndex, EdgeIndex> fmt::Display for CsrError<NodeIndex, EdgeIndex>
1174where
1175 NodeIndex: fmt::Display,
1176 EdgeIndex: fmt::Display,
1177{
1178 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1179 match self {
1180 Self::OffsetLengthOverflow { node_count } => {
1181 write!(
1182 formatter,
1183 "offset length overflow for node count {node_count}"
1184 )
1185 }
1186 Self::OffsetLength { expected, actual } => write!(
1187 formatter,
1188 "invalid CSR offset length: expected {expected}, got {actual}"
1189 ),
1190 Self::FirstOffset { actual } => {
1191 write!(formatter, "first CSR offset must be 0, got {actual}")
1192 }
1193 Self::NonMonotonicOffset {
1194 index,
1195 previous,
1196 actual,
1197 } => write!(
1198 formatter,
1199 "CSR offset at index {index} is not monotonic: previous {previous}, got {actual}"
1200 ),
1201 Self::FinalOffset {
1202 final_offset,
1203 target_len,
1204 } => write!(
1205 formatter,
1206 "final CSR offset {final_offset} does not match target length {target_len}"
1207 ),
1208 Self::TargetOutOfRange {
1209 index,
1210 target,
1211 node_count,
1212 } => write!(
1213 formatter,
1214 "CSR target at index {index} is out of range: target {target}, node count {node_count}"
1215 ),
1216 Self::TargetUsizeOverflow { index, value } => write!(
1217 formatter,
1218 "CSR target at index {index} value {value} does not fit usize"
1219 ),
1220 Self::NodeUsizeOverflow { value } => {
1221 write!(formatter, "CSR node index value {value} does not fit usize")
1222 }
1223 Self::EdgeUsizeOverflow { value } => {
1224 write!(formatter, "CSR edge index value {value} does not fit usize")
1225 }
1226 }
1227 }
1228}
1229
1230impl<NodeIndex, EdgeIndex> core::error::Error for CsrError<NodeIndex, EdgeIndex>
1231where
1232 NodeIndex: fmt::Debug + fmt::Display,
1233 EdgeIndex: fmt::Debug + fmt::Display,
1234{
1235}
1236
1237#[derive(Clone, Debug, Eq, PartialEq)]
1243pub enum CsrSnapshotError<NodeIndex, EdgeIndex> {
1244 MissingOffsets,
1246 MissingTargets,
1248 OffsetsVersion {
1250 kind: u32,
1252 expected: u32,
1254 actual: u32,
1256 },
1257 TargetsVersion {
1259 kind: u32,
1261 expected: u32,
1263 actual: u32,
1265 },
1266 OffsetsView(SectionViewError),
1269 TargetsView(SectionViewError),
1272 OffsetsEmpty,
1275 NodeCountOverflow {
1277 offsets_len: usize,
1279 },
1280 Csr(CsrError<NodeIndex, EdgeIndex>),
1282}
1283
1284impl<NodeIndex, EdgeIndex> fmt::Display for CsrSnapshotError<NodeIndex, EdgeIndex>
1285where
1286 NodeIndex: fmt::Display,
1287 EdgeIndex: fmt::Display,
1288{
1289 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1290 match self {
1291 Self::MissingOffsets => formatter.write_str("snapshot has no CSR offsets section"),
1292 Self::MissingTargets => formatter.write_str("snapshot has no CSR targets section"),
1293 Self::OffsetsVersion {
1294 kind,
1295 expected,
1296 actual,
1297 } => write!(
1298 formatter,
1299 "CSR offsets section {kind} version {actual} does not match expected {expected}"
1300 ),
1301 Self::TargetsVersion {
1302 kind,
1303 expected,
1304 actual,
1305 } => write!(
1306 formatter,
1307 "CSR targets section {kind} version {actual} does not match expected {expected}"
1308 ),
1309 Self::OffsetsView(error) => write!(
1310 formatter,
1311 "CSR offsets section cannot be borrowed as selected little-endian index words: {error}"
1312 ),
1313 Self::TargetsView(error) => write!(
1314 formatter,
1315 "CSR targets section cannot be borrowed as selected little-endian index words: {error}"
1316 ),
1317 Self::OffsetsEmpty => formatter.write_str("CSR offsets section is empty"),
1318 Self::NodeCountOverflow { offsets_len } => write!(
1319 formatter,
1320 "derived node count from offsets length {offsets_len} does not fit selected CSR index type"
1321 ),
1322 Self::Csr(error) => write!(formatter, "CSR validation failed: {error}"),
1323 }
1324 }
1325}
1326
1327impl<NodeIndex, EdgeIndex> core::error::Error for CsrSnapshotError<NodeIndex, EdgeIndex>
1328where
1329 NodeIndex: fmt::Debug + fmt::Display,
1330 EdgeIndex: fmt::Debug + fmt::Display,
1331{
1332}
1333
1334impl<NodeIndex, EdgeIndex> From<CsrError<NodeIndex, EdgeIndex>>
1335 for CsrSnapshotError<NodeIndex, EdgeIndex>
1336{
1337 fn from(error: CsrError<NodeIndex, EdgeIndex>) -> Self {
1338 Self::Csr(error)
1339 }
1340}