Skip to content

Commit 275074d

Browse files
committed
Add metadata trait and API.
Closes #19.
1 parent d89dcd1 commit 275074d

File tree

5 files changed

+194
-0
lines changed

5 files changed

+194
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ repository = "https://github.com/molpopgen/tskit_rust"
1313

1414
[dependencies]
1515
thiserror = "1.0"
16+
libc = "0.2.81"
1617

1718
[build-dependencies]
1819
bindgen = "0.56.0"

src/_macros.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,42 @@ macro_rules! build_tskit_type {
5858
};
5959
}
6060

61+
macro_rules! metadata_to_vector {
62+
($T: ty, $self: expr, $row: expr) => {
63+
crate::metadata::char_column_to_vector(
64+
$self.table_.metadata,
65+
$self.table_.metadata_offset,
66+
$row,
67+
$self.table_.num_rows,
68+
$self.table_.metadata_length,
69+
)?
70+
};
71+
}
72+
73+
macro_rules! decode_metadata_row {
74+
($T: ty, $buffer: expr) => {
75+
match $buffer {
76+
None => Ok(None),
77+
Some(v) => Ok(Some(<$T as crate::metadata::MetadataRoundtrip>::decode(
78+
&v,
79+
)?)),
80+
}
81+
};
82+
}
83+
84+
macro_rules! process_state_input {
85+
($state: expr) => {
86+
match $state {
87+
Some(x) => (
88+
x.as_ptr() as *const libc::c_char,
89+
x.len() as crate::bindings::tsk_size_t,
90+
$state,
91+
),
92+
None => (std::ptr::null(), 0, $state),
93+
}
94+
};
95+
}
96+
6197
#[cfg(test)]
6298
mod test {
6399
use crate::error::TskitRustError;

src/error.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ pub enum TskitRustError {
1616
/// Wrapper around tskit C API error codes.
1717
#[error("{}", get_tskit_error_message(*code))]
1818
ErrorCode { code: i32 },
19+
/// A redirection of [``crate::metadata::MetadataError``]
20+
#[error("{value:?}")]
21+
MetadataError {
22+
/// The redirected error
23+
#[from]
24+
value: crate::metadata::MetadataError,
25+
},
1926
}
2027

2128
/// Takes the return code from a tskit

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod _macros; // Starts w/_ to be sorted at front by rustfmt!
1010
mod edge_table;
1111
pub mod error;
1212
pub mod ffi;
13+
pub mod metadata;
1314
mod mutation_table;
1415
mod node_table;
1516
mod population_table;

src/metadata.rs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
use crate::bindings::{tsk_id_t, tsk_size_t};
2+
use thiserror::Error;
3+
4+
pub trait MetadataRoundtrip {
5+
fn encode(&self) -> Result<Vec<u8>, MetadataError>;
6+
fn decode(md: &[u8]) -> Result<Self, MetadataError>
7+
where
8+
Self: Sized;
9+
}
10+
11+
pub(crate) struct EncodedMetadata {
12+
encoded: Option<Vec<u8>>,
13+
}
14+
15+
impl EncodedMetadata {
16+
pub(crate) fn new(md: Option<&dyn MetadataRoundtrip>) -> Result<Self, MetadataError> {
17+
match md {
18+
Some(x) => {
19+
let e = x.encode()?;
20+
Ok(Self { encoded: Some(e) })
21+
}
22+
None => Ok(Self { encoded: None }),
23+
}
24+
}
25+
26+
pub(crate) fn as_ptr(&self) -> *const libc::c_char {
27+
match &self.encoded {
28+
Some(x) => x.as_ptr() as *const libc::c_char,
29+
None => std::ptr::null(),
30+
}
31+
}
32+
33+
pub(crate) fn len(&self) -> tsk_size_t {
34+
match &self.encoded {
35+
Some(x) => x.len() as tsk_size_t,
36+
None => 0,
37+
}
38+
}
39+
}
40+
41+
#[derive(Error, Debug, PartialEq)]
42+
pub enum MetadataError {
43+
/// Error related to types implementing
44+
/// [``MetadataRoundtrip``]
45+
#[error("{}", *msg)]
46+
RoundtripError { msg: String },
47+
}
48+
49+
pub(crate) fn char_column_to_vector(
50+
column: *const libc::c_char,
51+
column_offset: *const tsk_size_t,
52+
row: tsk_id_t,
53+
num_rows: tsk_size_t,
54+
column_length: tsk_size_t,
55+
) -> Result<Option<Vec<u8>>, crate::TskitRustError> {
56+
if row < 0 || (row as tsk_size_t) >= num_rows {
57+
return Err(crate::TskitRustError::IndexError {});
58+
}
59+
if column_length == 0 {
60+
return Ok(None);
61+
}
62+
let start = unsafe { *column_offset.offset(row as isize) };
63+
let stop = if (row as tsk_size_t) < num_rows {
64+
unsafe { *column_offset.offset((row + 1) as isize) }
65+
} else {
66+
column_length
67+
};
68+
if start >= stop {
69+
return Ok(None);
70+
}
71+
if column_length == 0 {
72+
return Ok(None);
73+
}
74+
let mut buffer = vec![];
75+
for i in start..stop {
76+
buffer.push(unsafe { *column.offset(i as isize) } as u8);
77+
}
78+
Ok(Some(buffer))
79+
}
80+
81+
#[cfg(test)]
82+
mod tests {
83+
use super::*;
84+
85+
#[test]
86+
fn test_vec8_cast_to_c_string() {
87+
let v: Vec<u8> = vec![0, 1, '\0' as u8, 2, 3];
88+
let c = v.as_ptr() as *const libc::c_char;
89+
for i in 0..v.len() {
90+
assert_eq!(v[i] as i8, unsafe { *c.offset(i as isize) });
91+
}
92+
93+
let _ = match Some(&v) {
94+
Some(x) => x.as_ptr() as *const libc::c_char,
95+
None => std::ptr::null(),
96+
};
97+
}
98+
99+
struct F {
100+
x: i32,
101+
y: u32,
102+
}
103+
104+
impl MetadataRoundtrip for F {
105+
fn encode(&self) -> Result<Vec<u8>, MetadataError> {
106+
let mut rv = vec![];
107+
rv.extend(self.x.to_le_bytes().iter().map(|&i| i));
108+
rv.extend(self.y.to_le_bytes().iter().map(|&i| i));
109+
Ok(rv)
110+
}
111+
fn decode(md: &[u8]) -> Result<Self, MetadataError> {
112+
use std::convert::TryInto;
113+
let (x_int_bytes, rest) = md.split_at(std::mem::size_of::<i32>());
114+
let (y_int_bytes, _) = rest.split_at(std::mem::size_of::<u32>());
115+
Ok(Self {
116+
x: i32::from_le_bytes(x_int_bytes.try_into().unwrap()),
117+
y: u32::from_le_bytes(y_int_bytes.try_into().unwrap()),
118+
})
119+
}
120+
}
121+
122+
#[test]
123+
fn test_metadata_round_trip() {
124+
let f = F { x: -3, y: 42 };
125+
let v = f.encode().unwrap();
126+
let c = v.as_ptr() as *const libc::c_char;
127+
let mut d = vec![];
128+
for i in 0..v.len() {
129+
d.push(unsafe { *c.offset(i as isize) as u8 });
130+
}
131+
let df = F::decode(&d).unwrap();
132+
assert_eq!(f.x, df.x);
133+
assert_eq!(f.y, df.y);
134+
}
135+
136+
#[test]
137+
fn test_encoded_metadata_roundtrip() {
138+
let f = F { x: -3, y: 42 };
139+
let enc = EncodedMetadata::new(Some(&f)).unwrap();
140+
let p = enc.as_ptr();
141+
let mut d = vec![];
142+
for i in 0..enc.len() {
143+
d.push(unsafe { *p.offset(i as isize) as u8 });
144+
}
145+
let df = F::decode(&d).unwrap();
146+
assert_eq!(f.x, df.x);
147+
assert_eq!(f.y, df.y);
148+
}
149+
}

0 commit comments

Comments
 (0)