openmassspec_core/source.rs
1//! The `SpectrumSource` trait: every vendor parser implements this.
2
3use crate::error::Result;
4use crate::types::{ChromatogramRecord, CvTerm, RunMetadata, SpectrumRecord};
5
6/// Boxed, fallible spectrum iterator returned by
7/// [`SpectrumSource::iter_spectra`].
8pub type SpectrumIter<'a> = Box<dyn Iterator<Item = Result<SpectrumRecord>> + 'a>;
9
10/// Boxed, fallible chromatogram iterator returned by
11/// [`SpectrumSource::iter_chromatograms`].
12pub type ChromatogramIter<'a> = Box<dyn Iterator<Item = Result<ChromatogramRecord>> + 'a>;
13
14/// A source of decoded mass spectra.
15///
16/// Vendors implement this on whatever value carries their open file state
17/// (e.g. `RawFileReader` + a `&mut Read+Seek` source for opentfraw, a
18/// `Reader` for opentimstdf).
19///
20/// The trait uses boxed iterators rather than RPITIT so that implementations
21/// can pick a different underlying iterator type per call without leaking
22/// that into the trait signature, and so consumers can hold a
23/// `&mut dyn SpectrumSource` for downstream plumbing (mzML writer, ingest
24/// pipelines, language bindings).
25///
26/// # Errors
27///
28/// Iterators yield `Result` items with the stack-wide
29/// [`Error`](crate::Error). A record the parser cannot decode is yielded as
30/// `Err`, never skipped: wrap the vendor error with
31/// [`Error::vendor`](crate::Error::vendor) (or a `From` impl in the vendor
32/// crate). One error type for every source keeps `dyn SpectrumSource`
33/// usable across vendors and lets adapters such as
34/// [`Centroided`](crate::Centroided) pass errors through unchanged.
35/// Consumers decide what an error means: the mzML writer stops at the first
36/// one unless the caller opts into
37/// [`RecordErrorPolicy::Skip`](crate::RecordErrorPolicy::Skip).
38///
39/// # Example
40///
41/// ```
42/// use openmassspec_core::{
43/// cv, CvTerm, Error, RunMetadata, SpectrumIter, SpectrumRecord, SpectrumSource,
44/// };
45///
46/// /// Stand-in for a vendor crate's own error type.
47/// #[derive(Debug)]
48/// struct DecodeError(u32);
49/// impl std::fmt::Display for DecodeError {
50/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51/// write!(f, "scan {} is truncated", self.0)
52/// }
53/// }
54/// impl std::error::Error for DecodeError {}
55///
56/// struct MyReader {
57/// scans: Vec<u32>,
58/// }
59///
60/// impl MyReader {
61/// fn decode(&self, i: usize, scan: u32) -> Result<SpectrumRecord, DecodeError> {
62/// if scan == 0 {
63/// return Err(DecodeError(scan));
64/// }
65/// Ok(SpectrumRecord::new(i, scan, format!("scan={scan}"))
66/// .with_ms_level(1)
67/// .with_retention_time_sec(f64::from(scan))
68/// .with_peaks(vec![100.0, 200.0], vec![1.0, 2.0]))
69/// }
70/// }
71///
72/// impl SpectrumSource for MyReader {
73/// fn run_metadata(&self) -> RunMetadata {
74/// RunMetadata::new(
75/// "run.raw",
76/// CvTerm::new("MS:1000563", "Thermo RAW format"),
77/// CvTerm::new("MS:1000768", "Thermo nativeID format"),
78/// CvTerm::new("MS:1000483", "Thermo Fisher Scientific instrument model"),
79/// "my-reader",
80/// "1.0.0",
81/// )
82/// }
83///
84/// fn iter_spectra<'a>(&'a mut self) -> SpectrumIter<'a> {
85/// let scans = self.scans.clone();
86/// Box::new(
87/// scans
88/// .into_iter()
89/// .enumerate()
90/// .map(move |(i, scan)| self.decode(i, scan).map_err(Error::vendor)),
91/// )
92/// }
93/// }
94///
95/// let mut reader = MyReader { scans: vec![1, 0] };
96/// let results: Vec<_> = reader.iter_spectra().collect();
97/// assert!(results[0].is_ok());
98/// assert!(results[1].is_err());
99/// ```
100pub trait SpectrumSource {
101 /// Run-level metadata. Cheap to call; vendors typically build this once.
102 fn run_metadata(&self) -> RunMetadata;
103
104 /// Iterate every spectrum the file contains, in file order.
105 ///
106 /// A spectrum the parser cannot decode is yielded as `Err` (see the
107 /// trait-level docs). Iteration may continue after an `Err` when the
108 /// parser can resume at the next spectrum; a parser that cannot resume
109 /// ends the iterator after yielding the error.
110 ///
111 /// The iterator borrows `self` mutably so vendors can stream from disk
112 /// without buffering the whole run in memory.
113 fn iter_spectra<'a>(&'a mut self) -> SpectrumIter<'a>;
114
115 /// Iterate chromatogram traces (TIC, BPC, SRM), with the same error
116 /// contract as [`iter_spectra`](Self::iter_spectra). Defaults to an
117 /// empty iterator.
118 fn iter_chromatograms<'a>(&'a mut self) -> ChromatogramIter<'a> {
119 Box::new(std::iter::empty())
120 }
121
122 /// Total number of spectra the source will yield, when known cheaply.
123 /// A sizing hint for consumers. The mzML writer does not use it: its
124 /// `<spectrumList count>` is the number of spectra actually written.
125 fn spectrum_count_hint(&self) -> Option<usize> {
126 None
127 }
128
129 /// Extra PSI-MS `<processingMethod>` steps, beyond the writer's own
130 /// default "Conversion to mzML" step, that belong in the emitted
131 /// `<dataProcessingList>`, in the order they should be recorded.
132 ///
133 /// Defaults to none. Adapters that transform spectra in a way a
134 /// consumer needs recorded in the file's processing history (e.g.
135 /// [`Centroided`](crate::Centroided) doing peak picking) override this,
136 /// delegating to the wrapped source first so steps accumulate through a
137 /// chain of adapters. Use the typed constants in [`crate::cv`] where one
138 /// exists (`cv::PEAK_PICKING.into()`).
139 fn additional_processing_steps(&self) -> Vec<CvTerm> {
140 Vec::new()
141 }
142}