Skip to content

Commit 2f14352

Browse files
authored
Add optional macro features for metadata trait implementation (#162)
1 parent 3201a2d commit 2f14352

File tree

7 files changed

+230
-208
lines changed

7 files changed

+230
-208
lines changed

Cargo.toml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ libc = "0.2.81"
2121
streaming-iterator = "0.1.5"
2222
bitflags = "1.2.1"
2323
chrono = {version = "0.4.19", optional = true}
24+
serde = {version = "1.0.118", features = ["derive"], optional = true}
25+
serde_json = {version = "1.0.67", optional = true}
26+
bincode = {version = "1.3.1", optional = true}
2427

2528
[dev-dependencies]
2629
clap = "~2.33.3"
@@ -36,16 +39,12 @@ pkg-config = "0.3"
3639

3740
[features]
3841
provenance = ["chrono"]
42+
serde_json_metadata = ["serde", "serde_json"]
43+
serde_bincode_metadata = ["serde", "bincode"]
3944

4045
[package.metadata.docs.rs]
4146
all-features = true
4247

43-
[[example]]
44-
name = "mutation_metadata_bincode"
45-
46-
[[example]]
47-
name = "mutation_metadata_std"
48-
4948
# Not run during tests
5049
[[example]]
5150
name = "tree_traversals"

examples/mutation_metadata_bincode.rs

Lines changed: 0 additions & 69 deletions
This file was deleted.

examples/mutation_metadata_std.rs

Lines changed: 0 additions & 75 deletions
This file was deleted.

src/_macros.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,66 @@ macro_rules! handle_metadata_return {
322322
};
323323
}
324324

