Skip to content

Commit aee0c7e

Browse files
committed
Add TableCollection and an error handling scheme.
1 parent 93de631 commit aee0c7e

14 files changed

+1123
-50
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/target
22
Cargo.lock
3+
auto_bindings.rs

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ edition = "2018"
88
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
99

1010
[dependencies]
11+
thiserror = "1.0"
1112

1213
[build-dependencies]
1314
bindgen = "0.56.0"

src/_macros.rs

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#![macro_use]
2+
3+
#[doc(hidden)]
4+
macro_rules! handle_tsk_return_value {
5+
($code: expr) => {{
6+
if $code < 0 {
7+
return Err(crate::error::TskitRustError::ErrorCode { code: $code });
8+
}
9+
return Ok($code);
10+
}};
11+
}
12+
13+
macro_rules! panic_on_tskit_error {
14+
($code: expr) => {
15+
if $code < 0 {
16+
let c_str = unsafe { std::ffi::CStr::from_ptr(crate::bindings::tsk_strerror($code)) };
17+
let str_slice: &str = c_str.to_str().unwrap();
18+
let message: String = str_slice.to_owned();
19+
panic!("{}", message);
20+
}
21+
};
22+
}
23+
24+
macro_rules! unsafe_tsk_column_access {
25+
($i: expr, $lo: expr, $hi: expr, $array: expr) => {{
26+
if $i < $lo || ($i as crate::tsk_size_t) >= $hi {
27+
return Err(crate::error::TskitRustError::IndexError {});
28+
}
29+
return Ok(unsafe { *$array.offset($i as isize) });
30+
}};
31+
}
32+
33+
#[cfg(test)]
34+
mod test {
35+
use crate::error::TskitRustError;
36+
use crate::TskReturnValue;
37+
38+
#[test]
39+
#[should_panic]
40+
fn test_tskit_panic() {
41+
panic_on_tskit_error!(-202); // "Node out of bounds"
42+
}
43+
44+
fn return_value_mock(rv: i32) -> TskReturnValue {
45+
handle_tsk_return_value!(rv);
46+
}
47+
48+
fn must_not_error(x: TskReturnValue) -> bool {
49+
return x.map_or_else(|_: TskitRustError| false, |_| true);
50+
}
51+
52+
fn must_error(x: TskReturnValue) -> bool {
53+
return x.map_or_else(|_: TskitRustError| true, |_| false);
54+
}
55+
56+
#[test]
57+
fn test_handle_good_return_value() {
58+
assert!(must_not_error(return_value_mock(0)));
59+
assert!(must_not_error(return_value_mock(1)));
60+
}
61+
62+
#[test]
63+
fn test_handle_return_value_test_panic() {
64+
assert!(must_error(return_value_mock(-207)));
65+
}
66+
}

src/bindings.rs

+27-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,34 @@
1+
//! Low-level ("unsafe") bindings to the C API.
2+
//!
3+
//! This module is a 1-to-1 mapping of C types
4+
//! and functions for both tskit and kastore.
5+
//! The bindings are generate via [bindgen](https://docs.rs/bindgen).
6+
//!
7+
//! Using things from this module will be ``unsafe``.
8+
//! Further, as many of the types require ``init()`` methods
9+
//! to correctly set up the structs, one has to coerce ``rust``
10+
//! into allowing uninitialized variables:
11+
//!
12+
//! ```
13+
//! use std::mem::MaybeUninit;
14+
//! let mut edges: MaybeUninit<tskit_rust::bindings::tsk_edge_table_t> = MaybeUninit::uninit();
15+
//! unsafe {
16+
//! let _ = tskit_rust::bindings::tsk_edge_table_init(edges.as_mut_ptr(), 0);
17+
//! let _ = tskit_rust::bindings::tsk_edge_table_add_row(edges.as_mut_ptr(), 0., 10., 0, 1, std::ptr::null(), 0);
18+
//! assert_eq!((*edges.as_ptr()).num_rows, 1);
19+
//! tskit_rust::bindings::tsk_edge_table_free(edges.as_mut_ptr());
20+
//! }
21+
//! ```
22+
//!
23+
//! The best source for documentation will be the [tskit docs](https://tskit.readthedocs.io).
24+
//! Those docs describe the most important parts of the C API.
25+
//! This module contains the same types/functions with the same names.
26+
127
// re-export the auto-generate bindings
228
pub use crate::auto_bindings::*;
329

430
// tskit defines this via a type cast
531
// in a macro. bindgen thus misses it.
632
// See bindgen issue 316.
33+
/// "Null" identifier value.
734
pub const TSK_NULL: tsk_id_t = -1;
8-

