Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ libc = "0.2.81"
streaming-iterator = "0.1.5"
bitflags = "1.2.1"
chrono = {version = "0.4.19", optional = true}
serde = {version = "1.0.118", features = ["derive"], optional = true}
serde_json = {version = "1.0.67", optional = true}
bincode = {version = "1.3.1", optional = true}

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

[features]
provenance = ["chrono"]
serde_json_metadata = ["serde", "serde_json"]
serde_bincode_metadata = ["serde", "bincode"]

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

[[example]]
name = "mutation_metadata_bincode"

[[example]]
name = "mutation_metadata_std"

# Not run during tests
[[example]]
name = "tree_traversals"
Expand Down
69 changes: 0 additions & 69 deletions examples/mutation_metadata_bincode.rs

This file was deleted.

75 changes: 0 additions & 75 deletions examples/mutation_metadata_std.rs

This file was deleted.

60 changes: 60 additions & 0 deletions src/_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,66 @@ macro_rules! handle_metadata_return {
};
}

/// Implement [`crate::metadata::MetadataRoundtrip`]
/// for a type using `serde_json`.
///
/// Requires the `serde_json_metadata` feature.
#[cfg(any(doc, feature = "serde_json_metadata"))]
#[macro_export]
macro_rules! serde_json_metadata {
($structname: ty) => {
impl $crate::metadata::MetadataRoundtrip for $structname {
fn encode(&self) -> Result<Vec<u8>, $crate::metadata::MetadataError> {
match serde_json::to_string(self) {
Ok(x) => Ok(x.as_bytes().to_vec()),
Err(e) => {
Err($crate::metadata::MetadataError::RoundtripError { value: Box::new(e) })
}
}
}

fn decode(md: &[u8]) -> Result<Self, $crate::metadata::MetadataError> {
let value: Result<Self, serde_json::Error> = serde_json::from_slice(md);
match value {
Ok(v) => Ok(v),
Err(e) => {
Err($crate::metadata::MetadataError::RoundtripError { value: Box::new(e) })
}
}
}
}
};
}

/// Implement [`crate::metadata::MetadataRoundtrip`]
/// for a type using `bincode`.
///
/// Requires the `serde_bincode_metadata` feature.
#[cfg(any(doc, feature = "serde_bincode_metadata"))]
#[macro_export]
macro_rules! serde_bincode_metadata {
($structname: ty) => {
impl $crate::metadata::MetadataRoundtrip for $structname {
fn encode(&self) -> Result<Vec<u8>, tskit::metadata::MetadataError> {
match bincode::serialize(&self) {
Ok(x) => Ok(x),
Err(e) => {
Err($crate::metadata::MetadataError::RoundtripError { value: Box::new(e) })
}
}
}
fn decode(md: &[u8]) -> Result<Self, tskit::metadata::MetadataError> {
match bincode::deserialize(md) {
Ok(x) => Ok(x),
Err(e) => {
Err($crate::metadata::MetadataError::RoundtripError { value: Box::new(e) })
}
}
}
}
};
}