325+
/// Implement [`crate::metadata::MetadataRoundtrip`]
326+
/// for a type using `serde_json`.
327+
///
328+
/// Requires the `serde_json_metadata` feature.
329+
#[cfg(any(doc, feature = "serde_json_metadata"))]
330+
#[macro_export]
331+
macro_rules! serde_json_metadata {
332+
($structname: ty) => {
333+
impl $crate::metadata::MetadataRoundtrip for $structname {
334+
fn encode(&self) -> Result<Vec<u8>, $crate::metadata::MetadataError> {
335+
match serde_json::to_string(self) {
336+
Ok(x) => Ok(x.as_bytes().to_vec()),
337+
Err(e) => {
338+
Err($crate::metadata::MetadataError::RoundtripError { value: Box::new(e) })
339+
}
340+
}
341+
}
342+
343+
fn decode(md: &[u8]) -> Result<Self, $crate::metadata::MetadataError> {
344+
let value: Result<Self, serde_json::Error> = serde_json::from_slice(md);
345+
match value {
346+
Ok(v) => Ok(v),
347+
Err(e) => {
348+
Err($crate::metadata::MetadataError::RoundtripError { value: Box::new(e) })
349+
}
350+
}
351+
}
352+
}
353+
};
354+
}
355+
356+
/// Implement [`crate::metadata::MetadataRoundtrip`]
357+
/// for a type using `bincode`.
358+
///
359+
/// Requires the `serde_bincode_metadata` feature.
360+
#[cfg(any(doc, feature = "serde_bincode_metadata"))]
361+
#[macro_export]
362+
macro_rules! serde_bincode_metadata {
363+
($structname: ty) => {
364+
impl $crate::metadata::MetadataRoundtrip for $structname {
365+
fn encode(&self) -> Result<Vec<u8>, tskit::metadata::MetadataError> {
366+
match bincode::serialize(&self) {
367+
Ok(x) => Ok(x),
368+
Err(e) => {
369+
Err($crate::metadata::MetadataError::RoundtripError { value: Box::new(e) })
370+
}
371+
}
372+
}
373+
fn decode(md: &[u8]) -> Result<Self, tskit::metadata::MetadataError> {
374+
match bincode::deserialize(md) {
375+
Ok(x) => Ok(x),
376+
Err(e) => {
377+
Err($crate::metadata::MetadataError::RoundtripError { value: Box::new(e) })
378+
}
379+
}
380+
}
381+
}
382+
};
383+
}
384+
325385
#[cfg(test)]
326386
mod test {
327387
use crate::error::TskitError;

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@
4545
//!
4646
//! * `provenance`
4747
//! * Enables [`provenance`]
48+
//! * `serde_json_metadata`
49+
//! * Enables [`serde_json_metadata`] macro.
50+
//! * `serde_bincode_metadata`
51+
//! * Enables [`serde_bincode_metadata`] macro.
4852
//!
4953
//! To add features to your `Cargo.toml` file:
5054
//!

src/metadata.rs

Lines changed: 64 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -14,67 +14,73 @@ use thiserror::Error;
1414
/// [bincode](https://crates.io/crates/bincode) will be one of
1515
/// the more useful `serde`-related crates.
1616
///
17-
/// # Examples
17+
/// The library provides two macros to facilitate implementing metadata
18+
/// traits:
1819
///
19-
/// ## Mutation metadata
20+
/// * [`serde_json_metadata`]
21+
/// * [`serde_bincode_metadata`]
2022
///
21-
/// ```
22-
/// use tskit::handle_metadata_return;
23-
/// use tskit::TableAccess;
23+
/// These macros are optional features.
24+
/// The feature names are the same as the macro names
2425
///
25-
/// #[derive(serde::Serialize, serde::Deserialize)]
26-
/// pub struct MyMutation {
27-
/// origin_time: i32,
28-
/// effect_size: f64,
29-
/// dominance: f64,
30-
/// }
31-
///
32-
/// impl tskit::metadata::MetadataRoundtrip for MyMutation {
33-
/// fn encode(&self) -> Result<Vec<u8>, tskit::metadata::MetadataError> {
34-
/// handle_metadata_return!(bincode::serialize(&self))
35-
/// }
36-
///
37-
/// fn decode(md: &[u8]) -> Result<Self, tskit::metadata::MetadataError> {
38-
/// handle_metadata_return!(bincode::deserialize(md))
39-
/// }
40-
/// }
41-
///
42-
/// impl tskit::metadata::MutationMetadata for MyMutation {}
43-
///
44-
/// let mut tables = tskit::TableCollection::new(100.).unwrap();
45-
/// let mutation = MyMutation{origin_time: 100,
46-
/// effect_size: -1e-4,
47-
/// dominance: 0.25};
48-
///
49-
/// // Add table row with metadata.
50-
/// tables.add_mutation_with_metadata(0, 0, tskit::MutationId::NULL, 100., None,
51-
/// &mutation).unwrap();
52-
///
53-
/// // Decode the metadata
54-
/// // The two unwraps are:
55-
/// // 1. Handle Errors vs Option.
56-
/// // 2. Handle the option for the case of no error.
57-
/// //
58-
/// // The .into() reflects the fact that metadata fetching
59-
/// // functions only take a strong ID type, and tskit-rust
60-
/// // adds Into<strong ID type> for i32 for all strong ID types.
61-
///
62-
/// let decoded = tables.mutations().metadata::<MyMutation>(0.into()).unwrap().unwrap();
63-
/// assert_eq!(mutation.origin_time, decoded.origin_time);
64-
/// match decoded.effect_size.partial_cmp(&mutation.effect_size) {
65-
/// Some(std::cmp::Ordering::Greater) => assert!(false),
66-
/// Some(std::cmp::Ordering::Less) => assert!(false),
67-
/// Some(std::cmp::Ordering::Equal) => (),
68-
/// None => panic!("bad comparison"),
69-
/// };
70-
/// match decoded.dominance.partial_cmp(&mutation.dominance) {
71-
/// Some(std::cmp::Ordering::Greater) => assert!(false),
72-
/// Some(std::cmp::Ordering::Less) => assert!(false),
73-
/// Some(std::cmp::Ordering::Equal) => (),
74-
/// None => panic!("bad comparison"),
75-
/// };
76-
///
77-
/// ```
26+
#[cfg_attr(
27+
feature = "provenance",
28+
doc = r##"
29+
# Examples
30+
31+
## Mutation metadata encoded as JSON
32+
33+
```
34+
use tskit::handle_metadata_return;
35+
use tskit::TableAccess;
36+
37+
#[derive(serde::Serialize, serde::Deserialize)]
38+
pub struct MyMutation {
39+
origin_time: i32,
40+
effect_size: f64,
41+
dominance: f64,
42+
}
43+
44+
// Implement tskit::metadata::MetadataRoundtrip
45+
tskit::serde_json_metadata!(MyMutation);
46+
47+
impl tskit::metadata::MutationMetadata for MyMutation {}
48+
49+
let mut tables = tskit::TableCollection::new(100.).unwrap();
50+
let mutation = MyMutation{origin_time: 100,
51+
effect_size: -1e-4,
52+
dominance: 0.25};
53+
54+
// Add table row with metadata.
55+
tables.add_mutation_with_metadata(0, 0, tskit::MutationId::NULL, 100., None,
56+
&mutation).unwrap();
57+
58+
// Decode the metadata
59+
// The two unwraps are:
60+
// 1. Handle Errors vs Option.
61+
// 2. Handle the option for the case of no error.
62+
//
63+
// The .into() reflects the fact that metadata fetching
64+
// functions only take a strong ID type, and tskit-rust
65+
// adds Into<strong ID type> for i32 for all strong ID types.
66+
67+
let decoded = tables.mutations().metadata::<MyMutation>(0.into()).unwrap().unwrap();
68+
assert_eq!(mutation.origin_time, decoded.origin_time);
69+
match decoded.effect_size.partial_cmp(&mutation.effect_size) {
70+
Some(std::cmp::Ordering::Greater) => assert!(false),
71+
Some(std::cmp::Ordering::Less) => assert!(false),
72+
Some(std::cmp::Ordering::Equal) => (),
73+
None => panic!("bad comparison"),
74+
};
75+
match decoded.dominance.partial_cmp(&mutation.dominance) {
76+
Some(std::cmp::Ordering::Greater) => assert!(false),
77+
Some(std::cmp::Ordering::Less) => assert!(false),
78+
Some(std::cmp::Ordering::Equal) => (),
79+
None => panic!("bad comparison"),
80+
};
81+
```
82+
"##
83+
)]
7884
pub trait MetadataRoundtrip {
7985
fn encode(&self) -> Result<Vec<u8>, MetadataError>;
8086
fn decode(md: &[u8]) -> Result<Self, MetadataError>

0 commit comments

Comments
 (0)