src/edge_table.rs

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use crate::bindings as ll_bindings;
2+
use crate::{tsk_id_t, tsk_size_t, TskitRustError};
3+
4+
/// An immutable view of an edge table.
5+
///
6+
/// These are not created directly.
7+
/// Instead, use [`TableCollection::edges`](crate::TableCollection::edges)
8+
/// to get a reference to an existing edge table;
9+
pub struct EdgeTable<'a> {
10+
table_: &'a ll_bindings::tsk_edge_table_t,
11+
}
12+
13+
impl<'a> EdgeTable<'a> {
14+
pub(crate) fn new_from_table(edges: &'a ll_bindings::tsk_edge_table_t) -> Self {
15+
return EdgeTable { table_: edges };
16+
}
17+
18+
/// Return the number of rows
19+
pub fn num_rows(&'a self) -> tsk_size_t {
20+
return self.table_.num_rows;
21+
}
22+
23+
/// Return the ``parent`` value from row ``row`` of the table.
24+
///
25+
/// # Errors
26+
///
27+
/// Will return [``IndexError``](crate::TskitRustError::IndexError)
28+
/// if ``row`` is out of range.
29+
pub fn parent(&'a self, row: tsk_id_t) -> Result<tsk_id_t, TskitRustError> {
30+
unsafe_tsk_column_access!(row, 0, self.num_rows(), self.table_.parent);
31+
}
32+
33+
/// Return the ``child`` value from row ``row`` of the table.
34+
///
35+
/// # Errors
36+
///
37+
/// Will return [``IndexError``](crate::TskitRustError::IndexError)
38+
/// if ``row`` is out of range.
39+
pub fn child(&'a self, row: tsk_id_t) -> Result<tsk_id_t, TskitRustError> {
40+
unsafe_tsk_column_access!(row, 0, self.num_rows(), self.table_.child);
41+
}
42+
43+
/// Return the ``left`` value from row ``row`` of the table.
44+
///
45+
/// # Errors
46+
///
47+
/// Will return [``IndexError``](crate::TskitRustError::IndexError)
48+
/// if ``row`` is out of range.
49+
pub fn left(&'a self, row: tsk_id_t) -> Result<f64, TskitRustError> {
50+
unsafe_tsk_column_access!(row, 0, self.num_rows(), self.table_.left);
51+
}
52+
53+
/// Return the ``right`` value from row ``row`` of the table.
54+
///
55+
/// # Errors
56+
///
57+
/// Will return [``IndexError``](crate::TskitRustError::IndexError)
58+
/// if ``row`` is out of range.
59+
pub fn right(&'a self, row: tsk_id_t) -> Result<f64, TskitRustError> {
60+
unsafe_tsk_column_access!(row, 0, self.num_rows(), self.table_.right);
61+
}
62+
}

src/error.rs

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
//! Error handling
2+
3+
use crate::TskReturnValue;
4+
use thiserror::Error;
5+
6+
#[derive(Error, Debug, PartialEq)]
7+
pub enum TskitRustError {
8+
/// Used when bad input is encountered.
9+
#[error("we received {} but expected {}",*got, *expected)]
10+
ValueError { got: String, expected: String },
11+
/// Used when array access is out of range.
12+
/// Typically, this is used when accessing
13+
/// arrays allocated on the C side.
14+
#[error("Invalid index")]
15+
IndexError,
16+
/// Wrapper around tskit C API error codes.
17+
#[error("{}", get_tskit_error_message(*code))]
18+
ErrorCode { code: i32 },
19+
}
20+
21+
/// Takes the return code from a tskit
22+
/// function and panics if the code indicates
23+
/// an error. The error message is included
24+
/// in the panic statement.
25+
///
26+
/// Examples:
27+
///
28+
/// ```
29+
/// let rv = 0; // All good!
30+
/// tskit_rust::error::panic_on_tskit_error(rv);
31+
/// let rv = 1; // Probably something like a new node id.
32+
/// tskit_rust::error::panic_on_tskit_error(rv);
33+
/// ```
34+
///
35+
/// This will panic:
36+
///
37+
/// ```should_panic
38+
/// let rv = -202; // "Node out of bounds error"
39+
/// tskit_rust::error::panic_on_tskit_error(rv);
40+
/// ```
41+
pub fn panic_on_tskit_error(code: i32) -> () {
42+
panic_on_tskit_error!(code);
43+
}
44+
45+
/// Given a return value from low-level tskit function,
46+
/// obtain the corresponding error message.
47+
///
48+
/// tskit returns 0 when there's no error:
49+
/// ```
50+
/// let x = tskit_rust::error::get_tskit_error_message(0);
51+
/// assert_eq!(x, "Normal exit condition. This is not an error!");
52+
/// ```
53+
///
54+
/// Values > 0 are considered errors, but have no known type/cause.
55+
/// tskit never returns error codes > 0 and there should be no attempt
56+
/// to ever do so by client code.
57+
///
58+
/// ```
59+
/// let x = tskit_rust::error::get_tskit_error_message(1);
60+
/// assert_eq!(x, "Unknown error");
61+
/// ```
62+
///
63+
/// Values < 0 may have known causes:
64+
///
65+
/// ```
66+
/// let x = tskit_rust::error::get_tskit_error_message(-207);
67+
/// assert_eq!(x, "Individual out of bounds");
68+
/// ```
69+
pub fn get_tskit_error_message(code: i32) -> String {
70+
let c_str = unsafe { std::ffi::CStr::from_ptr(crate::bindings::tsk_strerror(code)) };
71+
let str_slice: &str = c_str.to_str().unwrap();
72+
let message: String = str_slice.to_owned();
73+
return message;
74+
}
75+
76+
/// Given an instance of [``TskReturnValue``](crate::TskReturnValue),
77+
/// obtain the tskit error message if there is indeed an error.
78+
pub fn extract_error_message(x: TskReturnValue) -> Option<String> {
79+
return x.map_or_else(|e: TskitRustError| Some(format!("{}", e)), |_| None);
80+
}
81+
82+
#[cfg(test)]
83+
mod test {
84+
85+
use super::*;
86+
87+
#[test]
88+
fn test_get_tskit_error_message() {
89+
let m = get_tskit_error_message(0);
90+
assert_eq!(m, "Normal exit condition. This is not an error!");
91+
}
92+
93+
fn mock_error() -> TskReturnValue {
94+
handle_tsk_return_value!(-207);
95+
}
96+
97+
fn mock_success() -> TskReturnValue {
98+
return Ok(0);
99+
}
100+
101+
#[test]
102+
fn test_error_formatting() {
103+
let x = mock_error();
104+
let mut s: String = "nope!".to_string();
105+
x.map_or_else(|e: TskitRustError| s = format!("{}", e), |_| ());
106+
assert_eq!(s, "Individual out of bounds");
107+
}
108+
109+
#[test]
110+
fn test_extract_error_message() {
111+
let x = mock_error();
112+
match extract_error_message(x) {
113+
Some(s) => assert_eq!(s, "Individual out of bounds"),
114+
None => assert!(false),
115+
}
116+
117+
match extract_error_message(mock_success()) {
118+
Some(_) => assert!(false),
119+
None => assert!(true),
120+
}
121+
}
122+
}

