Skip to main content

mzdata_meta/
lib.rs

1/*! Metadata describing mass spectrometry data files and their contents.
2 */
3#[macro_use]
4mod file_description;
5mod activation;
6mod data_processing;
7mod instrument;
8mod run;
9mod sample;
10mod scan_settings;
11mod software;
12#[macro_use]
13mod traits;
14
15use mzdata_param as params;
16use mzdata_param::{curie, impl_param_described};
17
18use std::borrow::Cow;
19
20pub use data_processing::{
21    DataProcessing, DataProcessingAction, DataTransformationAction, FormatConversion,
22    ProcessingMethod,
23};
24pub use software::{
25    custom_software_name, Software, SoftwareTerm, SoftwareType as SoftwareTypeFlags,
26};
27
28pub use file_description::{
29    FileDescription, MassSpectrometerFileFormatTerm, NativeIDFormatError, NativeSpectrumIDFormat,
30    NativeSpectrumIdentifierFormatTerm, SourceFile, SpectrumType,
31};
32
33pub use instrument::{
34    Component, ComponentType, DetectorTypeTerm, InletTypeTerm, InstrumentConfiguration,
35    IonizationTypeTerm, MassAnalyzerTerm,
36};
37
38pub use activation::{DissociationEnergy, DissociationEnergyTerm, DissociationMethodTerm};
39pub use run::MassSpectrometryRun;
40pub use sample::Sample;
41pub use scan_settings::ScanSettings;
42pub use traits::{FileMetadataConfig, MSDataFileMetadata};
43
44use crate::params::{ParamValueParseError, Value, ValueRef};
45
46#[macro_export]
47macro_rules! cvmap {
48    (
49        #[flag_type=$flag_type:ty]
50        $(#[$enum_attrs:meta])*
51        $vis:vis enum $enum_name:ident {
52            $(
53                #[term(cv=$cv:ident, accession=$accession:literal, name=$term_name:literal, flags=$flags:tt, parents=$parents:tt)]
54                $(#[$variant_attrs:meta])*
55                $variant:ident
56            ),*
57            $(,)?
58        }
59    ) => {
60        $(#[$enum_attrs])*
61        $vis enum $enum_name {
62            $(
63                $(#[$variant_attrs])*
64                $variant,
65            )*
66        }
67
68        /// These methods are part of the controlled vocabulary mapping
69        impl $enum_name {
70
71            /// Retrieve the accession number for this term, independent of its controlled vocabulary
72            pub const fn accession(&self) -> $crate::params::AccessionIntCode {
73                match self {
74                    $(Self::$variant => $accession,)*
75                }
76            }
77
78            /// Retrieve the controlled vocabulary this term belongs to
79            pub const fn controlled_vocabulary(&self) -> $crate::params::ControlledVocabulary {
80                match self {
81                    $(Self::$variant => $crate::params::ControlledVocabulary::$cv,)*
82                }
83            }
84
85            /// Retrieve the plain text human readable name for this term
86            pub const fn name(&self) -> &'static str {
87                match self {
88                    $(Self::$variant => $term_name,)*
89                }
90            }
91
92            /// Attempt to map a string by name to retrieve one of the terms from this
93            /// set.
94            ///
95            /// If no match is found, [`None`] is returned.
96            pub fn from_name(name: &str) -> Option<Self> {
97                match name {
98                    $($term_name => Some(Self::$variant),)*
99                    _ => None
100                }
101            }
102
103
104            /// Attempt to map the numeric accession number to retrieve one of the terms from this
105            /// set.
106            ///
107            /// If no match is found, [`None`] is returned.
108            pub const fn from_accession(accession: $crate::params::AccessionIntCode) -> Option<Self> {
109                match accession {
110                    $($accession => Some(Self::$variant),)*
111                    _ => None
112                }
113            }
114
115            /// Convert this term into a [`ParamCow`](crate::params::ParamCow) without a value.
116            pub const fn to_param(self) -> $crate::params::ParamCow<'static> {
117                $crate::params::ParamCow::const_new(
118                    self.name(),
119                    $crate::params::ValueRef::Empty,
120                    Some(self.accession()),
121                    Some(self.controlled_vocabulary()),
122                    $crate::params::Unit::Unknown
123                )
124            }
125
126            /// Convert a [`CURIE`]($crate::params::CURIE) by accession.
127            ///
128            /// If no match is found, [`None`] is returned.
129            pub const fn from_curie(curie: &$crate::params::CURIE) -> Option<Self> {
130                if matches!(curie.controlled_vocabulary, $crate::params::ControlledVocabulary::MS) {
131                    Self::from_accession(curie.accession)
132                } else {
133                    None
134                }
135            }
136
137            /// Attempt to convert a [`ParamCow`](crate::params::ParamCow) to a term from this set.
138            ///
139            /// If no match is found, [`None`] is returned.
140            ///
141            /// # Note
142            /// This method can be called in `const` contexts, requiring the type be [`ParamCow`](crate::params::ParamCow) with a `'static`
143            /// lifetime parameter, but the regular [`From`] trait is implemented for all [`ParamLike`](crate::params::ParamLike) types.
144            pub const fn from_param(p: &$crate::params::ParamCow<'static>) -> Option<Self> {
145                if let Some(acc) = p.accession {
146                    Self::from_accession(acc)
147                } else {
148                    None
149                }
150            }
151
152            /// Retrieve a term set specific set of flags
153            pub fn flags(&self) -> $flag_type {
154                match self {
155                    $(Self::$variant => $flags.into(),)*
156                }
157            }
158
159            /// Retrieve the list of zero or more terms in the set which are
160            /// parents of this term.
161            pub fn parents(&self) -> Vec<Self> {
162                match self {
163                    $(Self::$variant => $parents.iter().flat_map(|s: &&str| {
164                        let curie = s.parse::<$crate::params::CURIE>().unwrap();
165                        Self::from_accession(curie.accession)
166                    }).collect(),)*
167                }
168            }
169
170            #[cfg(feature = "cv")]
171            /// Test whether a CURIE is descended from this term, even if it isn't part of the compile-time
172            /// constant list.
173            ///
174            /// This depends upon the `cv` feature which may support a variable version of the PSI-MS controlled
175            /// vocabulary.
176            pub fn is_parent_of(&self, curie: $crate::params::CURIE) -> bool {
177                let self_curie = $crate::params::CURIE::new(self.controlled_vocabulary(), self.accession());
178                $crate::params::MSVocabulary::is_child_of(curie, self_curie)
179            }
180        }
181
182        impl<P> From<P> for $enum_name where P: $crate::params::ParamLike {
183            fn from(value: P) -> Self {
184                Self::from_accession(
185                    value.accession().expect(
186                        concat!("Cannot convert an uncontrolled parameter to ", stringify!($enum_name)))
187                ).unwrap_or_else(
188                    || panic!(
189                        "Could not map {:?}:{} to {}",
190                        value.controlled_vocabulary().unwrap(),
191                        value.accession().unwrap(),
192                        stringify!($enum_name)
193                    )
194                )
195            }
196        }
197
198        impl From<$enum_name> for $crate::params::ParamCow<'static> {
199            fn from(value: $enum_name) -> Self {
200                value.to_param()
201            }
202        }
203
204        impl From<$enum_name> for $crate::params::Param {
205            fn from(value: $enum_name) -> Self {
206                value.to_param().into()
207            }
208        }
209
210        impl From<&$enum_name> for $crate::params::ParamCow<'static> {
211            fn from(value: &$enum_name) -> Self {
212                value.to_param()
213            }
214        }
215
216        impl From<&$enum_name> for $crate::params::Param {
217            fn from(value: &$enum_name) -> Self {
218                value.to_param().into()
219            }
220        }
221    };
222
223    (
224        #[value_type=$value_type:ty]
225        #[flag_type=$flag_type:ty]
226        $(#[$enum_attrs:meta])*
227        $vis:vis enum $enum_name:ident {
228            $(
229                #[term(cv=$cv:ident, accession=$accession:literal, name=$term_name:literal, flags=$flags:tt, parents=$parents:tt)]
230                $(#[$variant_attrs:meta])*
231                $variant:ident($value_type2:ty)
232            ),*
233            $(,)?
234        }
235    ) => {
236        $(#[$enum_attrs])*
237        $vis enum $enum_name {
238            $(
239                $(#[$variant_attrs])*
240                $variant($value_type),
241            )*
242        }
243
244        /// These methods are part of the controlled vocabulary mapping
245        impl $enum_name {
246
247            /// Retrieve the accession number for this term, independent of its controlled vocabulary
248            pub const fn accession(&self) -> $crate::params::AccessionIntCode {
249                match self {
250                    $(Self::$variant(_) => $accession,)*
251                }
252            }
253
254            /// Retrieve the controlled vocabulary this term belongs to
255            pub const fn controlled_vocabulary(&self) -> $crate::params::ControlledVocabulary {
256                match self {
257                    $(Self::$variant(_) => $crate::params::ControlledVocabulary::$cv,)*
258                }
259            }
260
261            /// Retrieve the plain text human readable name for this term
262            pub const fn name(&self) -> &'static str {
263                match self {
264                    $(Self::$variant(_) => $term_name,)*
265                }
266            }
267
268            /// Attempt to map a string by name to retrieve one of the terms from this
269            /// set.
270            ///
271            /// If no match is found, [`None`] is returned.
272            pub fn from_name(name: &str) -> Option<Self> {
273                match name {
274                    $($term_name => Some(Self::$variant(Default::default())),)*
275                    _ => None
276                }
277            }
278
279
280            /// Attempt to map the numeric accession number to retrieve one of the terms from this
281            /// set.
282            ///
283            /// If no match is found, [`None`] is returned.
284            pub const fn from_accession(accession: $crate::params::AccessionIntCode, value: $value_type) -> Option<Self> {
285                match accession {
286                    $($accession => Some(Self::$variant(value)),)*
287                    _ => None
288                }
289            }
290
291            /// Convert this term into a [`ParamCow`](crate::params::ParamCow) without a value.
292            pub const fn to_param(self) -> $crate::params::ParamCow<'static> {
293                $crate::params::ParamCow::const_new(
294                    self.name(),
295                    $crate::params::ValueRef::Empty,
296                    Some(self.accession()),
297                    Some(self.controlled_vocabulary()),
298                    $crate::params::Unit::Unknown
299                )
300            }
301
302            /// Convert this term into a [`ParamCow`](crate::params::ParamCow) a specific boxed value.
303            pub const fn to_param_value(self, value: $crate::params::ValueRef<'static>) -> $crate::params::ParamCow<'static> {
304                $crate::params::ParamCow::const_new(
305                    self.name(),
306                    value,
307                    Some(self.accession()),
308                    Some(self.controlled_vocabulary()),
309                    $crate::params::Unit::Unknown
310                )
311            }
312
313            /// Convert a [`CURIE`]($crate::params::CURIE) by accession.
314            ///
315            /// If no match is found, [`None`] is returned.
316            pub fn from_curie(curie: &$crate::params::CURIE, value: f32) -> Option<Self> {
317                if matches!(curie.controlled_vocabulary, $crate::params::ControlledVocabulary::MS) {
318                    Self::from_accession(curie.accession, value)
319                } else {
320                    None
321                }
322            }
323
324            /// Attempt to convert a [`ParamCow`](crate::params::ParamCow) to a term from this set.
325            ///
326            /// If no match is found, [`None`] is returned.
327            ///
328            /// # Note
329            /// This method can be called in `const` contexts, requiring the type be [`ParamCow`](crate::params::ParamCow) with a `'static`
330            /// lifetime parameter, but the regular [`From`] trait is implemented for all [`ParamLike`](crate::params::ParamLike) types.
331            pub fn from_param(p: &$crate::params::ParamCow<'static>) -> Option<Self> {
332                if let Some(acc) = p.accession {
333                    Self::from_accession(acc, p.value.clone().into())
334                } else {
335                    None
336                }
337            }
338
339            /// Retrieve a term set specific set of flags
340            pub fn flags(&self) -> $flag_type {
341                match self {
342                    $(Self::$variant(_) => $flags.into(),)*
343                }
344            }
345
346            /// Retrieve the list of zero or more terms in the set which are
347            /// parents of this term.
348            pub fn parents(&self) -> Vec<Self> {
349                match self {
350                    $(Self::$variant(_) => $parents.iter().flat_map(|s: &&str| {
351                        let curie = s.parse::<$crate::params::CURIE>().unwrap();
352                        Self::from_accession(curie.accession, Default::default())
353                    }).collect(),)*
354                }
355            }
356
357        }
358
359        impl<P> From<P> for $enum_name where P: $crate::params::ParamLike {
360            fn from(value: P) -> Self {
361                Self::from_accession(
362                    value.accession().expect(
363                        concat!("Cannot convert an uncontrolled parameter to ", stringify!($enum_name))),
364                    value.value().into()
365                ).unwrap_or_else(
366                    || panic!(
367                        "Could not map {:?}:{} to {}",
368                        value.controlled_vocabulary().unwrap(),
369                        value.accession().unwrap(),
370                        stringify!($enum_name)
371                    )
372                )
373            }
374        }
375
376        impl From<$enum_name> for $crate::params::ParamCow<'static> {
377            fn from(value: $enum_name) -> Self {
378                value.to_param()
379            }
380        }
381
382        impl From<$enum_name> for $crate::params::Param {
383            fn from(value: $enum_name) -> Self {
384                value.to_param().into()
385            }
386        }
387
388        impl From<&$enum_name> for $crate::params::ParamCow<'static> {
389            fn from(value: &$enum_name) -> Self {
390                value.to_param()
391            }
392        }
393
394        impl From<&$enum_name> for $crate::params::Param {
395            fn from(value: &$enum_name) -> Self {
396                value.to_param().into()
397            }
398        }
399    };
400}
401
402bitflags::bitflags! {
403    #[doc="A bit mask encoding the different value types a [`Param`](crate::params::Param) can take on, associated with a term."]
404    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
405    pub struct ValueType: u16 {
406        const NoType = 0;
407        const String = 0b00000001;
408        const Integer = 0b00000010;
409        const Float = 0b00000100;
410        const Double = 0b00001000;
411        const NonNegativeInteger = 0b00010000;
412        const PositiveInteger = 0b00100000;
413        const DateTime = 0b01000000;
414        const Boolean = 0b10000000;
415
416        const ListOf = 0b1000000000000000;
417    }
418}
419
420impl From<u16> for ValueType {
421    fn from(value: u16) -> Self {
422        Self::from_bits_retain(value)
423    }
424}
425
426impl ValueType {
427    pub fn parse(&self, s: String) -> Result<Value, ParamValueParseError> {
428        let v = match *self {
429            Self::Integer | Self::NonNegativeInteger | Self::PositiveInteger => Value::Int(
430                s.parse()
431                    .map_err(|_| ParamValueParseError::FailedToExtractInt(Some(s)))?,
432            ),
433            Self::Float | Self::Double => Value::Float(
434                s.parse()
435                    .map_err(|_| ParamValueParseError::FailedToExtractFloat(Some(s)))?,
436            ),
437            _ => Value::String(s),
438        };
439        Ok(v)
440    }
441
442    pub fn parse_str<'a>(&self, s: &'a str) -> Result<ValueRef<'a>, ParamValueParseError> {
443        let v = match *self {
444            Self::Integer | Self::NonNegativeInteger | Self::PositiveInteger => ValueRef::Int(
445                s.parse()
446                    .map_err(|_| ParamValueParseError::FailedToExtractInt(Some(s.to_string())))?,
447            ),
448            Self::Float | Self::Double => ValueRef::Float(
449                s.parse()
450                    .map_err(|_| ParamValueParseError::FailedToExtractFloat(Some(s.to_string())))?,
451            ),
452            _ => ValueRef::String(Cow::Borrowed(s)),
453        };
454        Ok(v)
455    }
456}