Skip to main content

gapsmith_core/
id.rs

1//! Newtype wrappers for the three identifier namespaces used throughout gapseq.
2//!
3//! Keeping them distinct at the type level prevents passing a reaction id where
4//! a compound id is expected. The underlying storage is `String` for now;
5//! once the model-loading hot paths are profiled we can swap to interned
6//! `Arc<str>` without touching call sites.
7
8use 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        // The following must NOT compile; kept as a doctest-style reminder:
75        // let _: RxnId = c;
76    }
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}