Skip to content

Commit d8d7019

Browse files
committed
Better error messages
1 parent 98a2926 commit d8d7019

File tree

1 file changed

+153
-99
lines changed

1 file changed

+153
-99
lines changed

src/lib.rs

Lines changed: 153 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use std::fmt;
1111
use std::mem;
1212
use std::os;
1313
use std::ptr;
14+
use std::result::Result as StdResult;
1415
use std::slice;
1516

1617
pub mod option;
@@ -47,8 +48,14 @@ pub enum Error {
4748

4849
impl std::error::Error for Error {}
4950

50-
impl From<NewError> for Error {
51-
fn from(_: NewError) -> Self {
51+
impl From<NewGraphError> for Error {
52+
fn from(_: NewGraphError) -> Self {
53+
Self::Input
54+
}
55+
}
56+
57+
impl From<NewMeshError> for Error {
58+
fn from(_: NewMeshError) -> Self {
5259
Self::Input
5360
}
5461
}
@@ -64,7 +71,7 @@ impl fmt::Display for Error {
6471
}
6572

6673
/// The result of a partitioning.
67-
pub type Result<T> = std::result::Result<T, Error>;
74+
pub type Result<T> = StdResult<T, Error>;
6875

6976
trait ErrorCode {
7077
/// Makes a [`Result`] from a return code (int) from METIS.
@@ -83,53 +90,62 @@ impl ErrorCode for m::rstatus_et {
8390
}
8491
}
8592

93+
/// Error raised when the graph data fed to [`Graph::new`] cannot be safely
94+
/// passed to METIS.
95+
///
96+
/// Graph data must follow the format described in [`Graph::new`].
8697
#[derive(Debug)]
87-
enum NewErrorKind {
88-
InvalidNumberOfConstraints,
89-
InvalidNumberOfParts,
90-
XadjIsEmpty,
91-
XadjIsTooLarge,
92-
XadjIsNotSorted,
93-
InvalidLastXadj,
94-
BadAdjncyLength,
95-
AdjncyOutOfBounds,
98+
pub struct InvalidGraphError {
99+
msg: &'static str,
96100
}
97101

98-
impl fmt::Display for NewErrorKind {
102+
impl fmt::Display for InvalidGraphError {
99103
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100-
match self {
101-
Self::InvalidNumberOfConstraints => write!(f, "ncon must be strictly positive"),
102-
Self::InvalidNumberOfParts => write!(f, "nparts must be strictly positive"),
103-
Self::XadjIsEmpty => write!(f, "xadj/eptr is empty"),
104-
Self::XadjIsTooLarge => write!(f, "xadj/eptr's length cannot be held by an Idx"),
105-
Self::XadjIsNotSorted => write!(f, "xadj/eptr is not sorted"),
106-
Self::InvalidLastXadj => write!(f, "the last element of xadj/eptr is invalid"),
107-
Self::BadAdjncyLength => write!(
108-
f,
109-
"the last element of xadj/eptr must be adjncy/eind's length"
110-
),
111-
112-
Self::AdjncyOutOfBounds => write!(f, "an element of adjncy/eind is out of bounds"),
113-
}
104+
self.msg.fmt(f)
114105
}
115106
}
116107

117-
/// Error type returned by [`Graph::new`] and [`Mesh::new`].
108+
/// Error type returned by [`Graph::new`].
118109
///
119-
/// This error means the input arrays are malformed and cannot be safely passed
120-
/// to METIS.
110+
/// Unlike [`Error`], this error originates from the Rust bindings.
121111
#[derive(Debug)]
122-
pub struct NewError {
123-
kind: NewErrorKind,
112+
#[non_exhaustive]
113+
pub enum NewGraphError {
114+
/// `ncon` must be greater than 1.
115+
NoConstraints,
116+
117+
/// `nparts` must be greater than 1.
118+
NoParts,
119+
120+
/// Graph is too large. One of the array's length doesn't fit into [`Idx`].
121+
TooLarge,
122+
123+
/// The input arrays are malformed and cannot be safely passed to METIS.
124+
///
125+
/// Note that these bindings do not check for all the invariants. Some might
126+
/// be raised during [`Graph::part_recursive`] and [`Graph::part_kway`] as
127+
/// [`Error::Input`].
128+
InvalidGraph(InvalidGraphError),
124129
}
125130

126-
impl fmt::Display for NewError {
131+
impl fmt::Display for NewGraphError {
127132
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128-
self.kind.fmt(f)
133+
match self {
134+
Self::NoConstraints => write!(f, "there must be at least one constraint"),
135+
Self::NoParts => write!(f, "there must be at least one part"),
136+
Self::TooLarge => write!(f, "graph is too large"),
137+
Self::InvalidGraph(err) => write!(f, "invalid graph structure: {err}"),
138+
}
129139
}
130140
}
131141

132-
impl std::error::Error for NewError {}
142+
impl std::error::Error for NewGraphError {}
143+
144+
impl NewGraphError {
145+
fn msg(msg: &'static str) -> Self {
146+
Self::InvalidGraph(InvalidGraphError { msg })
147+
}
148+
}
133149

134150
/// Builder structure to setup a graph partition computation.
135151
///
@@ -283,53 +299,44 @@ impl<'a> Graph<'a> {
283299
nparts: Idx,
284300
xadj: &'a mut [Idx],
285301
adjncy: &'a mut [Idx],
286-
) -> std::result::Result<Graph<'a>, NewError> {
302+
) -> StdResult<Graph<'a>, NewGraphError> {
287303
if ncon <= 0 {
288-
return Err(NewError {
289-
kind: NewErrorKind::InvalidNumberOfConstraints,
290-
});
304+
return Err(NewGraphError::NoConstraints);
291305
}
292306
if nparts <= 0 {
293-
return Err(NewError {
294-
kind: NewErrorKind::InvalidNumberOfParts,
295-
});
307+
return Err(NewGraphError::NoParts);
296308
}
297309

298-
let last_xadj = xadj.last().ok_or(NewError {
299-
kind: NewErrorKind::XadjIsEmpty,
300-
})?;
301-
let last_xadj = usize::try_from(*last_xadj).map_err(|_| NewError {
302-
kind: NewErrorKind::InvalidLastXadj,
303-
})?;
304-
if last_xadj != adjncy.len() {
305-
return Err(NewError {
306-
kind: NewErrorKind::BadAdjncyLength,
307-
});
310+
let last_xadj = *xadj
311+
.last()
312+
.ok_or(NewGraphError::msg("index list is empty"))?;
313+
let adjncy_len = Idx::try_from(adjncy.len()).map_err(|_| NewGraphError::TooLarge)?;
314+
if last_xadj != adjncy_len {
315+
return Err(NewGraphError::msg(
316+
"length mismatch between index and adjacency lists",
317+
));
308318
}
309319

320+
let nvtxs = match Idx::try_from(xadj.len()) {
321+
Ok(xadj_len) => xadj_len - 1,
322+
Err(_) => {
323+
return Err(NewGraphError::TooLarge);
324+
}
325+
};
326+
310327
let mut prev = 0;
311328
for x in &*xadj {
312329
if prev > *x {
313-
return Err(NewError {
314-
kind: NewErrorKind::XadjIsNotSorted,
315-
});
330+
return Err(NewGraphError::msg("index list is not sorted"));
316331
}
317332
prev = *x;
318333
}
319334

320-
let nvtxs = match Idx::try_from(xadj.len()) {
321-
Ok(xadj_len) => xadj_len - 1,
322-
Err(_) => {
323-
return Err(NewError {
324-
kind: NewErrorKind::XadjIsTooLarge,
325-
})
326-
}
327-
};
328335
for a in &*adjncy {
329336
if *a < 0 || *a >= nvtxs {
330-
return Err(NewError {
331-
kind: NewErrorKind::AdjncyOutOfBounds,
332-
});
337+
return Err(NewGraphError::msg(
338+
"some values in the adjacency list are out of bounds",
339+
));
333340
}
334341
}
335342

@@ -651,42 +658,94 @@ impl<'a> Graph<'a> {
651658
}
652659
}
653660

654-
/// Check the given mesh structure for any construct that might make METIS
655-
/// segfault or otherwise corrupt memory.
661+
/// Error raised when the mesh data fed to [`Mesh::new`] cannot be safely passed
662+
/// to METIS.
656663
///
657-
/// Returns the number of nodes in the mesh.
658-
fn check_mesh_structure(eptr: &[Idx], eind: &[Idx]) -> std::result::Result<Idx, NewError> {
664+
/// Mesh data must follow the format described in [`Mesh::new`].
665+
#[derive(Debug)]
666+
pub struct InvalidMeshError {
667+
msg: &'static str,
668+
}
669+
670+
impl fmt::Display for InvalidMeshError {
671+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
672+
self.msg.fmt(f)
673+
}
674+
}
675+
676+
/// Error type returned by [`Mesh::new`].
677+
///
678+
/// Unlike [`Error`], this error originates from the Rust bindings.
679+
#[derive(Debug)]
680+
#[non_exhaustive]
681+
pub enum NewMeshError {
682+
/// `nparts` must be greater than 1.
683+
NoParts,
684+
685+
/// Mesh is too large. One of the array's length doesn't fit into [`Idx`].
686+
TooLarge,
687+
688+
/// The input arrays are malformed and cannot be safely passed to METIS.
689+
///
690+
/// Note that these bindings do not check for all the invariants. Some might
691+
/// be raised during [`Mesh::part_dual`] and [`Mesh::part_nodal`] as
692+
/// [`Error::Input`].
693+
InvalidMesh(InvalidMeshError),
694+
}
695+
696+
impl fmt::Display for NewMeshError {
697+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
698+
match self {
699+
Self::NoParts => write!(f, "there must be at least one part"),
700+
Self::TooLarge => write!(f, "mesh is too large"),
701+
Self::InvalidMesh(err) => write!(f, "invalid mesh structure: {err}"),
702+
}
703+
}
704+
}
705+
706+
impl std::error::Error for NewMeshError {}
707+
708+
impl NewMeshError {
709+
fn msg(msg: &'static str) -> Self {
710+
Self::InvalidMesh(InvalidMeshError { msg })
711+
}
712+
}
713+
714+
/// Returns the number of elements and the number of nodes in the mesh.
715+
fn check_mesh_structure(eptr: &[Idx], eind: &[Idx]) -> StdResult<(Idx, Idx), NewMeshError> {
716+
let last_eptr = *eptr
717+
.last()
718+
.ok_or(NewMeshError::msg("element index is empty"))?;
719+
let eind_len = Idx::try_from(eind.len()).map_err(|_| NewMeshError::TooLarge)?;
720+
if last_eptr != eind_len {
721+
return Err(NewMeshError::msg(
722+
"length mismatch between element and node indices",
723+
));
724+
}
725+
726+
let ne = Idx::try_from(eptr.len()).map_err(|_| NewMeshError::TooLarge)? - 1;
727+
659728
let mut prev = 0;
660729
for x in eptr {
661730
if prev > *x {
662-
return Err(NewError {
663-
kind: NewErrorKind::XadjIsNotSorted,
664-
});
731+
return Err(NewMeshError::msg("element index is not sorted"));
665732
}
666733
prev = *x;
667734
}
668-
let last_eptr = usize::try_from(prev).map_err(|_| NewError {
669-
kind: NewErrorKind::InvalidLastXadj,
670-
})?;
671-
if last_eptr != eind.len() {
672-
return Err(NewError {
673-
kind: NewErrorKind::BadAdjncyLength,
674-
});
675-
}
676735

677736
let mut max_node = 0;
678737
for a in eind {
679738
if *a < 0 {
680-
return Err(NewError {
681-
kind: NewErrorKind::AdjncyOutOfBounds,
682-
});
739+
return Err(NewMeshError::msg(
740+
"values in the node index are out of bounds",
741+
));
683742
}
684743
if *a > max_node {
685744
max_node = *a;
686745
}
687746
}
688747

689-
Ok(max_node + 1)
748+
Ok((ne, max_node + 1))
690749
}
691750

692751
/// Builder structure to setup a mesh partition computation.
@@ -769,13 +828,11 @@ impl<'a> Mesh<'a> {
769828
nparts: Idx,
770829
eptr: &'a mut [Idx],
771830
eind: &'a mut [Idx],
772-
) -> std::result::Result<Mesh<'a>, NewError> {
831+
) -> StdResult<Mesh<'a>, NewMeshError> {
773832
if nparts <= 0 {
774-
return Err(NewError {
775-
kind: NewErrorKind::InvalidNumberOfParts,
776-
});
833+
return Err(NewMeshError::NoParts);
777834
}
778-
let nn = check_mesh_structure(&*eptr, &*eind)?;
835+
let (_ne, nn) = check_mesh_structure(&*eptr, &*eind)?;
779836
Ok(unsafe { Mesh::new_unchecked(nn, nparts, eptr, eind) })
780837
}
781838

@@ -1066,15 +1123,12 @@ impl Drop for Dual {
10661123

10671124
/// Generate the dual graph of a mesh.
10681125
///
1069-
/// # Panics
1070-
///
1071-
/// This function panics if:
1126+
/// # Errors
10721127
///
1073-
/// - `eptr` is empty, or
1074-
/// - `eptr`'s length doesn't fit in [`Idx`].
1128+
/// This function returns an error if `eptr` and `eind` don't follow the mesh
1129+
/// format given in [`Mesh::new`].
10751130
pub fn mesh_to_dual(eptr: &mut [Idx], eind: &mut [Idx], mut ncommon: Idx) -> Result<Dual> {
1076-
let nn = &mut check_mesh_structure(&*eptr, &*eind)?;
1077-
let ne = &mut (eptr.len() as Idx - 1); // `as` cast already checked by `check_mesh_structure`
1131+
let (mut ne, mut nn) = check_mesh_structure(&*eptr, &*eind)?;
10781132
let mut xadj = mem::MaybeUninit::uninit();
10791133
let mut adjncy = mem::MaybeUninit::uninit();
10801134
let mut numbering_flag = 0;
@@ -1083,8 +1137,8 @@ pub fn mesh_to_dual(eptr: &mut [Idx], eind: &mut [Idx], mut ncommon: Idx) -> Res
10831137
// SAFETY: hopefully those arrays are of correct length.
10841138
unsafe {
10851139
m::METIS_MeshToDual(
1086-
ne,
1087-
nn,
1140+
&mut ne,
1141+
&mut nn,
10881142
eptr.as_mut_ptr(),
10891143
eind.as_mut_ptr(),
10901144
&mut ncommon,

0 commit comments

Comments
 (0)