1use serde::{Deserialize, Serialize};
9use std::fmt;
10
11macro_rules! id_newtype {
12 ($name:ident, $doc:literal) => {
13 #[doc = $doc]
14 #[derive(
15 Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize,
16 )]
17 #[serde(transparent)]
18 pub struct $name(String);
19
20 impl $name {
21 pub fn new(s: impl Into<String>) -> Self {
22 Self(s.into())
23 }
24
25 pub fn as_str(&self) -> &str {
26 &self.0
27 }
28
29 pub fn into_string(self) -> String {
30 self.0
31 }
32 }
33
34 impl fmt::Display for $name {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 f.write_str(&self.0)
37 }
38 }
39
40 impl From<String> for $name {
41 fn from(s: String) -> Self {
42 Self(s)
43 }
44 }
45
46 impl From<&str> for $name {
47 fn from(s: &str) -> Self {
48 Self(s.to_owned())
49 }
50 }
51
52 impl AsRef<str> for $name {
53 fn as_ref(&self) -> &str {
54 &self.0
55 }
56 }
57 };
58}
59
60id_newtype!(RxnId, "Reaction identifier (e.g. `rxn00148_c0`, `EX_cpd00007_e0`, `bio1`).");
61id_newtype!(CpdId, "Compound identifier (e.g. `cpd00001`).");
62id_newtype!(GeneId, "Gene identifier (e.g. `fig|83333.1.peg.3`, locus tag).");
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn distinct_types_do_not_cross() {
70 let r = RxnId::new("rxn00001");
71 let c = CpdId::new("cpd00001");
72 assert_eq!(r.as_str(), "rxn00001");
73 assert_eq!(c.as_str(), "cpd00001");
74 }
77
78 #[test]
79 fn serde_transparent_json() {
80 let r = RxnId::new("rxn00148");
81 let j = serde_json::to_string(&r).unwrap();
82 assert_eq!(j, "\"rxn00148\"");
83 let back: RxnId = serde_json::from_str(&j).unwrap();
84 assert_eq!(r, back);
85 }
86}