1#![no_std]
30
31#[cfg(feature = "build")]
32extern crate alloc;
33
34#[cfg(kani)]
35extern crate kani;
36
37#[cfg(kani)]
38mod proofs;
39
40#[cfg(feature = "build")]
41pub mod build;
42
43use core::{fmt, marker::PhantomData};
44
45use oxgraph_graph::{
46 ContainsElement, ContainsRelation, DenseElementIndex, DenseRelationIndex, EdgeTargetGraph,
47 ElementSuccessors, OutgoingEdgeCount, OutgoingGraph, TopologyBase, TopologyCounts,
48};
49use oxgraph_layout_util::{
50 IdSlice, LocalId, NodeAxis, SnapshotWidth,
51 integrity::{OffsetIntegrityIssue, check_offset_section, check_value_range},
52};
53pub use oxgraph_layout_util::{LayoutIndex, LayoutSnapshotWord, LayoutWord};
54use oxgraph_snapshot::{SectionBindError, SectionViewError, Snapshot};
55
56pub const SNAPSHOT_KIND_CSR_OFFSETS_BASE: u32 = 0x0004;
59pub const SNAPSHOT_KIND_CSR_TARGETS_BASE: u32 = 0x0008;
67
68pub const SNAPSHOT_CSR_SECTION_VERSION: u32 = 1;
70
71pub trait CsrSnapshotIndex: SnapshotWidth {
88 const OFFSETS_KIND: u32 = SNAPSHOT_KIND_CSR_OFFSETS_BASE | Self::WIDTH_CODE;
95
96 const TARGETS_KIND: u32 = SNAPSHOT_KIND_CSR_TARGETS_BASE | Self::WIDTH_CODE;
103
104 const SECTION_VERSION: u32 = SNAPSHOT_CSR_SECTION_VERSION;
110}
111
112impl CsrSnapshotIndex for u16 {}
113impl CsrSnapshotIndex for u32 {}
114impl CsrSnapshotIndex for u64 {}
115
116pub type CsrNativeGraph<'view, NodeIndex, EdgeIndex> =
125 CsrGraph<'view, NodeIndex, EdgeIndex, EdgeIndex, NodeIndex>;
126
127pub type CsrSnapshotGraph<'view, NodeIndex, EdgeIndex> = CsrGraph<
137 'view,
138 NodeIndex,
139 EdgeIndex,
140 <EdgeIndex as SnapshotWidth>::LittleEndianWord,
141 <NodeIndex as SnapshotWidth>::LittleEndianWord,
142>;
143
144pub type CsrNodeId<Index> = LocalId<NodeAxis, Index>;
157
158pub type CsrEdgeId<Index> = LocalId<oxgraph_layout_util::EdgeAxis, Index>;
171
172#[derive(Clone, Copy, Debug)]
174struct Unchecked;
175
176#[derive(Clone, Copy, Debug)]
178struct Checked;
179
180#[derive(Clone, Copy, Debug)]
182struct NodeSlot<State, Index> {
183 raw: Index,
185 slot: usize,
187 state: PhantomData<fn() -> State>,
189}
190
191impl<Index> NodeSlot<Unchecked, Index> {
192 fn from_id(id: CsrNodeId<Index>) -> Self
198 where
199 Index: Copy,
200 {
201 Self {
202 raw: id.get(),
203 slot: 0,
204 state: PhantomData,
205 }
206 }
207}
208
209impl<Index> NodeSlot<Checked, Index> {
210 fn from_raw_slot(raw: Index, slot: usize) -> Self {
216 Self {
217 raw,
218 slot,
219 state: PhantomData,
220 }
221 }
222
223 const fn index(&self) -> usize {
229 self.slot
230 }
231}
232
233#[derive(Clone, Copy, Debug)]
235struct EdgeSlot<State, Index> {
236 raw: Index,
238 slot: usize,
240 state: PhantomData<fn() -> State>,
242}
243
244impl<Index> EdgeSlot<Unchecked, Index> {
245 fn from_id(id: CsrEdgeId<Index>) -> Self
251 where
252 Index: Copy,
253 {
254 Self {
255 raw: id.get(),
256 slot: 0,
257 state: PhantomData,
258 }
259 }
260}
261
262impl<Index> EdgeSlot<Checked, Index> {
263 fn from_raw_slot(raw: Index, slot: usize) -> Self {
269 Self {
270 raw,
271 slot,
272 state: PhantomData,
273 }
274 }
275
276 fn from_csr_range_slot(slot: usize) -> Option<Self>
288 where
289 Index: LayoutIndex,
290 {
291 let raw = oxgraph_layout_util::integrity::usize_to_index_validated::<Index>(slot)?;
292 Some(Self::from_raw_slot(raw, slot))
293 }
294
295 fn from_csr_range_slot_unchecked(slot: usize) -> Self
307 where
308 Index: LayoutIndex,
309 {
310 Self::from_csr_range_slot(slot)
311 .unwrap_or_else(|| unreachable!("checked CSR edge slot must fit index type"))
312 }
313
314 const fn index(&self) -> usize {
320 self.slot
321 }
322
323 const fn id(&self) -> CsrEdgeId<Index>
329 where
330 Index: Copy,
331 {
332 CsrEdgeId::new(self.raw)
333 }
334}
335
336#[derive(Clone, Copy, Debug)]
338struct EdgeRange<State, Index> {
339 start: usize,
341 end: usize,
343 state: PhantomData<fn() -> State>,
345 index: PhantomData<fn() -> Index>,
347}
348
349impl<Index> EdgeRange<Checked, Index>
350where
351 Index: LayoutIndex,
352{
353 fn from_bounds(start: usize, end: usize) -> Self {
359 Self {
360 start,
361 end,
362 state: PhantomData,
363 index: PhantomData,
364 }
365 }
366
367 const fn as_range(&self) -> core::ops::Range<usize> {
373 self.start..self.end
374 }
375
376 const fn len(&self) -> usize {
382 self.end - self.start
383 }
384
385 fn next_slot(&mut self) -> Option<EdgeSlot<Checked, Index>> {
391 if self.start == self.end {
392 return None;
393 }
394
395 let slot = EdgeSlot::from_csr_range_slot_unchecked(self.start);
396 self.start += 1;
397 Some(slot)
398 }
399}
400
401#[derive(Clone, Copy, Debug)]
416pub struct CsrGraph<'view, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
417where
418 NodeIndex: LayoutIndex,
419 EdgeIndex: LayoutIndex,
420 OffsetWord: LayoutWord<Index = EdgeIndex>,
421 TargetWord: LayoutWord<Index = NodeIndex>,
422{
423 node_count: NodeIndex,
425 node_bound: usize,
427 offsets: &'view [OffsetWord],
429 targets: &'view [TargetWord],
431}
432
433impl<'view, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
434 CsrGraph<'view, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
435where
436 NodeIndex: LayoutIndex,
437 EdgeIndex: LayoutIndex,
438 OffsetWord: LayoutWord<Index = EdgeIndex>,
439 TargetWord: LayoutWord<Index = NodeIndex>,
440{
441 pub fn validate(
454 node_count: NodeIndex,
455 offsets: &'view [OffsetWord],
456 targets: &'view [TargetWord],
457 ) -> Result<Self, CsrError<NodeIndex, EdgeIndex>> {
458 let node_bound = node_count
459 .to_usize()
460 .ok_or(CsrError::NodeUsizeOverflow { value: node_count })?;
461 if node_bound.checked_add(1).is_none() {
462 return Err(CsrError::OffsetLengthOverflow { node_count });
463 }
464
465 check_offset_section(offsets, node_bound, targets.len())
466 .map_err(|issue| map_offsets_issue::<NodeIndex, EdgeIndex, _>(offsets, issue))?;
467 check_value_range(targets, node_bound).map_err(|issue| {
468 map_targets_issue::<NodeIndex, EdgeIndex, _>(targets, node_count, issue)
469 })?;
470
471 Ok(Self {
472 node_count,
473 node_bound,
474 offsets,
475 targets,
476 })
477 }
478
479 pub(crate) const fn from_validated_parts(
490 node_count: NodeIndex,
491 node_bound: usize,
492 offsets: &'view [OffsetWord],
493 targets: &'view [TargetWord],
494 ) -> Self {
495 Self {
496 node_count,
497 node_bound,
498 offsets,
499 targets,
500 }
501 }
502
503 #[must_use]
509 pub const fn offsets(&self) -> &'view [OffsetWord] {
510 self.offsets
511 }
512
513 #[must_use]
519 pub const fn targets(&self) -> &'view [TargetWord] {
520 self.targets
521 }
522
523 pub fn for_each_out_target(
531 &self,
532 source: CsrNodeId<NodeIndex>,
533 mut visit: impl FnMut(CsrNodeId<NodeIndex>) -> bool,
534 ) -> bool {
535 let Some(node) = self.try_node_slot(source) else {
536 return false;
537 };
538 let Some(index) = node.raw.to_usize() else {
539 return false;
540 };
541 let Some(start) = self.offsets[index].get().to_usize() else {
542 return false;
543 };
544 let Some(end) = self.offsets[index + 1].get().to_usize() else {
545 return false;
546 };
547 for word in &self.targets[start..end] {
548 if visit(CsrNodeId::new(word.get())) {
549 return true;
550 }
551 }
552 false
553 }
554
555 #[must_use]
561 pub fn contains_node(&self, node: CsrNodeId<NodeIndex>) -> bool {
562 self.try_node_slot(node).is_some()
563 }
564
565 #[must_use]
571 pub fn contains_edge(&self, edge: CsrEdgeId<EdgeIndex>) -> bool {
572 self.try_edge_slot(edge).is_some()
573 }
574
575 #[must_use]
581 pub fn try_target(&self, edge: CsrEdgeId<EdgeIndex>) -> Option<CsrNodeId<NodeIndex>> {
582 self.try_edge_slot(edge)
583 .map(|checked| self.target_node(checked))
584 }
585
586 fn try_node_slot(&self, node: CsrNodeId<NodeIndex>) -> Option<NodeSlot<Checked, NodeIndex>> {
592 self.check_node_slot(NodeSlot::from_id(node))
593 }
594
595 fn check_node_slot(
601 &self,
602 node: NodeSlot<Unchecked, NodeIndex>,
603 ) -> Option<NodeSlot<Checked, NodeIndex>> {
604 let slot = node.raw.to_usize()?;
605 if node.raw < self.node_count && slot < self.node_bound {
606 Some(NodeSlot::from_raw_slot(node.raw, slot))
607 } else {
608 None
609 }
610 }
611
612 fn checked_node_slot(&self, node: CsrNodeId<NodeIndex>) -> NodeSlot<Checked, NodeIndex> {
623 self.try_node_slot(node)
624 .unwrap_or_else(|| panic!("CSR node ID {node:?} is invalid for this graph"))
625 }
626
627 fn try_edge_slot(&self, edge: CsrEdgeId<EdgeIndex>) -> Option<EdgeSlot<Checked, EdgeIndex>> {
633 self.check_edge_slot(EdgeSlot::from_id(edge))
634 }
635
636 fn check_edge_slot(
642 &self,
643 edge: EdgeSlot<Unchecked, EdgeIndex>,
644 ) -> Option<EdgeSlot<Checked, EdgeIndex>> {
645 let slot = edge.raw.to_usize()?;
646 if slot < self.targets.len() {
647 Some(EdgeSlot::from_raw_slot(edge.raw, slot))
648 } else {
649 None
650 }
651 }
652
653 fn checked_edge_slot(&self, edge: CsrEdgeId<EdgeIndex>) -> EdgeSlot<Checked, EdgeIndex> {
664 self.try_edge_slot(edge)
665 .unwrap_or_else(|| panic!("CSR edge ID {edge:?} is invalid for this graph"))
666 }
667
668 fn checked_offset_slot(offset: EdgeIndex) -> usize {
681 oxgraph_layout_util::integrity::index_to_usize_validated(offset)
682 .unwrap_or_else(|| unreachable!("checked CSR offset must fit usize"))
683 }
684
685 fn target_node(&self, edge: EdgeSlot<Checked, EdgeIndex>) -> CsrNodeId<NodeIndex> {
691 CsrNodeId::new(self.targets[edge.index()].get())
692 }
693
694 fn outgoing_range(&self, node: NodeSlot<Checked, NodeIndex>) -> EdgeRange<Checked, EdgeIndex> {
700 let index = node.index();
701 EdgeRange::from_bounds(
702 Self::checked_offset_slot(self.offsets[index].get()),
703 Self::checked_offset_slot(self.offsets[index + 1].get()),
704 )
705 }
706}
707
708impl<'view, NodeIndex, EdgeIndex>
709 CsrGraph<
710 'view,
711 NodeIndex,
712 EdgeIndex,
713 <EdgeIndex as SnapshotWidth>::LittleEndianWord,
714 <NodeIndex as SnapshotWidth>::LittleEndianWord,
715 >
716where
717 NodeIndex: CsrSnapshotIndex,
718 EdgeIndex: CsrSnapshotIndex,
719{
720 pub fn from_snapshot(
743 snapshot: &Snapshot<'view>,
744 ) -> Result<Self, CsrSnapshotError<NodeIndex, EdgeIndex>> {
745 Self::from_snapshot_with_kinds(
746 snapshot,
747 EdgeIndex::OFFSETS_KIND,
748 NodeIndex::TARGETS_KIND,
749 EdgeIndex::SECTION_VERSION,
750 )
751 }
752
753 pub fn from_snapshot_with_kinds(
771 snapshot: &Snapshot<'view>,
772 offsets_kind: u32,
773 targets_kind: u32,
774 version: u32,
775 ) -> Result<Self, CsrSnapshotError<NodeIndex, EdgeIndex>> {
776 let offsets = snapshot
777 .typed_section::<EdgeIndex>(offsets_kind, version)
778 .map_err(|error| map_offsets_bind(offsets_kind, error))?;
779 let targets = snapshot
780 .typed_section::<NodeIndex>(targets_kind, version)
781 .map_err(|error| map_targets_bind(targets_kind, error))?;
782
783 if offsets.is_empty() {
784 return Err(CsrSnapshotError::OffsetsEmpty);
785 }
786
787 let node_count_usize = offsets.len() - 1;
788 let node_count =
789 NodeIndex::from_usize(node_count_usize).ok_or(CsrSnapshotError::NodeCountOverflow {
790 offsets_len: offsets.len(),
791 })?;
792
793 Ok(Self::validate(node_count, offsets, targets)?)
794 }
795}
796
797const fn map_offsets_bind<NodeIndex, EdgeIndex>(
799 kind: u32,
800 error: SectionBindError,
801) -> CsrSnapshotError<NodeIndex, EdgeIndex> {
802 match error {
803 SectionBindError::Missing { .. } => CsrSnapshotError::MissingOffsets,
804 SectionBindError::VersionMismatch {
805 expected, actual, ..
806 } => CsrSnapshotError::OffsetsVersion {
807 kind,
808 expected,
809 actual,
810 },
811 SectionBindError::View { error, .. } => CsrSnapshotError::OffsetsView(error),
812 }
813}
814
815const fn map_targets_bind<NodeIndex, EdgeIndex>(
817 kind: u32,
818 error: SectionBindError,
819) -> CsrSnapshotError<NodeIndex, EdgeIndex> {
820 match error {
821 SectionBindError::Missing { .. } => CsrSnapshotError::MissingTargets,
822 SectionBindError::VersionMismatch {
823 expected, actual, ..
824 } => CsrSnapshotError::TargetsVersion {
825 kind,
826 expected,
827 actual,
828 },
829 SectionBindError::View { error, .. } => CsrSnapshotError::TargetsView(error),
830 }
831}
832
833impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> TopologyBase
834 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
835where
836 NodeIndex: LayoutIndex,
837 EdgeIndex: LayoutIndex,
838 OffsetWord: LayoutWord<Index = EdgeIndex>,
839 TargetWord: LayoutWord<Index = NodeIndex>,
840{
841 type ElementId = CsrNodeId<NodeIndex>;
842 type RelationId = CsrEdgeId<EdgeIndex>;
843}
844
845impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> TopologyCounts
846 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
847where
848 NodeIndex: LayoutIndex,
849 EdgeIndex: LayoutIndex,
850 OffsetWord: LayoutWord<Index = EdgeIndex>,
851 TargetWord: LayoutWord<Index = NodeIndex>,
852{
853 fn element_count(&self) -> usize {
854 self.node_bound
855 }
856
857 fn relation_count(&self) -> usize {
858 self.targets.len()
859 }
860}
861
862impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> DenseElementIndex
863 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
864where
865 NodeIndex: LayoutIndex,
866 EdgeIndex: LayoutIndex,
867 OffsetWord: LayoutWord<Index = EdgeIndex>,
868 TargetWord: LayoutWord<Index = NodeIndex>,
869{
870 fn element_bound(&self) -> usize {
871 self.node_bound
872 }
873
874 fn element_index(&self, element: CsrNodeId<NodeIndex>) -> usize {
875 self.checked_node_slot(element).index()
876 }
877}
878
879impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> DenseRelationIndex
880 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
881where
882 NodeIndex: LayoutIndex,
883 EdgeIndex: LayoutIndex,
884 OffsetWord: LayoutWord<Index = EdgeIndex>,
885 TargetWord: LayoutWord<Index = NodeIndex>,
886{
887 fn relation_bound(&self) -> usize {
888 self.targets.len()
889 }
890
891 fn relation_index(&self, relation: CsrEdgeId<EdgeIndex>) -> usize {
892 self.checked_edge_slot(relation).index()
893 }
894}
895
896impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> ContainsElement
897 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
898where
899 NodeIndex: LayoutIndex,
900 EdgeIndex: LayoutIndex,
901 OffsetWord: LayoutWord<Index = EdgeIndex>,
902 TargetWord: LayoutWord<Index = NodeIndex>,
903{
904 fn contains_element(&self, element: CsrNodeId<NodeIndex>) -> bool {
905 self.contains_node(element)
906 }
907}
908
909impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> ContainsRelation
910 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
911where
912 NodeIndex: LayoutIndex,
913 EdgeIndex: LayoutIndex,
914 OffsetWord: LayoutWord<Index = EdgeIndex>,
915 TargetWord: LayoutWord<Index = NodeIndex>,
916{
917 fn contains_relation(&self, relation: CsrEdgeId<EdgeIndex>) -> bool {
918 self.contains_edge(relation)
919 }
920}
921
922impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> EdgeTargetGraph
923 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
924where
925 NodeIndex: LayoutIndex,
926 EdgeIndex: LayoutIndex,
927 OffsetWord: LayoutWord<Index = EdgeIndex>,
928 TargetWord: LayoutWord<Index = NodeIndex>,
929{
930 fn target(&self, edge: CsrEdgeId<EdgeIndex>) -> CsrNodeId<NodeIndex> {
931 self.target_node(self.checked_edge_slot(edge))
932 }
933}
934
935impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> OutgoingGraph
936 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
937where
938 NodeIndex: LayoutIndex,
939 EdgeIndex: LayoutIndex,
940 OffsetWord: LayoutWord<Index = EdgeIndex>,
941 TargetWord: LayoutWord<Index = NodeIndex>,
942{
943 type OutEdges<'view>
944 = CsrOutEdges<EdgeIndex>
945 where
946 Self: 'view;
947
948 fn outgoing_edges(&self, node: CsrNodeId<NodeIndex>) -> Self::OutEdges<'_> {
949 CsrOutEdges {
950 range: self.outgoing_range(self.checked_node_slot(node)),
951 }
952 }
953}
954
955impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> OutgoingEdgeCount
956 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
957where
958 NodeIndex: LayoutIndex,
959 EdgeIndex: LayoutIndex,
960 OffsetWord: LayoutWord<Index = EdgeIndex>,
961 TargetWord: LayoutWord<Index = NodeIndex>,
962{
963 fn out_degree(&self, node: CsrNodeId<NodeIndex>) -> usize {
964 self.outgoing_range(self.checked_node_slot(node)).len()
965 }
966}
967
968impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> ElementSuccessors
969 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
970where
971 NodeIndex: LayoutIndex,
972 EdgeIndex: LayoutIndex,
973 OffsetWord: LayoutWord<Index = EdgeIndex>,
974 TargetWord: LayoutWord<Index = NodeIndex>,
975{
976 type Successors<'view>
977 = IdSlice<'view, TargetWord, CsrNodeId<NodeIndex>>
978 where
979 Self: 'view;
980
981 fn element_successors(&self, node: CsrNodeId<NodeIndex>) -> Self::Successors<'_> {
982 let range = self.outgoing_range(self.checked_node_slot(node));
983 IdSlice::new(&self.targets[range.as_range()])
984 }
985}
986
987#[derive(Clone, Debug)]
993pub struct CsrOutEdges<Index> {
994 range: EdgeRange<Checked, Index>,
996}
997
998impl<Index> Iterator for CsrOutEdges<Index>
999where
1000 Index: LayoutIndex,
1001{
1002 type Item = CsrEdgeId<Index>;
1003
1004 fn next(&mut self) -> Option<Self::Item> {
1005 self.range.next_slot().map(|slot| slot.id())
1006 }
1007}
1008
1009impl<Index> ExactSizeIterator for CsrOutEdges<Index>
1010where
1011 Index: LayoutIndex,
1012{
1013 fn len(&self) -> usize {
1014 self.range.len()
1015 }
1016}
1017
1018fn map_offsets_issue<NodeIndex, EdgeIndex, OffsetWord>(
1025 offsets: &[OffsetWord],
1026 issue: OffsetIntegrityIssue,
1027) -> CsrError<NodeIndex, EdgeIndex>
1028where
1029 NodeIndex: LayoutIndex,
1030 EdgeIndex: LayoutIndex,
1031 OffsetWord: LayoutWord<Index = EdgeIndex>,
1032{
1033 match issue {
1034 OffsetIntegrityIssue::Length { expected, actual } => {
1035 CsrError::OffsetLength { expected, actual }
1036 }
1037 OffsetIntegrityIssue::FirstNonZero { .. } => CsrError::FirstOffset {
1038 actual: offsets[0].get(),
1039 },
1040 OffsetIntegrityIssue::NonMonotonic { index, .. } => CsrError::NonMonotonicOffset {
1041 index,
1042 previous: offsets[index - 1].get(),
1043 actual: offsets[index].get(),
1044 },
1045 OffsetIntegrityIssue::FinalMismatch { value_len, .. } => CsrError::FinalOffset {
1046 final_offset: offsets[offsets.len() - 1].get(),
1047 target_len: value_len,
1048 },
1049 OffsetIntegrityIssue::UsizeOverflow { index } => CsrError::EdgeUsizeOverflow {
1050 value: offsets[index].get(),
1051 },
1052 OffsetIntegrityIssue::ValueOutOfRange { .. } => {
1053 CsrError::EdgeUsizeOverflow {
1055 value: EdgeIndex::ZERO,
1056 }
1057 }
1058 _ => CsrError::EdgeUsizeOverflow {
1059 value: EdgeIndex::ZERO,
1060 },
1061 }
1062}
1063
1064fn map_targets_issue<NodeIndex, EdgeIndex, TargetWord>(
1068 targets: &[TargetWord],
1069 node_count: NodeIndex,
1070 issue: OffsetIntegrityIssue,
1071) -> CsrError<NodeIndex, EdgeIndex>
1072where
1073 NodeIndex: LayoutIndex,
1074 EdgeIndex: LayoutIndex,
1075 TargetWord: LayoutWord<Index = NodeIndex>,
1076{
1077 match issue {
1078 OffsetIntegrityIssue::ValueOutOfRange { index, .. } => CsrError::TargetOutOfRange {
1079 index,
1080 target: targets[index].get(),
1081 node_count,
1082 },
1083 OffsetIntegrityIssue::UsizeOverflow { index } => CsrError::TargetUsizeOverflow {
1087 index,
1088 value: targets[index].get(),
1089 },
1090 _ => CsrError::TargetOutOfRange {
1091 index: 0,
1092 target: NodeIndex::ZERO,
1093 node_count,
1094 },
1095 }
1096}
1097
1098#[derive(Clone, Debug, Eq, PartialEq)]
1104pub enum CsrError<NodeIndex, EdgeIndex> {
1105 OffsetLengthOverflow {
1107 node_count: NodeIndex,
1109 },
1110 OffsetLength {
1112 expected: usize,
1114 actual: usize,
1116 },
1117 FirstOffset {
1119 actual: EdgeIndex,
1121 },
1122 NonMonotonicOffset {
1124 index: usize,
1126 previous: EdgeIndex,
1128 actual: EdgeIndex,
1130 },
1131 FinalOffset {
1133 final_offset: EdgeIndex,
1135 target_len: usize,
1137 },
1138 TargetOutOfRange {
1140 index: usize,
1142 target: NodeIndex,
1144 node_count: NodeIndex,
1146 },
1147 TargetUsizeOverflow {
1150 index: usize,
1152 value: NodeIndex,
1154 },
1155 NodeUsizeOverflow {
1157 value: NodeIndex,
1159 },
1160 EdgeUsizeOverflow {
1162 value: EdgeIndex,
1164 },
1165}
1166
1167impl<NodeIndex, EdgeIndex> fmt::Display for CsrError<NodeIndex, EdgeIndex>
1168where
1169 NodeIndex: fmt::Display,
1170 EdgeIndex: fmt::Display,
1171{
1172 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1173 match self {
1174 Self::OffsetLengthOverflow { node_count } => {
1175 write!(
1176 formatter,
1177 "offset length overflow for node count {node_count}"
1178 )
1179 }
1180 Self::OffsetLength { expected, actual } => write!(
1181 formatter,
1182 "invalid CSR offset length: expected {expected}, got {actual}"
1183 ),
1184 Self::FirstOffset { actual } => {
1185 write!(formatter, "first CSR offset must be 0, got {actual}")
1186 }
1187 Self::NonMonotonicOffset {
1188 index,
1189 previous,
1190 actual,
1191 } => write!(
1192 formatter,
1193 "CSR offset at index {index} is not monotonic: previous {previous}, got {actual}"
1194 ),
1195 Self::FinalOffset {
1196 final_offset,
1197 target_len,
1198 } => write!(
1199 formatter,
1200 "final CSR offset {final_offset} does not match target length {target_len}"
1201 ),
1202 Self::TargetOutOfRange {
1203 index,
1204 target,
1205 node_count,
1206 } => write!(
1207 formatter,
1208 "CSR target at index {index} is out of range: target {target}, node count {node_count}"
1209 ),
1210 Self::TargetUsizeOverflow { index, value } => write!(
1211 formatter,
1212 "CSR target at index {index} value {value} does not fit usize"
1213 ),
1214 Self::NodeUsizeOverflow { value } => {
1215 write!(formatter, "CSR node index value {value} does not fit usize")
1216 }
1217 Self::EdgeUsizeOverflow { value } => {
1218 write!(formatter, "CSR edge index value {value} does not fit usize")
1219 }
1220 }
1221 }
1222}
1223
1224impl<NodeIndex, EdgeIndex> core::error::Error for CsrError<NodeIndex, EdgeIndex>
1225where
1226 NodeIndex: fmt::Debug + fmt::Display,
1227 EdgeIndex: fmt::Debug + fmt::Display,
1228{
1229}
1230
1231#[derive(Clone, Debug, Eq, PartialEq)]
1237pub enum CsrSnapshotError<NodeIndex, EdgeIndex> {
1238 MissingOffsets,
1240 MissingTargets,
1242 OffsetsVersion {
1244 kind: u32,
1246 expected: u32,
1248 actual: u32,
1250 },
1251 TargetsVersion {
1253 kind: u32,
1255 expected: u32,
1257 actual: u32,
1259 },
1260 OffsetsView(SectionViewError),
1263 TargetsView(SectionViewError),
1266 OffsetsEmpty,
1269 NodeCountOverflow {
1271 offsets_len: usize,
1273 },
1274 Csr(CsrError<NodeIndex, EdgeIndex>),
1276}
1277
1278impl<NodeIndex, EdgeIndex> fmt::Display for CsrSnapshotError<NodeIndex, EdgeIndex>
1279where
1280 NodeIndex: fmt::Display,
1281 EdgeIndex: fmt::Display,
1282{
1283 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1284 match self {
1285 Self::MissingOffsets => formatter.write_str("snapshot has no CSR offsets section"),
1286 Self::MissingTargets => formatter.write_str("snapshot has no CSR targets section"),
1287 Self::OffsetsVersion {
1288 kind,
1289 expected,
1290 actual,
1291 } => write!(
1292 formatter,
1293 "CSR offsets section {kind} version {actual} does not match expected {expected}"
1294 ),
1295 Self::TargetsVersion {
1296 kind,
1297 expected,
1298 actual,
1299 } => write!(
1300 formatter,
1301 "CSR targets section {kind} version {actual} does not match expected {expected}"
1302 ),
1303 Self::OffsetsView(error) => write!(
1304 formatter,
1305 "CSR offsets section cannot be borrowed as selected little-endian index words: {error}"
1306 ),
1307 Self::TargetsView(error) => write!(
1308 formatter,
1309 "CSR targets section cannot be borrowed as selected little-endian index words: {error}"
1310 ),
1311 Self::OffsetsEmpty => formatter.write_str("CSR offsets section is empty"),
1312 Self::NodeCountOverflow { offsets_len } => write!(
1313 formatter,
1314 "derived node count from offsets length {offsets_len} does not fit selected CSR index type"
1315 ),
1316 Self::Csr(error) => write!(formatter, "CSR validation failed: {error}"),
1317 }
1318 }
1319}
1320
1321impl<NodeIndex, EdgeIndex> core::error::Error for CsrSnapshotError<NodeIndex, EdgeIndex>
1322where
1323 NodeIndex: fmt::Debug + fmt::Display,
1324 EdgeIndex: fmt::Debug + fmt::Display,
1325{
1326}
1327
1328impl<NodeIndex, EdgeIndex> From<CsrError<NodeIndex, EdgeIndex>>
1329 for CsrSnapshotError<NodeIndex, EdgeIndex>
1330{
1331 fn from(error: CsrError<NodeIndex, EdgeIndex>) -> Self {
1332 Self::Csr(error)
1333 }
1334}