Skip to main content

mzdata_param/
lib.rs

1//! Elements of controlled vocabularies used to describe mass spectra and their components.
2//!
3//! Directly maps to the usage of the PSI-MS controlled vocabulary in mzML
4use std::borrow::Cow;
5use std::convert::TryFrom;
6use std::fmt::Display;
7use std::hash::Hash;
8use std::num;
9use std::str::{self, FromStr};
10
11use thiserror::Error;
12
13pub(crate) mod value;
14
15pub use value::{ParamValue, ParamValueParseError, Value};
16
17pub(crate) mod value_ref;
18
19pub use value_ref::ValueRef;
20
21pub(crate) mod curie_;
22
23pub use curie_::{
24    AccessionCodeParseError, AccessionIntCode, CURIE, CURIEParsingError, ControlledVocabulary,
25    ControlledVocabularyResolutionError, curie_to_num,
26};
27
28#[cfg(feature = "cv")]
29pub(crate) mod cv;
30
31#[cfg(feature = "cv")]
32pub use cv::{CVTraversal, MSTerm, MSVocabulary, VocabularyData};
33
34/// A helper to generate methods that find a value by a [`CURIE`]
35#[macro_export]
36macro_rules! find_param_method {
37    ($meth:ident, $curie:expr) => {
38        $crate::find_param_method!($meth, $curie, "Find a parameter by its CURIE");
39    };
40    ($meth:ident, $curie:expr, $desc:literal) => {
41        #[doc=$desc]
42        pub fn $meth(&self) -> Option<$crate::ValueRef<'_>> {
43            self.get_param_by_curie($curie)
44                .map(|p| $crate::ParamLike::value(p))
45        }
46    };
47    ($meth:ident, $curie:expr, $conv:expr, $result:ty) => {
48        $crate::find_param_method!(
49            $meth,
50            $curie,
51            $conv,
52            $result,
53            "Find a parameter by its CURIE"
54        );
55    };
56    ($meth:ident, $curie:expr, $conv:expr, $result:ty, $desc:literal) => {
57        #[doc=$desc]
58        pub fn $meth(&self) -> $result {
59            self.get_param_by_curie($curie).map($conv)
60        }
61    };
62}
63
64/// A syntactic shortcut for creating [`CURIE`] instances using compact notation
65#[macro_export]
66macro_rules! curie {
67    ($ns:ident:$acc:literal) => {
68        $crate::CURIE {
69            controlled_vocabulary: $crate::ControlledVocabulary::$ns,
70            accession: $acc,
71        }
72    };
73}
74
75/// Describe a controlled vocabulary parameter or a user-defined parameter
76pub trait ParamLike {
77    fn name(&self) -> &str;
78    fn value(&self) -> ValueRef<'_>;
79    fn accession(&self) -> Option<AccessionIntCode>;
80    fn controlled_vocabulary(&self) -> Option<ControlledVocabulary>;
81    fn unit(&self) -> Unit;
82    fn is_ms(&self) -> bool {
83        if let Some(cv) = self.controlled_vocabulary() {
84            cv == ControlledVocabulary::MS
85        } else {
86            false
87        }
88    }
89
90    fn parse<T: str::FromStr>(&self) -> Result<T, T::Err> {
91        self.value().parse::<T>()
92    }
93
94    fn is_controlled(&self) -> bool {
95        self.accession().is_some()
96    }
97
98    fn curie(&self) -> Option<CURIE> {
99        if !self.is_controlled() {
100            None
101        } else {
102            let cv = self.controlled_vocabulary().unwrap();
103            let acc = self.accession().unwrap();
104            // let accession_str = format!("{}:{:07}", cv.prefix(), acc);
105            Some(CURIE::new(cv, acc))
106        }
107    }
108}
109
110pub(crate) mod param_cow;
111pub use param_cow::ParamCow;
112
113pub(crate) mod param;
114
115pub use param::{Param, ParamBuilder};
116
117/// Anything that can be converted into an accession code portion of a [`CURIE`]
118#[derive(Debug, Clone)]
119pub enum AccessionLike<'a> {
120    Text(Cow<'a, str>),
121    Number(AccessionIntCode),
122    CURIE(CURIE),
123}
124
125impl From<AccessionIntCode> for AccessionLike<'_> {
126    fn from(value: AccessionIntCode) -> Self {
127        Self::Number(value)
128    }
129}
130
131impl<'a> From<&'a str> for AccessionLike<'a> {
132    fn from(value: &'a str) -> Self {
133        Self::Text(Cow::Borrowed(value))
134    }
135}
136
137impl From<String> for AccessionLike<'_> {
138    fn from(value: String) -> Self {
139        Self::Text(Cow::Owned(value))
140    }
141}
142
143pub type ParamList = Vec<Param>;
144
145/// A read-only form of [`ParamDescribed`]
146pub trait ParamDescribedRead {
147    /// Obtain an immutable slice over the encapsulated [`Param`] list
148    fn params(&self) -> &[Param];
149
150    /// Find the first [`Param`] whose name matches `name`
151    fn get_param_by_name(&self, name: &str) -> Option<&Param> {
152        self.params().iter().find(|&param| param.name == name)
153    }
154
155    /// Find the first [`Param`] whose [`CURIE`] matches `curie`
156    fn get_param_by_curie(&self, curie: &CURIE) -> Option<&Param> {
157        self.params().iter().find(|&param| curie == param)
158    }
159
160    /// Find the first [`Param`] whose [`Param::accession`] matches `accession`
161    ///
162    /// This is equivalent to [`ParamDescribed::get_param_by_curie`] on `accession.parse::<CURIE>().unwrap()`
163    fn get_param_by_accession(&self, accession: &str) -> Option<&Param> {
164        let (cv, acc_num) = curie_to_num(accession);
165        self.params()
166            .iter()
167            .find(|&param| param.accession == acc_num && param.controlled_vocabulary == cv)
168    }
169
170    /// Iterate over the encapsulated parameter list
171    fn iter_params(&self) -> std::slice::Iter<'_, Param> {
172        self.params().iter()
173    }
174}
175
176impl ParamDescribedRead for &[Param] {
177    fn params(&self) -> &[Param] {
178        self
179    }
180}
181
182
183/// A type that has a [`ParamList`] that uses [`Param`] instances to describe an entity
184/// with key-value pairs.
185pub trait ParamDescribed {
186    /// Obtain an immutable slice over the encapsulated [`Param`] list
187    fn params(&self) -> &[Param];
188
189    /// Obtain an mutable slice over the encapsulated [`Param`] list
190    fn params_mut(&mut self) -> &mut ParamList;
191
192    /// Add a new [`Param`] to the entity
193    fn add_param(&mut self, param: Param) {
194        self.params_mut().push(param);
195    }
196
197    /// Add all parameters from an iterator of [`Param`] to the entity
198    fn extend_params(&mut self, it: impl IntoIterator<Item = Param>) {
199        self.params_mut().extend(it)
200    }
201
202    /// Remove the `i`th [`Param`] from the entity.
203    fn remove_param(&mut self, index: usize) -> Param {
204        self.params_mut().remove(index)
205    }
206
207    /// Find the first [`Param`] whose name matches `name`
208    fn get_param_by_name(&self, name: &str) -> Option<&Param> {
209        self.params().iter().find(|&param| param.name == name)
210    }
211
212    /// Find the first [`Param`] whose [`CURIE`] matches `curie`
213    fn get_param_by_curie(&self, curie: &CURIE) -> Option<&Param> {
214        self.params().iter().find(|&param| curie == param)
215    }
216
217    /// Find the first [`Param`] whose [`Param::accession`] matches `accession`
218    ///
219    /// This is equivalent to [`ParamDescribed::get_param_by_curie`] on `accession.parse::<CURIE>().unwrap()`
220    fn get_param_by_accession(&self, accession: &str) -> Option<&Param> {
221        let (cv, acc_num) = curie_to_num(accession);
222        self.params()
223            .iter()
224            .find(|&param| param.accession == acc_num && param.controlled_vocabulary == cv)
225    }
226
227    /// Iterate over the encapsulated parameter list
228    fn iter_params(&self) -> std::slice::Iter<'_, Param> {
229        self.params().iter()
230    }
231
232    /// Iterate mutably over the encapsulated parameter list
233    fn iter_params_mut(&mut self) -> std::slice::IterMut<'_, Param> {
234        self.params_mut().iter_mut()
235    }
236}
237
238impl ParamDescribed for ParamList {
239    fn params(&self) -> &[Param] {
240        self
241    }
242
243    fn params_mut(&mut self) -> &mut ParamList {
244        self
245    }
246}
247
248
249/// Implement the [`ParamDescribed`] trait for type `$t`, referencing a `params` member
250/// of type `Vec<`[`Param`]`>`.
251#[macro_export]
252macro_rules! impl_param_described {
253    ($($t:ty), +) => {$(
254
255        impl $crate::ParamDescribed for $t {
256            fn params(&self) -> &[$crate::Param] {
257                return &self.params
258            }
259
260            fn params_mut(&mut self) -> &mut $crate::ParamList {
261                return &mut self.params
262            }
263        }
264    )+};
265}
266
267#[doc(hidden)]
268pub const _EMPTY_PARAM: &[Param] = &[];
269
270/// Implement the [`ParamDescribed`] trait for type `$t`, referencing a `params` member
271/// that is an `Option<Vec<`[`Param`]`>>` that will lazily be initialized automatically
272/// when it is accessed mutably.
273#[macro_export]
274macro_rules! impl_param_described_deferred {
275    ($($t:ty), +) => {$(
276        impl $crate::ParamDescribed for $t {
277            fn params(&self) -> &[$crate::Param] {
278                match &self.params {
279                    Some(val) => &val,
280                    None => {
281                        $crate::_EMPTY_PARAM
282                    }
283                }
284            }
285
286            fn params_mut(&mut self) -> &mut $crate::ParamList {
287                let val = &mut self.params;
288                if val.is_some() {
289                    return val.as_deref_mut().unwrap()
290                } else {
291                    *val = Some(Box::default());
292                    return val.as_deref_mut().unwrap()
293                }
294            }
295        }
296    )+};
297}
298
299pub(crate) mod units;
300
301pub use units::Unit;
302
303#[cfg(test)]
304mod test {
305    use super::*;
306
307    #[test]
308    fn test_build_param() {
309        assert_eq!(
310            ParamBuilder::default()
311                .name("dalton")
312                .curie(curie!(UO:221))
313                .build(),
314            ControlledVocabulary::UO.param("UO:000221", "dalton")
315        );
316        // <cvParam cvRef="MS" accession="MS:1000529" name="instrument serial number" value="FSN10375"/>
317        let p = ParamBuilder::default()
318            .controlled_vocabulary(ControlledVocabulary::MS)
319            .accession(1000529)
320            .name("instrument serial number")
321            .value("FSN10375")
322            .unit(Unit::Unknown)
323            .build();
324        assert_eq!(p.value(), "FSN10375");
325        assert_eq!(p.unit(), Unit::Unknown);
326    }
327
328    #[test]
329    fn test_value() {
330        let x = 42;
331        let mut val: Value = x.into();
332        let mut val_ref: ValueRef = x.into();
333        let mut val_ref2: ValueRef = (&x).into();
334        assert_eq!(val, x);
335        assert_eq!(val_ref, x);
336        assert_eq!(val_ref, val);
337        assert_eq!(val_ref2, val_ref);
338        assert!(val.to_bool().unwrap());
339        assert!(val_ref.to_bool().unwrap());
340        assert_eq!(val.to_str(), x.to_string());
341        assert_eq!(val_ref.to_str(), x.to_string());
342        val = x.to_string().parse().unwrap();
343        val_ref = x.to_string().parse().unwrap();
344        assert_eq!(val, x);
345        assert_eq!(val_ref, x);
346        assert_eq!(val_ref, val);
347
348        let x2 = Some(x);
349        val = x2.into();
350        val_ref = x2.into();
351        val_ref2 = (&x).into();
352        assert_eq!(val, x);
353        assert_eq!(val_ref, x);
354        assert_eq!(val_ref, val);
355        assert_eq!(val_ref2, val_ref);
356        assert!(val.to_bool().unwrap());
357        assert!(val_ref.to_bool().unwrap());
358        assert_eq!(val.to_str(), x.to_string());
359        assert_eq!(val_ref.to_str(), x.to_string());
360        val = x.to_string().parse().unwrap();
361        val_ref = x.to_string().parse().unwrap();
362        assert_eq!(val, x);
363        assert_eq!(val_ref, x);
364        assert_eq!(val_ref, val);
365
366        let x = 42.01;
367        val = x.into();
368        val_ref = x.into();
369        val_ref2 = (&x).into();
370        assert_eq!(val, x);
371        assert_eq!(val_ref, x);
372        assert_eq!(val_ref, val);
373        assert_eq!(val_ref2, val_ref);
374        assert!(val.to_bool().unwrap());
375        assert!(val_ref.to_bool().unwrap());
376        assert_eq!(val.to_str(), x.to_string());
377        assert_eq!(val_ref.to_str(), x.to_string());
378        val = x.to_string().parse().unwrap();
379        val_ref = x.to_string().parse().unwrap();
380        assert_eq!(val, x);
381        assert_eq!(val_ref, x);
382        assert_eq!(val_ref, val);
383
384        let x2 = Some(x);
385        val = x2.into();
386        val_ref = x2.into();
387        assert_eq!(val, x);
388        assert_eq!(val_ref, x);
389        assert_eq!(val_ref, val);
390        assert!(val.to_bool().unwrap());
391        assert!(val_ref.to_bool().unwrap());
392        assert_eq!(val.to_str(), x.to_string());
393        assert_eq!(val_ref.to_str(), x.to_string());
394        val = x.to_string().parse().unwrap();
395        val_ref = x.to_string().parse().unwrap();
396        assert_eq!(val, x);
397        assert_eq!(val_ref, x);
398        assert_eq!(val_ref, val);
399
400        let x = true;
401        val = x.into();
402        val_ref = x.into();
403        assert_eq!(val, x);
404        assert_eq!(val_ref, x);
405        assert_eq!(val_ref, val);
406        assert!(val.to_bool().unwrap());
407        assert!(val_ref.to_bool().unwrap());
408        assert_eq!(val.to_str(), x.to_string());
409        assert_eq!(val_ref.to_str(), x.to_string());
410        val = x.to_string().parse().unwrap();
411        val_ref = x.to_string().parse().unwrap();
412        assert_eq!(val, x);
413        assert_eq!(val_ref, x);
414        assert_eq!(val_ref, val);
415
416        let x = "Foobar".to_string();
417        val = x.clone().into();
418        val_ref = x.clone().into();
419        val_ref2 = x.as_str().into();
420        assert_eq!(val, x);
421        assert_eq!(val_ref, x);
422        assert_eq!(val_ref, val);
423        assert_eq!(val_ref2, val_ref);
424        assert_eq!(val.to_str(), x.to_string());
425        assert_eq!(val_ref.to_str(), x.to_string());
426        val = x.to_string().parse().unwrap();
427        val_ref = x.to_string().parse().unwrap();
428        assert_eq!(val, x);
429        assert_eq!(val_ref, x);
430        assert_eq!(val_ref, val);
431        assert_eq!(val.to_buffer().unwrap(), x.as_bytes());
432        assert_eq!(val_ref.to_buffer().unwrap(), x.as_bytes());
433        assert_eq!(val_ref.to_buffer().unwrap(), val.to_buffer().unwrap());
434    }
435
436    #[cfg(feature = "static_data")]
437    #[test]
438    fn test_mzcv_ms() {
439        let cv = MSVocabulary::init();
440        let term = cv.get_by_name("ms level").unwrap();
441        assert_eq!(term.name.as_ref(), "ms level");
442        let parents_of = CVTraversal::parents_of(cv, term.curie()).unwrap();
443        let parent = cv
444            .get_by_index(&parents_of.iter().next().unwrap().0)
445            .unwrap();
446        assert_eq!(parent.name.as_ref(), "spectrum attribute");
447    }
448}