use serde::{Deserialize, Serialize};
use std::path::Path;
#[allow(missing_docs)]
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct Config {
pub mz_half_width: Option<u32>,
pub min_feature_length: Option<usize>,
pub max_internal_gap: Option<usize>,
pub min_window_intensity: Option<u64>,
pub min_feature_intensity: Option<u64>,
pub iterations: Option<usize>,
pub frame_half_width: Option<usize>,
pub halo: Option<bool>,
pub halo_peak_fraction: Option<f64>,
pub halo_mz_idx_half_width: Option<u32>,
pub halo_scan_half_width: Option<usize>,
pub denoise_msms: Option<bool>,
pub msms_mz_half_width: Option<u32>,
pub msms_min_feature_length: Option<usize>,
pub msms_max_internal_gap: Option<usize>,
pub msms_min_window_intensity: Option<u64>,
pub msms_min_feature_intensity: Option<u64>,
pub msms_iterations: Option<usize>,
pub smooth: Option<bool>,
pub smooth_mz_idx_half_width: Option<u32>,
pub smooth_scan_half_width: Option<usize>,
pub smooth_iterations: Option<usize>,
pub watershed: Option<bool>,
pub watershed_box_scan: Option<u32>,
pub watershed_box_mz_idx: Option<u32>,
pub watershed_min_seed_intensity: Option<u64>,
pub watershed_min_centroid_total: Option<u64>,
pub watershed_max_tof_offset: Option<u32>,
pub box_centroid: Option<bool>,
pub box_centroid_mz_idx_half: Option<u32>,
pub box_centroid_scan_half: Option<u32>,
pub box_centroid_min_total: Option<u64>,
pub dia_window: Option<bool>,
pub dia_window_scan_pad: Option<u32>,
pub dia_per_window: Option<bool>,
pub dda_window: Option<bool>,
pub dda_window_scan_pad: Option<u32>,
pub dia_ms1_window: Option<bool>,
pub dia_ms1_mz_pad: Option<f64>,
pub dia_ms1_im_pad: Option<f64>,
pub ms1_polygon: Option<bool>,
pub ms1_polygon_mz_pad: Option<f64>,
pub ms1_polygon_im_pad: Option<f64>,
pub mz_min: Option<f64>,
pub mz_max: Option<f64>,
pub im_min: Option<f64>,
pub im_max: Option<f64>,
pub rt_min: Option<f64>,
pub rt_max: Option<f64>,
pub min_intensity: Option<u32>,
pub max_intensity: Option<u32>,
pub crop_only: Option<bool>,
pub mz_ppm: Option<f64>,
pub mz_ppm_ref: Option<f64>,
pub all_frames: Option<bool>,
pub threads: Option<usize>,
}
impl Config {
pub fn load(path: &Path) -> Result<Self, String> {
let text = std::fs::read_to_string(path)
.map_err(|e| format!("reading config {}: {e}", path.display()))?;
toml::from_str(&text).map_err(|e| format!("parsing config {}: {e}", path.display()))
}
pub fn to_toml_string(&self) -> Result<String, String> {
toml::to_string_pretty(self).map_err(|e| format!("serializing config: {e}"))
}
pub fn save(&self, path: &Path) -> Result<(), String> {
let text = self.to_toml_string()?;
std::fs::write(path, text).map_err(|e| format!("writing config {}: {e}", path.display()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trips_through_toml() {
let c = Config {
mz_half_width: Some(4),
min_feature_length: Some(6),
ms1_polygon: Some(true),
mz_min: Some(400.5),
..Config::default()
};
let s = c.to_toml_string().unwrap();
assert!(!s.contains("mz_max"));
assert!(s.contains("mz_half_width = 4"));
let back: Config = toml::from_str(&s).unwrap();
assert_eq!(c, back);
}
#[test]
fn unknown_key_is_rejected() {
assert!(toml::from_str::<Config>("bogus_key = 3").is_err());
}
#[test]
fn save_then_load_round_trips_through_a_file() {
let path =
std::env::temp_dir().join(format!("dnoise_cfg_roundtrip_{}.toml", std::process::id()));
let _ = std::fs::remove_file(&path);
let c = Config {
mz_half_width: Some(9),
halo: Some(true),
im_max: Some(1.3),
..Config::default()
};
c.save(&path).unwrap();
let back = Config::load(&path).unwrap();
assert_eq!(c, back);
let _ = std::fs::remove_file(&path);
}
#[test]
fn load_missing_file_is_err() {
let path =
std::env::temp_dir().join(format!("dnoise_cfg_absent_{}.toml", std::process::id()));
let _ = std::fs::remove_file(&path);
assert!(Config::load(&path).is_err());
}
}