#![no_std]
#[cfg(feature = "build")]
extern crate alloc;
#[cfg(kani)]
extern crate kani;
#[cfg(kani)]
mod proofs;
#[cfg(feature = "build")]
pub mod build;
use core::{fmt, marker::PhantomData};
use oxgraph_graph::{
ContainsElement, ContainsRelation, EdgeTargetGraph, ElementIndex, ElementSuccessors,
GraphCounts, OutgoingEdgeCount, OutgoingGraph, RelationIndex, TopologyBase, TopologyCounts,
};
use oxgraph_layout_util::{
IdSlice, LocalId, NodeAxis, OffsetIntegrityIssue, SnapshotWidth, check_offset_section,
check_value_range,
};
pub use oxgraph_layout_util::{LayoutIndex, LayoutSnapshotWord, LayoutWord};
use oxgraph_snapshot::{SectionBindError, SectionViewError, Snapshot};
pub const SNAPSHOT_KIND_CSR_OFFSETS_U16: u32 = 0x0001;
pub const SNAPSHOT_KIND_CSR_OFFSETS_U32: u32 = 0x0002;
pub const SNAPSHOT_KIND_CSR_OFFSETS_U64: u32 = 0x0003;
pub const SNAPSHOT_KIND_CSR_TARGETS_U16: u32 = 0x0004;
pub const SNAPSHOT_KIND_CSR_TARGETS_U32: u32 = 0x0005;
pub const SNAPSHOT_KIND_CSR_TARGETS_U64: u32 = 0x0006;
pub const SNAPSHOT_CSR_SECTION_VERSION: u32 = 1;
pub trait CsrSnapshotIndex: SnapshotWidth {
const OFFSETS_KIND: u32;
const TARGETS_KIND: u32;
const SECTION_VERSION: u32;
}
macro_rules! impl_csr_snapshot_index {
($index:ty, $offsets_kind:expr, $targets_kind:expr) => {
impl CsrSnapshotIndex for $index {
const OFFSETS_KIND: u32 = $offsets_kind;
const TARGETS_KIND: u32 = $targets_kind;
const SECTION_VERSION: u32 = SNAPSHOT_CSR_SECTION_VERSION;
}
};
}
impl_csr_snapshot_index!(
u16,
SNAPSHOT_KIND_CSR_OFFSETS_U16,
SNAPSHOT_KIND_CSR_TARGETS_U16
);
impl_csr_snapshot_index!(
u32,
SNAPSHOT_KIND_CSR_OFFSETS_U32,
SNAPSHOT_KIND_CSR_TARGETS_U32
);
impl_csr_snapshot_index!(
u64,
SNAPSHOT_KIND_CSR_OFFSETS_U64,
SNAPSHOT_KIND_CSR_TARGETS_U64
);
pub type CsrNativeGraph<'view, NodeIndex, EdgeIndex> =
CsrGraph<'view, NodeIndex, EdgeIndex, EdgeIndex, NodeIndex>;
pub type CsrSnapshotGraph<'view, NodeIndex, EdgeIndex> = CsrGraph<
'view,
NodeIndex,
EdgeIndex,
<EdgeIndex as SnapshotWidth>::LittleEndianWord,
<NodeIndex as SnapshotWidth>::LittleEndianWord,
>;
pub type CsrNodeId<Index> = LocalId<NodeAxis, Index>;
pub type CsrEdgeId<Index> = LocalId<oxgraph_layout_util::EdgeAxis, Index>;
#[derive(Clone, Copy, Debug)]
struct Unchecked;
#[derive(Clone, Copy, Debug)]
struct Checked;
#[derive(Clone, Copy, Debug)]
struct NodeSlot<State, Index> {
raw: Index,
slot: usize,
state: PhantomData<fn() -> State>,
}
impl<Index> NodeSlot<Unchecked, Index> {
fn from_id(id: CsrNodeId<Index>) -> Self
where
Index: Copy,
{
Self {
raw: id.get(),
slot: 0,
state: PhantomData,
}
}
}
impl<Index> NodeSlot<Checked, Index> {
fn from_raw_slot(raw: Index, slot: usize) -> Self {
Self {
raw,
slot,
state: PhantomData,
}
}
const fn index(&self) -> usize {
self.slot
}
}
#[derive(Clone, Copy, Debug)]
struct EdgeSlot<State, Index> {
raw: Index,
slot: usize,
state: PhantomData<fn() -> State>,
}
impl<Index> EdgeSlot<Unchecked, Index> {
fn from_id(id: CsrEdgeId<Index>) -> Self
where
Index: Copy,
{
Self {
raw: id.get(),
slot: 0,
state: PhantomData,
}
}
}
impl<Index> EdgeSlot<Checked, Index> {
fn from_raw_slot(raw: Index, slot: usize) -> Self {
Self {
raw,
slot,
state: PhantomData,
}
}
fn from_csr_range_slot(slot: usize) -> Option<Self>
where
Index: LayoutIndex,
{
let raw = oxgraph_layout_util::usize_to_index_validated::<Index>(slot)?;
Some(Self::from_raw_slot(raw, slot))
}
fn from_csr_range_slot_unchecked(slot: usize) -> Self
where
Index: LayoutIndex,
{
Self::from_csr_range_slot(slot)
.unwrap_or_else(|| unreachable!("checked CSR edge slot must fit index type"))
}
const fn index(&self) -> usize {
self.slot
}
const fn id(&self) -> CsrEdgeId<Index>
where
Index: Copy,
{
CsrEdgeId::new(self.raw)
}
}
#[derive(Clone, Copy, Debug)]
struct EdgeRange<State, Index> {
start: usize,
end: usize,
state: PhantomData<fn() -> State>,
index: PhantomData<fn() -> Index>,
}
impl<Index> EdgeRange<Checked, Index>
where
Index: LayoutIndex,
{
fn from_bounds(start: usize, end: usize) -> Self {
Self {
start,
end,
state: PhantomData,
index: PhantomData,
}
}
const fn as_range(&self) -> core::ops::Range<usize> {
self.start..self.end
}
const fn len(&self) -> usize {
self.end - self.start
}
fn next_slot(&mut self) -> Option<EdgeSlot<Checked, Index>> {
if self.start == self.end {
return None;
}
let slot = EdgeSlot::from_csr_range_slot_unchecked(self.start);
self.start += 1;
Some(slot)
}
}
#[derive(Clone, Copy, Debug)]
pub struct CsrGraph<'view, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
where
NodeIndex: LayoutIndex,
EdgeIndex: LayoutIndex,
OffsetWord: LayoutWord<Index = EdgeIndex>,
TargetWord: LayoutWord<Index = NodeIndex>,
{
node_count: NodeIndex,
node_bound: usize,
offsets: &'view [OffsetWord],
targets: &'view [TargetWord],
}
impl<'view, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
CsrGraph<'view, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
where
NodeIndex: LayoutIndex,
EdgeIndex: LayoutIndex,
OffsetWord: LayoutWord<Index = EdgeIndex>,
TargetWord: LayoutWord<Index = NodeIndex>,
{
pub fn validate(
node_count: NodeIndex,
offsets: &'view [OffsetWord],
targets: &'view [TargetWord],
) -> Result<Self, CsrError<NodeIndex, EdgeIndex>> {
let node_bound = node_count
.to_usize()
.ok_or(CsrError::NodeUsizeOverflow { value: node_count })?;
if node_bound.checked_add(1).is_none() {
return Err(CsrError::OffsetLengthOverflow { node_count });
}
check_offset_section(offsets, node_bound, targets.len())
.map_err(|issue| map_offsets_issue::<NodeIndex, EdgeIndex, _>(offsets, issue))?;
check_value_range(targets, node_bound).map_err(|issue| {
map_targets_issue::<NodeIndex, EdgeIndex, _>(targets, node_count, issue)
})?;
Ok(Self {
node_count,
node_bound,
offsets,
targets,
})
}
#[must_use]
pub const fn offsets(&self) -> &'view [OffsetWord] {
self.offsets
}
#[must_use]
pub const fn targets(&self) -> &'view [TargetWord] {
self.targets
}
pub fn for_each_out_target(
&self,
source: CsrNodeId<NodeIndex>,
mut visit: impl FnMut(CsrNodeId<NodeIndex>) -> bool,
) -> bool {
let Some(node) = self.try_node_slot(source) else {
return false;
};
let Some(index) = node.raw.to_usize() else {
return false;
};
let Some(start) = self.offsets[index].get().to_usize() else {
return false;
};
let Some(end) = self.offsets[index + 1].get().to_usize() else {
return false;
};
for word in &self.targets[start..end] {
if visit(CsrNodeId::new(word.get())) {
return true;
}
}
false
}
#[must_use]
pub fn contains_node(&self, node: CsrNodeId<NodeIndex>) -> bool {
self.try_node_slot(node).is_some()
}
#[must_use]
pub fn contains_edge(&self, edge: CsrEdgeId<EdgeIndex>) -> bool {
self.try_edge_slot(edge).is_some()
}
#[must_use]
pub fn try_target(&self, edge: CsrEdgeId<EdgeIndex>) -> Option<CsrNodeId<NodeIndex>> {
self.try_edge_slot(edge)
.map(|checked| self.target_node(checked))
}
fn try_node_slot(&self, node: CsrNodeId<NodeIndex>) -> Option<NodeSlot<Checked, NodeIndex>> {
self.check_node_slot(NodeSlot::from_id(node))
}
fn check_node_slot(
&self,
node: NodeSlot<Unchecked, NodeIndex>,
) -> Option<NodeSlot<Checked, NodeIndex>> {
let slot = node.raw.to_usize()?;
if node.raw < self.node_count && slot < self.node_bound {
Some(NodeSlot::from_raw_slot(node.raw, slot))
} else {
None
}
}
fn checked_node_slot(&self, node: CsrNodeId<NodeIndex>) -> NodeSlot<Checked, NodeIndex> {
self.try_node_slot(node)
.unwrap_or_else(|| panic!("CSR node ID {node:?} is invalid for this graph"))
}
fn try_edge_slot(&self, edge: CsrEdgeId<EdgeIndex>) -> Option<EdgeSlot<Checked, EdgeIndex>> {
self.check_edge_slot(EdgeSlot::from_id(edge))
}
fn check_edge_slot(
&self,
edge: EdgeSlot<Unchecked, EdgeIndex>,
) -> Option<EdgeSlot<Checked, EdgeIndex>> {
let slot = edge.raw.to_usize()?;
if slot < self.targets.len() {
Some(EdgeSlot::from_raw_slot(edge.raw, slot))
} else {
None
}
}
fn checked_edge_slot(&self, edge: CsrEdgeId<EdgeIndex>) -> EdgeSlot<Checked, EdgeIndex> {
self.try_edge_slot(edge)
.unwrap_or_else(|| panic!("CSR edge ID {edge:?} is invalid for this graph"))
}
fn checked_offset_slot(offset: EdgeIndex) -> usize {
oxgraph_layout_util::index_to_usize_validated(offset)
.unwrap_or_else(|| unreachable!("checked CSR offset must fit usize"))
}
fn target_node(&self, edge: EdgeSlot<Checked, EdgeIndex>) -> CsrNodeId<NodeIndex> {
CsrNodeId::new(self.targets[edge.index()].get())
}
fn outgoing_range(&self, node: NodeSlot<Checked, NodeIndex>) -> EdgeRange<Checked, EdgeIndex> {
let index = node.index();
EdgeRange::from_bounds(
Self::checked_offset_slot(self.offsets[index].get()),
Self::checked_offset_slot(self.offsets[index + 1].get()),
)
}
}
impl<'view, NodeIndex, EdgeIndex>
CsrGraph<
'view,
NodeIndex,
EdgeIndex,
<EdgeIndex as SnapshotWidth>::LittleEndianWord,
<NodeIndex as SnapshotWidth>::LittleEndianWord,
>
where
NodeIndex: CsrSnapshotIndex,
EdgeIndex: CsrSnapshotIndex,
{
pub fn from_snapshot(
snapshot: &Snapshot<'view>,
) -> Result<Self, CsrSnapshotError<NodeIndex, EdgeIndex>> {
Self::from_snapshot_with_kinds(
snapshot,
EdgeIndex::OFFSETS_KIND,
NodeIndex::TARGETS_KIND,
EdgeIndex::SECTION_VERSION,
)
}
pub fn from_snapshot_with_kinds(
snapshot: &Snapshot<'view>,
offsets_kind: u32,
targets_kind: u32,
version: u32,
) -> Result<Self, CsrSnapshotError<NodeIndex, EdgeIndex>> {
let offsets = snapshot
.typed_section::<EdgeIndex>(offsets_kind, version)
.map_err(|error| map_offsets_bind(offsets_kind, error))?;
let targets = snapshot
.typed_section::<NodeIndex>(targets_kind, version)
.map_err(|error| map_targets_bind(targets_kind, error))?;
if offsets.is_empty() {
return Err(CsrSnapshotError::OffsetsEmpty);
}
let node_count_usize = offsets.len() - 1;
let node_count =
NodeIndex::from_usize(node_count_usize).ok_or(CsrSnapshotError::NodeCountOverflow {
offsets_len: offsets.len(),
})?;
Ok(Self::validate(node_count, offsets, targets)?)
}
}
const fn map_offsets_bind<NodeIndex, EdgeIndex>(
kind: u32,
error: SectionBindError,
) -> CsrSnapshotError<NodeIndex, EdgeIndex> {
match error {
SectionBindError::Missing { .. } => CsrSnapshotError::MissingOffsets,
SectionBindError::VersionMismatch {
expected, actual, ..
} => CsrSnapshotError::OffsetsVersion {
kind,
expected,
actual,
},
SectionBindError::View { error, .. } => CsrSnapshotError::OffsetsView(error),
}
}
const fn map_targets_bind<NodeIndex, EdgeIndex>(
kind: u32,
error: SectionBindError,
) -> CsrSnapshotError<NodeIndex, EdgeIndex> {
match error {
SectionBindError::Missing { .. } => CsrSnapshotError::MissingTargets,
SectionBindError::VersionMismatch {
expected, actual, ..
} => CsrSnapshotError::TargetsVersion {
kind,
expected,
actual,
},
SectionBindError::View { error, .. } => CsrSnapshotError::TargetsView(error),
}
}
impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> TopologyBase
for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
where
NodeIndex: LayoutIndex,
EdgeIndex: LayoutIndex,
OffsetWord: LayoutWord<Index = EdgeIndex>,
TargetWord: LayoutWord<Index = NodeIndex>,
{
type ElementId = CsrNodeId<NodeIndex>;
type RelationId = CsrEdgeId<EdgeIndex>;
}
impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> TopologyCounts
for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
where
NodeIndex: LayoutIndex,
EdgeIndex: LayoutIndex,
OffsetWord: LayoutWord<Index = EdgeIndex>,
TargetWord: LayoutWord<Index = NodeIndex>,
{
fn element_count(&self) -> usize {
self.node_bound
}
fn relation_count(&self) -> usize {
self.targets.len()
}
}
impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> GraphCounts
for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
where
NodeIndex: LayoutIndex,
EdgeIndex: LayoutIndex,
OffsetWord: LayoutWord<Index = EdgeIndex>,
TargetWord: LayoutWord<Index = NodeIndex>,
{
}
impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> ElementIndex
for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
where
NodeIndex: LayoutIndex,
EdgeIndex: LayoutIndex,
OffsetWord: LayoutWord<Index = EdgeIndex>,
TargetWord: LayoutWord<Index = NodeIndex>,
{
fn element_bound(&self) -> usize {
self.node_bound
}
fn element_index(&self, element: CsrNodeId<NodeIndex>) -> usize {
self.checked_node_slot(element).index()
}
}
impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> RelationIndex
for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
where
NodeIndex: LayoutIndex,
EdgeIndex: LayoutIndex,
OffsetWord: LayoutWord<Index = EdgeIndex>,
TargetWord: LayoutWord<Index = NodeIndex>,
{
fn relation_bound(&self) -> usize {
self.targets.len()
}
fn relation_index(&self, relation: CsrEdgeId<EdgeIndex>) -> usize {
self.checked_edge_slot(relation).index()
}
}
impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> ContainsElement
for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
where
NodeIndex: LayoutIndex,
EdgeIndex: LayoutIndex,
OffsetWord: LayoutWord<Index = EdgeIndex>,
TargetWord: LayoutWord<Index = NodeIndex>,
{
fn contains_element(&self, element: CsrNodeId<NodeIndex>) -> bool {
self.contains_node(element)
}
}
impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> ContainsRelation
for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
where
NodeIndex: LayoutIndex,
EdgeIndex: LayoutIndex,
OffsetWord: LayoutWord<Index = EdgeIndex>,
TargetWord: LayoutWord<Index = NodeIndex>,
{
fn contains_relation(&self, relation: CsrEdgeId<EdgeIndex>) -> bool {
self.contains_edge(relation)
}
}
impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> EdgeTargetGraph
for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
where
NodeIndex: LayoutIndex,
EdgeIndex: LayoutIndex,
OffsetWord: LayoutWord<Index = EdgeIndex>,
TargetWord: LayoutWord<Index = NodeIndex>,
{
fn target(&self, edge: CsrEdgeId<EdgeIndex>) -> CsrNodeId<NodeIndex> {
self.target_node(self.checked_edge_slot(edge))
}
}
impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> OutgoingGraph
for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
where
NodeIndex: LayoutIndex,
EdgeIndex: LayoutIndex,
OffsetWord: LayoutWord<Index = EdgeIndex>,
TargetWord: LayoutWord<Index = NodeIndex>,
{
type OutEdges<'view>
= CsrOutEdges<EdgeIndex>
where
Self: 'view;
fn outgoing_edges(&self, node: CsrNodeId<NodeIndex>) -> Self::OutEdges<'_> {
CsrOutEdges {
range: self.outgoing_range(self.checked_node_slot(node)),
}
}
}
impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> OutgoingEdgeCount
for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
where
NodeIndex: LayoutIndex,
EdgeIndex: LayoutIndex,
OffsetWord: LayoutWord<Index = EdgeIndex>,
TargetWord: LayoutWord<Index = NodeIndex>,
{
fn out_degree(&self, node: CsrNodeId<NodeIndex>) -> usize {
self.outgoing_range(self.checked_node_slot(node)).len()
}
}
impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> ElementSuccessors
for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
where
NodeIndex: LayoutIndex,
EdgeIndex: LayoutIndex,
OffsetWord: LayoutWord<Index = EdgeIndex>,
TargetWord: LayoutWord<Index = NodeIndex>,
{
type Successors<'view>
= IdSlice<'view, TargetWord, CsrNodeId<NodeIndex>>
where
Self: 'view;
fn element_successors(&self, node: CsrNodeId<NodeIndex>) -> Self::Successors<'_> {
let range = self.outgoing_range(self.checked_node_slot(node));
IdSlice::new(&self.targets[range.as_range()])
}
}
#[derive(Clone, Debug)]
pub struct CsrOutEdges<Index> {
range: EdgeRange<Checked, Index>,
}
impl<Index> Iterator for CsrOutEdges<Index>
where
Index: LayoutIndex,
{
type Item = CsrEdgeId<Index>;
fn next(&mut self) -> Option<Self::Item> {
self.range.next_slot().map(|slot| slot.id())
}
}
impl<Index> ExactSizeIterator for CsrOutEdges<Index>
where
Index: LayoutIndex,
{
fn len(&self) -> usize {
self.range.len()
}
}
fn map_offsets_issue<NodeIndex, EdgeIndex, OffsetWord>(
offsets: &[OffsetWord],
issue: OffsetIntegrityIssue,
) -> CsrError<NodeIndex, EdgeIndex>
where
NodeIndex: LayoutIndex,
EdgeIndex: LayoutIndex,
OffsetWord: LayoutWord<Index = EdgeIndex>,
{
match issue {
OffsetIntegrityIssue::Length { expected, actual } => {
CsrError::OffsetLength { expected, actual }
}
OffsetIntegrityIssue::FirstNonZero { .. } => CsrError::FirstOffset {
actual: offsets[0].get(),
},
OffsetIntegrityIssue::NonMonotonic { index, .. } => CsrError::NonMonotonicOffset {
index,
previous: offsets[index - 1].get(),
actual: offsets[index].get(),
},
OffsetIntegrityIssue::FinalMismatch { value_len, .. } => CsrError::FinalOffset {
final_offset: offsets[offsets.len() - 1].get(),
target_len: value_len,
},
OffsetIntegrityIssue::UsizeOverflow { index } => CsrError::EdgeUsizeOverflow {
value: offsets[index].get(),
},
OffsetIntegrityIssue::ValueOutOfRange { .. } => {
CsrError::EdgeUsizeOverflow {
value: EdgeIndex::ZERO,
}
}
_ => CsrError::EdgeUsizeOverflow {
value: EdgeIndex::ZERO,
},
}
}
fn map_targets_issue<NodeIndex, EdgeIndex, TargetWord>(
targets: &[TargetWord],
node_count: NodeIndex,
issue: OffsetIntegrityIssue,
) -> CsrError<NodeIndex, EdgeIndex>
where
NodeIndex: LayoutIndex,
EdgeIndex: LayoutIndex,
TargetWord: LayoutWord<Index = NodeIndex>,
{
match issue {
OffsetIntegrityIssue::ValueOutOfRange { index, .. } => CsrError::TargetOutOfRange {
index,
target: targets[index].get(),
node_count,
},
OffsetIntegrityIssue::UsizeOverflow { index } => CsrError::TargetUsizeOverflow {
index,
value: targets[index].get(),
},
_ => CsrError::TargetOutOfRange {
index: 0,
target: NodeIndex::ZERO,
node_count,
},
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CsrError<NodeIndex, EdgeIndex> {
OffsetLengthOverflow {
node_count: NodeIndex,
},
OffsetLength {
expected: usize,
actual: usize,
},
FirstOffset {
actual: EdgeIndex,
},
NonMonotonicOffset {
index: usize,
previous: EdgeIndex,
actual: EdgeIndex,
},
FinalOffset {
final_offset: EdgeIndex,
target_len: usize,
},
TargetOutOfRange {
index: usize,
target: NodeIndex,
node_count: NodeIndex,
},
TargetUsizeOverflow {
index: usize,
value: NodeIndex,
},
NodeUsizeOverflow {
value: NodeIndex,
},
EdgeUsizeOverflow {
value: EdgeIndex,
},
}
impl<NodeIndex, EdgeIndex> fmt::Display for CsrError<NodeIndex, EdgeIndex>
where
NodeIndex: fmt::Display,
EdgeIndex: fmt::Display,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::OffsetLengthOverflow { node_count } => {
write!(
formatter,
"offset length overflow for node count {node_count}"
)
}
Self::OffsetLength { expected, actual } => write!(
formatter,
"invalid CSR offset length: expected {expected}, got {actual}"
),
Self::FirstOffset { actual } => {
write!(formatter, "first CSR offset must be 0, got {actual}")
}
Self::NonMonotonicOffset {
index,
previous,
actual,
} => write!(
formatter,
"CSR offset at index {index} is not monotonic: previous {previous}, got {actual}"
),
Self::FinalOffset {
final_offset,
target_len,
} => write!(
formatter,
"final CSR offset {final_offset} does not match target length {target_len}"
),
Self::TargetOutOfRange {
index,
target,
node_count,
} => write!(
formatter,
"CSR target at index {index} is out of range: target {target}, node count {node_count}"
),
Self::TargetUsizeOverflow { index, value } => write!(
formatter,
"CSR target at index {index} value {value} does not fit usize"
),
Self::NodeUsizeOverflow { value } => {
write!(formatter, "CSR node index value {value} does not fit usize")
}
Self::EdgeUsizeOverflow { value } => {
write!(formatter, "CSR edge index value {value} does not fit usize")
}
}
}
}
impl<NodeIndex, EdgeIndex> core::error::Error for CsrError<NodeIndex, EdgeIndex>
where
NodeIndex: fmt::Debug + fmt::Display,
EdgeIndex: fmt::Debug + fmt::Display,
{
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CsrSnapshotError<NodeIndex, EdgeIndex> {
MissingOffsets,
MissingTargets,
OffsetsVersion {
kind: u32,
expected: u32,
actual: u32,
},
TargetsVersion {
kind: u32,
expected: u32,
actual: u32,
},
OffsetsView(SectionViewError),
TargetsView(SectionViewError),
OffsetsEmpty,
NodeCountOverflow {
offsets_len: usize,
},
Csr(CsrError<NodeIndex, EdgeIndex>),
}
impl<NodeIndex, EdgeIndex> fmt::Display for CsrSnapshotError<NodeIndex, EdgeIndex>
where
NodeIndex: fmt::Display,
EdgeIndex: fmt::Display,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingOffsets => formatter.write_str("snapshot has no CSR offsets section"),
Self::MissingTargets => formatter.write_str("snapshot has no CSR targets section"),
Self::OffsetsVersion {
kind,
expected,
actual,
} => write!(
formatter,
"CSR offsets section {kind} version {actual} does not match expected {expected}"
),
Self::TargetsVersion {
kind,
expected,
actual,
} => write!(
formatter,
"CSR targets section {kind} version {actual} does not match expected {expected}"
),
Self::OffsetsView(error) => write!(
formatter,
"CSR offsets section cannot be borrowed as selected little-endian index words: {error}"
),
Self::TargetsView(error) => write!(
formatter,
"CSR targets section cannot be borrowed as selected little-endian index words: {error}"
),
Self::OffsetsEmpty => formatter.write_str("CSR offsets section is empty"),
Self::NodeCountOverflow { offsets_len } => write!(
formatter,
"derived node count from offsets length {offsets_len} does not fit selected CSR index type"
),
Self::Csr(error) => write!(formatter, "CSR validation failed: {error}"),
}
}
}
impl<NodeIndex, EdgeIndex> core::error::Error for CsrSnapshotError<NodeIndex, EdgeIndex>
where
NodeIndex: fmt::Debug + fmt::Display,
EdgeIndex: fmt::Debug + fmt::Display,
{
}
impl<NodeIndex, EdgeIndex> From<CsrError<NodeIndex, EdgeIndex>>
for CsrSnapshotError<NodeIndex, EdgeIndex>
{
fn from(error: CsrError<NodeIndex, EdgeIndex>) -> Self {
Self::Csr(error)
}
}