feos_core/parameter/
identifier.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3use std::hash::{Hash, Hasher};
4
5/// Possible variants to identify a substance.
6#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
7pub enum IdentifierOption {
8    Cas,
9    Name,
10    IupacName,
11    Smiles,
12    Inchi,
13    Formula,
14}
15
16impl fmt::Display for IdentifierOption {
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        let str = match self {
19            IdentifierOption::Cas => "CAS",
20            IdentifierOption::Name => "name",
21            IdentifierOption::IupacName => "IUPAC name",
22            IdentifierOption::Smiles => "SMILES",
23            IdentifierOption::Inchi => "InChI",
24            IdentifierOption::Formula => "formula",
25        };
26        write!(f, "{str}")
27    }
28}
29
30/// A collection of identifiers for a chemical structure or substance.
31#[derive(Serialize, Deserialize, Debug, Clone, Default)]
32pub struct Identifier {
33    /// CAS number
34    #[serde(default)]
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub cas: Option<String>,
37    /// Commonly used english name
38    #[serde(default)]
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub name: Option<String>,
41    /// IUPAC name
42    #[serde(default)]
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub iupac_name: Option<String>,
45    /// SMILES key
46    #[serde(default)]
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub smiles: Option<String>,
49    /// InchI key
50    #[serde(default)]
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub inchi: Option<String>,
53    /// Chemical formula
54    #[serde(default)]
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub formula: Option<String>,
57}
58
59impl Identifier {
60    /// Create a new identifier.
61    ///
62    /// # Examples
63    ///
64    /// ```no_run
65    /// # use feos_core::parameter::Identifier;
66    /// let methanol = Identifier::new(
67    ///     Some("67-56-1"),
68    ///     Some("methanol"),
69    ///     Some("methanol"),
70    ///     Some("CO"),
71    ///     Some("InChI=1S/CH4O/c1-2/h2H,1H3"),
72    ///     Some("CH4O")
73    /// );
74    pub fn new(
75        cas: Option<&str>,
76        name: Option<&str>,
77        iupac_name: Option<&str>,
78        smiles: Option<&str>,
79        inchi: Option<&str>,
80        formula: Option<&str>,
81    ) -> Identifier {
82        Identifier {
83            cas: cas.map(Into::into),
84            name: name.map(Into::into),
85            iupac_name: iupac_name.map(Into::into),
86            smiles: smiles.map(Into::into),
87            inchi: inchi.map(Into::into),
88            formula: formula.map(Into::into),
89        }
90    }
91
92    pub fn as_str(&self, option: IdentifierOption) -> Option<&str> {
93        match option {
94            IdentifierOption::Cas => self.cas.as_deref(),
95            IdentifierOption::Name => self.name.as_deref(),
96            IdentifierOption::IupacName => self.iupac_name.as_deref(),
97            IdentifierOption::Smiles => self.smiles.as_deref(),
98            IdentifierOption::Inchi => self.inchi.as_deref(),
99            IdentifierOption::Formula => self.formula.as_deref(),
100        }
101    }
102
103    // returns the first available identifier in a somewhat arbitrary
104    // prioritization. Used for readable outputs.
105    pub fn as_readable_str(&self) -> Option<&str> {
106        self.name
107            .as_deref()
108            .or(self.iupac_name.as_deref())
109            .or(self.smiles.as_deref())
110            .or(self.cas.as_deref())
111            .or(self.inchi.as_deref())
112            .or(self.formula.as_deref())
113    }
114}
115
116impl std::fmt::Display for Identifier {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        let mut ids = Vec::new();
119        if let Some(n) = &self.cas {
120            ids.push(format!("cas={n}"));
121        }
122        if let Some(n) = &self.name {
123            ids.push(format!("name={n}"));
124        }
125        if let Some(n) = &self.iupac_name {
126            ids.push(format!("iupac_name={n}"));
127        }
128        if let Some(n) = &self.smiles {
129            ids.push(format!("smiles={n}"));
130        }
131        if let Some(n) = &self.inchi {
132            ids.push(format!("inchi={n}"));
133        }
134        if let Some(n) = &self.formula {
135            ids.push(format!("formula={n}"));
136        }
137        write!(f, "Identifier({})", ids.join(", "))
138    }
139}
140
141impl PartialEq for Identifier {
142    fn eq(&self, other: &Self) -> bool {
143        self.cas == other.cas
144    }
145}
146impl Eq for Identifier {}
147
148impl Hash for Identifier {
149    fn hash<H: Hasher>(&self, state: &mut H) {
150        self.cas.hash(state);
151    }
152}
153
154#[cfg(test)]
155mod test {
156    use super::*;
157
158    #[test]
159    fn test_fmt() {
160        let id = Identifier::new(None, Some("acetone"), None, Some("CC(=O)C"), None, None);
161        assert_eq!(id.to_string(), "Identifier(name=acetone, smiles=CC(=O)C)");
162    }
163}