#[cfg(test)]
mod test {
use crate::error::TskitError;
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
//!
//! * `provenance`
//! * Enables [`provenance`]
//! * `serde_json_metadata`
//! * Enables [`serde_json_metadata`] macro.
//! * `serde_bincode_metadata`
//! * Enables [`serde_bincode_metadata`] macro.
//!
//! To add features to your `Cargo.toml` file:
//!
Expand Down
122 changes: 64 additions & 58 deletions src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,67 +14,73 @@ use thiserror::Error;
/// [bincode](https://crates.io/crates/bincode) will be one of
/// the more useful `serde`-related crates.
///
/// # Examples
/// The library provides two macros to facilitate implementing metadata
/// traits:
///
/// ## Mutation metadata
/// * [`serde_json_metadata`]
/// * [`serde_bincode_metadata`]
///
/// ```
/// use tskit::handle_metadata_return;
/// use tskit::TableAccess;
/// These macros are optional features.
/// The feature names are the same as the macro names
///
/// #[derive(serde::Serialize, serde::Deserialize)]
/// pub struct MyMutation {
/// origin_time: i32,
/// effect_size: f64,
/// dominance: f64,
/// }
///
/// impl tskit::metadata::MetadataRoundtrip for MyMutation {
/// fn encode(&self) -> Result<Vec<u8>, tskit::metadata::MetadataError> {
/// handle_metadata_return!(bincode::serialize(&self))
/// }
///
/// fn decode(md: &[u8]) -> Result<Self, tskit::metadata::MetadataError> {
/// handle_metadata_return!(bincode::deserialize(md))
/// }
/// }
///
/// impl tskit::metadata::MutationMetadata for MyMutation {}
///
/// let mut tables = tskit::TableCollection::new(100.).unwrap();
/// let mutation = MyMutation{origin_time: 100,
/// effect_size: -1e-4,
/// dominance: 0.25};
///
/// // Add table row with metadata.
/// tables.add_mutation_with_metadata(0, 0, tskit::MutationId::NULL, 100., None,
/// &mutation).unwrap();
///
/// // Decode the metadata
/// // The two unwraps are:
/// // 1. Handle Errors vs Option.
/// // 2. Handle the option for the case of no error.
/// //
/// // The .into() reflects the fact that metadata fetching
/// // functions only take a strong ID type, and tskit-rust
/// // adds Into<strong ID type> for i32 for all strong ID types.
///
/// let decoded = tables.mutations().metadata::<MyMutation>(0.into()).unwrap().unwrap();
/// assert_eq!(mutation.origin_time, decoded.origin_time);
/// match decoded.effect_size.partial_cmp(&mutation.effect_size) {
/// Some(std::cmp::Ordering::Greater) => assert!(false),
/// Some(std::cmp::Ordering::Less) => assert!(false),
/// Some(std::cmp::Ordering::Equal) => (),
/// None => panic!("bad comparison"),
/// };
/// match decoded.dominance.partial_cmp(&mutation.dominance) {
/// Some(std::cmp::Ordering::Greater) => assert!(false),
/// Some(std::cmp::Ordering::Less) => assert!(false),
/// Some(std::cmp::Ordering::Equal) => (),
/// None => panic!("bad comparison"),
/// };
///
/// ```
#[cfg_attr(
feature = "provenance",
doc = r##"
# Examples

## Mutation metadata encoded as JSON

```
use tskit::handle_metadata_return;
use tskit::TableAccess;

#[derive(serde::Serialize, serde::Deserialize)]
pub struct MyMutation {
origin_time: i32,
effect_size: f64,
dominance: f64,
}

// Implement tskit::metadata::MetadataRoundtrip
tskit::serde_json_metadata!(MyMutation);

impl tskit::metadata::MutationMetadata for MyMutation {}

let mut tables = tskit::TableCollection::new(100.).unwrap();
let mutation = MyMutation{origin_time: 100,
effect_size: -1e-4,
dominance: 0.25};

// Add table row with metadata.
tables.add_mutation_with_metadata(0, 0, tskit::MutationId::NULL, 100., None,
&mutation).unwrap();

// Decode the metadata
// The two unwraps are:
// 1. Handle Errors vs Option.
// 2. Handle the option for the case of no error.
//
// The .into() reflects the fact that metadata fetching
// functions only take a strong ID type, and tskit-rust
// adds Into<strong ID type> for i32 for all strong ID types.

let decoded = tables.mutations().metadata::<MyMutation>(0.into()).unwrap().unwrap();
assert_eq!(mutation.origin_time, decoded.origin_time);
match decoded.effect_size.partial_cmp(&mutation.effect_size) {
Some(std::cmp::Ordering::Greater) => assert!(false),
Some(std::cmp::Ordering::Less) => assert!(false),
Some(std::cmp::Ordering::Equal) => (),
None => panic!("bad comparison"),
};
match decoded.dominance.partial_cmp(&mutation.dominance) {
Some(std::cmp::Ordering::Greater) => assert!(false),
Some(std::cmp::Ordering::Less) => assert!(false),
Some(std::cmp::Ordering::Equal) => (),
None => panic!("bad comparison"),
};
```
"##
)]
pub trait MetadataRoundtrip {
fn encode(&self) -> Result<Vec<u8>, MetadataError>;
fn decode(md: &[u8]) -> Result<Self, MetadataError>
Expand Down
Loading