1
1
//! 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`.
2
165
3
166
use crate :: bindings:: { tsk_id_t, tsk_size_t} ;
4
167
use thiserror:: Error ;
5
168
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;
32
172
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 ,
74
178
} ;
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
84
181
pub trait MetadataRoundtrip {
182
+ /// Encode `self` as bytes
85
183
fn encode ( & self ) -> Result < Vec < u8 > , MetadataError > ;
184
+ /// Decond `Self` from bytes
86
185
fn decode ( md : & [ u8 ] ) -> Result < Self , MetadataError >
87
186
where
88
187
Self : Sized ;
@@ -101,7 +200,7 @@ pub trait NodeMetadata: MetadataRoundtrip {}
101
200
pub trait EdgeMetadata : MetadataRoundtrip { }
102
201
///
103
202
/// Marker trait indicating [`MetadataRoundtrip`]
104
- /// for the migratoin table of a [`TableCollection`](crate::TableCollection).
203
+ /// for the migration table of a [`TableCollection`](crate::TableCollection).
105
204
pub trait MigrationMetadata : MetadataRoundtrip { }
106
205
107
206
/// Marker trait indicating [`MetadataRoundtrip`]
0 commit comments