1use core::fmt;
4
5use oxgraph_snapshot::SectionViewError;
6
7#[derive(Clone, Debug, Eq, PartialEq)]
17pub enum BcsrError {
18 OffsetLengthOverflow {
20 count: usize,
22 },
23 OffsetLength {
25 section: BcsrSection,
27 expected: usize,
29 actual: usize,
31 },
32 HyperedgeOffsetLengthMismatch {
34 head_offsets_len: usize,
36 tail_offsets_len: usize,
38 },
39 VertexOffsetLengthMismatch {
42 outgoing_offsets_len: usize,
44 incoming_offsets_len: usize,
46 },
47 FirstOffset {
49 section: BcsrSection,
51 actual: usize,
53 },
54 NonMonotonicOffset {
56 section: BcsrSection,
58 index: usize,
60 previous: usize,
62 actual: usize,
64 },
65 FinalOffset {
67 section: BcsrSection,
69 final_offset: usize,
71 value_len: usize,
73 },
74 VertexOutOfRange {
76 section: BcsrSection,
78 index: usize,
80 vertex: usize,
82 vertex_count: usize,
84 },
85 HyperedgeOutOfRange {
87 section: BcsrSection,
89 index: usize,
91 hyperedge: usize,
93 hyperedge_count: usize,
95 },
96 OutgoingTotalMismatch {
99 head_participants_len: usize,
101 outgoing_hyperedges_len: usize,
103 },
104 IncomingTotalMismatch {
107 tail_participants_len: usize,
109 incoming_hyperedges_len: usize,
111 },
112 NotStrictlyAscending {
119 section: BcsrSection,
121 index: usize,
123 previous: usize,
125 actual: usize,
127 },
128 UsizeOverflow {
130 value: usize,
132 },
133 TotalIncidenceCountOverflow {
136 p_head: usize,
138 p_tail: usize,
140 },
141 CrossDirectionMismatch {
144 side: BcsrRoleSide,
146 hyperedge: usize,
148 vertex: usize,
150 },
151}
152
153#[derive(Clone, Copy, Debug, Eq, PartialEq)]
162pub enum BcsrSection {
163 HeadOffsets,
165 HeadParticipants,
167 TailOffsets,
169 TailParticipants,
171 VertexOutgoingOffsets,
173 VertexOutgoingHyperedges,
175 VertexIncomingOffsets,
177 VertexIncomingHyperedges,
179}
180
181impl fmt::Display for BcsrSection {
182 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
183 formatter.write_str(match self {
184 Self::HeadOffsets => "BCSR_HEAD_OFFSETS",
185 Self::HeadParticipants => "BCSR_HEAD_PARTICIPANTS",
186 Self::TailOffsets => "BCSR_TAIL_OFFSETS",
187 Self::TailParticipants => "BCSR_TAIL_PARTICIPANTS",
188 Self::VertexOutgoingOffsets => "BCSR_VERTEX_OUTGOING_OFFSETS",
189 Self::VertexOutgoingHyperedges => "BCSR_VERTEX_OUTGOING_HYPEREDGES",
190 Self::VertexIncomingOffsets => "BCSR_VERTEX_INCOMING_OFFSETS",
191 Self::VertexIncomingHyperedges => "BCSR_VERTEX_INCOMING_HYPEREDGES",
192 })
193 }
194}
195
196#[derive(Clone, Copy, Debug, Eq, PartialEq)]
202pub enum BcsrRoleSide {
203 Outgoing,
205 Incoming,
207}
208
209impl fmt::Display for BcsrRoleSide {
210 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
211 formatter.write_str(match self {
212 Self::Outgoing => "outgoing",
213 Self::Incoming => "incoming",
214 })
215 }
216}
217
218impl fmt::Display for BcsrError {
219 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
220 match self {
221 Self::OffsetLengthOverflow { .. }
222 | Self::OffsetLength { .. }
223 | Self::HyperedgeOffsetLengthMismatch { .. }
224 | Self::VertexOffsetLengthMismatch { .. } => fmt_length_variant(self, formatter),
225 Self::FirstOffset { .. }
226 | Self::NonMonotonicOffset { .. }
227 | Self::FinalOffset { .. } => fmt_offset_variant(self, formatter),
228 Self::VertexOutOfRange { .. }
229 | Self::HyperedgeOutOfRange { .. }
230 | Self::NotStrictlyAscending { .. } => fmt_value_variant(self, formatter),
231 Self::OutgoingTotalMismatch { .. }
232 | Self::IncomingTotalMismatch { .. }
233 | Self::UsizeOverflow { .. }
234 | Self::TotalIncidenceCountOverflow { .. }
235 | Self::CrossDirectionMismatch { .. } => fmt_total_variant(self, formatter),
236 }
237 }
238}
239
240fn fmt_length_variant(error: &BcsrError, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
242 match error {
243 BcsrError::OffsetLengthOverflow { count } => {
244 write!(formatter, "offset length overflow for count {count}")
245 }
246 BcsrError::OffsetLength {
247 section,
248 expected,
249 actual,
250 } => write!(
251 formatter,
252 "{section} has wrong length: expected {expected}, got {actual}"
253 ),
254 BcsrError::HyperedgeOffsetLengthMismatch {
255 head_offsets_len,
256 tail_offsets_len,
257 } => write!(
258 formatter,
259 "head_offsets length {head_offsets_len} disagrees with tail_offsets length {tail_offsets_len}"
260 ),
261 BcsrError::VertexOffsetLengthMismatch {
262 outgoing_offsets_len,
263 incoming_offsets_len,
264 } => write!(
265 formatter,
266 "vertex_outgoing_offsets length {outgoing_offsets_len} disagrees with vertex_incoming_offsets length {incoming_offsets_len}"
267 ),
268 _ => Ok(()),
269 }
270}
271
272fn fmt_offset_variant(error: &BcsrError, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
274 match error {
275 BcsrError::FirstOffset { section, actual } => {
276 write!(formatter, "{section} first offset must be 0, got {actual}")
277 }
278 BcsrError::NonMonotonicOffset {
279 section,
280 index,
281 previous,
282 actual,
283 } => write!(
284 formatter,
285 "{section} offset at index {index} is not monotonic: previous {previous}, got {actual}"
286 ),
287 BcsrError::FinalOffset {
288 section,
289 final_offset,
290 value_len,
291 } => write!(
292 formatter,
293 "{section} final offset {final_offset} does not match value length {value_len}"
294 ),
295 _ => Ok(()),
296 }
297}
298
299fn fmt_value_variant(error: &BcsrError, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
301 match error {
302 BcsrError::VertexOutOfRange {
303 section,
304 index,
305 vertex,
306 vertex_count,
307 } => write!(
308 formatter,
309 "{section} vertex {vertex} at index {index} is out of range (vertex count {vertex_count})"
310 ),
311 BcsrError::HyperedgeOutOfRange {
312 section,
313 index,
314 hyperedge,
315 hyperedge_count,
316 } => write!(
317 formatter,
318 "{section} hyperedge {hyperedge} at index {index} is out of range (hyperedge count {hyperedge_count})"
319 ),
320 BcsrError::NotStrictlyAscending {
321 section,
322 index,
323 previous,
324 actual,
325 } => write!(
326 formatter,
327 "{section} value at index {index} is not strictly ascending: previous {previous}, got {actual}"
328 ),
329 _ => Ok(()),
330 }
331}
332
333fn fmt_total_variant(error: &BcsrError, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
335 match error {
336 BcsrError::OutgoingTotalMismatch {
337 head_participants_len,
338 outgoing_hyperedges_len,
339 } => write!(
340 formatter,
341 "head_participants length {head_participants_len} disagrees with vertex_outgoing_hyperedges length {outgoing_hyperedges_len}"
342 ),
343 BcsrError::IncomingTotalMismatch {
344 tail_participants_len,
345 incoming_hyperedges_len,
346 } => write!(
347 formatter,
348 "tail_participants length {tail_participants_len} disagrees with vertex_incoming_hyperedges length {incoming_hyperedges_len}"
349 ),
350 BcsrError::UsizeOverflow { value } => {
351 write!(formatter, "BCSR index value {value} does not fit usize")
352 }
353 BcsrError::TotalIncidenceCountOverflow { p_head, p_tail } => write!(
354 formatter,
355 "incidence ID space P_head ({p_head}) + P_tail ({p_tail}) overflows usize"
356 ),
357 BcsrError::CrossDirectionMismatch {
358 side,
359 hyperedge,
360 vertex,
361 } => write!(
362 formatter,
363 "cross-direction mismatch on {side}: hyperedge {hyperedge} and vertex {vertex} disagree"
364 ),
365 _ => Ok(()),
366 }
367}
368
369impl core::error::Error for BcsrError {}
370
371#[derive(Clone, Debug, Eq, PartialEq)]
378pub enum BcsrSnapshotError {
379 MissingSection {
381 section: BcsrSection,
383 kind: u32,
385 },
386 SectionView {
388 section: BcsrSection,
390 error: SectionViewError,
392 },
393 OffsetsEmpty {
396 section: BcsrSection,
398 },
399 CountOverflow {
401 section: BcsrSection,
403 offsets_len: usize,
405 },
406 Bcsr(BcsrError),
408}
409
410impl fmt::Display for BcsrSnapshotError {
411 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
412 match self {
413 Self::MissingSection { section, kind } => write!(
414 formatter,
415 "snapshot has no {section} section (kind 0x{kind:04x})"
416 ),
417 Self::SectionView { section, error } => write!(
418 formatter,
419 "{section} cannot be borrowed as little-endian u32: {error}"
420 ),
421 Self::OffsetsEmpty { section } => write!(formatter, "{section} section is empty"),
422 Self::CountOverflow {
423 section,
424 offsets_len,
425 } => write!(
426 formatter,
427 "derived count from {section} length {offsets_len} does not fit u32"
428 ),
429 Self::Bcsr(error) => {
430 write!(formatter, "bipartite-CSR validation failed: {error}")
431 }
432 }
433 }
434}
435
436impl core::error::Error for BcsrSnapshotError {}
437
438impl From<BcsrError> for BcsrSnapshotError {
439 fn from(error: BcsrError) -> Self {
440 Self::Bcsr(error)
441 }
442}