src/lib.rs

+33-26
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,42 @@
44
#![allow(non_camel_case_types)]
55
#![allow(non_snake_case)]
66

7-
/// Low-level ("unsafe") bindings to the C API.
8-
///
9-
/// This module is a 1-to-1 mapping of C types
10-
/// and functions for both tskit and kastore.
11-
/// The bindings are generate via [bindgen](https://docs.rs/bindgen).
12-
///
13-
/// Using things from this module will be ``unsafe``.
14-
/// Further, as many of the types require ``init()`` methods
15-
/// to correctly set up the structs, one has to coerce ``rust``
16-
/// into allowing uninitialized variables:
17-
///
18-
/// ```
19-
/// use std::mem::MaybeUninit;
20-
/// let mut edges: MaybeUninit<tskit_rust::bindings::tsk_edge_table_t> = MaybeUninit::uninit();
21-
/// unsafe {
22-
/// let _ = tskit_rust::bindings::tsk_edge_table_init(edges.as_mut_ptr(), 0);
23-
/// let _ = tskit_rust::bindings::tsk_edge_table_add_row(edges.as_mut_ptr(), 0., 10., 0, 1, std::ptr::null(), 0);
24-
/// assert_eq!((*edges.as_ptr()).num_rows, 1);
25-
/// tskit_rust::bindings::tsk_edge_table_free(edges.as_mut_ptr());
26-
/// }
27-
/// ```
28-
///
29-
/// The best source for documentation will be the [tskit docs](https://tskit.readthedocs.io).
30-
/// Those docs describe the most important parts of the C API.
31-
/// This module contains the same types/functions with the same names.
327
pub mod bindings;
338

349
mod auto_bindings;
3510

11+
mod _macros; // Starts w/_ to be sorted at front by rustfmt!
12+
mod edge_table;
13+
pub mod error;
14+
mod mutation_table;
15+
mod node_table;
16+
mod site_table;
17+
mod table_collection;
18+
pub mod types;
19+
20+
// re-export fundamental constants that
21+
// we can't live without
22+
pub use bindings::TSK_NODE_IS_SAMPLE;
23+
pub use bindings::TSK_NO_BUILD_INDEXES;
24+
pub use bindings::TSK_NULL;
25+
pub use bindings::TSK_SAMPLE_LISTS;
26+
27+
// re-export types, too
28+
pub use bindings::tsk_flags_t;
29+
pub use bindings::tsk_id_t;
30+
pub use bindings::tsk_size_t;
31+
32+
pub use edge_table::EdgeTable;
33+
pub use error::TskitRustError;
34+
pub use mutation_table::MutationTable;
35+
pub use node_table::NodeTable;
36+
pub use site_table::SiteTable;
37+
pub use table_collection::TableCollection;
38+
/// Handles return codes from low-level tskit functions.
39+
///
40+
/// When an error from the tskit C API is detected,
41+
/// the error message is stored for diplay.
42+
pub type TskReturnValue = Result<i32, TskitRustError>;
43+
3644
// Testing modules
37-
mod test_table_collection;
3845
mod test_tsk_variables;

0 commit comments

Comments
 (0)