11//! Support for table row metadata
2+ //!
3+ //! Metadata refers to data that client code may need to associate
4+ //! with table rows, but the data are not necessary to perform algorithms
5+ //! on tables nor on trees.
6+ //!
7+ //! For complete details, see the data model descriptions
8+ //! [`here`](https://tskit.dev/tskit/docs/stable/)
9+ //!
10+ //! The most straightfoward way to implement metadata
11+ //! is to use the optional `derive` feature of `tskit`.
12+ //! This feature enables derive macros to convert
13+ //! your types to metadata types via [`serde`](https://docs.rs/serde).
14+ //!
15+ //! Note that you will need to add `serde` as a dependency of your
16+ //! package, as you will need its `Serialize` and `Deserialize`
17+ //! derive macros available.
18+ //!
19+ //! Without the derive macros provided by tskit, you must `impl` [`MetadataRoundtrip`]
20+ //! and the approprate table metadata tag marker for your type.
21+ //! An example of such "manual" metadata type registration is shown
22+ //! as the last example below.
23+ //!
24+ //! A technical details section follows the examples
25+ //!
26+ //! # Examples
27+ //!
28+ //! ## Mutation metadata encoded as JSON
29+ //!
30+ //! ```
31+ //! # #[cfg(feature = "derive")] {
32+ //! use tskit::handle_metadata_return;
33+ //! use tskit::TableAccess;
34+ //!
35+ //! #[derive(serde::Serialize, serde::Deserialize, tskit::metadata::MutationMetadata)]
36+ //! #[serializer("serde_json")]
37+ //! pub struct MyMutation {
38+ //! origin_time: i32,
39+ //! effect_size: f64,
40+ //! dominance: f64,
41+ //! }
42+ //!
43+ //! let mut tables = tskit::TableCollection::new(100.).unwrap();
44+ //! let mutation = MyMutation{origin_time: 100,
45+ //! effect_size: -1e-4,
46+ //! dominance: 0.25};
47+ //!
48+ //! // Add table row with metadata.
49+ //! let id = tables.add_mutation_with_metadata(0, 0, tskit::MutationId::NULL, 100., None,
50+ //! &mutation).unwrap();
51+ //!
52+ //! // Decode the metadata
53+ //! // The two unwraps are:
54+ //! // 1. Handle Errors vs Option.
55+ //! // 2. Handle the option for the case of no error.
56+ //! let decoded = tables.mutations().metadata::<MyMutation>(id).unwrap().unwrap();
57+ //! assert_eq!(mutation.origin_time, decoded.origin_time);
58+ //! match decoded.effect_size.partial_cmp(&mutation.effect_size) {
59+ //! Some(std::cmp::Ordering::Greater) => assert!(false),
60+ //! Some(std::cmp::Ordering::Less) => assert!(false),
61+ //! Some(std::cmp::Ordering::Equal) => (),
62+ //! None => panic!("bad comparison"),
63+ //! };
64+ //! match decoded.dominance.partial_cmp(&mutation.dominance) {
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+ //! # }
71+ //! ```
72+ //! ## Example: individual metadata implemented via newtypes
73+ //!
74+ //! This time, we use [`bincode`](https://docs.rs/bincode/) via `serde`.
75+ //!
76+ //! ```
77+ //! # #[cfg(feature = "derive")] {
78+ //! use tskit::TableAccess;
79+ //! #[derive(serde::Serialize, serde::Deserialize, PartialEq, PartialOrd)]
80+ //! struct GeneticValue(f64);
81+ //!
82+ //! #[derive(serde::Serialize, serde::Deserialize, tskit::metadata::IndividualMetadata)]
83+ //! #[serializer("bincode")]
84+ //! struct IndividualMetadata {
85+ //! genetic_value: GeneticValue,
86+ //! }
87+ //! let mut tables = tskit::TableCollection::new(100.).unwrap();
88+ //! let individual = IndividualMetadata {
89+ //! genetic_value: GeneticValue(0.0),
90+ //! };
91+ //! let id = tables.add_individual_with_metadata(0, &[], &[tskit::IndividualId::NULL], &individual).unwrap();
92+ //! let decoded = tables.individuals().metadata::<IndividualMetadata>(id).unwrap().unwrap();
93+ //! assert_eq!(decoded.genetic_value.partial_cmp(&individual.genetic_value).unwrap(), std::cmp::Ordering::Equal);
94+ //! # }
95+ //! ```
96+ //!
97+ //! ## Example: manual implementation of all of the traits.
98+ //!
99+ //! Okay, let's do things the hard way.
100+ //! We will use a serializer not supported by `tskit` right now.
101+ //! For fun, we'll use the Python [`pickle`](https://docs.rs/crate/serde-pickle/) format.
102+ //!
103+ //! ```
104+ //! use tskit::TableAccess;
105+ //!
106+ //! #[derive(serde::Serialize, serde::Deserialize)]
107+ //! struct Metadata {
108+ //! data: String,
109+ //! }
110+ //!
111+ //! // Manually implement the metadata round trip trait.
112+ //! // You must propogate any errors back via Box, else
113+ //! // risk a `panic!`.
114+ //! impl tskit::metadata::MetadataRoundtrip for Metadata {
115+ //! fn encode(&self) -> Result<Vec<u8>, tskit::metadata::MetadataError> {
116+ //! match serde_pickle::to_vec(self, true) {
117+ //! Ok(v) => Ok(v),
118+ //! Err(e) => Err(tskit::metadata::MetadataError::RoundtripError{ value: Box::new(e) }),
119+ //! }
120+ //! }
121+ //!
122+ //! fn decode(md: &[u8]) -> Result<Self, tskit::metadata::MetadataError> {
123+ //! match serde_pickle::from_slice(md) {
124+ //! Ok(x) => Ok(x),
125+ //! Err(e) => Err(tskit::metadata::MetadataError::RoundtripError{ value: Box::new(e) }),
126+ //! }
127+ //! }
128+ //! }
129+ //!
130+ //! // If we want this to be, say, node metadata, then we need to mark
131+ //! // it as such:
132+ //! impl tskit::metadata::NodeMetadata for Metadata {}
133+ //!
134+ //! // Ready to rock:
135+ //! let mut tables = tskit::TableCollection::new(1.).unwrap();
136+ //! let id = tables
137+ //! .add_node_with_metadata(
138+ //! 0,
139+ //! 0.0,
140+ //! tskit::PopulationId::NULL,
141+ //! tskit::IndividualId::NULL,
142+ //! &Metadata {
143+ //! data: "Bananas".to_string(),
144+ //! },
145+ //! )
146+ //! .unwrap();
147+ //!
148+ //! let decoded = tables.nodes().metadata::<Metadata>(id).unwrap().unwrap();
149+ //! assert_eq!(decoded.data, "Bananas".to_string());
150+ //! ```
151+ //!
152+ //! # Technial details and notes
153+ //!
154+ //! * The derive macros currently support two `serde` methods:
155+ //! `serde_json` and `bincode`.
156+ //! * A concept like "mutation metadata" is the combination of two traits:
157+ //! [`MetadataRoundtrip`] plus [`MutationMetadata`].
158+ //! The latter is a marker trait.
159+ //! The derive macros handle all of this "boiler plate" for you.
160+ //!
161+ //! ## Limitations/unknowns
162+ //!
163+ //! * We have not yet tested importing metadata encoded using `rust`
164+ //! into `Python` via the `tskit` `Python API`.
2165
3166use crate :: bindings:: { tsk_id_t, tsk_size_t} ;
4167use thiserror:: Error ;
5168
6- /// Enable a type to be used as table metadata
7- ///
8- /// See [`handle_metadata_return`] for a macro to help implement this trait,
9- /// and its use in examples below.
10- ///
11- /// We strongly recommend the use of the [serde](https://serde.rs/) ecosystem
12- /// for row metadata.
13- /// For many use cases, we imagine that
14- /// [bincode](https://crates.io/crates/bincode) will be one of
15- /// the more useful `serde`-related crates.
16- ///
17- /// The library provides two macros to facilitate implementing metadata
18- /// traits:
19- ///
20- /// * [`serde_json_metadata`]
21- /// * [`serde_bincode_metadata`]
22- ///
23- /// These macros are optional features.
24- /// The feature names are the same as the macro names
25- ///
26- #[ cfg_attr(
27- feature = "provenance" ,
28- doc = r##"
29- # Examples
30-
31- ## Mutation metadata encoded as JSON
169+ #[ cfg( feature = "derive" ) ]
170+ #[ doc( hidden) ]
171+ pub extern crate tskit_derive;
32172
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"),
173+ #[ cfg( feature = "derive" ) ]
174+ #[ doc( hidden) ]
175+ pub use tskit_derive:: {
176+ EdgeMetadata , IndividualMetadata , MigrationMetadata , MutationMetadata , NodeMetadata ,
177+ PopulationMetadata , SiteMetadata ,
74178} ;
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- ) ]
179+
180+ /// Trait marking a type as table metadata
84181pub trait MetadataRoundtrip {
182+ /// Encode `self` as bytes
85183 fn encode ( & self ) -> Result < Vec < u8 > , MetadataError > ;
184+ /// Decond `Self` from bytes
86185 fn decode ( md : & [ u8 ] ) -> Result < Self , MetadataError >
87186 where
88187 Self : Sized ;
@@ -101,7 +200,7 @@ pub trait NodeMetadata: MetadataRoundtrip {}
101200pub trait EdgeMetadata : MetadataRoundtrip { }
102201///
103202/// Marker trait indicating [`MetadataRoundtrip`]
104- /// for the migratoin table of a [`TableCollection`](crate::TableCollection).
203+ /// for the migration table of a [`TableCollection`](crate::TableCollection).
105204pub trait MigrationMetadata : MetadataRoundtrip { }
106205
107206/// Marker trait indicating [`MetadataRoundtrip`]
0 commit comments